Lezione 43 di 43

Lezione 42

⭐ PIC16 — From Zero to Hero · Lezione 42 · FINALE

Progetto 5: Sistema Integrato Completo 🏆

Auto-test RAM al boot con pattern 0xAA/0x55 · Scheduler cooperativo round-robin a 3 task
Watchdog software con timeout e soft-reset · Mini-interprete seriale peek/poke/dump

🎯 Il progetto finale — tutto il corso in un sistema

  • RAM test al boot: 80 byte da 0x20 a 0x6F verificati con doppio pattern (0xAA e 0x55). Se una cella non si scrive o non si legge correttamente, l’indirizzo che ha fallito appare su PORTD e il sistema si blocca con PORTB=0xFF — visibile a colpo d’occhio. Tecnica usata nei firmware safety-critical
  • Sequenza di boot strutturata: blink 3× → RAM test → init USART → init task → abilita interrupt → main loop. Ogni fase è verificabile visivamente o via terminale
  • Scheduler cooperativo round-robin: 3 task (TERMINAL, BLINK, ADC) eseguiti a rotazione. Ogni task chiama WDT_KICK e ritorna entro pochi µs — mai blocca. CUR_TASK avanza modulo 3 ad ogni giro
  • Task non bloccante per ADC: avvia conversione (BSF ADCON0,GO), ritorna. Al giro successivo controlla ADCON0,GO — se 0, legge ADRESL. Zero cicli di attesa per il main loop
  • Watchdog software: ISR incrementa WDT_CNT ogni 256µs. Ogni task resetta il contatore con WDT_KICK. Se un task si blocca senza cedere il controllo per oltre 51ms (200 tick), WDT_CHECK rileva il timeout e forza un soft-reset tramite GOTO 0x0000
  • Mini-interprete seriale: prompt interattivo con backspace, echo, parsing token. Comandi peek, poke, dump, ver. Input con READ_LINE (max 23 char), parsing HEX con PARSE_HEX (due nibble → byte)
  • TX_HEX_BYTE e TX_DEC_BYTE: conversione binario→ASCII senza librerie, con SWAPF + ANDLW + tabella nibble. TX_DEC_BYTE usa doppio loop di sottrazione (centinaia poi decine) — stesso pattern visto in Lez.38

📋 Scheda progetto

Lezione: 42 / 42 — ⭐ FINALE
Modulo: 7 — Progetti Completi
Device: PIC16F877A
Prerequisiti: tutto il corso
Difficoltà: ★★★★★
Hardware: UART-USB, LED PORTB, ADC
Output: sistema completo multi-task

🗺️ Mappa RAM

0x20–0x2F variabili sistema
0x30–0x3F task 0 (terminal)
0x40–0x4F task 1 (blink)
0x50–0x5F task 2 (ADC)
0x60–0x6F buffer USART output
0x70–0x7F temporanei shared


Modulo 1 · Boot e RAM test

Sequenza di boot — RAM test + init sistema ① Power-on: configura TRIS, USART 9600, TMR0 no-prescaler ② Blink 3× PORTB (segnale visivo di vita) ③ RAM TEST (0x20–0x6F = 80 byte) write 0xAA → check 0xAA → write 0x55 → check 0x55 → clear FAIL OK ERR_CODE → PORTD · PORTB=0xFF · loop ∞ PORTD=0x00 · abilita GIE/T0IE ④ Init task variables → MAIN_LOOP

💡 Perché testare la RAM al boot? La RAM statica (SRAM) del PIC16F877A è volatile: al power-on ogni bit può essere 0 o 1 in modo imprevedibile. Nei firmware critici (medico, industriale, automotive) si esegue sempre un test di integrità prima di usare la RAM. Il pattern 0xAA (10101010) e 0x55 (01010101) sono complementari — garantiscono che ogni singolo bit sia testato sia a 0 che a 1. Un solo bit stuck-at-1 o stuck-at-0 (difetto di fabbrica o danno ESD) viene rilevato immediatamente. Nei microcontrollori moderni questo test è spesso richiesto dalla norma IEC 60730 per gli elettrodomestici e dalla ISO 26262 per l’automotive.

; ============================================================
; Lezione 42 — Sistema integrato: boot, RAM test, task init
; PIC16F877A @ 4 MHz
; Auto-test RAM al boot: scrive pattern 0xAA/0x55 in ogni cella
; GPR da 0x20 a 0x6F (80 byte): verifica scrittura/lettura
; Se fallisce: codice errore su PORTD + loop forever
; Poi inizializza il sistema multi-task cooperativo
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

; ---- Mappa RAM (Bank 0) ----
; 0x20-0x2F: variabili sistema
; 0x30-0x3F: task 0 (USART/terminale)
; 0x40-0x4F: task 1 (timer/blink LED)
; 0x50-0x5F: task 2 (ADC/misure)
; 0x60-0x6F: buffer output USART

#DEFINE RAM_START   0x20
#DEFINE RAM_END     0x6F
#DEFINE RAM_SIZE    D'80'

; Variabili sistema @ 0x20
SYS_FLAGS   EQU 0x20   ; bit0=wdt_kick, bit1=uart_ready, bit2=adc_ready
TICK_L      EQU 0x21   ; tick sistema (TMR0 overflow count, low)
TICK_H      EQU 0x22   ; tick sistema high
CUR_TASK    EQU 0x23   ; task corrente (0-2)
WDT_CNT     EQU 0x24   ; contatore watchdog software

; Variabili task 0 (terminale) @ 0x30
T0_STATE    EQU 0x30
T0_RXBUF    EQU 0x31   ; buffer 8 byte
T0_RXHEAD   EQU 0x39
T0_RXTAIL   EQU 0x3A
T0_ECHO     EQU 0x3B

