Lezione 10 di 43

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 k
  • RETURN — ritorna: ripristina PC dallo stack
  • RETLW 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

Modulo1 — Assembly Base
Lezione09 di 42
Livello⭐⭐ Principiante+
Tempo~35 minuti
Istruzioni3 nuove (CALL, RETURN, RETLW)
Stack8 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.

Stack hardware PIC16 — 8 livelli × 13 bit livello contenuto (indirizzo di ritorno) stato livello 0 (TOS) 0x008 ← PC dopo 3° CALL ← SP punta qui (da pop) livello 1 0x005 ← PC dopo 2° CALL livello 2 0x003 ← PC dopo 1° CALL livello 3 — vuoto — livello 4 — vuoto — … … livello 7 — vuoto — CALL push PC+1 SP avanza RETURN pop → PC SP indietro ⚠️ Al 9° CALL senza RETURN: il puntatore torna a 0 e sovrascrive il primo indirizzo — stack overflow silenzioso, nessun errore o flag
Figura 1 — Lo stack hardware: 8 celle LIFO. CALL fa push del PC successivo, RETURN fa pop. Senza limite fisico visibile — overflow silenzioso al 9° livello.

① CALL k — chiama una subroutine

Sintassi
CALL k
Operazione
PC+1 → TOS; k → PC
Flag
nessuno
Cicli
2

CALL 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

Sintassi
RETURN
Operazione
TOS → PC
Flag
nessuno
Cicli
2

Preleva 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.

Flusso CALL / RETURN — tracciamento del PC MAIN 0x002: MOVLW 0x05 0x003: CALL FUNZ ① 0x004: MOVWF RES ③ 0x005: GOTO MAIN W=0x05 prima del CALL ① FUNZ (subroutine) 0x010: ADDLW 0x03 ② ; W = 0x05+0x03 = 0x08 0x011: RETURN ② W=0x08 al momento del RETURN ② ritorna a 0x004 Stack dopo CALL: TOS = 0x004 dopo RETURN: TOS = vuoto
Figura 2 — Flusso CALL/RETURN. CALL salva 0x004 nello stack e salta a FUNZ. RETURN ripristina 0x004: il MOVWF RES in 0x004 riceve W=0x08 calcolato dalla subroutine.

; 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

Sintassi
RETLW k
Operazione
k → W; TOS → PC
Flag
nessuno
Cicli
2

RETLW 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.

SituazioneLivelli usatiLivelli rimastiRischio
Solo MAIN (nessun CALL)08Nessuno
MAIN → sub1 → sub2 → sub335Basso
Interrupt attivi + 6 CALL annidati71Alto — prossimo CALL sovrascrive
Ricorsione (anche indiretta)N8−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

IstruzioneOperazioneFlagCicliUso tipico
CALL kpush PC+1; PC = k—2Chiamata a subroutine
RETURNPC = pop TOS—2Fine subroutine (senza valore di ritorno)
RETLW kW = k; PC = pop TOS—2Fine 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!

  1. Scrivi una subroutine NIBBLE_ALTO che 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.
  2. 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.
  3. Sfida: scrivi una subroutine MAX2 che riceve due parametri (uno in W, uno in un registro GPR PARAM) 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