Le subroutines

Scheda con PIC18

Una subroutine (o anche routine o sottoprogramma) è una sequenza di istruzioni assembly che realizza un determinato compito. Questa sequenza non è autonoma, ma viene chiamata (call) all'interno di un generico programma; al termine della esecuzione della subroutine, il Program Counter ritorna (return) al suo valore precedente, per eseguire l'istruzione immediatamente seguente la call.

Una subroutine è generalmente identificata da una label e deve terminare con l'istruzione return.

Un esempio semplice

Per una migliore comprensione di questo esempio, si consiglia di aver prima completato gli esempi ed esercizi da 4a a 4d e di aver provato a far lampeggiare un LED.

Il seguente esempio d'uso mostra il programma principale main al cui interno, per due volte, è invocata la stessa subroutine delay:

Ritardo

Il funzionamento è intuitivo:

Alcune osservazioni:

Vediamo nel dettaglio il funzionamento dell'istruzione call, con riferimento ai fogli tecnici (nota 4):

Call

Vediamo nel dettaglio il funzionamento dell'istruzione return, con riferimento ai fogli tecnici (nota 4):

Return

Prima di proseguire, completare l'esercizio 1 e l'esercizio 2

Un secondo esempio

La soluzione proposta nel primo esempio ha vari difetti:

L'esercizio 2 ha creato, a livello umano, un ritardo molto breve. Un'idea potrebbe essere quella di scrivere una subroutine che richiama decine di volte ritardo_breve, per ottenere un ritardo più lungo. Per esempio il codice seguente mostra ritardo_lungo_1, ovviamente dalla durata di circa 30 ms

Ritardo Lungo - prima versione

Si tratta di una soluzione molto rigida e ben poco elegante; sarebbe più comodo usare un loop che richiama ritardo_breve qualche decina di volte, decrementando per esempio da venti a zero un'apposita variabile contatore:

Ritardo lungo - loop

Ancora meglio sarebbe usare WREG invece che contatore, per non sprecare una cella di memoria. Peccato che tale soluzione non funziona (perché? La risposta poco più avanti, ma vale la pena provare a rispondere usando la propria intelligenza...).

Una subroutine, per essere davvero utilizzabile, non deve come effetto collaterale modificare i registri che descrivono lo stato del processore, cioè i registri WREG, STATUS e BSR.

Una possibile soluzione è quella di creare alcuni registri temporanei che permettono di salvare i valori di tali registri all'inizio del codice della subroutine e di recuperarli al termine.

Questa tecnica è mostrata nella codice che potete scaricare a fondo pagina. Questo codice contiene due subroutines:

Il salvataggio nei registri temporanei di WREG e STATUS è reso necessario dal fatto che entrambe le subroutines usano gli stessi registri: non salvarli avrebbe causato un'interferenza tra le due subroutines e, in definitiva, il loro mancato funzionamento.

Si noti che per quanto riguarda ritardo_lungo non è stato implementato alcun meccanismo di registri temporanei; sarebbe stata necessaria la creazione di ulteriori registri temporanei, con ulteriore spreco di spazio e potenziali rischi difficili da controllare nel caso di omonimie. (nota 2)

Un esempio avanzato

L'ultimo esempio mostra l'utilizzo dello stack software per il salvataggio dei registri: in pratica ogni variabile che verrà modificata all'interno della subroutine viene salvata all'inizio e recuperata prima del return. Il codice lo potete scaricare a fondo pagina

Come prassi tutte le subroutines iniziano con il salvataggio del contesto nello stack software (nota 6):

 movwf POSTDEC1         ; salva WREG nello stack
 movff STATUS, POSTDEC1 ; salva i flags nello stack
 movff BSR, POSTDEC1    ; salva BSR nello stack

Terminano con il ripristino del contesto, prelevando i contenuti originali dei registri dalla stack software:

 movff PREINC1, BSR    ; ripristina il registro BSR
 movff PREINC1, STATUS ; ripristina i flags
 movf  PREINC1, W      ; ripristina il registro WREG

Si noti la simmetria tra l'ordine di salvataggio e quello di ripristino; non ha invece particolare importanza l'ordine di salvataggio, purché venga mantenuta la simmetria al momento del ripristino.

Questa può essere considerata la soluzione definitiva, utilizzabile anche in altri contesti, come quello delle interruzioni.

Codice

Esercizi

  1. Eseguire passo-passo il primo codice di esempio (saltando i blocchi che si ripetono centinaia di volte...) ed osservare il contenuto dello stack hardware
  2. Commentare il seguente esempio, eseguirlo ed individuare gli effetti collaterali (in particolare: quali registri vengono riscritti all'interno di ritardo_breve?)
    Ritardo breve
  3. (Avanzato) Calcolare il tempo impiegato dalla subroutine ritardo_breve per completare la sua esecuzione.
  4. Trasformare il codice dell'esempio 6b in una subroutine

Note

  1. L'esempio mostra, per generalità, anche il salvataggio del Bank Select Register, sebbene in questo caso non viene modificato all'interno della subroutine
  2. In teoria PIC18 implementa un metodo automatico per il salvataggio dei registri (fast register stack), ma, essendo ad un solo livello è assolutamente sconsigliato il suo uso per subroutines generiche. In genere viene riservato alle interrupt ad alta priorità
  3. In realtà alcuni livelli dello stack sono usati dal debugger
  4. Nell'estratto dei fogli tecnici sono stati tolti i riferimenti al flag {,s} ed ai registri shadow WS, STATUSS e BSRS: questa scelta è motivata dal fatto che è bene non utilizzare tale flag e, in questo caso, tali registri
  5. Sarebbe più corretto riportare l'indirizzo di memoria invece che la riga del codice sorgente, dove molte righe sono vuote
  6. Non sempre è strettamente necessario salvare tutti i registri, ma è una buona prassi per evitare errori

 

Data di creazione di questa pagina:  maggio 2016
Ultima modifica: 5 luglio 2017


Licenza Creative Commons Attribuzione 4.0 Internazionale


Pagina principaleAccessibilitàNote legaliPosta elettronicaXHTML 1.0 StrictCSS 3

Vai in cima