Lezione 29 di 43

Lezione 28

🎓 PIC16 — From Zero to Hero · Lezione 28 · Modulo 5

I²C — Comunicazione con sensori e display 🌡️🖥️

Il modulo MSSP del PIC16F877A in modalità I²C Master. Lettura di temperatura
dal DS1621 e scrittura di testo su LCD HD44780 tramite I/O expander PCF8574.

🎯 Cosa impari in questa lezione

  • Principio del bus I²C: 2 fili (SDA + SCL), multi-slave, open-drain + pull-up
  • Condizioni di bus: START, Repeated START, STOP, ACK, NACK
  • Modulo MSSP in I²C Master mode: registri SSPCON, SSPCON2, SSPSTAT, SSPADD
  • Formula baud rate: Fbaud = Fosc / (4 × (SSPADD+1))
  • Sequenza completa di lettura con Repeated START (write cmd → read data)
  • Lettura temperatura dal DS1621: 2 byte, gradi interi + frazione (±0.5°C)
  • LCD HD44780 via PCF8574 I/O expander: nibble mode, mapping bit, strobe EN

📋 Scheda lezione

Lezione: 28 / 42
Modulo: 5 — Periferiche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 25 (UART), Lez. 26 (ADC)
Difficoltà: ★★★★★
Periferica: MSSP I²C Master
Slave: DS1621, PCF8574+LCD


1 · Il protocollo I²C — transazione e timing

I²C usa solo 2 fili per connettere fino a 127 dispositivi: SCL (clock generato dal master) e SDA (dati bidirezionali). Entrambe le linee sono open-drain — ogni dispositivo può solo portarle a 0, mai a 1. Il livello alto è garantito da resistenze di pull-up esterne (tipicamente 4.7kΩ per 100kHz). Una transazione parte con lo START (SDA scende mentre SCL è alto), poi master invia l’indirizzo a 7 bit + bit R/W, lo slave risponde con ACK (SDA=0 durante il 9° clock). I dati seguono in byte, ognuno con ACK. Il STOP (SDA sale mentre SCL è alto) libera il bus.

Protocollo I²C — transazione write completa con ACK SDA SCL START 1 0 0 1 0 0 0 0 addr[6:0] = 0x48 R/W=0 A ACK d7 d6 d5 d4 d3 d2 d1 d0 DATA byte A STOP 9 clock addr+R/W 9 clock data+ACK Open-drain + pull-up RC3 = SCL RC4 = SDA SSPADD=9 → 100kHz Pull-up 4.7kΩ a Vdd Fbaud = Fosc / (4 × (SSPADD+1)) SSPADD = Fosc/(4×Fbaud) − 1 100kHz: SSPADD = 4MHz/(4×100000)−1 = 9 400kHz: SSPADD = 4MHz/(4×400000)−1 = 1 (Fast mode — verificare SMP=0)

2 · Codice — configurazione MSSP e invio comandi al DS1621

; ============================================================
; Lezione 28 — I2C Master: invio START, indirizzo, dati
; PIC16F877A @ 4 MHz, modulo MSSP in I2C Master mode
; Frequenza I2C: Fbaud = Fosc / (4 × (SSPADD+1))
; 100kHz: SSPADD = 4MHz/(4×100000) - 1 = 9
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

; Registri MSSP I2C:
; SSPCON  (0x14) Bank0: WCOL SSPOV SSPEN CKP SSPM3-0
;   SSPM=1000 → I2C Master mode
; SSPCON2 (0x91) Bank1: GCEN ACKSTAT ACKDT ACKEN RCEN PEN RSEN SEN
; SSPSTAT (0x94) Bank1: SMP CKE D/A P S R/W UA BF
; SSPBUF  (0x13) Bank0: buffer TX/RX
; SSPADD  (0x93) Bank1: baud rate divisor