; Variabili task 1 (blink) @ 0x40
T1_PERIOD   EQU 0x40   ; periodo blink (tick)
T1_CNT      EQU 0x41   ; contatore
T1_STATE    EQU 0x42   ; 0=off, 1=on

; Variabili task 2 (ADC) @ 0x50
T2_RESULT   EQU 0x50   ; ultimo valore ADC
T2_PERIOD   EQU 0x51   ; periodo campionamento
T2_CNT      EQU 0x52

; Buffer output USART @ 0x60
OUT_BUF     EQU 0x60   ; 16 byte buffer
OUT_HEAD    EQU 0x70
OUT_TAIL    EQU 0x71

; Temporanei (shared 0x70-0x7F visibili da tutti i banchi)
TMP         EQU 0x70
TMP2        EQU 0x71
ERR_CODE    EQU 0x72
FSR_SAVE    EQU 0x73

    CBLOCK  0x74
    ENDC

    ORG     0x0000
    GOTO    BOOT

    ORG     0x0004
; ============================================================
; ISR — gestisce TMR0 overflow per tick sistema
; Ogni overflow: 256µs @ 4MHz senza prescaler
; ============================================================
ISR
    BTFSS   INTCON,T0IF
    RETFIE
    BCF     INTCON,T0IF
    ; Incrementa tick sistema
    INCF    TICK_L,F
    BTFSC   STATUS,Z
    INCF    TICK_H,F
    ; WDT software: incrementa contatore
    INCF    WDT_CNT,F
    RETFIE

; ============================================================
; RAM_TEST — testa tutta la RAM GPR con pattern 0xAA / 0x55
; Salva e ripristina il contenuto originale? No: al boot la RAM
; ha valori non definiti — il test la azzerera' alla fine
; Errore: ERR_CODE = indirizzo fallito, GOTO BOOT_FAIL
; ============================================================
RAM_TEST
    MOVLW   RAM_START
    MOVWF   FSR
    ; --- passata 1: scrivi 0xAA ---
RT_WRITE_AA
    MOVLW   0xAA
    MOVWF   INDF
    INCF    FSR,F
    MOVF    FSR,W
    SUBLW   RAM_END + 1
    BTFSS   STATUS,Z
    GOTO    RT_WRITE_AA
    ; --- passata 2: verifica 0xAA ---
    MOVLW   RAM_START
    MOVWF   FSR
RT_CHECK_AA
    MOVF    INDF,W
    SUBLW   0xAA
    BTFSS   STATUS,Z
    GOTO    RT_FAIL        ; valore non corrisponde
    INCF    FSR,F
    MOVF    FSR,W
    SUBLW   RAM_END + 1
    BTFSS   STATUS,Z
    GOTO    RT_CHECK_AA
    ; --- passata 3: scrivi 0x55 ---
    MOVLW   RAM_START
    MOVWF   FSR
RT_WRITE_55
    MOVLW   0x55
    MOVWF   INDF
    INCF    FSR,F
    MOVF    FSR,W
    SUBLW   RAM_END + 1
    BTFSS   STATUS,Z
    GOTO    RT_WRITE_55
    ; --- passata 4: verifica 0x55 ---
    MOVLW   RAM_START
    MOVWF   FSR
RT_CHECK_55
    MOVF    INDF,W
    SUBLW   0x55
    BTFSS   STATUS,Z
    GOTO    RT_FAIL
    INCF    FSR,F
    MOVF    FSR,W
    SUBLW   RAM_END + 1
    BTFSS   STATUS,Z
    GOTO    RT_CHECK_55
    ; --- azzeramento finale ---
    MOVLW   RAM_START
    MOVWF   FSR
RT_CLEAR
    CLRF    INDF
    INCF    FSR,F
    MOVF    FSR,W
    SUBLW   RAM_END + 1
    BTFSS   STATUS,Z
    GOTO    RT_CLEAR
    RETURN                 ; RAM OK
RT_FAIL
    MOVF    FSR,W
    MOVWF   ERR_CODE       ; salva indirizzo che ha fallito
    GOTO    BOOT_FAIL

; ============================================================
; BOOT — sequenza di avvio
; ============================================================
BOOT
    ; Configura I/O
    BSF     STATUS,RP0
    CLRF    TRISA          ; PORTA output
    CLRF    TRISB          ; PORTB output (LED)
    MOVLW   b'11111111'    ; PORTC input (RX incluso)
    MOVWF   TRISC
    CLRF    TRISD          ; PORTD output (debug/errore)
    ; USART 9600 baud
    MOVLW   D'25'
    MOVWF   SPBRG
    MOVLW   b'00100100'    ; TXEN=1, BRGH=1
    MOVWF   TXSTA
    ; Timer0 senza prescaler
    MOVLW   b'11001000'
    MOVWF   OPTION_REG
    BCF     STATUS,RP0

    MOVLW   b'10010000'    ; SPEN=1, CREN=1
    MOVWF   RCSTA

    ; Pattern di avvio su PORTB: lampeggio 3 volte
    MOVLW   D'3'
    MOVWF   TMP
