Lezione 39 di 43

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

Architettura termostato digitale — blocchi e connessioni DS18B20 1-Wire RC0 PIC16F877A READ_DS18B20 CHECK_RELAY_HYST MUX_DISPLAY EEPROM R/W READ_BUTTONS EEPROM interna @0x2100 setpoint + isteresi persistenti magic byte 0xA5 3× 7seg comune anodo PORTD+PORTC Relay 230V AC RB7 + ULN2003 UP / DOWN pull-up int. RB0 / RB1 Ciclo principale ~15ms MUX×3 pulsanti + debounce relay check ogni ~1s: 1-Wire

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 &lt; 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

Display 7 segmenti — segmenti, codice e multiplexing a f b g e c d = cifra 7 Tabella codici 7 segmenti — comune anodo (0=acceso) Cifra dp g f e d c b a Hex Accesi 0 1 1 0 0 0 0 0 0 0xC0 a b c d e f 1 1 1 1 1 1 0 0 1 0xF9 b c 2 1 0 1 0 0 1 0 0 0xA4 a b d e g 5 1 0 0 1 0 0 1 0 0x92 a c d f g 8 1 0 0 0 0 0 0 0 0x80 tutti blank 1 1 1 1 1 1 1 1 0xFF nessuno – 1 0 1 1 1 1 1 1 0xBF g (minus) Multiplexing: PORTD = segmenti, BCF PORTC,N accende anodo digit N — uno alla volta, 5ms ciascuno → 60Hz percepito

🔢 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 &lt; 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

EEPROM interna PIC16F877A — mappa, sequenza scrittura e isteresi termostato EEPROM @ 0x2100 0x00 SET_INT = 25 0x01 SET_HALF = 0 0x02 HYSTERESIS = 2 0x03 MAGIC = 0xA5 0x04-0xFF riservato Scrittura sequenza unlock: EECON2=0x55, EECON2=0xAA Logica isteresi termostato (riscaldatore) SET=25°C SET-HYST 24°C SET+HYST 26°C zona isteresi mantieni stato relay ON (riscalda) relay OFF (troppo caldo) 20°C 25°C 30°C Senza isteresi il relay commuta continuamente vicino al setpoint (hunting) — l’isteresi elimina questo problema

⚠️ 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 &gt; 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 &lt; 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

  1. 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.
  2. 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).
  3. 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.
  4. 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