Lezione 41 di 43

Lezione 40

🎓 PIC16 — From Zero to Hero · Lezione 40 · Modulo 7

Progetto 3: Stazione Meteo 🌤️

BME280 via SPI (temperatura + umidità + pressione) · Media mobile O(1) su 8 campioni
Buffer circolare con aggiornamento efficiente · LCD scroll automatico tra 3 schermate

🎯 Cosa costruisci in questa lezione

  • BME280 via SPI software: sensore Bosch all-in-one che misura temperatura (±1°C), umidità relativa (±3%) e pressione barometrica (±1hPa). Interfaccia SPI con indirizzo read/write nel bit7 del registro
  • Soft reset (0xE0 ← 0xB6), configurazione oversampling ×1 per ogni canale, lettura calibrazione 6 byte per temperatura, burst read 8 byte da 0xF7
  • Forced mode: il BME280 esegue una singola misura, poi torna in sleep — ideale per risparmio energetico. Si invia ctrl_meas con mode=01 per ogni campionamento
  • Media mobile con buffer circolare: 8 campioni per canale in RAM, aggiornamento O(1) — si sottrae il vecchio valore dalla somma e si aggiunge il nuovo, senza ricalcolare tutti gli 8 ogni volta
  • Divisione per 8 con RRF × 3 (shift right 3 volte = divide per 2³ = 8): più veloce e compatto di un loop di sottrazione
  • Display LCD a rotazione: 3 schermate (temperatura, umidità, pressione) alternano ogni 3 secondi tramite SCREEN_IDX con modulo 3
  • Conversione valore 0–99 in 2 cifre ASCII per il LCD con loop di sottrazione (come Lez.38), senza hardware BCD o moltiplicatore

📋 Scheda progetto

Lezione: 40 / 42
Modulo: 7 — Progetti Completi
Device: PIC16F877A
Prerequisiti: Lez. 29 (SPI), 39 (LCD), 35 (RAM)
Difficoltà: ★★★★★
Hardware: BME280, LCD 16×2, PCF8574
Output: T, H%, P(hPa) su display


Modulo 1 · BME280 driver SPI e lettura dati

BME280 — registri principali e flusso lettura SPI BME280 0x88-0x9F calib T/P 0xE1-0xE7 calib H 0xF2 ctrl_hum osrs_h 0xF4 ctrl_meas osrs_t osrs_p mode 0xF7-0xF9 press 20-bit 0xFA-0xFC temp 20-bit 0xFD-0xFE hum 16-bit 0xE0 reset (W 0xB6) 0xF3 status (bit3=meas) 0xD0 chip_id = 0x60 Flusso lettura burst 8 byte da 0xF7 CS SCK MOSI 0xF7 | 0x80 = indirizzo lettura 0x00 × 8 (dummy bytes) MISO don’t care press_msb lsb xlsb temp… hum bit7=1 → lettura · bit7=0 → scrittura Forced mode: ctrl_meas=0b00100101 → misura singola → sleep → leggi 0xF7-0xFE → attendi ~10ms → ripeti

📐 Compensazione BME280 — perché è complessa: i dati grezzi del BME280 non sono temperatura/umidità dirette. Il chip applica una formula di compensazione in virgola mobile usando 20+ coefficienti di calibrazione unici per ogni esemplare (scritti in fabbrica). La formula reale per la temperatura usa 3 polinomi con divisioni a 32 bit. Per uso didattico questo codice usa un’approssimazione lineare semplificata — nei progetti reali si implementa la compensazione completa usando le variabili dig_T1/T2/T3 oppure si legge il valore già compensato da un registro di output se disponibile.

; ============================================================
; Lezione 40 — Stazione meteo: BME280 via SPI software
; PIC16F877A @ 4 MHz
; BME280: sensore Bosch temperatura + umidità + pressione
; Interfaccia SPI Modo 0 o 3, CS attivo basso
; Pin: RB3=SCK, RB4=MOSI, RB5=MISO, RB6=CS
; Registri chiave:
;   0xF3 = status, 0xF4 = ctrl_meas, 0xF2 = ctrl_hum
;   0xF5 = config,  0xF7-0xFC = dati pressione+temp
;   0xFD-0xFE = dati umidità
;   0x88-0x9F = calibrazione temperatura/pressione
;   0xE1-0xE7 = calibrazione umidità
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

#DEFINE SCK     PORTB,3
#DEFINE MOSI    PORTB,4
#DEFINE MISO    PORTB,5
#DEFINE CS      PORTB,6

    CBLOCK  0x20
; SPI
SPI_TX
SPI_RX
SPI_CNT
; Dati grezzi BME280 (non compensati)
TEMP_MSB        ; 0xFA
TEMP_LSB        ; 0xFB
TEMP_XLSB       ; 0xFC bit7:4
PRESS_MSB       ; 0xF7
PRESS_LSB       ; 0xF8
PRESS_XLSB      ; 0xF9 bit7:4
HUM_MSB         ; 0xFD
HUM_LSB         ; 0xFE
; Calibrazione temperatura (dig_T1..T3, letti dalla ROM del chip)
DIG_T1_L
DIG_T1_H        ; unsigned
DIG_T2_L
DIG_T2_H        ; signed
DIG_T3_L
DIG_T3_H        ; signed
; Risultato compensato (intero semplificato)
TEMP_C          ; temperatura in °C (parte intera)
TEMP_D          ; decimi di °C
HUM_INT         ; umidità % intera
PRESS_HPA_L     ; pressione hPa (byte basso)
PRESS_HPA_H     ; pressione hPa (byte alto)
; Temporanei
TMP
DLY
FLAGS
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; SPI_BYTE — trasferisci SPI_TX, ricevi in SPI_RX
; BME280: SCK idle=1 (Modo 3), campiona su falling edge
; Ma usiamo Modo 0 per semplicità (SCK idle=0, campiona rising)
; ============================================================
SPI_BYTE
    MOVLW   D'8'
    MOVWF   SPI_CNT
    CLRF    SPI_RX