BOOT_BLINK
    MOVLW   0xFF
    MOVWF   PORTB
    CALL    DELAY_100MS
    CLRF    PORTB
    CALL    DELAY_100MS
    DECFSZ  TMP,F
    GOTO    BOOT_BLINK

    ; RAM test
    MOVLW   0x01           ; codice errore default
    MOVWF   ERR_CODE
    CALL    RAM_TEST

    ; Abilita interrupt TMR0
    MOVLW   b'10100000'    ; GIE=1, T0IE=1
    MOVWF   INTCON

    ; Inizializza variabili sistema
    CLRF    SYS_FLAGS
    CLRF    TICK_L
    CLRF    TICK_H
    CLRF    CUR_TASK
    CLRF    WDT_CNT
    ; Task 1 (blink): periodo 100 tick (~25ms)
    MOVLW   D'100'
    MOVWF   T1_PERIOD
    CLRF    T1_CNT
    CLRF    T1_STATE
    ; Task 2 (ADC): periodo 200 tick (~50ms)
    MOVLW   D'200'
    MOVWF   T2_PERIOD
    CLRF    T2_CNT
    ; Segnala boot OK su PORTD
    MOVLW   0x00
    MOVWF   PORTD
    GOTO    MAIN_LOOP

BOOT_FAIL
    ; Errore RAM: mostra codice su PORTD, LED rosso, loop
    MOVF    ERR_CODE,W
    MOVWF   PORTD
    MOVLW   0xFF
    MOVWF   PORTB          ; tutti LED accesi
FAIL_LOOP
    GOTO    FAIL_LOOP      ; blocca sistema

DELAY_100MS
    MOVLW   D'100'
    MOVWF   TMP2
DLY_OUT MOVLW D'250'
    MOVWF   TMP
DLY_IN  DECFSZ TMP,F
    GOTO    DLY_IN
    DECFSZ  TMP2,F
    GOTO    DLY_OUT
    RETURN

MAIN_LOOP
    ; Placeholder per scheduler (vedi Modulo 2)
    GOTO    MAIN_LOOP

    END

Modulo 2 · Scheduler cooperativo e watchdog software

Scheduler cooperativo round-robin + watchdog software SCHEDULER CUR_TASK 0→1→2→0 TASK 0 TERMINAL RX/TX un byte, ritorna TASK 1 BLINK LED toggle ogni N tick TASK 2 ADC AN0 avvia/leggi, ritorna Watchdog software (ISR incrementa WDT_CNT ogni 256µs) ogni task chiama WDT_KICK (CLRF WDT_CNT) · se WDT_CNT > 200 senza kick → PORTD=0xDD + soft-reset

⚠️ Cooperative vs preemptive multitasking: lo scheduler qui è cooperativo — ogni task deve cedere il controllo volontariamente. Se un task entra in un loop infinito (bug, hardware bloccato) l’intero sistema si blocca. Il watchdog software è la contromisura: misura il tempo tra una WDT_KICK e la successiva, e forza un reset se supera 51ms. Il watchdog hardware del PIC16 (CLRWDT ogni tot) opera allo stesso modo ma a livello hardware con timeout configurabili fino a 2.3s. Nei sistemi RTOS reali (FreeRTOS, Zephyr) lo scheduler è preemptivo con priority: il kernel interrompe i task a scadenza di timeslice senza la loro cooperazione.

; ============================================================
; Lezione 42 — Scheduler cooperativo round-robin + watchdog
; PIC16F877A @ 4 MHz
; 3 task: TASK_TERMINAL, TASK_BLINK, TASK_ADC
; Ogni task viene chiamato nel main loop, uno alla volta
; I task cedono il controllo velocemente (non bloccano)
; Watchdog software: se un task non cede entro N tick → reset
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

; Variabili sistema (stessa mappa di Modulo 1)
SYS_FLAGS   EQU 0x20
TICK_L      EQU 0x21
TICK_H      EQU 0x22
CUR_TASK    EQU 0x23
WDT_CNT     EQU 0x24
T0_STATE    EQU 0x30
T0_RXBUF    EQU 0x31
T0_RXHEAD   EQU 0x39
T0_RXTAIL   EQU 0x3A
T1_PERIOD   EQU 0x40
T1_CNT      EQU 0x41
T1_STATE    EQU 0x42
T2_RESULT   EQU 0x50
T2_PERIOD   EQU 0x51
T2_CNT      EQU 0x52
TMP         EQU 0x70
TMP2        EQU 0x71
WDT_LIMIT   EQU D'200'     ; tick massimi per task (200×256µs ≈ 51ms)
NUM_TASKS   EQU D'3'

    ORG     0x0000
    GOTO    INIT

    ORG     0x0004
ISR
    BTFSS   INTCON,T0IF
    RETFIE
    BCF     INTCON,T0IF
    INCF    TICK_L,F
    BTFSC   STATUS,Z
    INCF    TICK_H,F
    INCF    WDT_CNT,F
    RETFIE

; ============================================================
; WDT_KICK — resetta il contatore watchdog (task coopera)
; Deve essere chiamato frequentemente da ogni task
; ============================================================
WDT_KICK
    CLRF    WDT_CNT
    RETURN

; ============================================================
; WDT_CHECK — verifica che WDT_CNT non abbia superato il limite
; Se supera: segnala errore su PORTD e soft-reset
; ============================================================
WDT_CHECK
    MOVF    WDT_CNT,W
    SUBLW   WDT_LIMIT
    BTFSS   STATUS,C       ; C=0 → WDT_CNT &gt; WDT_LIMIT
    GOTO    WDT_TIMEOUT
    RETURN
WDT_TIMEOUT
    MOVLW   0xDD           ; codice errore watchdog su PORTD
    MOVWF   PORTD
    MOVLW   0xAA
    MOVWF   PORTB          ; pattern visivo
    ; Soft reset tramite GOTO 0x0000
    GOTO    0x0000

