Lezione 08
🎓 PIC16 — From Zero to Hero · Lezione 08
Salti e controllo del flusso 🔀
GOTO
BTFSC
BTFSS
DECFSZ
INCFSZ
Con queste cinque istruzioni scrivi if/else, while e for in assembly.
🎯 Cosa impari in questa lezione
GOTO k— salto incondizionato a labelBTFSC f,b— salta se il bit b di f è 0 (Skip if Clear)BTFSS f,b— salta se il bit b di f è 1 (Skip if Set)DECFSZ f,d— decrementa e salta se zero (loop counter)INCFSZ f,d— incrementa e salta se zero- La struttura fondamentale init → loop infinito di ogni firmware PIC
- Come costruire if/else, while e for in assembly
- Il meccanismo di skip: 1 ciclo normale, 2 cicli se salta
📋 Scheda lezione
| Modulo | 1 — Assembly Base |
| Lezione | 08 di 42 |
| Livello | ⭐⭐ Principiante+ |
| Tempo | ~35 minuti |
| Istruzioni | 5 nuove |
| Concetti | label, skip, loop counter, flusso condizionale |
🧠 Il meccanismo di skip
BTFSC, BTFSS, DECFSZ e INCFSZ usano tutti lo stesso meccanismo: se la condizione è vera, l’istruzione immediatamente successiva viene sostituita da un NOP. Il PC salta avanti di 2 invece di 1. Questo costa un ciclo extra (2 cicli totali invece di 1). Il salto è sempre di esattamente una istruzione — non di più.
📌 Pattern universale: l’istruzione dopo BTFSC/BTFSS è quasi sempre un GOTO. Se la condizione è vera → GOTO saltato → esegui il codice qui sotto. Se falsa → GOTO eseguito → salta altrove. Questo è l’if/else del PIC16.
① GOTO k — salto incondizionato
GOTO kSalta incondizionatamente all’indirizzo k. In pratica si usa sempre con una label — l’assembler calcola l’indirizzo numerico. GOTO porta 11 bit di indirizzo nell’opcode (PC<10:0>), quindi può raggiungere i 2048 indirizzi di una singola pagina da 2K. Costa sempre 2 cicli (il pipeline deve essere svuotato).
; Loop infinito — la struttura base di ogni firmware PIC
ORG 0x00
GOTO START ; salta oltre il vettore interrupt
ORG 0x04 ; vettore interrupt (per ora vuoto)
RETFIE
START:
; === Inizializzazione (eseguita una volta) ===
BSF STATUS, RP0 ; Bank 1
CLRF TRISB ; PORTB = output
BCF STATUS, RP0 ; Bank 0
MAIN:
; === Loop principale (eseguito all’infinito) ===
; … il tuo codice qui …
GOTO MAIN ; torna all’inizio del loop — 2 cicli
💡 La struttura START + MAIN è il template di ogni firmware PIC. START viene eseguito una volta sola all’avvio (configurazione hardware). MAIN è il loop che gira per sempre. Questa separazione è fondamentale: la configurazione in Bank 1 non si ripete ogni ciclo.
② BTFSC e BTFSS — il nucleo degli if/else
BTFSC f,b
BTFSS f,bBTFSS: skip se f<b>=1
BTFSC = Bit Test File, Skip if Clear. BTFSS = Bit Test File, Skip if Set. Testano un singolo bit di qualsiasi registro (SFR o GPR) e saltano la prossima istruzione se la condizione è verificata. Non modificano nessun flag — sono test puri.
Pattern if/else in assembly
; Esempio 1: if (Z == 1) → accendi LED su RB0
BTFSS STATUS, Z ; salta se Z=1 (risultato era zero)
GOTO NO_LED ; Z=0 → salta al ramo else
; Z=1: accendi LED
BSF PORTB, 0 ; RB0 = 1
GOTO FINE_IF
NO_LED:
BCF PORTB, 0 ; RB0 = 0
FINE_IF:
; Esempio 2: while (pulsante su RB7 == 0) aspetta
WAIT:
BTFSC PORTB, 7 ; salta se RB7=0 (premuto)
GOTO WAIT ; RB7=1 → non premuto → ripeti
; RB7=0: pulsante premuto → continua
; Esempio 3: if (Carry == 1) → overflow rilevato
ADDWF REG, F
BTFSC STATUS, C ; salta se C=0 (nessun overflow)
GOTO OVERFLOW ; C=1 → overflow → gestisci
⚠️ BTFSC e BTFSS sono facilmente confusi: SC = Skip if Clear (bit=0), SS = Skip if Set (bit=1). Ricorda: “SC = salta se il bit è spento”, “SS = salta se il bit è settato”. L’errore tipico è usare l’uno al posto dell’altro e ottenere la logica invertita.
③ DECFSZ — il loop counter del PIC
DECFSZ f,dDecrementa il registro e salta l’istruzione successiva solo se il risultato è 0. Non modifica nessun flag — questo è il punto critico che lo distingue da DECF. È il modo idiomatico PIC per costruire un for loop: carica il contatore, esegui il corpo, DECFSZ + GOTO riporta all’inizio.
CNT EQU 0x0C
; Loop che esegue il corpo esattamente 10 volte
MOVLW 0x0A ; N = 10
MOVWF CNT
LOOP:
; — corpo del loop —
BSF PORTB, 0 ; accendi LED
BCF PORTB, 0 ; spegni LED
; — fine corpo —
DECFSZ CNT, F ; CNT–; se CNT=0 salta GOTO
GOTO LOOP ; CNT≠0 → ripeti
; CNT=0 → uscito dal loop, eseguito 10 volte
; Delay software: loop annidato (doppio contatore)
D1 EQU 0x0D
D2 EQU 0x0E
MOVLW 0xFF
MOVWF D1
MOVWF D2
DELAY_OUTER:
DELAY_INNER:
DECFSZ D1, F ; loop interno: 255 volte
GOTO DELAY_INNER
DECFSZ D2, F ; loop esterno: 255 volte
GOTO DELAY_OUTER ; totale: ~65025 iterazioni
🔍 DECFSZ non tocca Z, C, DC. Al contrario di DECF che aggiorna Z, DECFSZ usa il meccanismo di skip interno senza passare per STATUS. Questo è intenzionale: puoi usare DECFSZ dentro un loop senza perdere i flag aritmetici precedenti. Il delay software a doppio contatore è il pattern più classico per generare ritardi temporizzati prima di imparare i timer hardware.
④ INCFSZ — incrementa e salta se zero
INCFSZ f,dSpeculare a DECFSZ: incrementa e salta se il risultato è 0. Lo skip avviene solo quando il valore va da 0xFF a 0x00 (overflow 8-bit). Meno usato di DECFSZ nei loop standard, ma utile quando si conta verso l’alto fino all’overflow, o per saltare quando un indice raggiunge 256.
; Conta da 0 fino all’overflow (256 iterazioni)
CLRF CNT ; CNT = 0
UP_LOOP:
; corpo del loop
INCFSZ CNT, F ; CNT++; skip se CNT→0 (overflow)
GOTO UP_LOOP ; ripeti finché CNT ≠ 0
; uscito: CNT è tornato a 0 dopo 256 iterazioni
; Uso pratico: salta se indice == 0 (dopo wrap-around)
INCFSZ IDX, W ; W = IDX+1, IDX invariato
GOTO NO_WRAP ; IDX+1 ≠ 0 → nessun overflow
; IDX+1 == 0: IDX era 0xFF → gestisci wrap
📊 Riepilogo — le 5 istruzioni a confronto
| Istruzione | Condizione di skip | Flag | Cicli | Uso tipico |
|---|---|---|---|---|
GOTO k | sempre (incondizionato) | — | 2 | loop, salti strutturali |
BTFSC f,b | bit b di f = 0 | — | 1(2) | if (bit==0), while (bit==0) |
BTFSS f,b | bit b di f = 1 | — | 1(2) | if (bit==1), while (bit==1) |
DECFSZ f,d | f−1 = 0 | — | 1(2) | for loop con contatore N→0 |
INCFSZ f,d | f+1 = 0 (overflow 0xFF→0x00) | — | 1(2) | loop verso l’alto fino a overflow |
💡 Nessuna delle cinque modifica STATUS. Questo le rende sicure da usare in qualsiasi contesto senza perdere i flag aritmetici. Il flusso di controllo e l’aritmetica sono ortogonali nel PIC16.
💻 Programma — blink con loop e pulsante
Il primo programma “reale”: un LED che lampeggia su RB0, con un pulsante su RB7 che ferma il lampeggio finché rimane premuto. Struttura classica init + main loop.
; ============================================
; Lezione 08 — Blink con loop e pulsante
; RB0 = LED output
; RB7 = pulsante input (0=premuto, 1=rilasciato)
; ============================================
LIST p=16F84A
#include <p16f84a.inc>
CNT1 EQU 0x0C ; contatore delay esterno
CNT2 EQU 0x0D ; contatore delay interno
ORG 0x00
GOTO START
ORG 0x04
RETFIE
START:
; — Configurazione (una volta sola) —
BSF STATUS, RP0 ; Bank 1
MOVLW 0x80 ; 1000 0000: RB7=input, RB0..6=output
MOVWF TRISB
BCF STATUS, RP0 ; Bank 0
CLRF PORTB ; LED spento
MAIN:
; Controlla pulsante: se premuto (RB7=0) aspetta
WAIT_RELEASE:
BTFSS PORTB, 7 ; salta se RB7=1 (rilasciato)
GOTO WAIT_RELEASE ; RB7=0 → premuto → aspetta
; LED ON
BSF PORTB, 0
CALL DELAY
; LED OFF
BCF PORTB, 0
CALL DELAY
GOTO MAIN
; — Subroutine delay (anticipazione lezione 09) —
DELAY:
MOVLW 0xFF
MOVWF CNT1
MOVWF CNT2
DLY_OUT:
DLY_IN:
DECFSZ CNT1, F
GOTO DLY_IN
DECFSZ CNT2, F
GOTO DLY_OUT
RETURN
END
💡 CALL e RETURN sono usati qui ma non ancora spiegati formalmente — arrivano nella lezione 09. Per ora trattali come “chiama una funzione” e “torna da dove eri”. Il simulatore li supporta già completamente.
🏋️ Prova tu!
- Carica il programma blink nel simulatore. Nella sezione I/O, imposta RB7=1 (pulsante rilasciato) e usa Run per vedere il LED lampeggiare. Poi imposta RB7=0 (premuto): il LED si ferma? Perché il loop WAIT_RELEASE trattiene il flusso?
- Modifica il programma per fare lampeggiare il LED esattamente 5 volte al passaggio da premuto a rilasciato, poi fermarsi. Suggerimento: usa DECFSZ con un contatore da 5 e un flag di stato.
- Sfida: scrivi un programma che conti quante volte viene premuto il pulsante (debounce semplificato: considera solo il fronte di discesa RB7=1→0) e accenda RB1 quando il contatore raggiunge 3, RB2 quando raggiunge 6. Usa DECFSZ o BTFSC/BTFSS su un registro contatore.
❓ Quiz rapido
1. BTFSC STATUS, Z seguito da GOTO FINE: quando viene eseguito il GOTO?
Mostra risposta
Il GOTO viene eseguito quando Z=1 (bit 2 di STATUS è settato). BTFSC salta se il bit è Clear (=0); se Z=0 salta il GOTO; se Z=1 NON salta e il GOTO viene eseguito. Attenzione all’inversione logica: BTFSC Z salta quando il risultato era NON zero.
2. CNT = 1. Esegui DECFSZ CNT, F. Cosa succede al prossimo step?
Mostra risposta
CNT diventa 0, quindi la condizione di skip è vera. L’istruzione successiva (tipicamente GOTO LOOP) viene sostituita da NOP e saltata. Il PC avanza di 2 invece di 1, e il ciclo costa 2 cicli di clock. Il loop termina e l’esecuzione riprende dall’istruzione dopo il GOTO.
3. Qual è la differenza tra DECFSZ CNT, F e DECF CNT, F + BTFSC STATUS, Z?
Mostra risposta
Entrambi decrementano CNT e saltano se il risultato è 0, ma con una differenza critica: DECF modifica Z in STATUS (e può modificare altri flag), mentre DECFSZ non modifica nessun flag. La versione con DECF+BTFSC è 2 istruzioni (2 cicli normali) contro 1 istruzione DECFSZ; inoltre DECFSZ è più robusta perché non disturba STATUS, rendendola sicura dentro loop che usano anche operazioni aritmetiche.
4. Perché GOTO costa sempre 2 cicli, mentre BTFSC/BTFSS costano 1 o 2?
Mostra risposta
Il PIC16 ha un pipeline a 2 stadi: mentre esegue un’istruzione, sta già fetchando la successiva. GOTO deve sempre svuotare il pipeline e ricaricare da un nuovo indirizzo: sempre 2 cicli. BTFSC/BTFSS invece: se non salta (1 ciclo), l’istruzione già fetchata è quella giusta e si usa normalmente. Se salta, l’istruzione fetchata deve essere scartata (NOP’d) e si ripaga il ciclo extra: 2 cicli.
Prossima lezione →
Lezione 09 — CALL, RETURN e lo stack
Subroutine, parametri in W, lo stack hardware a 8 livelli.
Come strutturare programmi complessi in funzioni riutilizzabili.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 08