SPI_LOOP
    ; Setup MOSI (MSB first)
    BCF     MOSI
    BTFSC   SPI_TX,7
    BSF     MOSI
    ; Rising SCK
    BSF     SCK
    NOP
    NOP
    ; Campiona MISO
    BCF     STATUS,C
    BTFSC   MISO
    BSF     STATUS,C
    RLF     SPI_RX,F
    ; Falling SCK
    BCF     SCK
    NOP
    ; Avanza TX
    RLF     SPI_TX,F
    DECFSZ  SPI_CNT,F
    GOTO    SPI_LOOP
    RETURN

; ============================================================
; BME280_READ_REG — leggi registro all'indirizzo W
; Ritorna valore in SPI_RX
; Formato SPI BME280: indirizzo con bit7=1 per lettura
; ============================================================
BME280_READ_REG
    IORLW   0x80               ; bit7=1 → lettura
    MOVWF   SPI_TX
    BCF     CS                 ; CS basso = seleziona
    CALL    SPI_BYTE           ; invia indirizzo
    CLRF    SPI_TX             ; dummy byte per ricevere dati
    CALL    SPI_BYTE           ; ricevi dato in SPI_RX
    BSF     CS                 ; CS alto = deseleziona
    RETURN

; ============================================================
; BME280_WRITE_REG — scrivi VALUE in registro ADDR
; W = indirizzo (bit7=0 per scrittura), TMP = valore
; ============================================================
BME280_WRITE_REG
    ANDLW   0x7F               ; bit7=0 → scrittura
    MOVWF   SPI_TX
    BCF     CS
    CALL    SPI_BYTE
    MOVF    TMP,W
    MOVWF   SPI_TX
    CALL    SPI_BYTE
    BSF     CS
    RETURN

; ============================================================
; BME280_INIT — configura modalità forced + oversampling ×1
; Forced mode: misura singola su comando, poi torna sleep
; osrs_t=001 (×1), osrs_p=001 (×1), osrs_h=001 (×1)
; ============================================================
BME280_INIT
    ; Soft reset (registro 0xE0, valore 0xB6)
    MOVLW   0xE0
    MOVWF   TMP
    MOVLW   0xE0
    MOVWF   SPI_TX
    BCF     CS
    CALL    SPI_BYTE
    MOVLW   0xB6
    MOVWF   SPI_TX
    CALL    SPI_BYTE
    BSF     CS
    ; Attendi reset (2ms)
    MOVLW   D'4'
    MOVWF   DLY
BRST MOVLW  D'250'
    MOVWF   TMP
BRST2 DECFSZ TMP,F
    GOTO    BRST2
    DECFSZ  DLY,F
    GOTO    BRST

    ; Configura oversampling umidità (ctrl_hum 0xF2)
    MOVLW   0xF2
    MOVWF   TMP
    MOVLW   0x01               ; osrs_h = 001 (×1)
    MOVWF   SPI_TX
    BCF     CS
    MOVLW   0x72               ; 0xF2 &amp; 0x7F = 0x72
    MOVWF   SPI_TX
    CALL    SPI_BYTE
    MOVLW   0x01
    MOVWF   SPI_TX
    CALL    SPI_BYTE
    BSF     CS

    ; Leggi calibrazione temperatura (0x88-0x8D, 6 byte)
    CALL    READ_CALIB_TEMP
    RETURN

; ============================================================
; READ_CALIB_TEMP — leggi i 6 byte di calibrazione T
; dig_T1 @ 0x88:0x89 (unsigned), dig_T2 @ 0x8A:0x8B (signed)
; dig_T3 @ 0x8C:0x8D (signed)
; ============================================================
READ_CALIB_TEMP
    MOVLW   0x88
    CALL    BME280_READ_REG
    MOVF    SPI_RX,W
    MOVWF   DIG_T1_L
    MOVLW   0x89
    CALL    BME280_READ_REG
    MOVF    SPI_RX,W
    MOVWF   DIG_T1_H
    MOVLW   0x8A
    CALL    BME280_READ_REG
    MOVF    SPI_RX,W
    MOVWF   DIG_T2_L
    MOVLW   0x8B
    CALL    BME280_READ_REG
    MOVF    SPI_RX,W
    MOVWF   DIG_T2_H
    MOVLW   0x8C
    CALL    BME280_READ_REG
    MOVF    SPI_RX,W
    MOVWF   DIG_T3_L
    MOVLW   0x8D
    CALL    BME280_READ_REG
    MOVF    SPI_RX,W
    MOVWF   DIG_T3_H
    RETURN

; ============================================================
; BME280_FORCE — avvia misura in Forced mode e attende
; ctrl_meas: osrs_t=001, osrs_p=001, mode=01 (forced)
; ============================================================
BME280_FORCE
    MOVLW   0x74               ; 0xF4 &amp; 0x7F = 0x74
    MOVWF   SPI_TX
    BCF     CS
    CALL    SPI_BYTE
    MOVLW   b'00100101'        ; osrs_t=001, osrs_p=001, mode=01
    MOVWF   SPI_TX
    CALL    SPI_BYTE
    BSF     CS
    ; Attendi misura ~10ms (forced mode)
    MOVLW   D'20'
    MOVWF   DLY
