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
📐 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 & 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 & 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
💡 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 >> 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
; ============================================================ ; 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
- 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).
- 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).
- 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).
- 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