; ============================================================
; TASK_TERMINAL — gestisce USART RX/TX (non bloccante)
; Legge UN byte se disponibile, processa, ritorna subito
; ============================================================
TASK_TERMINAL
    CALL    WDT_KICK
    ; Controlla se c'è un byte RX
    BSF     STATUS,RP0
    BTFSS   PIR1,RCIF
    GOTO    TT_NO_RX
    BCF     STATUS,RP0
    ; Leggi byte
    MOVF    RCREG,W
    MOVWF   TMP
    ; Echo: ritrasmetti il byte
    CALL    USART_TX_BYTE
    ; Processa comando (semplice: 'h'=help, 'r'=reset, ecc.)
    MOVF    TMP,W
    SUBLW   'h'
    BTFSC   STATUS,Z
    CALL    CMD_HELP
    MOVF    TMP,W
    SUBLW   'r'
    BTFSC   STATUS,Z
    CALL    CMD_REPORT
    MOVF    TMP,W
    SUBLW   0x0D           ; CR → newline
    BTFSC   STATUS,Z
    CALL    TX_NEWLINE
    GOTO    TT_DONE
TT_NO_RX
    BCF     STATUS,RP0
TT_DONE
    RETURN

USART_TX_BYTE
    ; W contiene il byte da trasmettere
    MOVWF   TMP2
    BSF     STATUS,RP0
UTXW BTFSS   TXSTA,TRMT
    GOTO    UTXW
    BCF     STATUS,RP0
    MOVF    TMP2,W
    MOVWF   TXREG
    RETURN

TX_NEWLINE
    MOVLW   0x0D
    CALL    USART_TX_BYTE
    MOVLW   0x0A
    CALL    USART_TX_BYTE
    RETURN

CMD_HELP
    ; Invia stringa "?:h=help r=report" un byte alla volta
    MOVLW   '?'
    CALL    USART_TX_BYTE
    MOVLW   ':'
    CALL    USART_TX_BYTE
    MOVLW   'h'
    CALL    USART_TX_BYTE
    MOVLW   '='
    CALL    USART_TX_BYTE
    MOVLW   'H'
    CALL    USART_TX_BYTE
    MOVLW   ' '
    CALL    USART_TX_BYTE
    MOVLW   'r'
    CALL    USART_TX_BYTE
    MOVLW   '='
    CALL    USART_TX_BYTE
    MOVLW   'R'
    CALL    USART_TX_BYTE
    CALL    TX_NEWLINE
    RETURN

CMD_REPORT
    ; Invia "T:XXX A:XXX" (tick e ADC)
    MOVLW   'T'
    CALL    USART_TX_BYTE
    MOVLW   ':'
    CALL    USART_TX_BYTE
    MOVF    TICK_L,W
    CALL    TX_HEX          ; invia valore TICK_L in HEX
    MOVLW   ' '
    CALL    USART_TX_BYTE
    MOVLW   'A'
    CALL    USART_TX_BYTE
    MOVLW   ':'
    CALL    USART_TX_BYTE
    MOVF    T2_RESULT,W
    CALL    TX_HEX
    CALL    TX_NEWLINE
    RETURN

TX_HEX
    ; Invia W come 2 cifre HEX ASCII
    MOVWF   TMP2
    SWAPF   TMP2,W
    ANDLW   0x0F
    CALL    HEX_NIBBLE
    CALL    USART_TX_BYTE
    MOVF    TMP2,W
    ANDLW   0x0F
    CALL    HEX_NIBBLE
    CALL    USART_TX_BYTE
    RETURN

HEX_NIBBLE
    ; Converte nibble (0-15) in ASCII '0'-'9','A'-'F'
    ADDLW   0
    SUBLW   D'9'
    BTFSS   STATUS,C
    GOTO    HN_ALPHA
    MOVF    TMP2,W          ; restore
    ANDLW   0x0F
    ADDLW   '0'
    RETURN
HN_ALPHA
    MOVF    TMP2,W
    ANDLW   0x0F
    ADDLW   'A' - D'10'
    RETURN

; ============================================================
; TASK_BLINK — lampeggio LED non bloccante
; Controlla TICK_L, alterna PORTB bit0 ogni T1_PERIOD tick
; ============================================================
TASK_BLINK
    CALL    WDT_KICK
    ; Decrementa contatore
    DECF    T1_CNT,F
    BTFSS   STATUS,Z
    RETURN                 ; non ancora
    ; Ricarica periodo
    MOVF    T1_PERIOD,W
    MOVWF   T1_CNT
    ; Toggle LED (bit0 PORTB)
    MOVLW   0x01
    XORWF   PORTB,F
    ; Aggiorna stato
    MOVF    T1_STATE,F
    BTFSC   STATUS,Z
    GOTO    TB_SET_ON
    CLRF    T1_STATE
    RETURN
TB_SET_ON
    MOVLW   1
    MOVWF   T1_STATE
    RETURN

; ============================================================
; TASK_ADC — campionamento ADC canale AN0 non bloccante
; Avvia conversione, poi torna (non aspetta!)
; Al prossimo giro controlla se la conversione è finita
; ============================================================
TASK_ADC
    CALL    WDT_KICK
    ; Controlla contatore campionamento
    DECF    T2_CNT,F
    BTFSS   STATUS,Z
    GOTO    TA_CHECK_DONE
    ; Ricarica e avvia nuova conversione
    MOVF    T2_PERIOD,W
    MOVWF   T2_CNT
    ; Configura ADC: AN0, Vdd/Vss, ADCS=01 (Fosc/8)
    MOVLW   b'01000001'    ; ADON=1, CHS=000 (AN0), ADCS0=1
    MOVWF   ADCON0
    ; Avvia conversione
    BSF     ADCON0,GO
    RETURN