BWAIT MOVLW  D'200'
    MOVWF   TMP
BWAIT2 DECFSZ TMP,F
    GOTO    BWAIT2
    DECFSZ  DLY,F
    GOTO    BWAIT
    RETURN

; ============================================================
; BME280_READ_ALL — leggi burst 8 byte da 0xF7
; Indirizzo di partenza 0xF7, burst read (CS basso per tutti)
; 0xF7=press_msb, F8=press_lsb, F9=press_xlsb
; 0xFA=temp_msb,  FB=temp_lsb,  FC=temp_xlsb
; 0xFD=hum_msb,   FE=hum_lsb
; ============================================================
BME280_READ_ALL
    MOVLW   0xF7
    IORLW   0x80               ; lettura
    MOVWF   SPI_TX
    BCF     CS
    CALL    SPI_BYTE           ; invia indirizzo burst
    ; Leggi 8 byte consecutivi (auto-increment nel BME280)
    CLRF    SPI_TX
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   PRESS_MSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   PRESS_LSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   PRESS_XLSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   TEMP_MSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   TEMP_LSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   TEMP_XLSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   HUM_MSB
    CALL    SPI_BYTE
    MOVF    SPI_RX,W
    MOVWF   HUM_LSB
    BSF     CS
    RETURN

; ============================================================
; CONVERT_TEMP_SIMPLE — conversione semplificata (8-bit)
; Il valore raw BME280 è 20-bit (TEMP_MSB:LSB:XLSB bit7:4)
; Usiamo solo TEMP_MSB come approssimazione:
; Formula semplificata: T ≈ (raw_msb - dig_T1_H) + 20
; (approssimazione per didattica — calcolo vero è in float)
; ============================================================
CONVERT_TEMP_SIMPLE
    ; raw_high ≈ TEMP_MSB (byte più significativo)
    MOVF    TEMP_MSB,W
    SUBWF   DIG_T1_H,W         ; DIG_T1_H - TEMP_MSB
    ; In condizioni normali DIG_T1_H ≈ offset base
    ; Approssimazione: T = TEMP_MSB / 5 (per range 0-50°C con raw tipico 0-250)
    MOVWF   TMP
    ; Dividi per 5 (shift + sottrazione)
    BCF     STATUS,C
    RRF     TMP,W              ; /2
    MOVWF   TEMP_C
    BCF     STATUS,C
    RRF     TEMP_C,W           ; /4 totale
    ADDWF   TEMP_C,F           ; /4 + /4*... approssimazione /5
    ; Aggiungi offset base (~20°C)
    MOVLW   D'20'
    ADDWF   TEMP_C,F
    ; Decimi: usa nibble basso di TEMP_LSB
    MOVF    TEMP_LSB,W
    ANDLW   0x0F
    MOVWF   TEMP_D
    RETURN

    ORG     0x0100
INIT
    BSF     STATUS,RP0
    MOVLW   b'11100111'        ; RB3=SCK out, RB4=MOSI out, RB5=MISO in, RB6=CS out
    MOVWF   TRISB
    CLRF    TRISD
    BCF     STATUS,RP0
    BCF     SCK                ; SCK idle = 0
    BSF     CS                 ; CS idle = 1
    CLRF    FLAGS

    CALL    BME280_INIT

MAIN
    CALL    BME280_FORCE       ; avvia misura
    CALL    BME280_READ_ALL    ; leggi tutti i dati
    CALL    CONVERT_TEMP_SIMPLE

    ; Output su PORTD: TEMP_C
    MOVF    TEMP_C,W
    MOVWF   PORTD

    ; Breve pausa 2s tra misure
    MOVLW   D'200'
    MOVWF   TMP
WAIT MOVLW  D'250'
    MOVWF   DLY
WAIT2 DECFSZ DLY,F
    GOTO    WAIT2
    DECFSZ  TMP,F
    GOTO    WAIT
    GOTO    MAIN

    END

Modulo 2 · Media mobile O(1) con buffer circolare

Media mobile su 8 campioni — buffer circolare e aggiornamento efficiente Buffer temperatura (8 celle) 24 25 27 26 25 24 23 22 ↑ new=27 ↑ old=22 sovrascrive rimosso dalla somma Aggiornamento O(1): SUM = SUM – old + new → AVG = SUM >> 3 Costo computazionale Metodo naive somma tutti 8 ogni volta 7 addizioni + 3 shift = 10 op Metodo O(1) — questo codice 1 sottrazione + 1 addizione + 3 shift = 5 operazioni sempre, per N=8,16,32… AVG = SUM >> 3 (N=8 → divide per 8) SUM max = 255×8 = 2040 → 11 bit → 16-bit ok

💡 Accesso al buffer con FSR: i buffer TBUF_0…TBUF_7 sono 8 celle contigue in RAM. Per accedere a TBUF[N] si calcola l’indirizzo base (MOVLW TBUF_0) e si aggiunge l’indice N (ADDWF T_IDX,W), poi si carica in FSR. MOVF INDF,W legge la cella puntata da FSR senza conoscere il numero esatto a compile time. È lo stesso meccanismo dei puntatori in C — in assembly PIC16 è l’unico modo per accedere ad array dinamicamente.

