Lezione 09
🎓 PIC16 — From Zero to Hero · Lezione 09
CALL, RETURN e lo stack 📚
CALL
RETURN
RETLW
Lo stack hardware a 8 livelli, W come registro parametri, lookup table con RETLW.
🎯 Cosa impari in questa lezione
- Come funziona lo stack hardware del PIC16: 8 livelli × 13 bit, circolare
CALL k— chiama una subroutine: salva PC nello stack, salta a kRETURN— ritorna: ripristina PC dallo stackRETLW k— ritorna con un valore in W (lookup table)- W come unico canale per passare parametri e valori di ritorno
- Tecnica della lookup table con RETLW e PCL
- Il limite degli 8 livelli e cosa succede se si supera (stack overflow silenzioso)
📋 Scheda lezione
| Modulo | 1 — Assembly Base |
| Lezione | 09 di 42 |
| Livello | ⭐⭐ Principiante+ |
| Tempo | ~35 minuti |
| Istruzioni | 3 nuove (CALL, RETURN, RETLW) |
| Stack | 8 livelli × 13 bit, circolare |
🧠 Lo stack hardware del PIC16
Il PIC16F84A ha uno stack hardware dedicato: 8 celle × 13 bit, completamente separato dalla RAM. Non è accessibile direttamente — solo CALL e interrupt lo scrivono (push), solo RETURN/RETLW/RETFIE lo leggono (pop). Il puntatore avanza circolarmente: al nono CALL senza RETURN, sovrascrive silenziosamente la prima cella. Non c’è eccezione, non c’è flag di overflow.
① CALL k — chiama una subroutine
CALL kCALL salva il PC+1 (l’indirizzo dell’istruzione subito dopo CALL) in cima allo stack, poi salta all’indirizzo k. Come GOTO porta 11 bit di indirizzo (k<10:0>). Costa sempre 2 cicli per lo svuotamento del pipeline.
② RETURN — ritorna dalla subroutine
RETURNPreleva il valore in cima allo stack e lo carica nel PC. L’esecuzione riprende dall’istruzione subito dopo il CALL originale. Non modifica nessun registro né flag — il contenuto di W dopo la subroutine è il valore di ritorno se la subroutine lo ha impostato.
; Struttura subroutine con passaggio parametri via W
; Convenzione: W = input, W = output (un solo valore)
; === Chiamante ===
MOVLW 0x05 ; parametro in W
CALL AGGIUNGI_3 ; chiama la funzione
MOVWF RISULTATO ; W = valore di ritorno
; Passare più parametri: salvarli in GPR prima del CALL
MOVLW 0x0A
MOVWF PARAM1 ; primo parametro in RAM
MOVLW 0x03 ; secondo parametro in W
CALL MOLTIPLICA
; === Subroutine AGGIUNGI_3 ===
; Input: W = numero
; Output: W = numero + 3
AGGIUNGI_3:
ADDLW 0x03 ; W = W + 3
RETURN ; torna al chiamante con W modificato
③ RETLW k — la lookup table
RETLW kRETLW fa due cose in un solo colpo: carica la costante k in W e ritorna come RETURN. È il mattone della lookup table: una serie di RETLW consecutive, accessibili modificando direttamente PCL per saltare all’entry corrispondente all’indice passato in W.
Pattern lookup table con PCL
Quando scrivi su PCL, il PC cambia immediatamente — questo è il trucco. CALL salta alla subroutine, che aggiunge W a PCL per saltare alla RETLW corretta. Ogni RETLW ha una costante diversa: la “tabella” è direttamente nel codice.
; Tabella dei quadrati: quadrato(n) per n=0..7
; Input: W = indice (0..7)
; Output: W = n²
; === Chiamante ===
MOVLW 0x03 ; voglio il quadrato di 3
CALL TAB_QUADRATI
; W = 9 (3²)
; === Lookup table ===
TAB_QUADRATI:
ADDWF PCL, F ; PCL += W → salta alla entry W
RETLW 0 ; W=0: 0² = 0
RETLW 1 ; W=1: 1² = 1
RETLW 4 ; W=2: 2² = 4
RETLW 9 ; W=3: 3² = 9 ← W=3, salta qui
RETLW 16 ; W=4: 4² = 16
RETLW 25 ; W=5: 5² = 25
RETLW 36 ; W=6: 6² = 36
RETLW 49 ; W=7: 7² = 49
🔍 Come funziona: quando CALL salta a TAB_QUADRATI, il PC punta a ADDWF PCL, F. Questo indirizzo è, diciamo, 0x020. ADDWF aggiunge W=3 a PCL: il PC diventa 0x023. L’istruzione a 0x023 è RETLW 9 → W=9, ritorna. Ogni RETLW occupa esattamente 1 word di program memory, quindi l’aritmetica funziona perfettamente. Vincolo critico: tutta la tabella deve stare nella stessa pagina da 256 word (PCL è solo 8 bit).
⚠️ Il limite degli 8 livelli e le sue trappole
Otto livelli possono sembrare molti, ma in un firmware reale con interrupt abilitati bisogna contare attentamente: ogni interrupt consuma un livello automaticamente. Con RETFIE ne recuperi uno. Rimangono effettivamente 7 livelli utili per il codice normale se gli interrupt sono attivi.
| Situazione | Livelli usati | Livelli rimasti | Rischio |
|---|---|---|---|
| Solo MAIN (nessun CALL) | 0 | 8 | Nessuno |
| MAIN → sub1 → sub2 → sub3 | 3 | 5 | Basso |
| Interrupt attivi + 6 CALL annidati | 7 | 1 | Alto — prossimo CALL sovrascrive |
| Ricorsione (anche indiretta) | N | 8−N | ⛔ Evitare sempre |
⚠️ Stack overflow silenzioso: il PIC16 non genera nessun errore se si supera l’ottavo livello. Il puntatore torna semplicemente a 0 e sovrascrive il primo indirizzo salvato. Il programma continuerà a girare ma tornerà all’indirizzo sbagliato — bug difficilissimo da diagnosticare. La regola d’oro: mai più di 4-5 livelli di CALL annidati, e mai la ricorsione.
📊 Riepilogo — le 3 istruzioni
| Istruzione | Operazione | Flag | Cicli | Uso tipico |
|---|---|---|---|---|
CALL k | push PC+1; PC = k | — | 2 | Chiamata a subroutine |
RETURN | PC = pop TOS | — | 2 | Fine subroutine (senza valore di ritorno) |
RETLW k | W = k; PC = pop TOS | — | 2 | Fine subroutine con valore; lookup table |
💻 Programma — subroutine e lookup table
; ============================================
; Lezione 09 — CALL, RETURN, RETLW
; Osserva il pannello Stack nel simulatore
; ============================================
LIST p=16F84A
#include <p16f84a.inc>
RES EQU 0x0C
TEMP EQU 0x0D
IDX EQU 0x0E
ORG 0x00
GOTO START
ORG 0x04
RETFIE
START:
; [A] Subroutine semplice con RETURN
MOVLW 0x07 ; W = 7 (parametro)
CALL DOPPIO ; W = 7*2 = 14 = 0x0E
MOVWF RES ; RES = 0x0E
; [B] Lookup table con RETLW
MOVLW 0x05 ; indice = 5
CALL TAB_QUADRATI ; W = 5² = 25
MOVWF TEMP ; TEMP = 25 = 0x19
; [C] Chiamate annidate (osserva lo stack crescere)
MOVLW 0x04
CALL LIVELLO_A ; A chiama B che chiama C: stack a 3
MOVWF IDX
FINE GOTO FINE
; — Subroutine: DOPPIO (W = W * 2) —
DOPPIO:
BCF STATUS, C ; azzera C prima di RLF
RLF RES, W ; W = W<<1 (×2) via RES temporaneo
; Nota: RLF lavora su RAM, usiamo RES come temp
MOVWF RES ; salva W in RES
BCF STATUS, C
RLF RES, W ; non serve, usando ADDWF è più semplice:
; Versione semplice con ADDWF:
MOVWF RES ; RES = parametro originale
ADDWF RES, W ; W = RES + RES = W_originale * 2
RETURN
; — Lookup table quadrati —
TAB_QUADRATI:
ADDWF PCL, F ; salta alla entry W
RETLW 0 RETLW 1 RETLW 4
RETLW 9 RETLW 16 RETLW 25
RETLW 36 RETLW 49
; — Chiamate annidate A → B → C —
LIVELLO_A:
CALL LIVELLO_B ; stack: 2 livelli
RETURN
LIVELLO_B:
CALL LIVELLO_C ; stack: 3 livelli
RETURN
LIVELLO_C:
ADDLW 0x01 ; W = W + 1
RETURN ; stack: torna a 0
END
💡 Usa Step nel simulatore e guarda il pannello Stack. Vedrai i valori push/pop in tempo reale. Alla sezione [C] con le chiamate annidate, osserva come il puntatore dello stack avanza di 1 ad ogni CALL e regredisce di 1 ad ogni RETURN. Quando tutte le RETURN sono eseguite, lo stack torna vuoto.
🏋️ Prova tu!
- Scrivi una subroutine
NIBBLE_ALTOche riceve un byte in W e restituisce in W il nibble alto shiftato a destra (es: W=0xA3 → W=0x0A). Usa SWAPF + ANDLW. Testa con W=0xC7 — il risultato atteso è 0x0C. - Estendi la lookup table dei quadrati fino a n=9 (aggiungi RETLW 64, RETLW 81). Verifica che chiamare la tabella con W=8 e W=9 restituisca i valori corretti.
- Sfida: scrivi una subroutine
MAX2che riceve due parametri (uno in W, uno in un registro GPRPARAM) e restituisce in W il maggiore dei due. Usa SUBWF + BTFSC STATUS,C per il confronto. Ricorda: C=0 dopo SUBWF significa che W era maggiore di PARAM.
❓ Quiz rapido
1. Cosa viene salvato nello stack da CALL FUNZ?
Mostra risposta
Viene salvato PC+1, cioè l’indirizzo dell’istruzione che segue immediatamente CALL. Non il PC corrente di CALL, ma quello successivo — l’indirizzo dove il programma deve riprendere dopo il RETURN. W, STATUS e tutti gli altri registri non vengono toccati dallo stack.
2. Qual è la differenza tra RETURN e RETLW 0x00?
Mostra risposta
RETURN ripristina il PC dallo stack e lascia W invariato — W conserva il valore che aveva al momento del RETURN. RETLW 0x00 fa la stessa cosa ma forza W = 0x00 prima di tornare. Se la subroutine deve comunicare un valore di ritorno specifico si usa RETLW; se il valore utile è già in W (calcolato nel corpo della subroutine) si usa RETURN.
3. Hai 8 livelli di stack e gli interrupt sono abilitati. Quanti livelli di CALL puoi annidare con sicurezza nel codice principale?
Mostra risposta
7 livelli al massimo — e spesso si raccomanda 6. Un interrupt può arrivare in qualsiasi momento e consuma un livello di stack (push automatico del PC). Se la ISR stessa usa CALL, consuma un altro livello. La regola pratica: con interrupt attivi, non annidare più di 4-5 CALL nel codice principale. Il limite è rigido e silenzioso — non c’è nessun avviso di overflow.
4. Nella lookup table: perché ADDWF PCL, F deve essere la prima istruzione dopo la label, e non può essere separata dalle RETLW da altre istruzioni?
Mostra risposta
Perché l’aritmetica del salto dipende dalla posizione relativa. ADDWF PCL,F aggiunge W al PC corrente — che punta all’istruzione successiva (la prima RETLW). Se ci fossero istruzioni tra ADDWF e le RETLW, gli offset sarebbero sbagliati e il salto arriverebbe nel mezzo della tabella o fuori. Inoltre tutta la tabella deve essere nella stessa pagina da 256 word (PCL è 8 bit): se ADDWF è a 0x0FE e le RETLW partono da 0x0FF, la RETLW per W=1 sarebbe a 0x100 — overflow di PCL, bug.
Prossima lezione →
Lezione 10 — Direttive dell’assembler
ORG, EQU, CBLOCK/ENDC, #include, LIST, __CONFIG.
Come organizzare un file sorgente professionale dall’inizio.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 09