TA_CHECK_DONE
    ; Controlla se conversione precedente è finita
    BTFSC   ADCON0,GO
    RETURN                 ; ancora in corso
    ; Leggi risultato
    BSF     STATUS,RP0
    MOVF    ADRESL,W
    BCF     STATUS,RP0
    MOVWF   T2_RESULT
    ; Segnala dato pronto
    BSF     SYS_FLAGS,2
    RETURN

; ============================================================
; SCHEDULER — round-robin a 3 task
; ============================================================
SCHEDULER
    ; Esegui task corrente
    MOVF    CUR_TASK,W
    ADDWF   PCL,F
    GOTO    RUN_T0
    GOTO    RUN_T1
    GOTO    RUN_T2

RUN_T0  CALL TASK_TERMINAL : GOTO SCHED_NEXT
RUN_T1  CALL TASK_BLINK    : GOTO SCHED_NEXT
RUN_T2  CALL TASK_ADC      : GOTO SCHED_NEXT

SCHED_NEXT
    ; Avanza task (modulo 3)
    INCF    CUR_TASK,F
    MOVF    CUR_TASK,W
    SUBLW   NUM_TASKS
    BTFSC   STATUS,Z
    CLRF    CUR_TASK       ; 3→0
    ; Controlla watchdog
    CALL    WDT_CHECK
    RETURN

    ORG     0x0100
INIT
    BSF     STATUS,RP0
    CLRF    TRISB
    CLRF    TRISD
    MOVLW   b'11111111'
    MOVWF   TRISC
    MOVLW   D'25'
    MOVWF   SPBRG
    MOVLW   b'00100100'
    MOVWF   TXSTA
    MOVLW   b'11001000'
    MOVWF   OPTION_REG
    BCF     STATUS,RP0
    MOVLW   b'10010000'
    MOVWF   RCSTA
    ; Configura ADCON1: AN0 analogico, resto digitale
    BSF     STATUS,RP0
    MOVLW   b'00001110'    ; PCFG=0111: AN0 analogico, AN1-7 digitali
    MOVWF   ADCON1
    BCF     STATUS,RP0
    ; Init variabili
    CLRF    SYS_FLAGS
    CLRF    TICK_L
    CLRF    TICK_H
    CLRF    CUR_TASK
    CLRF    WDT_CNT
    MOVLW   D'100'
    MOVWF   T1_PERIOD
    MOVLW   D'100'
    MOVWF   T1_CNT
    CLRF    T1_STATE
    MOVLW   D'200'
    MOVWF   T2_PERIOD
    MOVLW   D'200'
    MOVWF   T2_CNT
    CLRF    T2_RESULT
    ; Abilita interrupt
    MOVLW   b'10100000'    ; GIE=1, T0IE=1
    MOVWF   INTCON
    ; Messaggio avvio
    MOVLW   'O'
    CALL    USART_TX_BYTE
    MOVLW   'K'
    CALL    USART_TX_BYTE
    CALL    TX_NEWLINE

MAIN_LOOP
    CALL    SCHEDULER
    GOTO    MAIN_LOOP

    END

Modulo 3 · Mini-interprete seriale peek/poke/dump

Mini-interprete seriale — sessione terminale con PIC16F877A PIC16-SYS v1.0 > ver PIC16-SYS v1.0 > peek 20 00 > poke 20 A5 K > dump 20: A5 00 00 00 24: 00 00 00 00 28: 00 00 00 00 > _ Flusso parsing comando READ_LINE: echo + buffer, ritorna su CR confronto primo byte: p/d/v/? → dispatch peek/poke: PARSE_HEX × 1-2 dump: FSR loop 4×4 byte TX_HEX_BYTE / TX_DEC_BYTE / TX_CRLF PARSE_HEX: 2 cifre ASCII → nibble×16 + nibble cmd sconosciuto → ‘?’ + CR · prompt ‘> ‘ → loop

💡 L’interprete come strumento di debug: con peek e poke puoi leggere e scrivere qualsiasi indirizzo RAM dal terminale durante il funzionamento del sistema — senza arrestarlo, senza reflashare, senza debugger hardware. Questo è il principio dei monitor ROM dei vecchi computer (Apple Monitor, EPROM monitor dei sistemi 8080). Con dump vedi lo stato istantaneo delle variabili. Con poke 40 32 puoi cambiare il periodo del blink task a runtime e vederne l’effetto immediato. Nei sistemi embedded professionali questa funzionalità si chiama “background debug mode” (BDM) o “JTAG debug” — ma il principio è identico: accesso alla RAM in-circuit durante l’esecuzione.

; ============================================================
; Lezione 42 — Mini-interprete seriale completo
; PIC16F877A @ 4 MHz
; Comandi interattivi via terminale seriale:
;   peek ADDR     → legge byte RAM
;   poke ADDR VAL → scrive byte RAM
;   dump          → dump 16 byte da 0x20
;   adc           → legge AN0
;   blink N       → periodo blink N×10ms
;   ver           → versione
;   help          → lista comandi
; Input: testo ASCII con CR, parsing token semplice
; Output: risposta su linea successiva
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

; Variabili interprete
    CBLOCK  0x20
LINE_BUF    : 24    ; buffer input (24 byte, 0x20-0x37)
LINE_LEN            ; lunghezza corrente
LINE_IDX            ; indice parsing
CMD_IDX             ; 0-6 = comando riconosciuto, 0xFF = errore
ARG1                ; primo argomento (HEX)
ARG2                ; secondo argomento (HEX)
; Uscite
OUT_VAL             ; valore da stampare
; Temporanei
TMP
TMP2
TMP3
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; USART: TX e RX
; ============================================================
TX_W
    MOVWF   TMP3
    BSF     STATUS,RP0