; Indirizzo slave DS1621 (sensore temp): 0x90 (A2=A1=A0=0)
#DEFINE DS1621_ADDR  0x90      ; 7 bit addr + write bit (0)

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    BSF     STATUS,RP0         ; Bank 1
    ; RC3=SCL, RC4=SDA → open-drain, pull-up esterni necessari
    BSF     TRISC,3            ; RC3 input (I2C usa open-drain)
    BSF     TRISC,4            ; RC4 input
    MOVLW   D'9'
    MOVWF   SSPADD             ; 100kHz @ 4MHz
    MOVLW   b'10000000'        ; SMP=1 (slew rate off per 100kHz)
    MOVWF   SSPSTAT
    BCF     STATUS,RP0         ; Bank 0
    ; SSPCON: SSPEN=1, SSPM=1000 (I2C Master)
    MOVLW   b'00101000'        ; SSPEN=1, SSPM=1000
    MOVWF   SSPCON

    ; Configura DS1621: invia comando 0xAC (Access Config)
    ; poi byte 0x00 (continuous conversion, pol=active high)
    CALL    I2C_START
    MOVLW   DS1621_ADDR        ; 0x90 = addr+write
    CALL    I2C_SEND_BYTE
    MOVLW   0xAC               ; Access Config
    CALL    I2C_SEND_BYTE
    MOVLW   0x00               ; continuous, polarity=0
    CALL    I2C_SEND_BYTE
    CALL    I2C_STOP

    ; Invia Start Convert (0xEE)
    CALL    I2C_START
    MOVLW   DS1621_ADDR
    CALL    I2C_SEND_BYTE
    MOVLW   0xEE               ; Start Convert T
    CALL    I2C_SEND_BYTE
    CALL    I2C_STOP

MAIN
    GOTO    MAIN               ; configurazione completata

; ============================================================
; I2C_START — genera condizione di START (SDA↓ mentre SCL alto)
; ============================================================
I2C_START
    BSF     STATUS,RP0
    BSF     SSPCON2,SEN        ; invia START
    BCF     STATUS,RP0
I2C_START_WAIT
    BSF     STATUS,RP0
    BTFSC   SSPCON2,SEN        ; SEN=0 quando START completato
    GOTO    I2C_START_WAIT
    BCF     STATUS,RP0
    RETURN

; ============================================================
; I2C_STOP — genera condizione di STOP (SDA↑ mentre SCL alto)
; ============================================================
I2C_STOP
    BSF     STATUS,RP0
    BSF     SSPCON2,PEN        ; invia STOP
    BCF     STATUS,RP0
I2C_STOP_WAIT
    BSF     STATUS,RP0
    BTFSC   SSPCON2,PEN        ; PEN=0 quando STOP completato
    GOTO    I2C_STOP_WAIT
    BCF     STATUS,RP0
    RETURN

; ============================================================
; I2C_SEND_BYTE — trasmette byte in W, aspetta ACK
; Copia W in SSPBUF, attende fine trasmissione
; ============================================================
I2C_SEND_BYTE
    MOVWF   SSPBUF             ; carica byte da trasmettere
I2C_TX_WAIT
    BSF     STATUS,RP0
    BTFSS   SSPSTAT,BF         ; BF=1 → trasmissione in corso
    BTFSC   SSPCON2,ACKEN      ; controlla anche ACKEN
    GOTO    I2C_TX_WAIT
    ; Controlla ACK ricevuto dallo slave
    BTFSC   SSPCON2,ACKSTAT    ; ACKSTAT=0 → ACK OK, =1 → NACK
    GOTO    I2C_NACK           ; errore: nessun ACK
    BCF     STATUS,RP0
    RETURN
I2C_NACK
    BCF     STATUS,RP0
    RETURN                     ; gestione errore (semplificata)

    END

3 · Lettura DS1621 — sequenza con Repeated START

Leggere un dato da un sensore I²C richiede due fasi: prima si scrive il registro da leggere (0xAA = “Read Temperature”), poi si rilancia una condizione di Repeated START senza liberare il bus e si invia l’indirizzo in modalità lettura (bit R/W=1). Lo slave risponde con i dati. Il master segnala la fine della lettura con un NACK prima dell’ultimo byte, poi invia STOP.

Sequenza lettura DS1621 — Write (cmd) + Repeated START + Read S 0x90 addr+W ACK 0xAA Read Temp ACK Rs rSTART 0x91 addr+R ACK TEMP_INT gradi interi mACK TEMP_FRAC 0x00 / 0x80 NACK +P Legenda: S=START, Rs=Repeated START, P=STOP ACK dallo slave (SDA=0) mACK=ACK dal master (vuole altro byte) Indirizzo slave (7 bit + R/W) Comando registro Dati ricevuti dallo slave Esempio: TEMP_INT=0x17(23°C), TEMP_FRAC=0x80 → temperatura = 23.5°C TEMP_INT=0xE9(−23 in complemento a 2), TEMP_FRAC=0x00 → temperatura = −23.0°C
; ============================================================
; Lezione 28 — I2C: lettura temperatura da DS1621
; Sequenza: START → addr+W → 0xAA → RESTART → addr+R → leggi → STOP
; PIC16F877A @ 4 MHz, I2C 100kHz
; DS1621 restituisce 2 byte: integer (°C) + 0x80 se +0.5°C
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