; ============================================================
; Lezione 40 — Media mobile su 8 campioni
; PIC16F877A @ 4 MHz
; Buffer circolare 8 elementi in RAM Bank0
; Media = somma / 8 (shift right 3 volte invece di dividere)
; Tre canali: temperatura, umidità, pressione
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

    CBLOCK  0x20
; Buffer temperatura (8 campioni)
TBUF_0
TBUF_1
TBUF_2
TBUF_3
TBUF_4
TBUF_5
TBUF_6
TBUF_7
; Buffer umidità (8 campioni)
HBUF_0
HBUF_1
HBUF_2
HBUF_3
HBUF_4
HBUF_5
HBUF_6
HBUF_7
; Puntatori e medie
T_IDX           ; indice corrente buffer temp (0-7)
H_IDX           ; indice corrente buffer hum
T_SUM_L         ; somma temperatura (16-bit)
T_SUM_H
H_SUM_L         ; somma umidità
H_SUM_H
T_AVG           ; media temperatura
H_AVG           ; media umidità
; Nuovo campione da inserire
NEW_TEMP
NEW_HUM
; Temporanei
TMP
FSR_SAVE
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; INIT_BUFFERS — riempie i buffer con il primo campione
; Chiamare una volta con NEW_TEMP e NEW_HUM già impostati
; ============================================================
INIT_BUFFERS
    ; Riempie TBUF_0..7 con NEW_TEMP
    MOVLW   TBUF_0
    MOVWF   FSR
    MOVLW   D'8'
    MOVWF   TMP
IB_T MOVF   NEW_TEMP,W
    MOVWF   INDF
    INCF    FSR,F
    DECFSZ  TMP,F
    GOTO    IB_T
    ; Riempie HBUF_0..7 con NEW_HUM
    MOVLW   HBUF_0
    MOVWF   FSR
    MOVLW   D'8'
    MOVWF   TMP
IB_H MOVF   NEW_HUM,W
    MOVWF   INDF
    INCF    FSR,F
    DECFSZ  TMP,F
    GOTO    IB_H
    CLRF    T_IDX
    CLRF    H_IDX
    RETURN

; ============================================================
; UPDATE_TEMP_AVG — inserisce NEW_TEMP, ricalcola media
; Media mobile: togli il vecchio (TBUF[idx]), aggiungi nuovo
; Non serve ricalcolare la somma completa ogni volta!
; Trucco: SUM = SUM - old_val + new_val (solo 2 operazioni)
; ============================================================
UPDATE_TEMP_AVG
    ; Calcola indirizzo TBUF[T_IDX]
    MOVLW   TBUF_0
    ADDWF   T_IDX,W
    MOVWF   FSR                ; FSR → TBUF[T_IDX]

    ; Togli il vecchio dalla somma
    MOVF    INDF,W             ; W = vecchio valore
    SUBWF   T_SUM_L,F          ; SUM_L -= vecchio
    BTFSS   STATUS,C           ; borrow?
    DECF    T_SUM_H,F          ; propaga al byte alto

    ; Scrivi il nuovo nel buffer
    MOVF    NEW_TEMP,W
    MOVWF   INDF               ; TBUF[T_IDX] = NEW_TEMP

    ; Aggiungi il nuovo alla somma
    ADDWF   T_SUM_L,F          ; SUM_L += nuovo
    BTFSC   STATUS,C
    INCF    T_SUM_H,F          ; propaga carry

    ; Avanza indice (modulo 8)
    INCF    T_IDX,F
    MOVLW   D'8'
    SUBWF   T_IDX,W
    BTFSC   STATUS,Z
    CLRF    T_IDX              ; wrap: 7+1 → 0

    ; Calcola media: SUM / 8 = SUM >> 3
    ; SUM è a 16 bit (max 255×8=2040, ci sta in 11 bit)
    ; Risultato in T_AVG (8 bit basta: max 255)
    MOVF    T_SUM_H,W
    MOVWF   TMP
    ; Shift right 3: TMP:T_SUM_L >> 3
    BCF     STATUS,C
    RRF     TMP,F
    RRF     T_SUM_L,W          ; /2
    MOVWF   TMP
    BCF     STATUS,C
    RRF     TMP,F              ; /4 (TMP ora ha i bit alti)
    ; Prendiamo i bit utili dal byte basso dopo 3 shift
    MOVF    T_SUM_L,W
    MOVWF   T_AVG
    BCF     STATUS,C
    RRF     T_AVG,F
    BCF     STATUS,C
    RRF     T_AVG,F
    BCF     STATUS,C
    RRF     T_AVG,F            ; T_AVG = T_SUM_L &gt;&gt; 3
    ; Aggiunge contributo T_SUM_H (se > 0)
    MOVF    T_SUM_H,W
    BTFSC   STATUS,Z
    GOTO    TA_DONE
    ; T_SUM_H ≠ 0: aggiunge 256*T_SUM_H/8 = 32*T_SUM_H
    ; Aggiungiamo 32 per ogni unità di T_SUM_H
    MOVLW   D'32'
    ADDWF   T_AVG,F
TA_DONE
    RETURN