TWL BTFSS   TXSTA,TRMT
    GOTO    TWL
    BCF     STATUS,RP0
    MOVF    TMP3,W
    MOVWF   TXREG
    RETURN

RX_W
    BSF     STATUS,RP0
RXL BTFSS   PIR1,RCIF
    GOTO    RXL
    BCF     STATUS,RP0
    MOVF    RCREG,W
    RETURN

TX_CRLF
    MOVLW   0x0D
    CALL    TX_W
    MOVLW   0x0A
    CALL    TX_W
    RETURN

TX_SPACE
    MOVLW   ' '
    CALL    TX_W
    RETURN

; ============================================================
; TX_HEX_BYTE — invia W in formato "XX" (2 cifre HEX)
; ============================================================
TX_HEX_BYTE
    MOVWF   TMP2
    ; nibble alto
    SWAPF   TMP2,W
    ANDLW   0x0F
    ADDLW   0
    CALL    NIBBLE_TO_HEX
    CALL    TX_W
    ; nibble basso
    MOVF    TMP2,W
    ANDLW   0x0F
    CALL    NIBBLE_TO_HEX
    CALL    TX_W
    RETURN

NIBBLE_TO_HEX
    MOVWF   TMP
    SUBLW   D'9'
    BTFSS   STATUS,C
    GOTO    N2H_AF
    MOVF    TMP,W
    ADDLW   '0'
    RETURN
N2H_AF
    MOVF    TMP,W
    ADDLW   'A' - D'10'
    RETURN

; ============================================================
; TX_DEC_BYTE — invia W in decimale (3 cifre, 000-255)
; ============================================================
TX_DEC_BYTE
    MOVWF   TMP2
    ; Centinaia
    CLRF    TMP3
TD_H MOVLW  D'100'
    SUBWF   TMP2,W
    BTFSS   STATUS,C
    GOTO    TD_H_OUT
    MOVWF   TMP2
    INCF    TMP3,F
    GOTO    TD_H
TD_H_OUT
    MOVF    TMP3,W
    ADDLW   '0'
    CALL    TX_W
    ; Decine
    CLRF    TMP3
TD_T MOVLW  D'10'
    SUBWF   TMP2,W
    BTFSS   STATUS,C
    GOTO    TD_T_OUT
    MOVWF   TMP2
    INCF    TMP3,F
    GOTO    TD_T
TD_T_OUT
    MOVF    TMP3,W
    ADDLW   '0'
    CALL    TX_W
    ; Unità
    MOVF    TMP2,W
    ADDLW   '0'
    CALL    TX_W
    RETURN

; ============================================================
; READ_LINE — legge una linea da USART con echo
; Termina su CR (0x0D), max 23 caratteri
; ============================================================
READ_LINE
    CLRF    LINE_LEN
    MOVLW   LINE_BUF
    MOVWF   FSR
RL_LOOP
    CALL    RX_W
    ; CR = fine linea
    MOVWF   TMP
    SUBLW   0x0D
    BTFSC   STATUS,Z
    GOTO    RL_DONE
    ; BS (0x08) = backspace
    MOVF    TMP,W
    SUBLW   0x08
    BTFSC   STATUS,Z
    GOTO    RL_BS
    ; Carattere normale: echo e buffer
    MOVF    TMP,W
    CALL    TX_W
    MOVF    TMP,W
    MOVWF   INDF
    INCF    FSR,F
    INCF    LINE_LEN,F
    ; Limite buffer
    MOVF    LINE_LEN,W
    SUBLW   D'23'
    BTFSS   STATUS,Z
    GOTO    RL_LOOP
    GOTO    RL_DONE        ; buffer pieno
RL_BS
    MOVF    LINE_LEN,F
    BTFSC   STATUS,Z
    GOTO    RL_LOOP        ; niente da cancellare
    DECF    FSR,F
    DECF    LINE_LEN,F
    MOVLW   0x08           ; BS
    CALL    TX_W
    MOVLW   ' '
    CALL    TX_W
    MOVLW   0x08
    CALL    TX_W
    GOTO    RL_LOOP
RL_DONE
    ; Aggiungi terminatore 0x00
    CLRF    INDF
    CALL    TX_CRLF
    RETURN

; ============================================================
; PARSE_HEX — legge 2 cifre HEX dal buffer a LINE_IDX
; Risultato in W (0x00-0xFF), LINE_IDX avanza di 2
; ============================================================
PARSE_HEX
    MOVLW   LINE_BUF
    ADDWF   LINE_IDX,W
    MOVWF   FSR
    ; Cifra alta
    MOVF    INDF,W
    CALL    ASCII_TO_HEX
    MOVWF   TMP
    BCF     STATUS,C
    RLF     TMP,F
    RLF     TMP,F
    RLF     TMP,F
    RLF     TMP,F
    ; Cifra bassa
    INCF    FSR,F
    MOVF    INDF,W
    CALL    ASCII_TO_HEX
    IORWF   TMP,W
    INCF    LINE_IDX,F
    INCF    LINE_IDX,F
    RETURN

ASCII_TO_HEX
    ; '0'-'9' → 0-9, 'A'-'F'/'a'-'f' → 10-15
    MOVWF   TMP2
    SUBLW   '9'
    BTFSS   STATUS,C
    GOTO    A2H_ALPHA
    MOVF    TMP2,W
    SUBLW   '0'
    RETURN
A2H_ALPHA
    MOVF    TMP2,W
    ANDLW   0xDF           ; maiuscolo
    SUBLW   'A'
    SUBLW   0xFF
    ADDLW   D'10'
    RETURN