#DEFINE DS1621_W  0x90         ; addr + write (R/W=0)
#DEFINE DS1621_R  0x91         ; addr + read  (R/W=1)

    CBLOCK  0x20
TEMP_INT        ; gradi interi (con segno, complemento a 2)
TEMP_FRAC       ; 0x00=.0°C, 0x80=.5°C
I2C_RX_BYTE    ; byte ricevuto tramite I2C
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    ; (configurazione MSSP come lez. 28 programma 1)
    BSF     STATUS,RP0
    BSF     TRISC,3
    BSF     TRISC,4
    MOVLW   D'9'
    MOVWF   SSPADD
    MOVLW   b'10000000'
    MOVWF   SSPSTAT
    BCF     STATUS,RP0
    MOVLW   b'00101000'
    MOVWF   SSPCON
    ; Avvia conversione continua (già configurato)

MAIN
    CALL    DS1621_READ_TEMP
    ; TEMP_INT = temperatura in gradi (es. 23 → 0x17)
    ; TEMP_FRAC = 0x00 (.0°C) o 0x80 (.5°C)
    ; Qui potremmo trasmettere via UART (vedi lez. 26)
    CALL    DELAY_1S
    GOTO    MAIN

; ============================================================
; DS1621_READ_TEMP
; Legge 2 byte di temperatura e li salva in TEMP_INT, TEMP_FRAC
; Sequenza I2C: S → 0x90 → 0xAA → Rs → 0x91 → RX → RX → P
; ============================================================
DS1621_READ_TEMP
    ; 1. Invia comando "Read Temperature" (0xAA)
    CALL    I2C_START
    MOVLW   DS1621_W
    CALL    I2C_SEND
    MOVLW   0xAA               ; Read Temperature command
    CALL    I2C_SEND

    ; 2. Repeated START + indirizzo in lettura
    CALL    I2C_RESTART
    MOVLW   DS1621_R           ; 0x91
    CALL    I2C_SEND

    ; 3. Ricevi byte integer (ACK dal master → vogliamo il secondo)
    CALL    I2C_RECV_ACK       ; ricevi con ACK (seguirà altro byte)
    MOVF    SSPBUF,W
    MOVWF   TEMP_INT

    ; 4. Ricevi byte frazionario (NACK → ultimo byte)
    CALL    I2C_RECV_NACK      ; ricevi con NACK (fine lettura)
    MOVF    SSPBUF,W
    MOVWF   TEMP_FRAC

    CALL    I2C_STOP
    RETURN

; ============================================================
; I2C_START
; ============================================================
I2C_START
    BSF     STATUS,RP0
    BSF     SSPCON2,SEN
    BCF     STATUS,RP0
IS_W BTFSC   SSPSTAT,S         ; attendi S=1 (START sul bus)
    GOTO    $+2
    GOTO    IS_W
    RETURN

; ============================================================
; I2C_RESTART — Repeated START (senza STOP intermedio)
; ============================================================
I2C_RESTART
    BSF     STATUS,RP0
    BSF     SSPCON2,RSEN       ; Repeated START
    BCF     STATUS,RP0
IRS  BSF     STATUS,RP0
    BTFSC   SSPCON2,RSEN
    GOTO    IRS
    BCF     STATUS,RP0
    RETURN

; ============================================================
; I2C_STOP
; ============================================================
I2C_STOP
    BSF     STATUS,RP0
    BSF     SSPCON2,PEN
    BCF     STATUS,RP0
IP_W BSF     STATUS,RP0
    BTFSC   SSPCON2,PEN
    GOTO    IP_W
    BCF     STATUS,RP0
    RETURN

; ============================================================
; I2C_SEND — trasmette W, attende completamento
; ============================================================
I2C_SEND
    MOVWF   SSPBUF
