Lezione 38
🎓 PIC16 — From Zero to Hero · Lezione 38 · Modulo 7
Progetto 1: Termostato Digitale 🌡️
DS18B20 via 1-Wire · Display 7 segmenti multiplexato · Relè con isteresi
Setpoint persistente su EEPROM interna · Pulsanti UP/DOWN con debounce
🎯 Cosa costruisci in questa lezione
- Lettura temperatura DS18B20 con risoluzione 9-bit (0.5°C): sequenza completa Skip ROM → Convert T → attesa 100ms → Read Scratchpad → raw → °C con segno
- Display 3 digit 7 segmenti comune-anodo con multiplexing software: PORTD = segmenti, PORTC bit2:0 = anodi, 5ms/digit → 60Hz percepito
- Tabella RETLW per decodifica cifre 0–9 + blank + minus + punto decimale
- Isteresi termostato: relay ON se TEMP > SET+HYST, relay OFF se TEMP < SET–HYST — elimina il “hunting” continuo vicino al setpoint
- EEPROM interna PIC16F877A: sequenza unlock obbligatoria (0x55/0xAA su EECON2), magic byte 0xA5 per validare i dati salvati, load defaults se prima accensione
- Pulsanti UP/DOWN con debounce software: confronto con lettura precedente, ignora edge instabili
- Ciclo integrato: mux × 3 (15ms) → pulsanti → relay → ogni ~1s leggi temperatura
📋 Scheda progetto
Lezione: 38 / 42
Modulo: 7 — Progetti Completi
Device: PIC16F877A
Prerequisiti: tutti i moduli precedenti
Difficoltà: ★★★★★
Hardware: DS18B20, 3× display, relay, 2× pulsanti
Codice: ~200 istruzioni, 3 moduli
Modulo 1 · Lettura DS18B20 e controllo relay
Il cuore del termostato è la lettura 1-Wire del DS18B20. Con risoluzione 9-bit la conversione richiede solo 93.75ms (invece dei 750ms della risoluzione massima 12-bit), permettendo un ciclo di lettura ogni secondo senza bloccare il sistema. La logica di controllo relay con isteresi evita il problema del hunting: senza isteresi il relay commuterebbe centinaia di volte al minuto quando la temperatura oscilla attorno al setpoint, logorando i contatti meccanici e causando instabilità.
; ============================================================ ; Lezione 38 — Termostato digitale: sensore DS18B20 ; PIC16F877A @ 4 MHz ; Modulo 1/3: lettura temperatura + storage in RAM ; Risoluzione 9-bit (0.5°C), range -55 … +125°C ; TEMP_INT = parte intera, TEMP_SIGN = 0 pos / 1 neg ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF #DEFINE OW_TRIS TRISC,0 #DEFINE OW_PIN PORTC,0 CBLOCK 0x20 ; Temperatura corrente TEMP_INT ; gradi interi (0-125) TEMP_HALF ; 0=intero, 1=+0.5°C TEMP_SIGN ; 0=positivo, 1=negativo TEMP_RAW_L ; byte grezzo dal DS18B20 TEMP_RAW_H ; Setpoint (caricato da EEPROM) SET_INT ; setpoint intero SET_HALF ; setpoint mezzo grado ; Stato controllo RELAY_STATE ; 0=off, 1=on HYSTERESIS ; isteresi in 0.5°C units (default 2 = 1°C) FLAGS ; bit0=temp_valid, bit1=relay_on, bit2=eep_ok ; 1-Wire temporanei OW_BYTE OW_BIT_CNT DLY_H DLY_L ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; DELAY_500US — ritardo 500µs (loop 166 × 3cicli) ; ============================================================ DELAY_500US MOVLW D'166' MOVWF DLY_L D5 DECFSZ DLY_L,F GOTO D5 RETURN ; ============================================================ ; DELAY_MS — ritardo W millisecondi (max 255ms) ; ============================================================ DELAY_MS MOVWF DLY_H DMS CALL DELAY_500US CALL DELAY_500US DECFSZ DLY_H,F GOTO DMS RETURN ; ============================================================ ; OW_RESET — reset 1-Wire, ritorna Z=1 se no device ; ============================================================ OW_RESET BSF STATUS,RP0 BCF OW_TRIS ; output BCF STATUS,RP0 BCF OW_PIN ; LOW MOVLW D'160' ; 480µs MOVWF DLY_L OWR1 DECFSZ DLY_L,F GOTO OWR1 BSF STATUS,RP0 BSF OW_TRIS ; input BCF STATUS,RP0 MOVLW D'23' ; 70µs MOVWF DLY_L OWR2 DECFSZ DLY_L,F GOTO OWR2 BCF STATUS,Z BTFSC OW_PIN BSF STATUS,Z ; Z=1: nessun device MOVLW D'137' ; 410µs MOVWF DLY_L OWR3 DECFSZ DLY_L,F GOTO OWR3 RETURN ; ============================================================ ; OW_WRITE_BYTE — invia OW_BYTE sul bus 1-Wire ; ============================================================ OW_WRITE_BYTE MOVLW D'8' MOVWF OW_BIT_CNT OWW BTFSS OW_BYTE,0 GOTO OWW0 ; Write 1: basso 6µs, poi rilascia 64µs BSF STATUS,RP0 BCF OW_TRIS BCF STATUS,RP0 BCF OW_PIN NOP NOP NOP NOP NOP NOP BSF STATUS,RP0 BSF OW_TRIS BCF STATUS,RP0 MOVLW D'19' MOVWF DLY_L OWW1 DECFSZ DLY_L,F GOTO OWW1 GOTO OWWN OWW0 ; Write 0: basso 60µs BSF STATUS,RP0 BCF OW_TRIS BCF STATUS,RP0 BCF OW_PIN MOVLW D'18' MOVWF DLY_L OWW0L DECFSZ DLY_L,F GOTO OWW0L BSF STATUS,RP0 BSF OW_TRIS BCF STATUS,RP0 MOVLW D'3' MOVWF DLY_L OWW0R DECFSZ DLY_L,F GOTO OWW0R OWWN BCF STATUS,C RRF OW_BYTE,F DECFSZ OW_BIT_CNT,F GOTO OWW RETURN ; ============================================================ ; OW_READ_BYTE — leggi un byte in OW_BYTE ; ============================================================ OW_READ_BYTE MOVLW D'8' MOVWF OW_BIT_CNT CLRF OW_BYTE OWR_B BSF STATUS,RP0 BCF OW_TRIS BCF STATUS,RP0 BCF OW_PIN NOP NOP ; 2µs low BSF STATUS,RP0 BSF OW_TRIS BCF STATUS,RP0 NOP NOP NOP NOP NOP ; 5µs poi campiona BCF STATUS,C BTFSC OW_PIN BSF STATUS,C RRF OW_BYTE,F ; C → MSB (accumula LSB first) MOVLW D'17' MOVWF DLY_L OWRD DECFSZ DLY_L,F GOTO OWRD ; resto slot DECFSZ OW_BIT_CNT,F GOTO OWR_B RETURN ORG 0x0100 INIT BSF STATUS,RP0 CLRF TRISB CLRF TRISD BSF TRISC,0 ; RC0 input (idle 1-Wire) BCF STATUS,RP0 BCF PORTC,0 ; prepara low BCF PORTB,7 ; relay off ; Carica setpoint default (25°C) MOVLW D'25' MOVWF SET_INT CLRF SET_HALF MOVLW D'2' ; isteresi = 1°C (2 half-degree units) MOVWF HYSTERESIS CLRF FLAGS CLRF RELAY_STATE ; ============================================================ ; MAIN LOOP — leggi temperatura ogni ~1s ; ============================================================ MAIN ; Step 1: Reset CALL OW_RESET BTFSC STATUS,Z GOTO NO_SENSOR ; Step 2: Skip ROM + Convert T MOVLW 0xCC MOVWF OW_BYTE CALL OW_WRITE_BYTE MOVLW 0x44 MOVWF OW_BYTE CALL OW_WRITE_BYTE ; Step 3: Attendi conversione 9-bit = 93.75ms MOVLW D'100' CALL DELAY_MS ; Step 4: Reset + Skip ROM + Read Scratchpad CALL OW_RESET MOVLW 0xCC MOVWF OW_BYTE CALL OW_WRITE_BYTE MOVLW 0xBE MOVWF OW_BYTE CALL OW_WRITE_BYTE ; Leggi TEMP_L e TEMP_H CALL OW_READ_BYTE MOVF OW_BYTE,W MOVWF TEMP_RAW_L CALL OW_READ_BYTE MOVF OW_BYTE,W MOVWF TEMP_RAW_H ; Step 5: Converti ; Temperatura negativa: TEMP_RAW_H bit3=1 → complemento a 2 CLRF TEMP_SIGN BTFSC TEMP_RAW_H,3 ; segno (bit11 del raw) BSF TEMP_SIGN,0 ; negativo ; Parte intera: TEMP_RAW_L bit7..1 (/ 2) BCF STATUS,C RRF TEMP_RAW_L,W MOVWF TEMP_INT ; Mezzo grado: TEMP_RAW_L bit0 CLRF TEMP_HALF BTFSC TEMP_RAW_L,0 BSF TEMP_HALF,0 BSF FLAGS,0 ; temp valida MOVF TEMP_INT,W MOVWF PORTD ; output grezzo su PORTD (debug) CALL CHECK_RELAY CALL DISPLAY_TEMP ; Attesa ~900ms restanti MOVLW D'200' CALL DELAY_MS MOVLW D'200' CALL DELAY_MS MOVLW D'200' CALL DELAY_MS MOVLW D'200' CALL DELAY_MS GOTO MAIN NO_SENSOR BCF FLAGS,0 ; Lampeggia PORTD come errore COMF PORTD,F MOVLW D'250' CALL DELAY_MS GOTO MAIN ; ============================================================ ; CHECK_RELAY — logica termostato con isteresi ; Se TEMP > SET+HYST → relay ON (raffredda) ; Se TEMP < SET-HYST → relay OFF (smetti di raffreddare) ; (per riscaldatore: inverti la logica) ; ============================================================ CHECK_RELAY ; Confronta TEMP_INT con SET_INT MOVF SET_INT,W SUBWF TEMP_INT,W ; TEMP - SET BTFSS STATUS,C ; C=0 → TEMP < SET GOTO RELAY_OFF BTFSS STATUS,Z ; uguali? GOTO RELAY_ON_CHECK ; Uguali: controlla mezzo grado MOVF TEMP_HALF,W SUBWF SET_HALF,W BTFSS STATUS,C GOTO RELAY_ON_CHECK GOTO RELAY_OFF RELAY_ON_CHECK ; TEMP > SET: accendi se non già acceso con isteresi MOVF RELAY_STATE,F BTFSC STATUS,Z ; relay già off? BSF PORTB,7 ; relay ON BTFSC STATUS,Z BSF RELAY_STATE,0 RETURN RELAY_OFF ; TEMP < SET: spegni relay CLRF RELAY_STATE BCF PORTB,7 RETURN ; ============================================================ ; DISPLAY_TEMP — stub (vedremo Modulo 2: display 7seg) ; Per ora: PORTB basso = parte intera temperatura ; ============================================================ DISPLAY_TEMP MOVF TEMP_INT,W MOVWF PORTB RETURN END
Modulo 2 · Display 7 segmenti multiplexato
🔢 Decodifica BCD con loop di sottrazione: il PIC16 non ha istruzione di divisione. Per separare decine e unità da un numero 0–99, si sottrae 10 ripetutamente contando le sottrazioni (= decine). Il ciclo BCD_SPLIT costa al massimo 9 iterazioni × 4 cicli = 36µs — trascurabile rispetto ai 5ms del periodo di multiplexing.
; ============================================================ ; Lezione 38 — Termostato: display 7 segmenti multiplexato ; PIC16F877A @ 4 MHz ; 3 digit × 7 segmenti: PORTD = segmenti, PORTC bit2:0 = anodi ; Display comune-anodo: segmento acceso = PORTD bit = 0 ; Multiplexing: un digit alla volta, ~5ms each → 60Hz refresh ; Layout PORTD: bit7=dp, bit6=g, bit5=f, bit4=e, bit3=d, bit2=c, bit1=b, bit0=a ; Digit1=decine, Digit2=unità, Digit3=mezzo grado (. o blank) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Display DISP_D0 ; segmenti digit 0 (centinaia/decine) DISP_D1 ; segmenti digit 1 (unità) DISP_D2 ; segmenti digit 2 (decimale / segno) DISP_IDX ; 0,1,2 quale digit è attivo MUX_CNT ; contatore per multiplexing ; Temperatura TEMP_INT TEMP_HALF TEMP_SIGN ; Setpoint e stato SET_INT SET_HALF RELAY_STATE FLAGS ; Calcolo cifre TMP_BCD ; valore da decodificare TMP_TENS ; decine TMP_UNITS ; unità ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; TABELLA 7 SEGMENTI — comune anodo (0=segmento acceso) ; Ordine bit: dp g f e d c b a ; 0=11000000, 1=11111001, 2=10100100, 3=10110000 ; 4=10011001, 5=10010010, 6=10000010, 7=11111000 ; 8=10000000, 9=10010000 ; ============================================================ ORG 0x0020 SEG7_TABLE ADDWF PCL,F RETLW b'11000000' ; 0 RETLW b'11111001' ; 1 RETLW b'10100100' ; 2 RETLW b'10110000' ; 3 RETLW b'10011001' ; 4 RETLW b'10010010' ; 5 RETLW b'10000010' ; 6 RETLW b'11111000' ; 7 RETLW b'10000000' ; 8 RETLW b'10010000' ; 9 SEG7_BLANK RETLW b'11111111' ; blank (tutti spenti) SEG7_MINUS RETLW b'10111111' ; - (solo segmento g) SEG7_HALF RETLW b'01000000' ; 0 con punto decimale (dp=0) ; ============================================================ ; BCD_SPLIT — divide TMP_BCD in TMP_TENS e TMP_UNITS ; Algoritmo: sottrai 10 finché negativo, conta le sottrazioni ; ============================================================ BCD_SPLIT CLRF TMP_TENS MOVF TMP_BCD,W MOVWF TMP_UNITS BCD_LOOP MOVLW D'10' SUBWF TMP_UNITS,W ; TMP_UNITS - 10 BTFSS STATUS,C ; C=0 → TMP_UNITS < 10 RETURN ; done MOVWF TMP_UNITS ; aggiorna resto INCF TMP_TENS,F ; decine++ GOTO BCD_LOOP ; ============================================================ ; UPDATE_DISPLAY — aggiorna DISP_D0,D1,D2 da TEMP_INT/HALF/SIGN ; ============================================================ UPDATE_DISPLAY ; Calcola cifre MOVF TEMP_INT,W MOVWF TMP_BCD CALL BCD_SPLIT ; Digit 0: decine (0 se < 10 → blank) MOVF TMP_TENS,W BTFSC STATUS,Z ; decine = 0? GOTO TENS_BLANK CALL SEG7_TABLE MOVWF DISP_D0 GOTO TENS_DONE TENS_BLANK MOVLW D'10' ; indice blank CALL SEG7_TABLE MOVWF DISP_D0 TENS_DONE ; Se negativo: metti segno - al posto delle decine BTFSC TEMP_SIGN,0 MOVLW D'11' ; indice minus BTFSC TEMP_SIGN,0 CALL SEG7_TABLE BTFSC TEMP_SIGN,0 MOVWF DISP_D0 ; Digit 1: unità MOVF TMP_UNITS,W CALL SEG7_TABLE MOVWF DISP_D1 ; Digit 2: punto decimale se 0.5°C, altrimenti blank MOVLW D'10' ; blank default BTFSC TEMP_HALF,0 MOVLW D'12' ; indice half (0 con dp) CALL SEG7_TABLE MOVWF DISP_D2 RETURN ; ============================================================ ; MUX_DISPLAY — accende un digit alla volta (chiamare ~ogni 5ms) ; DISP_IDX avanza 0→1→2→0 ; ============================================================ MUX_DISPLAY ; Spegni tutti gli anodi BCF STATUS,RP0 MOVLW b'00000111' IORWF PORTC,F ; anodi = 1 (common anode: 1=spento) ; Seleziona dati per il digit corrente MOVF DISP_IDX,W ADDWF PCL,F GOTO MUX_D0 GOTO MUX_D1 GOTO MUX_D2 MUX_D0 MOVF DISP_D0,W MOVWF PORTD BCF PORTC,0 ; anodo 0 = 0 (acceso) GOTO MUX_NEXT MUX_D1 MOVF DISP_D1,W MOVWF PORTD BCF PORTC,1 GOTO MUX_NEXT MUX_D2 MOVF DISP_D2,W MOVWF PORTD BCF PORTC,2 MUX_NEXT INCF DISP_IDX,F MOVF DISP_IDX,W SUBLW D'3' BTFSC STATUS,Z CLRF DISP_IDX ; wrap 2→0 RETURN ORG 0x0140 INIT BSF STATUS,RP0 CLRF TRISB CLRF TRISD MOVLW b'11111000' ; RC0=OW, RC3/4=I²C, RC2:0 anodi out MOVWF TRISC BCF STATUS,RP0 ; Anodi tutti spenti (common anode: 1=off) MOVLW b'00000111' IORWF PORTC,F MOVLW D'25' MOVWF TEMP_INT ; temp esempio = 25.5°C MOVLW 1 MOVWF TEMP_HALF CLRF TEMP_SIGN CLRF DISP_IDX ; Demo statica: mostra 25.5 CALL UPDATE_DISPLAY ; ============================================================ ; MAIN — ciclo multiplexing continuo ~5ms/digit ; In una versione completa: ogni ~10 digit-refresh → read temp ; ============================================================ MAIN CALL MUX_DISPLAY ; Delay ~5ms per questo digit MOVLW D'5' MOVWF MUX_CNT MUX_WAIT ; 1ms = 333 cicli @ 3cicli/iter MOVLW D'111' MOVWF TMP_BCD MWAIT DECFSZ TMP_BCD,F GOTO MWAIT DECFSZ MUX_CNT,F GOTO MUX_WAIT ; Ogni 30 refresh (~0.45s) aggiorna temperatura INCF FLAGS,F MOVF FLAGS,W SUBLW D'30' BTFSC STATUS,Z CLRF FLAGS ; (in versione completa: CALL READ_TEMPERATURE qui) GOTO MAIN END
Modulo 3 · EEPROM setpoint e integrazione completa
⚠️ Sequenza unlock EEPROM obbligatoria: il PIC16F877A richiede che prima di ogni scrittura in EEPROM si scriva 0x55 poi 0xAA in EECON2 (registro write-only) con gli interrupt disabilitati. Se questa sequenza viene interrotta (es. da un interrupt non maskato), la scrittura non avviene silenziosamente — per questo BCF INTCON,GIE è fondamentale. L’EEPROM ha 1.000.000 cicli di scrittura garantiti: salva solo quando il setpoint cambia, non ad ogni ciclo.
; ============================================================ ; Lezione 38 — Termostato: EEPROM setpoint + progetto completo ; PIC16F877A @ 4 MHz ; EEPROM interna (256 byte @ 0x2100): salva/carica setpoint ; Integrazione: 1-Wire + display + relay + EEPROM + pulsanti ; Pulsanti: RB0=UP setpoint, RB1=DOWN setpoint (pull-up interni) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF ; Indirizzi EEPROM per parametri persistenti #DEFINE EEP_SET_INT 0x00 ; setpoint interi #DEFINE EEP_SET_HALF 0x01 ; setpoint 0.5°C #DEFINE EEP_HYST 0x02 ; isteresi #DEFINE EEP_MAGIC 0x03 ; 0xA5 = EEPROM valida CBLOCK 0x20 ; Temperatura TEMP_INT TEMP_HALF TEMP_SIGN TEMP_RAW_L TEMP_RAW_H ; Setpoint e controllo SET_INT SET_HALF RELAY_STATE HYSTERESIS FLAGS ; bit0=temp_valid, bit1=btn_debounce, bit2=save_needed ; Display DISP_D0 DISP_D1 DISP_D2 DISP_IDX ; EEPROM EEP_ADDR EEP_DATA ; Debounce pulsanti BTN_CNT BTN_PREV TMP DLY ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; EEPROM_WRITE — scrive EEP_DATA all'indirizzo EEP_ADDR ; Sequenza speciale richiesta dal PIC16F877A ; ============================================================ EEPROM_WRITE BSF STATUS,RP0 ; Bank 1 MOVF EEP_ADDR,W MOVWF EEADR MOVF EEP_DATA,W MOVWF EEDATA BSF EECON1,WREN ; abilita scrittura BCF INTCON,GIE ; disabilita interrupt MOVLW 0x55 ; sequenza unlock obbligatoria MOVWF EECON2 MOVLW 0xAA MOVWF EECON2 BSF EECON1,WR ; avvia scrittura BSF INTCON,GIE ; riabilita interrupt ; Attendi fine scrittura (WR si azzera automaticamente) EEPWAIT BTFSC EECON1,WR ; WR=1 → ancora in corso GOTO EEPWAIT BCF EECON1,WREN ; blocca scrittura BCF STATUS,RP0 RETURN ; ============================================================ ; EEPROM_READ — legge da EEP_ADDR in EEP_DATA ; ============================================================ EEPROM_READ BSF STATUS,RP0 MOVF EEP_ADDR,W MOVWF EEADR BSF EECON1,RD ; avvia lettura (1 ciclo) MOVF EEDATA,W BCF STATUS,RP0 MOVWF EEP_DATA RETURN ; ============================================================ ; SAVE_SETTINGS — salva setpoint in EEPROM ; ============================================================ SAVE_SETTINGS ; Scrivi SET_INT MOVLW EEP_SET_INT MOVWF EEP_ADDR MOVF SET_INT,W MOVWF EEP_DATA CALL EEPROM_WRITE ; Scrivi SET_HALF MOVLW EEP_SET_HALF MOVWF EEP_ADDR MOVF SET_HALF,W MOVWF EEP_DATA CALL EEPROM_WRITE ; Scrivi HYSTERESIS MOVLW EEP_HYST MOVWF EEP_ADDR MOVF HYSTERESIS,W MOVWF EEP_DATA CALL EEPROM_WRITE ; Scrivi magic byte (EEPROM valida) MOVLW EEP_MAGIC MOVWF EEP_ADDR MOVLW 0xA5 MOVWF EEP_DATA CALL EEPROM_WRITE BCF FLAGS,2 ; save non più necessario RETURN ; ============================================================ ; LOAD_SETTINGS — carica da EEPROM, verifica magic byte ; ============================================================ LOAD_SETTINGS ; Controlla magic MOVLW EEP_MAGIC MOVWF EEP_ADDR CALL EEPROM_READ MOVF EEP_DATA,W SUBLW 0xA5 BTFSS STATUS,Z GOTO LOAD_DEFAULTS ; no magic → usa default MOVLW EEP_SET_INT MOVWF EEP_ADDR CALL EEPROM_READ MOVF EEP_DATA,W MOVWF SET_INT MOVLW EEP_SET_HALF MOVWF EEP_ADDR CALL EEPROM_READ MOVF EEP_DATA,W MOVWF SET_HALF MOVLW EEP_HYST MOVWF EEP_ADDR CALL EEPROM_READ MOVF EEP_DATA,W MOVWF HYSTERESIS RETURN LOAD_DEFAULTS MOVLW D'25' ; 25°C default MOVWF SET_INT CLRF SET_HALF MOVLW D'2' ; 1°C isteresi MOVWF HYSTERESIS RETURN ; ============================================================ ; READ_BUTTONS — debounce RB0 (UP) e RB1 (DOWN) ; Ritorna: bit0=UP premuto, bit1=DOWN premuto in W ; Debounce: 20ms polling ; ============================================================ READ_BUTTONS CLRF TMP ; Leggi RB0 (active low con pull-up) BTFSS PORTB,0 ; RB0=0 → premuto BSF TMP,0 ; Leggi RB1 BTFSS PORTB,1 BSF TMP,1 ; Debounce: confronta con lettura precedente MOVF TMP,W XORWF BTN_PREV,W ; differenze? BTFSS STATUS,Z GOTO BTN_CHANGED ; Stesso stato: edge stabile, ritorna TMP MOVF TMP,W RETURN BTN_CHANGED MOVF TMP,W MOVWF BTN_PREV RETLW 0 ; ignora edge instabile ; ============================================================ ; ADJUST_SETPOINT — modifica SET in base a pulsanti ; ============================================================ ADJUST_SETPOINT CALL READ_BUTTONS MOVWF TMP ; UP: aumenta setpoint BTFSS TMP,0 GOTO CHECK_DOWN INCF SET_INT,F MOVF SET_INT,W SUBLW D'99' ; max 99°C BTFSS STATUS,C MOVLW D'99' BTFSS STATUS,C MOVWF SET_INT BSF FLAGS,2 ; save needed CHECK_DOWN BTFSS TMP,1 RETURN ; DOWN: diminuisci setpoint MOVF SET_INT,F BTFSC STATUS,Z ; già 0? RETURN DECF SET_INT,F BSF FLAGS,2 RETURN ORG 0x0180 INIT BSF STATUS,RP0 CLRF TRISD MOVLW b'11111111' ; PORTB input (pulsanti pull-up) MOVWF TRISB BSF OPTION_REG,7 ; pull-up interni abilitati (NOT_RBPU=0) MOVLW b'11111001' ; RC3/4 I²C, RC0=1-Wire MOVWF TRISC BCF STATUS,RP0 MOVLW b'00000111' IORWF PORTC,F ; anodi display off CALL LOAD_SETTINGS CLRF FLAGS CLRF RELAY_STATE CLRF DISP_IDX CLRF BTN_PREV ; ============================================================ ; MAIN INTEGRATO — ciclo principale termostato ; Ogni ciclo (~15ms): mux display × 3 + pulsanti + relay ; Ogni ~1s: leggi temperatura + aggiorna display ; ============================================================ MAIN ; Multiplexing display (3 digit × 5ms = 15ms) CALL MUX_STEP CALL MUX_STEP CALL MUX_STEP ; Controlla pulsanti CALL ADJUST_SETPOINT ; Salva se necessario (ritardato per evitare scritture continue) BTFSC FLAGS,2 CALL SAVE_SETTINGS ; Ogni ~1s: aggiorna temperatura (66 cicli × 15ms ≈ 1s) INCF DLY,F MOVF DLY,W SUBLW D'66' BTFSS STATUS,Z GOTO MAIN CLRF DLY ; Leggi temperatura (DS18B20 — corpo semplificato) ; In versione completa: CALL READ_DS18B20 ; Qui simulo con valore di test MOVF TEMP_INT,F ; usa valore esistente ; Controllo relay con isteresi CALL CHECK_RELAY_HYST ; Aggiorna display CALL UPDATE_DISP GOTO MAIN ; ============================================================ ; CHECK_RELAY_HYST — isteresi completa ; Accendi se TEMP > SET + HYST/2 ; Spegni se TEMP < SET - HYST/2 ; ============================================================ CHECK_RELAY_HYST ; Calcola soglia alta: SET + HYST/2 (arrotondato) MOVF SET_INT,W ADDWF HYSTERESIS,W ; rough: usa HYST come offset intero SUBWF TEMP_INT,W ; TEMP - (SET + HYST) BTFSC STATUS,C ; C=1 → TEMP > soglia alta GOTO DO_RELAY_ON ; Calcola soglia bassa: SET - HYST MOVF HYSTERESIS,W SUBWF SET_INT,W ; SET - HYST SUBWF TEMP_INT,W ; TEMP - (SET - HYST) BTFSS STATUS,C ; C=0 → TEMP < soglia bassa GOTO DO_RELAY_OFF ; Nella banda: mantieni stato attuale RETURN DO_RELAY_ON BSF RELAY_STATE,0 BSF PORTB,7 ; relay pin RETURN DO_RELAY_OFF CLRF RELAY_STATE BCF PORTB,7 RETURN ; Stub chiamate (le implementazioni complete sono nei moduli 1 e 2) MUX_STEP RETURN UPDATE_DISP RETURN END
🧪 Costruisci il circuito
Lista componenti e collegamenti
- DS18B20: VCC→5V, GND→GND, DATA→RC0 + resistore pull-up 4.7kΩ tra DATA e VCC
- 3× display 7seg comune anodo: pin comuni → RC0, RC1, RC2 (tramite transistor NPN o resistore 100Ω); segmenti a–g,dp → PORTD bit0–7 tramite resistori 220Ω
- Relay 5V: bobina tramite transistor NPN (base→RB7 con 1kΩ, collettore→relay, emettitore→GND) + diodo di protezione 1N4007 in antiparallelo sulla bobina
- Pulsanti UP/DOWN: un lato a RB0/RB1, altro lato a GND — pull-up interni abilitati con NOT_RBPU=0 in OPTION_REG
- Alimentazione: regolatore 7805 (5V), condensatori 100nF + 10µF vicino a MCLR e VDD del PIC
Verifica passo per passo nel simulatore
- Carica Modulo 1. Simula temperatura 36.5°C: imposta TEMP_RAW_L=0x49 (0100 1001), TEMP_RAW_H=0x01. Esegui la conversione: TEMP_INT deve essere 36, TEMP_HALF deve essere 1.
- Carica Modulo 2. Imposta TEMP_INT=25, TEMP_HALF=1, TEMP_SIGN=0. Chiama UPDATE_DISPLAY: DISP_D0 deve essere 0xFF (blank, nessuna decina), DISP_D1 deve essere 0x92 (cifra 5), DISP_D2 deve essere 0x40 (0 con punto decimale per il mezzo grado).
- Carica Modulo 3. Chiama SAVE_SETTINGS con SET_INT=28. Verifica EEPROM 0x2100=28, 0x2103=0xA5. Fai reset (simula spegnimento). Chiama LOAD_SETTINGS: SET_INT deve rileggere 28 dall’EEPROM.
- Test isteresi: imposta SET_INT=25, HYSTERESIS=2, RELAY_STATE=0. Con TEMP_INT=27: relay ON? Con TEMP_INT=26: relay rimane ON (nella banda)?. Con TEMP_INT=22: relay OFF?
❓ Quiz rapido
1. Il DS18B20 con risoluzione 9-bit restituisce TEMP_RAW = 0x00E0 (TEMP_H=0x00, TEMP_L=0xE0 = 11100000). Qual è la temperatura in °C?
Mostra risposta
Con risoluzione 9-bit solo il bit 0 di TEMP_L è valido come mezzo grado (gli altri bit frazionari non sono significativi). TEMP_L = 0xE0 = 1110 0000 in binario. TEMP_H = 0x00 → il segno è positivo (bit3 di TEMP_H = 0). La parte intera è data dai bit 7:1 di TEMP_L + bit 3:0 di TEMP_H: shift right di TEMP_L → 0x70 = 112. Quindi TEMP_INT = 112°C, TEMP_HALF = 0 (bit0 di TEMP_L = 0). Temperatura = 112.0°C. Questo è un valore di test estremo ma valido: il DS18B20 misura fino a +125°C. In pratica si tratta probabilmente di un sensore difettoso o cortocircuito.
2. Il multiplexing del display usa 5ms per digit × 3 digit = 15ms per ciclo completo. Perché 60Hz è sufficiente e cosa succede se si scende sotto ~30Hz?
Mostra risposta
L’occhio umano percepisce come continua una luce che lampeggia a ≥50Hz — la frequenza di fusione visiva (Critical Flicker Fusion). A 60Hz ogni digit è acceso ~16.7ms/ciclo × 1/3 = 5.6ms, con pausa di 11ms — il cervello interpola la sequenza come tre digit sempre accesi. Sotto ~30Hz il lampeggio diventa visibile: i digit sembrano sfarfallare, specialmente in visione periferica. Sotto 25Hz il display è chiaramente intermittente e illeggibile durante il movimento della testa. Il 60Hz del multiplexing corrisponde anche alla frequenza della rete elettrica europea (50Hz) + margine, evitando battimenti di frequenza visibili sotto luci fluorescenti.
3. Perché è necessario il magic byte 0xA5 all’indirizzo 0x03 dell’EEPROM, e cosa succederebbe senza?
Mostra risposta
Alla prima accensione dopo la programmazione, l’EEPROM contiene 0xFF in tutte le celle (valore di cancellazione). Senza magic byte, LOAD_SETTINGS leggerebbe SET_INT=0xFF=255°C come setpoint e HYSTERESIS=0xFF=255 come isteresi — valori assurdi che potrebbero accendere il relay permanentemente o causare comportamenti imprevedibili. Il magic byte è una firma: se l’EEPROM non contiene 0xA5 al byte 0x03 vuol dire che non è mai stata scritta (primo avvio) o è corrotta, e si usano i valori di default sicuri (25°C, isteresi 1°C). Valore 0xA5 (10100101) è scelto perché è diverso sia da 0x00 che da 0xFF — i due valori più comuni dopo cancellazione o cortocircuito.
Prossima lezione →
Lezione 39 — Progetto 2: Contatore di frequenza
Misura la frequenza di un segnale digitale da 1Hz a 99kHz con Timer1 in modalità contatore e Timer0 come base dei tempi da 1 secondo. Display LCD 16×2 via I²C. Autorange automatico.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 38