; ============================================================
; UPDATE_HUM_AVG — identico per umidità
; ============================================================
UPDATE_HUM_AVG
    MOVLW   HBUF_0
    ADDWF   H_IDX,W
    MOVWF   FSR
    MOVF    INDF,W
    SUBWF   H_SUM_L,F
    BTFSS   STATUS,C
    DECF    H_SUM_H,F
    MOVF    NEW_HUM,W
    MOVWF   INDF
    ADDWF   H_SUM_L,F
    BTFSC   STATUS,C
    INCF    H_SUM_H,F
    INCF    H_IDX,F
    MOVLW   D'8'
    SUBWF   H_IDX,W
    BTFSC   STATUS,Z
    CLRF    H_IDX
    ; Media umidità
    MOVF    H_SUM_L,W
    MOVWF   H_AVG
    BCF     STATUS,C
    RRF     H_AVG,F
    BCF     STATUS,C
    RRF     H_AVG,F
    BCF     STATUS,C
    RRF     H_AVG,F
    MOVF    H_SUM_H,W
    BTFSC   STATUS,Z
    RETURN
    MOVLW   D'32'
    ADDWF   H_AVG,F
    RETURN

; ============================================================
; DEMO — test con valori simulati (senza hardware reale)
; Simula una serie di temperature oscillanti 22-28°C
; ============================================================
    ORG     0x0100
    ; Tabella valori test
DEMO_TEMPS
    ADDWF   PCL,F
    RETLW   D'22'
    RETLW   D'23'
    RETLW   D'25'
    RETLW   D'27'
    RETLW   D'28'
    RETLW   D'27'
    RETLW   D'25'
    RETLW   D'23'
    RETLW   D'22'
    RETLW   D'24'
    RETLW   D'26'
    RETLW   D'26'

DEMO_HUMS
    ADDWF   PCL,F
    RETLW   D'55'
    RETLW   D'57'
    RETLW   D'58'
    RETLW   D'60'
    RETLW   D'62'
    RETLW   D'61'
    RETLW   D'59'
    RETLW   D'57'
    RETLW   D'55'
    RETLW   D'56'
    RETLW   D'58'
    RETLW   D'59'

    ORG     0x0140
INIT
    BSF     STATUS,RP0
    CLRF    TRISB
    CLRF    TRISD
    BCF     STATUS,RP0
    ; Inizializza somme a zero
    CLRF    T_SUM_L
    CLRF    T_SUM_H
    CLRF    H_SUM_L
    CLRF    H_SUM_H
    ; Primo campione
    MOVLW   D'24'              ; 24°C iniziale
    MOVWF   NEW_TEMP
    MOVLW   D'57'              ; 57% RH iniziale
    MOVWF   NEW_HUM
    CALL    INIT_BUFFERS
    ; Inizializza somme (8 × valore iniziale)
    MOVLW   D'24'
    MOVWF   TMP
    ; 8 × 24 = 192
    BCF     STATUS,C
    RLF     TMP,F              ; ×2 = 48
    BCF     STATUS,C
    RLF     TMP,F              ; ×4 = 96
    BCF     STATUS,C
    RLF     TMP,F              ; ×8 = 192
    MOVF    TMP,W
    MOVWF   T_SUM_L
    CLRF    T_SUM_H
    MOVLW   D'57'
    MOVWF   TMP
    BCF     STATUS,C
    RLF     TMP,F
    BCF     STATUS,C
    RLF     TMP,F
    BCF     STATUS,C
    RLF     TMP,F              ; 57×8 = 456 → H: 1, L: 200
    MOVF    TMP,W
    MOVWF   H_SUM_L
    ; 57×8=456: se overflow, correggi
    BTFSC   STATUS,C
    BSF     H_SUM_H,0

    CLRF    TMP                ; indice demo
MAIN
    ; Leggi campione demo
    MOVF    TMP,W
    CALL    DEMO_TEMPS
    MOVWF   NEW_TEMP
    MOVF    TMP,W
    CALL    DEMO_HUMS
    MOVWF   NEW_HUM
    ; Aggiorna medie
    CALL    UPDATE_TEMP_AVG
    CALL    UPDATE_HUM_AVG
    ; Output su PORTB = media temp, PORTD = media hum
    MOVF    T_AVG,W
    MOVWF   PORTB
    MOVF    H_AVG,W
    MOVWF   PORTD
    ; Avanza indice (modulo 12)
    INCF    TMP,F
    MOVF    TMP,W
    SUBLW   D'12'
    BTFSC   STATUS,Z
    CLRF    TMP
    GOTO    MAIN

    END

Modulo 3 · Display LCD con scroll automatico

Scroll automatico LCD — 3 schermate a rotazione con timing Schermata 0 Temperatura 3 secondi Schermata 1 Umidità 3 secondi Schermata 2 Pressione 3 secondi → torna a 0 Temperatura: T: 24°C Umidita: H: 58 %RH Pression: P: 1013hPa SCREEN_IDX 0→1→2→0 · ogni schermata: SHOW_x() + DELAY_3S() + INCF SCREEN_IDX + modulo 3
; ============================================================
; Lezione 40 — Display LCD con scroll automatico
; PIC16F877A @ 4 MHz
; Alterna 3 schermate ogni 3 secondi:
;   Schermata 1: Temperatura   "T: XX.X°C    "
;   Schermata 2: Umidità       "H: XX %RH    "
;   Schermata 3: Pressione     "P: XXXX hPa  "
; Usa i valori di T_AVG, H_AVG dalla media mobile
; LCD via PCF8574 I²C (da Lez.39, Modulo 2)
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

#DEFINE LCD_ADDR    0x27
#DEFINE LCD_BL      0x08
#DEFINE LCD_EN      0x04
#DEFINE LCD_RS      0x01

    CBLOCK  0x20