ITW  BSF     STATUS,RP0
    BTFSS   SSPSTAT,BF
    BTFSC   SSPCON2,ACKSTAT
    GOTO    ITW
    BCF     STATUS,RP0
    RETURN

; ============================================================
; I2C_RECV_ACK — riceve byte, risponde ACK (altri byte seguono)
; ============================================================
I2C_RECV_ACK
    BSF     STATUS,RP0
    BSF     SSPCON2,RCEN       ; abilita ricezione
    BCF     STATUS,RP0
IRW  BSF     STATUS,RP0
    BTFSS   SSPSTAT,BF         ; attendi buffer pieno
    GOTO    IRW
    ; Invia ACK (ACKDT=0=ACK, poi ACKEN=1 per inviarlo)
    BCF     SSPCON2,ACKDT      ; ACK
    BSF     SSPCON2,ACKEN      ; invia ACK
IAW  BTFSC   SSPCON2,ACKEN
    GOTO    IAW
    BCF     STATUS,RP0
    RETURN

; ============================================================
; I2C_RECV_NACK — riceve byte, risponde NACK (fine lettura)
; ============================================================
I2C_RECV_NACK
    BSF     STATUS,RP0
    BSF     SSPCON2,RCEN
    BCF     STATUS,RP0
INW  BSF     STATUS,RP0
    BTFSS   SSPSTAT,BF
    GOTO    INW
    BSF     SSPCON2,ACKDT      ; NACK (=1)
    BSF     SSPCON2,ACKEN
INA  BTFSC   SSPCON2,ACKEN
    GOTO    INA
    BCF     STATUS,RP0
    RETURN

DELAY_1S
    MOVLW   D'100'
    MOVWF   0x70
DLA MOVLW   D'250'
    MOVWF   0x71
DLB MOVLW   D'40'
    MOVWF   0x72
DLC DECFSZ  0x72,F
    GOTO    DLC
    DECFSZ  0x71,F
    GOTO    DLB
    DECFSZ  0x70,F
    GOTO    DLA
    RETURN

    END

4 · LCD via PCF8574 — nibble mode e mapping bit

Il PCF8574 è un I/O expander I²C a 8 bit: riceve un byte via I²C e porta i suoi 8 pin di output al livello corrispondente. Connettendo questi 8 pin all’LCD HD44780 si ottiene un LCD I²C. Il mapping standard assegna P7:P4 ai dati D7:D4, P3 al backlight, P2 al segnale EN (enable), P1 a R/W (tenuto sempre basso = write), P0 a RS (0=comando, 1=dato). Poiché si usano solo i 4 bit alti del bus dati, si lavora in nibble mode: ogni byte dell’LCD richiede due trasferimenti I²C, uno per il nibble alto e uno per il nibble basso, ciascuno con uno strobe dell’EN.

PCF8574 — mapping bit per LCD HD44780 in nibble mode PCF8574 — 8 bit di output P7 = D7 P6 = D6 P5 = D5 P4 = D4 P3 = BL P2 = EN P1 = RW P0 = RS Esempio: invia carattere ‘H’ (0x48 = 0100 1000) in nibble mode, RS=1 (data) 1° byte PCF8574: 0 1 0 0 1 1 0 1 = 0x4D (nibble alto 0x40, BL=1, EN=1, RW=0, RS=1) → strobe EN alto poi 0x49 (EN=0) per completare il ciclo, poi nibble basso 0x80 → 0x8D/0x89

⚠️ Attenzione al delay post-comando: l’LCD HD44780 richiede tempi di esecuzione diversi per comandi diversi. Il comando “Clear Display” (0x01) richiede ~1.52ms, gli altri comandi ~37µs, la scrittura di caratteri ~37µs. Il DELAY_1MS usato in questo esempio è sicuro per tutti i casi ma rallenta la scrittura. In applicazioni ad alta velocità si usa il busy flag (bit DB7 letto con RS=0, R/W=1) invece dei delay fissi.

; ============================================================
; Lezione 28 — I2C: scrittura su LCD con PCF8574 I/O expander
; PCF8574 addr: 0x40 (A2=A1=A0=0), controlla LCD HD44780 via I2C
; Nibble mode: D7-D6-D5-D4-BL-EN-RW-RS
; PIC16F877A @ 4 MHz, I2C 100kHz
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