; ============================================================
; DO_PEEK — peek ADDR: legge RAM[ARG1]
; ============================================================
DO_PEEK
    MOVF    ARG1,W
    MOVWF   FSR
    MOVF    INDF,W
    CALL    TX_HEX_BYTE
    CALL    TX_CRLF
    RETURN

; ============================================================
; DO_POKE — poke ADDR VAL: scrive VAL in RAM[ARG1]
; ============================================================
DO_POKE
    MOVF    ARG1,W
    MOVWF   FSR
    MOVF    ARG2,W
    MOVWF   INDF
    MOVLW   'K'
    CALL    TX_W
    CALL    TX_CRLF
    RETURN

; ============================================================
; DO_DUMP — dump: stampa 16 byte da 0x20
; Formato: "20: AA BB CC DD ..."
; ============================================================
DO_DUMP
    MOVLW   0x20
    MOVWF   FSR
    MOVLW   D'4'           ; 4 righe da 4 byte
    MOVWF   TMP3
DD_ROW
    MOVF    FSR,W
    CALL    TX_HEX_BYTE
    MOVLW   ':'
    CALL    TX_W
    MOVLW   D'4'
    MOVWF   TMP2
DD_BYTE
    CALL    TX_SPACE
    MOVF    INDF,W
    CALL    TX_HEX_BYTE
    INCF    FSR,F
    DECFSZ  TMP2,F
    GOTO    DD_BYTE
    CALL    TX_CRLF
    DECFSZ  TMP3,F
    GOTO    DD_ROW
    RETURN

; ============================================================
; DO_VER — stampa stringa versione
; ============================================================
DO_VER
    ; "PIC16-SYS v1.0"
    MOVLW   'P' : CALL TX_W
    MOVLW   'I' : CALL TX_W
    MOVLW   'C' : CALL TX_W
    MOVLW   '1' : CALL TX_W
    MOVLW   '6' : CALL TX_W
    MOVLW   '-' : CALL TX_W
    MOVLW   'S' : CALL TX_W
    MOVLW   'Y' : CALL TX_W
    MOVLW   'S' : CALL TX_W
    MOVLW   ' ' : CALL TX_W
    MOVLW   'v' : CALL TX_W
    MOVLW   '1' : CALL TX_W
    MOVLW   '.' : CALL TX_W
    MOVLW   '0' : CALL TX_W
    CALL    TX_CRLF
    RETURN

; ============================================================
; MAIN_LOOP — prompt e dispatch comandi
; ============================================================
    ORG     0x0140
INIT
    BSF     STATUS,RP0
    CLRF    TRISB
    CLRF    TRISD
    MOVLW   b'11111111'
    MOVWF   TRISC
    MOVLW   D'25'
    MOVWF   SPBRG
    MOVLW   b'00100100'
    MOVWF   TXSTA
    BCF     STATUS,RP0
    MOVLW   b'10010000'
    MOVWF   RCSTA
    ; Banner
    CALL    DO_VER
    MOVLW   '>'
    CALL    TX_W
    MOVLW   ' '
    CALL    TX_W

MAIN_LOOP
    CALL    READ_LINE
    CLRF    LINE_IDX

    ; Confronta con "peek" (confronto 4 byte)
    MOVLW   LINE_BUF
    MOVWF   FSR
    MOVF    INDF,W
    SUBLW   'p'
    BTFSS   STATUS,Z
    GOTO    TRY_DUMP
    INCF    FSR,F
    MOVF    INDF,W
    SUBLW   'e'
    BTFSS   STATUS,Z
    GOTO    TRY_POKE
    ; È "peek" o "poke"? Controlla 3° char
    INCF    FSR,F
    MOVF    INDF,W
    SUBLW   'e'
    BTFSC   STATUS,Z
    GOTO    DO_PEEK_CMD
TRY_POKE
    ; reset FSR
    MOVLW   LINE_BUF + D'2'
    MOVWF   FSR
    MOVF    INDF,W
    SUBLW   'k'
    BTFSS   STATUS,Z
    GOTO    TRY_DUMP
    ; "poke": leggi ADDR e VAL
    MOVLW   D'5'           ; salta &quot;poke &quot;
    MOVWF   LINE_IDX
    CALL    PARSE_HEX
    MOVWF   ARG1
    INCF    LINE_IDX,F     ; salta spazio
    CALL    PARSE_HEX
    MOVWF   ARG2
    CALL    DO_POKE
    GOTO    PROMPT

DO_PEEK_CMD
    MOVLW   D'5'           ; salta &quot;peek &quot;
    MOVWF   LINE_IDX
    CALL    PARSE_HEX
    MOVWF   ARG1
    CALL    DO_PEEK
    GOTO    PROMPT

TRY_DUMP
    MOVLW   LINE_BUF
    MOVWF   FSR
    MOVF    INDF,W
    SUBLW   'd'
    BTFSS   STATUS,Z
    GOTO    TRY_VER
    CALL    DO_DUMP
    GOTO    PROMPT

TRY_VER
    MOVLW   LINE_BUF
    MOVWF   FSR
    MOVF    INDF,W
    SUBLW   'v'
    BTFSS   STATUS,Z
    GOTO    CMD_UNKNOWN
    CALL    DO_VER
    GOTO    PROMPT

CMD_UNKNOWN
    MOVLW   '?'
    CALL    TX_W
    CALL    TX_CRLF

PROMPT
    MOVLW   '>'
    CALL    TX_W
    MOVLW   ' '
    CALL    TX_W
    GOTO    MAIN_LOOP

    END