; Dati da visualizzare
T_AVG           ; media temperatura
H_AVG           ; media umidità
P_AVG_H         ; media pressione (byte alto)
P_AVG_L         ; media pressione (byte basso)
; LCD
LCD_CMD
LCD_IS_DATA
LCD_NIBBLE
I2C_DATA
I2C_CNT
; Scroll
SCREEN_IDX      ; 0=temp, 1=hum, 2=press
SCROLL_CNT      ; contatore scroll (decrementa)
SCROLL_TICKS    ; numero tick per ogni schermata
; BCD per display
BCD_H
BCD_L
BCD_D3
BCD_D2
BCD_D1
BCD_D0
; Temporanei
TMP
DLY
DLY2
    ENDC

    ORG     0x0000
    GOTO    INIT

; ============================================================
; I2C e LCD minimal (da Lez.39)
; ============================================================
I2C_START
    BSF     STATUS,RP0
    BSF     TRISC,4
    BSF     TRISC,3
    BCF     STATUS,RP0
    NOP
    NOP
    NOP
    NOP
    NOP
    BSF     STATUS,RP0
    BCF     TRISC,4
    BCF     STATUS,RP0
    BCF     PORTC,4
    NOP
    NOP
    NOP
    NOP
    NOP
    BSF     STATUS,RP0
    BCF     TRISC,3
    BCF     STATUS,RP0
    RETURN

I2C_STOP
    BSF     STATUS,RP0
    BCF     TRISC,4
    BCF     STATUS,RP0
    BCF     PORTC,4
    NOP
    NOP
    BSF     STATUS,RP0
    BSF     TRISC,3
    BCF     STATUS,RP0
    NOP
    NOP
    NOP
    NOP
    BSF     STATUS,RP0
    BSF     TRISC,4
    BCF     STATUS,RP0
    NOP
    NOP
    RETURN

PCF_WRITE
    ; Invia W al PCF8574
    MOVWF   TMP
    CALL    I2C_START
    MOVLW   (LCD_ADDR << 1)
    MOVWF   I2C_DATA
    MOVLW   D'8'
    MOVWF   I2C_CNT
PCF_SB
    BSF     STATUS,RP0
    BTFSS   I2C_DATA,7
    BCF     TRISC,4
    BTFSC   I2C_DATA,7
    BSF     TRISC,4
    BCF     STATUS,RP0
    BTFSS   I2C_DATA,7
    BCF     PORTC,4
    NOP
    NOP
    BSF     STATUS,RP0
    BSF     TRISC,3
    BCF     STATUS,RP0
    NOP
    NOP
    NOP
    BSF     STATUS,RP0
    BCF     TRISC,3
    BCF     STATUS,RP0
    RLF     I2C_DATA,F
    DECFSZ  I2C_CNT,F
    GOTO    PCF_SB
    BSF     STATUS,RP0
    BSF     TRISC,4
    BSF     TRISC,3
    BCF     STATUS,RP0
    NOP
    NOP
    NOP
    NOP
    BSF     STATUS,RP0
    BCF     TRISC,3
    BCF     STATUS,RP0
    ; Ora invia il dato vero
    MOVF    TMP,W
    MOVWF   I2C_DATA
    MOVLW   D'8'
    MOVWF   I2C_CNT
PCF_SB2
    BSF     STATUS,RP0
    BTFSS   I2C_DATA,7
    BCF     TRISC,4
    BTFSC   I2C_DATA,7
    BSF     TRISC,4
    BCF     STATUS,RP0
    BTFSS   I2C_DATA,7
    BCF     PORTC,4
    NOP
    NOP
    BSF     STATUS,RP0
    BSF     TRISC,3
    BCF     STATUS,RP0
    NOP
    NOP
    NOP
    BSF     STATUS,RP0
    BCF     TRISC,3
    BCF     STATUS,RP0
    RLF     I2C_DATA,F
    DECFSZ  I2C_CNT,F
    GOTO    PCF_SB2
    BSF     STATUS,RP0
    BSF     TRISC,4
    BSF     TRISC,3
    BCF     STATUS,RP0
    NOP
    NOP
    BSF     STATUS,RP0
    BCF     TRISC,3
    BCF     STATUS,RP0
    CALL    I2C_STOP
    RETURN

LCD_NIBBLE_SEND
    MOVF    LCD_NIBBLE,W
    IORLW   LCD_EN
    CALL    PCF_WRITE
    MOVF    LCD_NIBBLE,W
    ANDLW   ~LCD_EN
    CALL    PCF_WRITE
    RETURN

LCD_SEND
    SWAPF   LCD_CMD,W
    ANDLW   0xF0
    IORLW   LCD_BL
    BTFSC   LCD_IS_DATA,0
    IORLW   LCD_RS
    MOVWF   LCD_NIBBLE
    CALL    LCD_NIBBLE_SEND
    MOVF    LCD_CMD,W
    ANDLW   0xF0
    IORLW   LCD_BL
    BTFSC   LCD_IS_DATA,0
    IORLW   LCD_RS
    MOVWF   LCD_NIBBLE
    CALL    LCD_NIBBLE_SEND
    MOVLW   D'12'
    MOVWF   DLY
LCDDL DECFSZ DLY,F
    GOTO    LCDDL
    RETURN

LCD_CMD_W
    MOVWF   LCD_CMD
    CLRF    LCD_IS_DATA
    CALL    LCD_SEND
    RETURN

LCD_CHAR
    MOVWF   LCD_CMD
    MOVLW   1
    MOVWF   LCD_IS_DATA
    CALL    LCD_SEND
    RETURN

; ============================================================
; LCD_PRINT_VAL — stampa T_AVG o H_AVG come 2 cifre + unità
; W = valore da stampare
; ============================================================
LCD_PRINT_VAL
    MOVWF   BCD_L
    ; Decine
    CLRF    BCD_D1
    CLRF    BCD_D0
