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
💡 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
⚠️ 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 > 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
💡 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 "poke " 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 "peek " 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
- 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.
- 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.
- 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′).
- 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 dadump?
❓ 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