🧪 Prova tu

  1. Carica Modulo 1 (RAM test). Inizializza FSR=0x20. Simula il test: dopo RT_WRITE_AA tutti i byte 0x20-0x6F devono essere 0xAA. Poi corrompi manualmente un byte (INDF=0x00 durante la verifica). Verifica che RT_FAIL venga raggiunto e ERR_CODE contenga l’indirizzo corretto. Poi simula RT_CLEAR: alla fine tutti i byte devono essere 0x00.
  2. Carica Modulo 2 (scheduler). Imposta CUR_TASK=0, WDT_CNT=0. Chiama SCHEDULER tre volte e traccia CUR_TASK: deve andare 0→1→2→0. Poi simula un task bloccato: imposta WDT_CNT=201 (sopra WDT_LIMIT=200) e chiama WDT_CHECK. Verifica che PORTD diventi 0xDD.
  3. Testa READ_LINE (Modulo 3): simula l’arrivo dei byte ‘p’,’e’,’e’,’k’,’ ‘,’2′,’0’,0x0D. Dopo READ_LINE, LINE_BUF deve contenere “peek 20” (7 byte + 0x00 terminatore), LINE_LEN=7. Poi chiama PARSE_HEX con LINE_IDX=5: deve ritornare 0x20 (due cifre ‘2’,’0′).
  4. Sfida finale: aggiungi il comando fill ADDR VAL N — scrive VAL nei byte a partire da ADDR per N byte. Richiede il parsing di tre argomenti HEX e un loop FSR. Quante istruzioni aggiuntive servono? Come modificheresti il dispatch per distinguerlo da dump?

❓ Quiz finale del corso

1. Il soft-reset in WDT_CHECK usa GOTO 0x0000. Perché questo non è identico a un vero power-on reset, e quali variabili sarebbero diverse?

Mostra risposta

Un power-on reset (POR) inizializza l’hardware: i registri SFR tornano ai valori di reset documentati nel datasheet (TRISB=0xFF, OPTION_REG=0xFF, INTCON=0x00, ecc.). Un GOTO 0x0000 è solo un salto al vettore di reset — il codice ricomincia dall’INIT, ma i registri SFR conservano il valore che avevano al momento del watchdog timeout. In particolare: PORTB/PORTD rimangono con i valori scritti prima del timeout, INTCON conserva GIE/T0IE abilitati (l’ISR potrebbe sparare prima che INIT li riconfiguri), ADCON0 potrebbe avere una conversione in corso. Il codice INIT deve quindi sempre riconfigurare esplicitamente tutti i registri critici, non fidarsi dei valori residui. Un watchdog hardware (CLRWDT) invece genera un vero reset hardware che inizializza tutti gli SFR.


2. TASK_ADC avvia la conversione ADC e ritorna senza aspettare. Come garantisce di non avviare una nuova conversione mentre quella precedente è ancora in corso?

Mostra risposta

TASK_ADC usa un doppio percorso: la prima parte (avvio) dipende dal decremento di T2_CNT — viene raggiunta solo quando T2_CNT arriva a zero (il task non avvia conversioni consecutive senza pausa). La seconda parte (TA_CHECK_DONE) viene eseguita ogni giro anche se T2_CNT è diverso da zero: controlla ADCON0,GO — se il bit GO è ancora 1 la conversione è in corso e il task ritorna immediatamente senza fare nulla. Solo quando GO torna a 0 (conversione completata) legge ADRESL. La sequenza è quindi: avvia → GO=1 → torna → giro dopo: GO ancora 1 → torna → … → GO=0 → leggi. Non c’è mai una seconda BSF ADCON0,GO mentre la prima conversione è in corso perché la parte di avvio si attiva solo ogni T2_PERIOD tick — molto dopo che la conversione (max 20µs) è terminata.


3. Il corso ha coperto 35 istruzioni del PIC16 mid-range. Quali tre istruzioni, tra quelle studiate, consideri le più “potenti” e perché — quelle senza cui sarebbe impossibile scrivere i progetti di questo modulo?

Mostra risposta

Non esiste una risposta unica, ma le candidate più forti sono: BTFSS/BTFSC — il cuore di ogni decisione condizionale; senza di loro non esistono if, while, for né macchine a stati. Ogni algoritmo complesso di questo corso dipende da questi due. DECFSZ — rende possibili i loop con una sola istruzione; senza di lei ogni ciclo richiederebbe DECF + BTFSC Z separati. È l’istruzione che rende l’assembly PIC più compatto di altri ISA 8-bit. MOVWF/MOVF — tecnicamente due istruzioni, ma forman la coppia inseparabile per muovere dati. Tutto il resto (aritmetica, logica, I/O) dipende dal poter caricare e salvare il registro W. Menzione speciale a ADDWF PCL,F: non è “potente” di per sé, ma permette jump table, lookup table e dispatch — tre pattern usati in ogni progetto avanzato del corso.

🏆 Complimenti — Hai completato il corso!

Hai percorso 43 lezioni, 8 moduli, oltre 4000 righe di assembly PIC16.
Dalla prima LED che lampeggia al sistema multi-task con scheduler, watchdog e interprete seriale.

Assembly base · I/O digitale · Timer · Interrupt · USART · SPI · I²C · ADC · PWM · EEPROM
FSM · Lookup table · Indirizzamento indiretto · Media mobile · Interpolazione · Multitasking


Dove andare adesso?

PIC18 / PIC32
Architettura Harvard avanzata, pipeline, istruzioni 16-bit, USB, CAN, Ethernet

ARM Cortex-M
STM32, nRF52, RP2040 — il mondo embedded moderno. Tutto ciò che hai imparato si trasferisce

RTOS
FreeRTOS, Zephyr — lo scheduler cooperativo di questa lezione è il punto di partenza

© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · 42 + 1 lezioni complete