home | area personale         schemi | tutorial | robotica | pic micro | recensioni         forum | chat irc         faq | contatti         store | Ordina PCB
username
password
cerca
 
(APPUNTI MIKROBASIC) - SUBROUTINES, PROCEDURE, FUNZIONI (1) tipo: livello:
Cosa sono e come si usano le subroutines, procedure e funzioni in mikroBasic (1)
 
 



mikroBasic - Subroutines, procedure, funzioni (1)

Cosa sono e come si usano le subroutines, procedure e funzioni in mikroBasic.
Questo articolo è tratto da una mia pagina personale , a cui potete riferirvi per aggiornamenti.

Descrizione

Quando si scrive un programma, si deve cercare, in tutti i modi, di fargli fare un sacco di cose, ma senza occupare troppa memoria programma (quella che viene anche detta ROM-memory).
Bisogna, soprattutto, cercare di non DUPLICARE alcune operazioni.
Per fare questo, ci vengono in aiuto i sottoprogrammi e le procedure, che sono piccoli blocchi di istruzioni (ma possono essere paragonati a piccoli singoli programmi).
Se, per esempio, dobbiamo mandare dei dati ad un LCD, invece di ripetere, le stesse istruzioni, un sacco di volte, le possiamo mettere in una procedura e chiamare questa, ogni volta che ci servono quelle istruzioni. Se le istruzioni, nel programma, occupavano 30 words e venivano eseguite 10 volte (totale 300 words), per mandare i dati al LCD, chiamando la procedura, adesso il programma occupa 270 words in meno.

Prendiamo, ad esempio, un programma molto semplice (il solito simil-supercar).
Per compilare il programma è stata usata la vers. 7.2 di mikroBasic.


program led_danzanti
main:
trisB = 0
portB = 0
danza:
portB = % 00011000
delay_ms(200)
portB = % 00100100
delay_ms(200)
portB = % 01000010
delay_ms(250)
portB = % 10000001
delay_ms(250)
portB = % 10000000
delay_ms(300)
portB = % 00000001
delay_ms(300)
portB = % 10000000
delay_ms(250)
portB = % 00000010
delay_ms(250)
portB = % 01000000
delay_ms(200)
portB = % 00000010
delay_ms(200)
portB = % 00100000
delay_ms(150)
portB = % 00000100
delay_ms(150)
portB = % 00100000
delay_ms(150)
portB = % 00001000
delay_ms(100)
portB = % 00010000
delay_ms(100)
portB = % 00001000
delay_ms(100)
goto danza
End.

Per questo esempio è stato usato un PIC16F877A con quarzo da 8MHz, collegando i LED su portB.

Partiamo accendendo i 2 LED centrali (portB = % 00011000)
e proseguiamo accendendo gli altri, allontanandoci dal centro, fino a quelli esterni (portB = %10000001)
e poi, palleggiando, (portB = %10000000)
ritornare al centro e ricominciare daccapo (goto danza).

Ogni LED, prima di cambiare stato, starà acceso per tutto il tempo definito da un'istruzione di ritardo (delay_ms(...)).

Questa istruzione genera un ritardo di un numero di millisecondi la cui quantità è scritta tra le parentesi tonde [delay_ms(200)].

Notiamo subito che ci sono delle istruzioni uguali che si ripetono [delay_ms(200), delay_ms(100), delay_ms(300), ecc. ecc.].

Se compiliamo il progetto, vedremo che il programma occuperà 532 words nella memoria programma.

   

Cliccando ora su View statistics si aprirà la finestra Statistics.

Adesso possiamo cliccare su Procedures (sizes) e vediamo che l'unica procedura esistente nel programma è la "main" ed occupa 531 locazioni della ROM.

Chiudiamo la finestra Statistics.

Cliccando su "View assembly",

(1) cerchiamo la prima istruzione delay_ms(200)
e notiamo che il codice prodotto per generare il ritardo

(2) è composto di 30 istruzioni assembly
e che la situazione si ripete per tutte le altre istruzioni delay_ms(...) simili.

Questo ci deve far pensare che, anche se in mikroBasic un'istruzione è di una sola riga, in assembly potrebbe essere tradotta in molte righe di codice.

Bastano poche istruzioni di questo tipo in mikroBasic, per far aumentare la dimensione dell'eseguibile finale da inserire nel PIC.

Dovremo, perciò, cercare di ridurre le istruzioni o blocchi di istruzioni uguali duplicate.

Lo faremo, modificando il programma, dopo aver visto cosa sono i sottoprogrammi.