#DEFINE LCD_ADDR  0x40         ; PCF8574 write address
#DEFINE BL        b'00001000'  ; backlight bit
#DEFINE EN        b'00000100'  ; enable strobe
#DEFINE RW        b'00000010'  ; 0=write (sempre 0 qui)
#DEFINE RS_CMD    0x00         ; RS=0: command
#DEFINE RS_DATA   0x01         ; RS=1: data

    CBLOCK  0x20
LCD_BYTE        ; byte da inviare all&#x27;LCD
LCD_NIBBLE      ; nibble corrente
LCD_RS          ; 0=command, 1=data
I2C_TEMP        ; registro temporaneo I2C
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    ; Init MSSP
    BSF     STATUS,RP0
    BSF     TRISC,3
    BSF     TRISC,4
    MOVLW   D'9'
    MOVWF   SSPADD
    MOVLW   b'10000000'
    MOVWF   SSPSTAT
    BCF     STATUS,RP0
    MOVLW   b'00101000'
    MOVWF   SSPCON
    ; Delay power-up LCD: 50ms
    CALL    DELAY_50MS
    ; Sequenza init LCD 4-bit
    CALL    LCD_INIT

; ============================================================
; MAIN: stampa "Hello PIC!" sulla prima riga
; ============================================================
MAIN
    MOVLW   0x80               ; DDRAM addr 0x00 (riga 1, col 0)
    MOVWF   LCD_BYTE
    CLRF    LCD_RS
    CALL    LCD_SEND

    MOVLW   'H'
    CALL    LCD_PUTCHAR
    MOVLW   'e'
    CALL    LCD_PUTCHAR
    MOVLW   'l'
    CALL    LCD_PUTCHAR
    MOVLW   'l'
    CALL    LCD_PUTCHAR
    MOVLW   'o'
    CALL    LCD_PUTCHAR
    MOVLW   ' '
    CALL    LCD_PUTCHAR
    MOVLW   'P'
    CALL    LCD_PUTCHAR
    MOVLW   'I'
    CALL    LCD_PUTCHAR
    MOVLW   'C'
    CALL    LCD_PUTCHAR
    MOVLW   '!'
    CALL    LCD_PUTCHAR

    ; Seconda riga
    MOVLW   0xC0               ; DDRAM addr 0x40 (riga 2)
    MOVWF   LCD_BYTE
    CLRF    LCD_RS
    CALL    LCD_SEND

    MOVLW   'I'
    CALL    LCD_PUTCHAR
    MOVLW   '2'
    CALL    LCD_PUTCHAR
    MOVLW   'C'
    CALL    LCD_PUTCHAR
    MOVLW   ' '
    CALL    LCD_PUTCHAR
    MOVLW   'O'
    CALL    LCD_PUTCHAR
    MOVLW   'K'
    CALL    LCD_PUTCHAR

LOOP
    GOTO    LOOP

; ============================================================
; LCD_PUTCHAR — invia carattere ASCII in W all'LCD
; ============================================================
LCD_PUTCHAR
    MOVWF   LCD_BYTE
    MOVLW   1
    MOVWF   LCD_RS             ; RS=1 (data)
    CALL    LCD_SEND
    RETURN

; ============================================================
; LCD_SEND — invia LCD_BYTE in nibble mode via PCF8574
; Prima il nibble alto, poi il nibble basso
; ============================================================
LCD_SEND
    ; Nibble alto (bit7:4)
    MOVF    LCD_BYTE,W
    ANDLW   0xF0               ; mantieni solo bit7:4
    MOVWF   LCD_NIBBLE

    ; Aggiungi RS e BL
    MOVF    LCD_RS,F
    BTFSC   STATUS,Z
    GOTO    LCD_NO_RS_H
    MOVF    LCD_NIBBLE,W
    IORLW   RS_DATA
    MOVWF   LCD_NIBBLE