BPV_LOOP
    MOVLW   D'10'
    SUBWF   BCD_L,W
    BTFSS   STATUS,C
    GOTO    BPV_DONE
    MOVWF   BCD_L
    INCF    BCD_D1,F
    GOTO    BPV_LOOP
BPV_DONE
    MOVF    BCD_D1,W
    ADDLW   '0'
    CALL    LCD_CHAR
    MOVF    BCD_L,W
    ADDLW   '0'
    CALL    LCD_CHAR
    RETURN

; ============================================================
; SHOW_TEMPERATURE — schermata 1
; Linea 1: "Temperatura:    "
; Linea 2: "T: 25°C  RH:57%"
; ============================================================
SHOW_TEMPERATURE
    ; Posiziona linea 1
    MOVLW   0x80
    CALL    LCD_CMD_W
    MOVLW   'T'
    CALL    LCD_CHAR
    MOVLW   'e'
    CALL    LCD_CHAR
    MOVLW   'm'
    CALL    LCD_CHAR
    MOVLW   'p'
    CALL    LCD_CHAR
    MOVLW   'e'
    CALL    LCD_CHAR
    MOVLW   'r'
    CALL    LCD_CHAR
    MOVLW   'a'
    CALL    LCD_CHAR
    MOVLW   't'
    CALL    LCD_CHAR
    ; Linea 2
    MOVLW   0xC0
    CALL    LCD_CMD_W
    MOVLW   'T'
    CALL    LCD_CHAR
    MOVLW   ':'
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    MOVF    T_AVG,W
    CALL    LCD_PRINT_VAL
    MOVLW   0xDF               ; ° (carattere gradi LCD HD44780)
    CALL    LCD_CHAR
    MOVLW   'C'
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    RETURN

; ============================================================
; SHOW_HUMIDITY — schermata 2
; ============================================================
SHOW_HUMIDITY
    MOVLW   0x80
    CALL    LCD_CMD_W
    MOVLW   'U'
    CALL    LCD_CHAR
    MOVLW   'm'
    CALL    LCD_CHAR
    MOVLW   'i'
    CALL    LCD_CHAR
    MOVLW   'd'
    CALL    LCD_CHAR
    MOVLW   'i'
    CALL    LCD_CHAR
    MOVLW   't'
    CALL    LCD_CHAR
    MOVLW   0xE0               ; à in charset LCD
    CALL    LCD_CHAR
    MOVLW   ':'
    CALL    LCD_CHAR
    MOVLW   0xC0
    CALL    LCD_CMD_W
    MOVLW   'H'
    CALL    LCD_CHAR
    MOVLW   ':'
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    MOVF    H_AVG,W
    CALL    LCD_PRINT_VAL
    MOVLW   ' '
    CALL    LCD_CHAR
    MOVLW   '%'
    CALL    LCD_CHAR
    MOVLW   'R'
    CALL    LCD_CHAR
    MOVLW   'H'
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    RETURN

; ============================================================
; SHOW_PRESSURE — schermata 3 (pressione su PORTB demo)
; ============================================================
SHOW_PRESSURE
    MOVLW   0x80
    CALL    LCD_CMD_W
    MOVLW   'P'
    CALL    LCD_CHAR
    MOVLW   'r'
    CALL    LCD_CHAR
    MOVLW   'e'
    CALL    LCD_CHAR
    MOVLW   's'
    CALL    LCD_CHAR
    MOVLW   's'
    CALL    LCD_CHAR
    MOVLW   'i'
    CALL    LCD_CHAR
    MOVLW   'o'
    CALL    LCD_CHAR
    MOVLW   'n'
    CALL    LCD_CHAR
    MOVLW   0xC0
    CALL    LCD_CMD_W
    MOVLW   'P'
    CALL    LCD_CHAR
    MOVLW   ':'
    CALL    LCD_CHAR
    MOVLW   ' '
    CALL    LCD_CHAR
    MOVLW   '1'
    CALL    LCD_CHAR
    MOVLW   '0'
    CALL    LCD_CHAR
    MOVLW   '1'
    CALL    LCD_CHAR
    MOVLW   '3'
    CALL    LCD_CHAR
    MOVLW   'h'
    CALL    LCD_CHAR
    MOVLW   'P'
    CALL    LCD_CHAR
    MOVLW   'a'
    CALL    LCD_CHAR
    RETURN

; ============================================================
; DELAY_3S — attesa 3 secondi tra schermate
; ============================================================
DELAY_3S
    MOVLW   D'3'
    MOVWF   DLY2
D3S MOVLW  D'250'
    MOVWF   TMP
D3S2 MOVLW D'250'
    MOVWF   DLY
D3S3 DECFSZ DLY,F
    GOTO    D3S3
    DECFSZ  TMP,F
    GOTO    D3S2
    DECFSZ  DLY2,F
    GOTO    D3S
    RETURN

    ORG     0x01C0
INIT
    BSF     STATUS,RP0
    BSF     TRISC,3
    BSF     TRISC,4
    CLRF    TRISB
    CLRF    TRISD
    BCF     STATUS,RP0
    BCF     PORTC,3
    BCF     PORTC,4
    ; Valori demo
    MOVLW   D'24'
    MOVWF   T_AVG
    MOVLW   D'58'
    MOVWF   H_AVG
    CLRF    SCREEN_IDX