Usiamo i sottoprogrammi

main:
......
(istruzioni)
......
Etichetta_1:
......
(istruzioni)
......

gosub etichetta_sub 'salta a subroutine
...... 'istruz. che verrà eseguita
'al ritorno dal gosub
gosub etichetta_sub 'salta a subroutine
goto etichetta_1 'istruz. che verrà eseguita
'al ritorno dal gosub

etichetta_sub: 'inizio subroutine
......
(istruzioni)
......
return 'termine subroutine
End.

Subroutines

Una subroutine è una parte di programma, autonomo ed indipendente (sottoprogramma), che esegue determinate istruzioni all'interno del programma principale.

Viene invocata con la parola chiave GOSUB, seguita dall'etichetta dell'inizio della subroutine (etichetta_sub).

Il programma proseguirà, eseguendo la prima istruzione che si trova alla posizione dell'etichetta (etichetta_sub) o riga successiva.

Quando incontrerà la parola chiave RETURN, ritornerà alla riga di programma successiva al comando GOSUB, dato precedentemente.

program led_danzanti_1

main:
trisB = 0
portB = 0
danza:
portB = % 00011000
gosub pausa200
portB = % 00100100
gosub pausa200
portB = % 01000010
gosub pausa250
portB = % 10000001
gosub pausa250
portB = % 10000000
gosub pausa300
portB = % 00000001
gosub pausa300
portB = % 10000000
gosub pausa250
portB = % 00000010
gosub pausa250
portB = % 01000000
gosub pausa200
portB = % 00000010
gosub pausa200
portB = % 00100000
gosub pausa150
portB = % 00000100
gosub pausa150
portB = % 00100000
gosub pausa150
portB = % 00001000
gosub pausa100
portB = % 00010000
gosub pausa100
portB = % 00001000
gosub pausa100
goto danza
pausa300:
delay_ms(300)
return
pausa250:
delay_ms(250)
return
pausa200:
delay_ms(200)
return
pausa150:
delay_ms(150)
return
pausa100:
delay_ms(100)
return
End.

Modifica al programma con uso di subroutines

La modifica al nostro programma, consiste nel sostituire le righe di programma, che invocavano il ritardo delay_ms(...), con altrettante che invocano un Gosub pausa..., eseguendo le subroutines corrispondenti.

Le subroutines sono 5 ed ognuna esegue un'istruzione delay_ms(...) per generare il ritardo voluto.

Nel programma precedente c'erano 16 istruzioni delay_ms(...) ed ora ne troviamo solo 5 per avere lo stesso risultato.

Compiliamo il programma e vediamo che adesso, lo spazio occupato dal programma, è diventato di 249 words.

   

Anche in questo caso, cliccando su View statistics
e poi su Procedures (sizes), vediamo che l'unica procedura esistente nel programma, è la "main".

Se il programma fosse composto da molte altre istruzioni, fino a generare codice assembly superiore a 2000 words, il compilatore genererebbe l'errore "Routine too large" (routine troppo grande).

Ma perchè ci da questo errore, se il PIC16F877A può contenere fino a 8192 words di codice?

Questo errore viene generato perchè, in mikroBasic, una procedura non può superare 2000 words di codice e, in questo caso, "main" è una procedura, la procedura principale del programma.

Le subroutines, invocate con Gosub, sono interne alla procedura "main" e ne fanno parte integrante.

Per non incorrere nell'errore "Routine too large", dovremo individuare, nel nostro programma, dei blocchi di istruzioni da poter essere tolti dal "main" ed essere inseriti in altrettante procedure.

In questa maniera, il programma viene diviso in varie procedure e si eviterà l'errore menzionato precedentemente (purchè, ogni singola procedura, non superi 2000 words).

L'uso di Goto e Gosub è fortemente sconsigliato perchè non efficiente (usarli solo in casi sporadici).

In alternativa a Goto è consigliabile l'uso di cicli While-Wend oppure Do-Loop.

In alternativa a Gosub è doveroso usare le sub-procedure e le sub-function, molto più efficienti e che rendono la programmazione più "pulita" e modulare.

Procedure e funzioni

sub procedure nome_procedura (lista_parametri)
[dichiarazioni variabili locali ]

corpo della procedura
end sub

Sub procedure

Una sub-procedura, è un sottoprogramma (blocco autonomo di dichiarazioni) che compie un certo compito basato su un numero di parametri di ingresso.
I parametri sono dei valori che vengono impostati (passati) quando la procedura viene invocata.