LCD_NO_RS_H
    MOVF    LCD_NIBBLE,W
    IORLW   BL                 ; sempre backlight acceso

    ; Strobe EN: alto → basso
    MOVWF   I2C_TEMP
    IORWF   I2C_TEMP,W
    CALL    PCF8574_SEND       ; EN=0
    MOVF    I2C_TEMP,W
    IORLW   EN                 ; EN=1
    CALL    PCF8574_SEND
    CALL    DELAY_1MS
    MOVF    I2C_TEMP,W
    ANDLW   b'11111011'        ; EN=0
    CALL    PCF8574_SEND

    ; Nibble basso (bit3:0 → sposta in posizione 7:4)
    MOVF    LCD_BYTE,W
    ANDLW   0x0F
    BCF     STATUS,C
    RLF     LCD_NIBBLE,F       ; × 2
    RLF     LCD_NIBBLE,F       ; × 4
    RLF     LCD_NIBBLE,F       ; × 8
    RLF     LCD_NIBBLE,F       ; × 16 (nibble basso ora in bit7:4)
    MOVWF   LCD_NIBBLE

    MOVF    LCD_RS,F
    BTFSC   STATUS,Z
    GOTO    LCD_NO_RS_L
    MOVF    LCD_NIBBLE,W
    IORLW   RS_DATA
    MOVWF   LCD_NIBBLE
LCD_NO_RS_L
    MOVF    LCD_NIBBLE,W
    IORLW   BL
    MOVWF   I2C_TEMP
    IORLW   EN
    CALL    PCF8574_SEND       ; EN=1
    CALL    DELAY_1MS
    MOVF    I2C_TEMP,W
    ANDLW   b'11111011'
    CALL    PCF8574_SEND       ; EN=0
    CALL    DELAY_1MS
    RETURN

; ============================================================
; LCD_INIT — sequenza di inizializzazione HD44780 in 4-bit
; ============================================================
LCD_INIT
    CALL    DELAY_50MS
    MOVLW   0x30               ; Function Set 8-bit (3×)
    CALL    LCD_PULSE
    CALL    DELAY_5MS
    MOVLW   0x30
    CALL    LCD_PULSE
    CALL    DELAY_1MS
    MOVLW   0x30
    CALL    LCD_PULSE
    MOVLW   0x20               ; Switch to 4-bit
    CALL    LCD_PULSE
    ; Ora in 4-bit: invia comandi completi
    MOVLW   0x28               ; 4-bit, 2 righe, 5×8
    MOVWF   LCD_BYTE
    CLRF    LCD_RS
    CALL    LCD_SEND
    MOVLW   0x0C               ; Display ON, cursor OFF
    MOVWF   LCD_BYTE
    CALL    LCD_SEND
    MOVLW   0x01               ; Clear display
    MOVWF   LCD_BYTE
    CALL    LCD_SEND
    CALL    DELAY_5MS
    MOVLW   0x06               ; Entry mode: increment, no shift
    MOVWF   LCD_BYTE
    CALL    LCD_SEND
    RETURN

LCD_PULSE
    MOVWF   I2C_TEMP
    IORLW   BL
    IORLW   EN
    CALL    PCF8574_SEND
    CALL    DELAY_1MS
    MOVF    I2C_TEMP,W
    IORLW   BL
    ANDLW   b'11111011'
    CALL    PCF8574_SEND
    CALL    DELAY_1MS
    RETURN

; ============================================================
; PCF8574_SEND — invia W al PCF8574 via I2C
; ============================================================
PCF8574_SEND
    MOVWF   I2C_TEMP
    ; START
    BSF     STATUS,RP0
    BSF     SSPCON2,SEN
    BCF     STATUS,RP0
PS1 BSF     STATUS,RP0
    BTFSC   SSPCON2,SEN
    GOTO    PS1
    BCF     STATUS,RP0
    ; Indirizzo
    MOVLW   LCD_ADDR
    MOVWF   SSPBUF
PS2 BSF     STATUS,RP0
    BTFSS   SSPSTAT,BF
    GOTO    PS2
    BCF     STATUS,RP0
    ; Dato
    MOVF    I2C_TEMP,W
    MOVWF   SSPBUF
PS3 BSF     STATUS,RP0
    BTFSS   SSPSTAT,BF
    GOTO    PS3
    BCF     STATUS,RP0
    ; STOP
    BSF     STATUS,RP0
    BSF     SSPCON2,PEN
    BCF     STATUS,RP0
PS4 BSF     STATUS,RP0
    BTFSC   SSPCON2,PEN
    GOTO    PS4
    BCF     STATUS,RP0
    RETURN

DELAY_50MS
    MOVLW   D'50'
    MOVWF   0x70
D5A CALL    DELAY_1MS
    DECFSZ  0x70,F
    GOTO    D5A
    RETURN

DELAY_5MS
    MOVLW   D'5'
    MOVWF   0x70