; ============================================================
; MAIN — ciclo scroll schermata
; ============================================================
MAIN
    ; Seleziona schermata
    MOVF    SCREEN_IDX,W
    ADDWF   PCL,F
    GOTO    DO_TEMP
    GOTO    DO_HUM
    GOTO    DO_PRESS

DO_TEMP
    CALL    SHOW_TEMPERATURE
    GOTO    NEXT_SCREEN
DO_HUM
    CALL    SHOW_HUMIDITY
    GOTO    NEXT_SCREEN
DO_PRESS
    CALL    SHOW_PRESSURE

NEXT_SCREEN
    CALL    DELAY_3S           ; mostra per 3 secondi
    INCF    SCREEN_IDX,F
    MOVF    SCREEN_IDX,W
    SUBLW   D'3'
    BTFSC   STATUS,Z
    CLRF    SCREEN_IDX         ; 2+1 → 0
    GOTO    MAIN

    END

🧪 Prova tu

  1. Carica Modulo 1. Simula lettura BME280: imposta manualmente TEMP_MSB=0x7C (valore raw medio). Chiama CONVERT_TEMP_SIMPLE. Verifica TEMP_C: con DIG_T1_H=0x00, TEMP_MSB/4 + 20 ≈ 49°C (approssimazione). Poi leggi la chip ID: chiama BME280_READ_REG con W=0xD0. SPI_RX deve simulare 0x60 (chip ID del BME280).
  2. Carica Modulo 2. Inizializza buffer con NEW_TEMP=24, chiama INIT_BUFFERS. Verifica T_SUM_L=192 (24×8). Poi inserisci NEW_TEMP=28 con UPDATE_TEMP_AVG: il vecchio TBUF_0=24 viene rimosso, 28 aggiunto. T_SUM deve diventare 192−24+28=196. T_AVG = 196>>3 = 24 (arrotondato).
  3. Testa lo scroll LCD (Modulo 3): imposta T_AVG=24, H_AVG=58, SCREEN_IDX=0. Chiama SHOW_TEMPERATURE. Poi imposta SCREEN_IDX=1 e chiama SHOW_HUMIDITY. Verifica che il codice LCD_PRINT_VAL produca i caratteri ASCII corretti: per valore 24 → ‘2’ (0x32) poi ‘4’ (0x34).
  4. Sfida: modifica UPDATE_TEMP_AVG per supportare N=16 invece di 8 (cambia la dimensione del buffer e sostituisci RRF × 3 con RRF × 4). Quanta RAM aggiuntiva serve? Qual è il vantaggio in termini di filtraggio del rumore rispetto a N=8?

❓ Quiz rapido

1. Il Forced mode del BME280 consuma molto meno del Normal mode. Perché, e quando conviene usare Normal mode invece?

Mostra risposta

In Forced mode il BME280 si attiva, esegue la misura (pochi ms), poi torna automaticamente in sleep mode (corrente ~0.1µA). Il master deve inviare un nuovo comando per ogni misura. In Normal mode il chip esegue misure continue in autonomia a intervalli configurabili (0.5ms–1000ms) senza bisogno di comandi — il master legge semplicemente i dati quando vuole. Normal mode conviene quando si vogliono misure frequenti (>1Hz) o quando il tempo di latenza è critico: il chip ha già i dati pronti prima che il master li chieda. Forced mode è ideale per stazioni meteo con aggiornamento ogni 30s–5min dove il risparmio energetico domina.


2. UPDATE_TEMP_AVG usa la somma a 16 bit T_SUM_H:T_SUM_L. Con N=8 e valori fino a 99°C, qual è il valore massimo della somma e perché basta un 16-bit?

Mostra risposta

Con 8 campioni da massimo 99°C ciascuno, la somma massima è 99 × 8 = 792. Un intero a 16 bit rappresenta fino a 65535 — quindi 792 ci sta comodamente in 10 bit, figuriamoci in 16. In realtà basterebbe anche un 16-bit per N=256 campioni × 255 valore massimo = 65280. Con N=8 sarebbe bastato anche un 11-bit ma il PIC lavora a 8 bit, quindi si usa comodamente una coppia di registri da 8 bit. La propagazione del carry tra T_SUM_L e T_SUM_H è essenziale: le istruzioni BTFSC STATUS,C e INCF/DECF T_SUM_H gestiscono il riporto aritmetico manualmente.


3. SCREEN_IDX usa ADDWF PCL,F per saltare alla schermata giusta. Qual è il rischio di questo pattern con più di 8 schermate e come si risolve?

Mostra risposta

Il pattern ADDWF PCL,F funziona solo se tutta la tabella di salto si trova nella stessa pagina di 256 byte del Program Counter. Se i GOTO della tabella attraversano un confine di pagina (ogni 256 parole), PCL si azzera e il salto va nel posto sbagliato. Per più di 8 schermate (o se il codice è abbastanza grande) si deve: 1) mettere la tabella di salto all’inizio di una pagina con ORG allineato (ORG 0x0100, 0x0200 ecc.); 2) aggiungere MOVLW HIGH(TABLE) e MOVWF PCLATH prima di ADDWF PCL per impostare il byte alto del PC; 3) usare un approccio alternativo con confronto sequenzivo (SUBWF + BTFSC) che non dipende dalla posizione del codice.

Prossima lezione →

Lezione 41 — Progetto 4: Servo controller a 8 canali
8 servo motori controllati con PWM software multiplexato via Timer0. Interfaccia USART per comandi da PC. Interpolazione lineare per movimenti fluidi. Gestione posizione con EEPROM.

© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 40