Il nome della procedura è un nome scelto arbitrariamente, per ricordarci cosa fanno le istruzioni contenute.
La lista dei parametri è composta da zero o più parametri (detti parametri formali), definiti alla stessa maniera di come si definiscono le variabili (può anche non avere parametri).

Una procedura viene invocata col suo relativo nome, con i parametri da passare (detti parametri attuali) messi nella stessa sequenza dei loro parametri formali corrispondenti.

Per essere parte integrante di un programma, la procedura deve trovarsi, come collocazione nel listato, prima della label "main".
Ma può trovarsi anche in un altro modulo, che dovrà essere incluso nel programma principale.
La regola vale anche quando una procedura chiama un'altra procedura; quest'ultima deve essere definita prima della chiamata.

Nel caso di una procedura senza parametri, significa che esegue operazioni con le variabili globali, ma potrebbe anche non usare variabili. Potrebbe, ad esempio, chiamare un'altra procedura, oppure eseguire qualche istruzione sui registri del PIC o le porte, oppure chiamare una procedura della libreria (ad es. Lcd_init per inizializzare un display LCD alfanumerico).

La procedura, deve essere invocata, esattamente, come è stata definita.
Bisogna fare attenzione, anche, al TIPO delle variabili da mandare, come parametri.
Se il TIPO non è quello corrispondente, il compilatore può non generare errori di compilazione, ma ci potrebbero essere risultati imprevisti nell'esecuzione.

program prova_proc

dim banana as byte
dim limone as byte

Sub procedure gelato(dim crema, panna as byte)
dim arancia as byte

arancia = crema + panna
banana = panna + arancia
end sub

sub procedure sorbetto
banana = limone + 45
end sub

main:
trisB = 0

While true
gelato(2, 5)

limone = arancia 'genera ERRORE

limone = banana - 6
banana = 4
gelato(limone, banana)

limone = banana

sorbetto

portB = banana
Wend

End.

Esempio di sub-procedure

Banana e limone sono variabili globali, cioè sono viste da tutto il programma.

La procedura gelato ha 2 parametri formali (crema, panna) di tipo byte.

Arancia è una variab. locale, cioè visibile solo dentro la procedura (se viene usata fuori dalla procedura, genera un errore del compilatore).

La procedura sorbetto è una procedura senza parametri (esegue operazioni con le variabili globali).

Nella main, invoco la procedura gelato, passandogli 2 valori (2, 5) detti parametri attuali. In pratica è come se avessi scritto crema = 2 e panna = 5. L'esecuzione del programma "salta" dentro la procedura ed esegue le istruzioni contenute
(arancia = 2 + 5) (banana = 5 + 7). Quando la procedura termina, si ritorna alla riga di programma successiva alla chiamata, in questo caso alla riga:

limone = arancia che genera un ERRORE del compilatore, perchè arancia è una variabile locale. Possiamo cancellare la riga (o mettere un apostrofo) per proseguire.

limone = banana - 6 e cioè limone = 12 - 6 = 6

Chiamo la procedura gelato passando 2 variabili come parametri attuali, che corrispondono a
crema = limone = 6 e panna = banana = 4.
Al ritorno banana = 14.

Quindi limone = banana = 14

Chiamo la procedura sorbetto senza parametri.
Al ritorno banana = 59 e quindi portB = 59.

Ripeto tutto dall'inizio.

Ritornando al nostro programmino, quella che io ho chiamato istruzione delay_ms(...) è in realtà una procedura contenuta nelle librerie di mikroBasic ed è definita così:

   

Notiamo che l'unico parametro definito è una costante di tipo word. Questo significa che non possiamo passare come parametro delle variabili, ma soltanto valori fissi (costanti). Oltre a ciò, è dichiarata come procedura "inline" (perchè, in realtà, si tratta di una macro). Questo significa che, per ogni riga di programma dove invochiamo questa procedura, verrà generato il codice assembly corrispondente (come abbiamo visto nella prima versione del prog.).

N.B.
Quando definiamo una nostra procedura non possiamo usare costanti nella dichiarazione dei parametri.

Clock MHz Valore max ms
2 100009
4 50004
8 25002
20 10000
40 5000

Misteri di mikroBasic

Esiste una limitazione sul valore che si può immettere, come parametro, per delay_ms. A seconda della frequenza di clock, non si può immettere un valore superiore a quello in tabella, altrimenti ci sarà un messaggio di errore del compilatore.