D1A CALL    DELAY_1MS
    DECFSZ  0x70,F
    GOTO    D1A
    RETURN

DELAY_1MS
    MOVLW   D'250'
    MOVWF   0x71
D1B DECFSZ  0x71,F
    GOTO    D1B
    RETURN

    END

🧪 Prova tu

  1. Nel simulatore, carica il programma 1. Nel pannello I²C configura uno slave con indirizzo 0x48 (DS1621 con tutti i pin di indirizzo a 0). Esegui step by step: verifica che dopo I2C_START il bit S di SSPSTAT diventi 1, e che dopo I2C_SEND_BYTE il bit ACKSTAT di SSPCON2 sia 0 (ACK ricevuto).
  2. Modifica il programma 2 per stampare la temperatura via UART nel formato “T=+023.5C “. Usa TEMP_INT per i gradi (controlla il segno: se bit7=1 è negativo, calcola il valore assoluto con COMF+ADDLW 1), TEMP_FRAC per decidere se aggiungere “.0” o “.5”.
  3. Carica il programma LCD. Verifica nel pannello I²C la sequenza di byte inviati al PCF8574. Il primo byte per ‘H’ dovrebbe essere 0x4D (nibble alto con EN=1, BL=1, RS=1), poi 0x49 (EN=0). Calcola manualmente il byte per il nibble basso di ‘H’.
  4. Sfida: combina il DS1621 e l’LCD in un unico programma: leggi la temperatura ogni secondo e visualizzala sulla riga 2 dell’LCD, mantenendo “Hello PIC!” fisso sulla riga 1. Entrambi i device sono sullo stesso bus I²C — il master gestisce alternandosi tra i due indirizzi (0x90 per DS1621, 0x40 per PCF8574).

❓ Quiz rapido

1. Perché RC3 e RC4 vanno configurati come input (TRISC,3=1 e TRISC,4=1) anche se il modulo MSSP usa queste linee come output?

Mostra risposta

Il bus I²C usa linee open-drain: ogni dispositivo può solo portare la linea a 0 (assorbendo corrente), mai a 1. Il livello alto viene garantito dalla resistenza di pull-up esterna. Sul PIC16F877A, quando il modulo MSSP è attivo e TRISC è configurato come input, il modulo MSSP prende il controllo dei driver dei pin in modo open-drain. Se TRISC fosse 0 (output), il latch di output del GPIO sovrascrive il modulo MSSP e la linea potrebbe essere forzata a 1 dal PIC stesso, violando il protocollo open-drain e causando conflitti di bus se uno slave tenta di portare la linea a 0.


2. Cosa succederebbe se si omettesse il Repeated START e si usasse invece STOP + START per la fase di lettura dal DS1621?

Mostra risposta

Funzionerebbe comunque in molti casi, ma è meno affidabile. Il Repeated START mantiene il master in controllo del bus senza liberarlo: nessun altro master (in sistemi multi-master) può inserirsi tra la scrittura del comando e la lettura. Con STOP+START c’è una finestra temporale durante la quale un altro master potrebbe acquisire il bus e inviare una transazione diversa al DS1621, resettando il puntatore del registro interno. Il DS1621 specificatamente richiede il Repeated START per garantire che si stia leggendo il registro appena comandato.


3. Il PCF8574 ha indirizzo I²C 0x40 con A2=A1=A0=0. Se si vogliono 2 LCD sullo stesso bus, quali indirizzi si usano?

Mostra risposta

Il PCF8574 ha indirizzo base 0x40 (0100 000x dove x=R/W). I bit A2:A0 sono configurabili hardware collegando i pin corrispondenti a Vdd o Vss: A2=A1=A0=0 → 0x40; A0=1 → 0x42; A1=1 → 0x44; A2=1 → 0x48. Per due LCD si usano 0x40 e 0x42 (diversi solo per A0). Sullo stesso bus si possono avere fino a 8 PCF8574 (indirizzi 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E), permettendo 8 LCD indipendenti. Il DS1621 (indirizzo base 0x90) non confligge perché ha un indirizzo di base diverso.

Prossima lezione →

Lezione 29 — SPI: comunicazione ad alta velocità
Il modulo MSSP in modalità SPI Master. Lettura da ADC esterno MCP3204, scrittura su DAC MCP4921 e controllo di display OLED SSD1306.

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