Nella seconda versione del nostro programma, abbiamo ridotto a 5 le chiamate alla procedura usando le subroutines.
Proviamo ad usare le procedure al posto delle subroutines e vediamo le differenze.

program led_danzanti_2

sub procedure pausa300
delay_ms(300)
end sub
sub procedure pausa250
delay_ms(250)
end sub
sub procedure pausa200
delay_ms(200)
end sub
sub procedure pausa150
delay_ms(150)
end sub
sub procedure pausa100
delay_ms(100)
end sub

main:
trisB = 0
portB = 0
While true
portB = % 00011000
pausa200
portB = % 00100100
pausa200
portB = % 01000010
pausa250
portB = % 10000001
pausa250
portB = % 10000000
pausa300
portB = % 00000001
pausa300
portB = % 10000000
pausa250
portB = % 00000010
pausa250
portB = % 01000000
pausa200
portB = % 00000010
pausa200
portB = % 00100000
pausa150
portB = % 00000100
pausa150
portB = % 00100000
pausa150
portB = % 00001000
pausa100
portB = % 00010000
pausa100
portB = % 00001000
pausa100
Wend
End.

Programma con uso di procedure

La modifica al nostro programma, consiste nel sostituire le 5 subroutines, del programma precedente, con altrettante procedure.

Definiamo le 5 procedure posizionandole prima dell'etichetta main (obbligatorio).

Esaminiamo la prima (le altre sono simili) a cui abbiamo dato il nome "pausa300". E' una procedura senza parametri la quale chiama la procedura della libreria di mikroBasic delay_ms(...) passando, come parametro (dentro le parentesi), 300 che sono i millisecondi di ritardo voluti.

Le altre procedure sono uguali, ma con parametri per ritardi differenti.

Quando il programma viene eseguito, ogni volta che incontra la riga con l'istruzione pausa300, l'esecuzione proseguirà dentro alla procedura pausa300 ed eseguirà le istruzioni contenute all'interno di essa.
In questo caso, l'unica istruzione presente richiede l'esecuzione di un'altra procedura, delay_ms(300) per creare un ritardo di 300 millisecondi, dopodichè la procedura termina e l'esecuzione proseguirà dall'istruzione successiva a quella della chiamata precedente.

Le altre procedure funzionano alla stessa maniera, generando ritardi differenti.

Compiliamo il programma e vediamo che adesso, lo spazio occupato dal programma, è diventato di 225 words.

   

Cliccando su View statistics e poi su Procedures (sizes), vediamo che il programma è stato diviso in 6 procedure (le nostre 5 procedure più la procedura principale main).

In questa maniera si evita che esca il messaggio d'errore "routine too large", menzionata precedentemente.

   

Abbiamo visto che la procedura delay_ms(...) accetta solo parametri di valore costante. E se avessimo bisogno di passare dei parametri con valori variabili, come facciamo?
Nelle librerie di mikroBasic c'è un'altra procedura con nome Vdelay_ms(...) che fa al caso nostro.

   

Notiamo che anche questa procedura richiede un'unico parametro, una variabile di tipo word che indicherà i millisecondi di ritardo voluti.
E' da notare che, questa procedura, è molto meno precisa di quella precedente. Oltre a ciò, questa è una vera procedura (non una macro, come la precedente). Questo significa che verrà generato il codice assembly corrispondente una sola volta, anche se la invochiamo infinite volte nel programma.
Proviamo a vedere le differenze con il programmino della prima versione modificato.

program led_danzanti_3
main:
trisB = 0
portB = 0
while true
portB = % 00011000
Vdelay_ms(200)
portB = % 00100100
Vdelay_ms(200)
portB = % 01000010
Vdelay_ms(250)
portB = % 10000001
Vdelay_ms(250)
portB = % 10000000
Vdelay_ms(300)
portB = % 00000001
Vdelay_ms(300)
portB = % 10000000
Vdelay_ms(250)
portB = % 00000010
Vdelay_ms(250)
portB = % 01000000
Vdelay_ms(200)
portB = % 00000010
Vdelay_ms(200)
portB = % 00100000
Vdelay_ms(150)
portB = % 00000100
Vdelay_ms(150)
portB = % 00100000
Vdelay_ms(150)
portB = % 00001000
Vdelay_ms(100)
portB = % 00010000
Vdelay_ms(100)
portB = % 00001000
Vdelay_ms(100)
wend
End.

Modifica al programma con uso della procedura Vdelay_ms(...)

La modifica al programma, consiste nel sostituire tutte le chiamate alla procedura delay_ms(...) con chiamate alla procedura Vdelay_ms(...).

E' bene sostituire anche l'etichetta danza: e l'istruzione goto danza, con l'istruzione While true e Wend.

Compiliamo il programma e vediamo che adesso, lo spazio occupato dal programma, è diventato di 297 words.
Abbiamo peggiorato rispetto a prima.

   

Cliccando su View statistics e poi su Procedures (sizes), vediamo che il programma è stato diviso in 3 procedure (la procedura Vdelay_ms, la funzione Mul_32x32_U (chiamata necessariamente da Vdelay_ms) e la procedura principale main).

Notiamo quindi che, nonostante abbiamo fatto numerose chiamate alla Vdelay_ms, il compilatore ha generato assembly solo per una copia della procedura.

Di negativo c'è che, per far funzionare detta procedura, il compilatore ha dovuto prendere dalle librerie ed inserire nel codice, anche la funzione Mul_32x32_U necessaria per il calcolo dei tempi.

   

Adesso vediamo un'altra modifica al nostro programmino, usando però una sola procedura delay_ms, usando un trucchetto per avere tempi variabili.

program led_danzanti_4

sub procedure pausa_ms(dim ms as word)
dim i as word
for i = 1 to ms
delay_ms(1)
next i
end sub

main:
trisB = 0
portB = 0
while true
portB = % 00011000
pausa_ms(200)
portB = % 00100100
pausa_ms(200)
portB = % 01000010
pausa_ms(250)
portB = % 10000001
pausa_ms(250)
portB = % 10000000
pausa_ms(300)
portB = % 00000001
pausa_ms(300)
portB = % 10000000
pausa_ms(250)
portB = % 00000010
pausa_ms(250)
portB = % 01000000
pausa_ms(200)
portB = % 00000010
pausa_ms(200)
portB = % 00100000
pausa_ms(150)
portB = % 00000100
pausa_ms(150)
portB = % 00100000
pausa_ms(150)
portB = % 00001000
pausa_ms(100)
portB = % 00010000
pausa_ms(100)
portB = % 00001000
pausa_ms(100)
wend
End.

Modifica al programma con uso di una sola procedura delay_ms.

La modifica al nostro programma, consiste nel creare una nostra procedura di nome pausa_ms, con un parametro formale di tipo word, al quale passeremo i valori di tempo voluti.

All'interno della procedura, definiamo una variabile locale i di tipo word, necessaria per il ciclo for.

All'interno del ciclo for invochiamo una chiamata alla procedura delay_ms(1) con il parametro impostato ad 1, che genererà un ritardo di 1 millisecondo.
Il ciclo for eseguirà un numero di cicli che va da 1 al valore contenuto nel parametro ms, il quale viene passato quando invochiamo la procedura pausa_ms e che indica quanti millisecondi vogliamo.

Se invochiamo la procedura, ad esempio, scrivendo pause_ms(300), passiamo alla variabile ms, contenuta nel parametro, il valore 300. Il ciclo for eseguirà quindi 300 cicli consecutivi, ognuno con ritardo di 1 millisecondo, perciò un totale di 300 millisecondi (circa).

Compiliamo il programma e vediamo che adesso, lo spazio occupato dal programma, è diventato di 147 words.
Abbiamo avuto un ulteriore miglioramento rispetto ai precedenti e credo che possiamo fermarci qua.

   

Cliccando su View statistics e poi su Procedures (sizes), vediamo che il programma è stato diviso in 2 procedure (la procedura pausa_ms e la procedura principale main).

   

Schema elettrico

   

Sorgenti mikroBasic per questi appunti

Bibliografia:
Manuale mikroBasic

Continua nella seconda parte



  il parere della community
esprimi il tuo voto approvi questa pagina? promo


  non sei autenticato, per questo non puoi visualizzare i commenti sulla pagina. Se sei registrato accedi oppure registrati.


difficoltà
costo
informazioni
Questa pagina è stata creata da beduino
il 24/12/2008 ore 12:28
ultima modifica del 17/08/2010 ore 20:34
la pagina è stata visitata 14253 volte




Lo staff di www.grix.it non si assume responsabilità sul contenuto di questa pagina.
Se tu sei l'autore originale degli schemi o progetti proposti in questo articolo, e ritieni che siano stati violati i tuoi diritti, contatta lo staff di www.grix.it per segnalarlo.

   
 







 
 
indietro | homepage | torna su copyright © 2004/2024 GRIX.IT - La community dell'elettronica Amatoriale