Lezione 29
🎓 PIC16 — From Zero to Hero · Lezione 29 · Modulo 5
SPI — Comunicazione ad alta velocità ⚡
Il modulo MSSP del PIC16F877A in modalità SPI Master. Lettura da ADC esterno
MCP3204 a 12 bit e generazione segnali analogici con DAC MCP4921.
🎯 Cosa impari in questa lezione
- Differenze tra SPI e I²C: velocità, pin, full-duplex, modalità clock
- I 4 modi SPI: CPOL (polarità clock) × CPHA (fase campionamento)
- Configurazione MSSP SPI Master:
SSPCON,SSPSTAT, baud rate (Fosc/4, /16, /64) - Temporizzazione SPI mode 0,0: dati stabili su fronte di salita SCK
- Lettura ADC esterno MCP3204 a 12 bit: sequenza 3 byte, ricostruzione risultato
- Scrittura su DAC MCP4921 a 12 bit: pacchetto 16 bit con header di controllo
- Segnale LDAC per sincronizzare l’aggiornamento dell’uscita analogica
📋 Scheda lezione
Lezione: 29 / 42
Modulo: 5 — Periferiche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 28 (I²C/MSSP)
Difficoltà: ★★★★☆
Periferica: MSSP SPI Master
Slave: MCP3204 ADC, MCP4921 DAC
1 · SPI vs I²C — quando usare quale
SPI è più veloce di I²C (da 10× a 100× in base all’hardware) ed è full-duplex — master e slave trasmettono contemporaneamente sulle linee MOSI e MISO. Il prezzo è un filo in più per ogni slave aggiuntivo (CS). I²C è preferibile quando i pin sono scarsi e la velocità è secondaria. Sul PIC16F877A entrambi usano lo stesso modulo MSSP (RC3/RC4/RC5), ma non possono essere attivi simultaneamente.
2 · Temporizzazione SPI mode 0,0 e configurazione MSSP
🔑 SSPSTAT per mode 0,0: impostare SMP=0 (campiona al centro del bit, obbligatorio in Master mode) e CKE=1 (i dati cambiano sul fronte attivo di SCK). Con CKP=0 in SSPCON (clock idle basso) si ottiene esattamente CPOL=0, CPHA=0. La combinazione SSPSTAT = 0x40 (CKE=1, SMP=0) è quella più comune per ADC e DAC SPI standard.
3 · Codice — configurazione MSSP SPI e trasferimento base
; ============================================================ ; Lezione 29 — SPI Master: configurazione MSSP e invio byte ; PIC16F877A @ 4 MHz, SPI mode 0,0 (CPOL=0, CPHA=0) ; Fbaud = Fosc/4 = 1 MHz (SSPM=0000) ; RC5=SDO, RC4=SDI, RC3=SCK, RB2=CS (chip select software) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF ; Registri MSSP SPI: ; SSPCON (0x14): WCOL SSPOV SSPEN CKP SSPM3-0 ; SSPM=0000 → SPI Master Fosc/4 ; SSPM=0001 → SPI Master Fosc/16 ; SSPM=0010 → SPI Master Fosc/64 ; CKP=0 → clock idle LOW (CPOL=0) ; SSPSTAT (0x94 Bank1): SMP CKE ... ; SMP=0 → campiona a metà (Master) ; CKE=1 → trasmette su fronte discendente (CPHA=0 con CKP=0) ; SSPBUF (0x13): dato TX/RX (stesso registro) #DEFINE CS_PIN PORTB,2 ; Chip Select attivo basso #DEFINE CS_TRIS TRISB,2 CBLOCK 0x20 SPI_TX ; byte da trasmettere SPI_RX ; byte ricevuto ENDC ORG 0x0000 GOTO INIT ORG 0x0005 INIT BSF STATUS,RP0 ; Bank 1 ; RC3=SCK output, RC4=SDI input, RC5=SDO output BCF TRISC,3 ; SCK output BSF TRISC,4 ; SDI input BCF TRISC,5 ; SDO output BCF CS_TRIS ; CS output ; SSPSTAT: SMP=0, CKE=1 (mode 0,0) MOVLW b'01000000' ; CKE=1 MOVWF SSPSTAT BCF STATUS,RP0 ; Bank 0 ; CS alto (inattivo) all'avvio BSF CS_PIN ; SSPCON: SSPEN=1, CKP=0, SSPM=0000 (Fosc/4=1MHz) MOVLW b'00100000' ; SSPEN=1, SSPM=0000 MOVWF SSPCON ; ============================================================ ; MAIN: invia byte 0xA5 e legge risposta ; ============================================================ MAIN MOVLW 0xA5 CALL SPI_TRANSFER ; TX=0xA5, RX salvato in SPI_RX MOVLW 0x3C CALL SPI_TRANSFER GOTO MAIN ; ============================================================ ; SPI_TRANSFER — invia W, restituisce byte ricevuto in SPI_RX ; CS non gestito qui: chiamare CS_LOW/CS_HIGH intorno ; ============================================================ SPI_TRANSFER MOVWF SSPBUF ; avvia trasmissione SPI_WAIT BSF STATUS,RP0 BTFSS SSPSTAT,BF ; BF=1 → trasmissione completata GOTO SPI_WAIT BCF STATUS,RP0 MOVF SSPBUF,W ; leggi byte ricevuto (azzera BF) MOVWF SPI_RX RETURN CS_LOW BCF CS_PIN RETURN CS_HIGH BSF CS_PIN RETURN END
4 · Codice — lettura ADC esterno MCP3204 (12 bit)
Il MCP3204 è un ADC SPI a 12 bit con 4 canali. La comunicazione richiede 3 byte: il primo trasmette il bit di START (bit0=1), il secondo seleziona la modalità (single-ended o differenziale) e il canale, il terzo è dummy per ricevere gli ultimi 8 bit. Il risultato a 12 bit si ottiene combinando i 4 bit meno significativi del byte 2 con i 8 bit del byte 3.
; ============================================================ ; Lezione 29 — SPI: lettura ADC esterno MCP3204 a 12 bit ; MCP3204: 4 canali, 12 bit, SPI mode 0,0 ; Sequenza: CS↓ → start(1) → SGL(1) → D2(0) → D1(0) → D0(ch) → 0 → 12 bit ; Risposta: null bit + B11..B0 ; PIC16F877A @ 4 MHz ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF #DEFINE CS_MCP PORTB,2 #DEFINE CS_TRIS TRISB,2 CBLOCK 0x20 ADC_HI ; bit11:8 del risultato ADC_LO ; bit7:0 del risultato SPI_RX TX_BYTE ENDC ORG 0x0000 GOTO INIT ORG 0x0005 INIT BSF STATUS,RP0 BCF TRISC,3 ; SCK BSF TRISC,4 ; SDI BCF TRISC,5 ; SDO BCF CS_TRIS MOVLW b'01000000' ; CKE=1, SMP=0 MOVWF SSPSTAT BCF STATUS,RP0 BSF CS_MCP ; CS alto MOVLW b'00100000' ; SSPEN=1, SSPM=0000 (Fosc/4) MOVWF SSPCON MAIN ; Leggi canale 0 (CH0, single-ended) MOVLW 0 CALL MCP3204_READ ; ADC_HI:ADC_LO contiene il valore 12 bit (0-4095) MOVF ADC_LO,W MOVWF PORTB ; mostra byte basso su PORTB (debug) CALL DELAY_10MS GOTO MAIN ; ============================================================ ; MCP3204_READ — legge canale W (0-3) dal MCP3204 ; Risultato: ADC_HI (bit11:8), ADC_LO (bit7:0) ; Protocollo SPI MCP3204: ; Byte 1 TX: 0000 0001 (leading zeros + start bit) ; Byte 2 TX: 1000 dcba (SGL=1, D2=0, D1=D0=canale) ; Byte 3 TX: 0000 0000 (clocking out) ; Risposta sul byte 2 (bit3:0) e byte 3 (bit7:0) = 12 bit ; ============================================================ MCP3204_READ ; Prepara comando canale: SGL=1, ch selezionato ; Byte 2: 1000 00ch → per ch=0: 0x80, ch=1: 0x90, ch=2: 0xA0, ch=3: 0xB0 MOVWF TX_BYTE ; salva numero canale (0-3) BCF STATUS,C RLF TX_BYTE,F ; × 2 RLF TX_BYTE,F ; × 4 RLF TX_BYTE,F ; × 8 RLF TX_BYTE,F ; × 16 → canale in posizione bit4 MOVF TX_BYTE,W IORLW b'10000000' ; SGL=1 (bit7) MOVWF TX_BYTE ; Avvia transazione SPI BCF CS_MCP ; CS basso ; Byte 1: 0x01 (start bit in posizione bit0) MOVLW 0x01 CALL SPI_TX ; Byte 2: comando canale MOVF TX_BYTE,W CALL SPI_TX ; bit3:0 di SPI_RX contengono bit11:8 del risultato MOVF SPI_RX,W ANDLW 0x0F MOVWF ADC_HI ; Byte 3: dummy 0x00 per ricevere bit7:0 MOVLW 0x00 CALL SPI_TX MOVF SPI_RX,W MOVWF ADC_LO BSF CS_MCP ; CS alto RETURN ; ============================================================ ; SPI_TX — trasmette W, risposta in SPI_RX ; ============================================================ SPI_TX MOVWF SSPBUF STW BSF STATUS,RP0 BTFSS SSPSTAT,BF GOTO STW BCF STATUS,RP0 MOVF SSPBUF,W MOVWF SPI_RX RETURN DELAY_10MS MOVLW D'10' MOVWF 0x70 DLA MOVLW D'250' MOVWF 0x71 DLB DECFSZ 0x71,F GOTO DLB DECFSZ 0x70,F GOTO DLA RETURN END
5 · DAC MCP4921 — pacchetto 16 bit e segnale LDAC
Il MCP4921 riceve un pacchetto da 16 bit (2 byte SPI) contenente 4 bit di controllo e 12 bit di dato. Il segnale LDAC (Load DAC) permette di caricare il valore nel registro interno senza ancora aggiornare l’uscita analogica: tenendo LDAC alto si può preparare il dato, poi abbassare LDAC su più DAC contemporaneamente per un aggiornamento sincronizzato — utile ad esempio per generare forme d’onda multi-canale in fase.
; ============================================================ ; Lezione 29 — SPI: scrittura su DAC MCP4921 a 12 bit ; MCP4921: 1 canale, 12 bit, SPI mode 0,0 ; Pacchetto 16 bit: A/B | BUF | GA | SHDN | D11..D0 ; A/B=0 (canale A), BUF=0, GA=1 (×1), SHDN=1 (output ON) ; Byte alto: 0011 D11 D10 D9 D8 ; Byte basso: D7 D6 D5 D4 D3 D2 D1 D0 ; Genera rampa 0→4095 in loop (onda dente di sega) ; PIC16F877A @ 4 MHz ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF #DEFINE CS_DAC PORTB,3 #DEFINE LDAC_PIN PORTB,4 ; LDAC: latch output (attivo basso) CBLOCK 0x20 DAC_HI ; byte alto del pacchetto 16 bit DAC_LO ; byte basso RAMP_HI ; valore corrente rampa (bit11:8) RAMP_LO ; valore corrente rampa (bit7:0) SPI_RX ENDC ORG 0x0000 GOTO INIT ORG 0x0005 INIT BSF STATUS,RP0 BCF TRISC,3 BSF TRISC,4 BCF TRISC,5 BCF TRISB,3 ; CS_DAC output BCF TRISB,4 ; LDAC output MOVLW b'01000000' MOVWF SSPSTAT BCF STATUS,RP0 BSF CS_DAC BSF LDAC_PIN ; LDAC alto (inattivo) MOVLW b'00100000' MOVWF SSPCON CLRF RAMP_HI CLRF RAMP_LO ; ============================================================ ; MAIN: rampa 0→4095, poi ricomincia ; ============================================================ MAIN ; Costruisci pacchetto MCP4921 ; Byte alto: 0011 + bit11:8 di DAC value ; Header: A/B=0, BUF=0, GA=1, SHDN=1 → 0011 MOVF RAMP_HI,W ANDLW 0x0F ; solo bit3:0 (= bit11:8 del valore) IORLW b'00110000' ; header 0011 MOVWF DAC_HI MOVF RAMP_LO,W MOVWF DAC_LO ; Invia al MCP4921 CALL MCP4921_WRITE ; Incrementa valore rampa (16 bit) INCF RAMP_LO,F BTFSC STATUS,Z ; overflow byte basso? INCF RAMP_HI,F ; Limita a 12 bit (max 0x0FFF) MOVF RAMP_HI,W ANDLW 0x0F MOVWF RAMP_HI GOTO MAIN ; ============================================================ ; MCP4921_WRITE — invia DAC_HI:DAC_LO al DAC ; ============================================================ MCP4921_WRITE BCF CS_DAC ; CS basso MOVF DAC_HI,W CALL SPI_TX MOVF DAC_LO,W CALL SPI_TX BSF CS_DAC ; CS alto ; Strobe LDAC per aggiornare output BCF LDAC_PIN ; LDAC basso → latch dati NOP NOP BSF LDAC_PIN ; LDAC alto RETURN ; ============================================================ ; SPI_TX — invia W via SPI, risultato ignorato (DAC = write only) ; ============================================================ SPI_TX MOVWF SSPBUF SPI_W BSF STATUS,RP0 BTFSS SSPSTAT,BF GOTO SPI_W BCF STATUS,RP0 MOVF SSPBUF,W ; svuota buffer (necessario) MOVWF SPI_RX RETURN END
🧪 Prova tu
- Carica il programma base. Nel pannello SPI del simulatore, collega uno slave loopback (MISO=MOSI). Esegui SPI_TRANSFER con W=0xA5: SPI_RX deve contenere 0xA5. Verifica nel pannello che SCK ha generato 8 impulsi durante il trasferimento.
- Nel programma MCP3204, calcola manualmente il valore atteso per CH0 con tensione di ingresso = 2.5V e Vref=5V. Formula: D = 2.5/5 × 4096 = 2048 = 0x800. Verifica che ADC_HI=0x08 e ADC_LO=0x00 nel simulatore con CH0=2048.
- Carica il DAC. Calcola il pacchetto per Vout=1.0V con Vref=2.048V e gain×1: D = 1.0/2.048 × 4096 = 2000 = 0x7D0. Byte alto = 0x37, byte basso = 0xD0. Verifica che il simulatore invii esattamente questi due byte e che LDAC faccia un impulso basso.
- Sfida: combina MCP3204 e MCP4921 in un convertitore analogico passante: leggi CH0 dal MCP3204 (12 bit), converti da 12 a 12 bit (scala 1:1), scrivi sul MCP4921. Il risultato: Vout segue Vin in tempo reale. Attenzione ai CS separati (RB2 per ADC, RB3 per DAC).
❓ Quiz rapido
1. Perché dopo aver scritto in SSPBUF bisogna leggere SSPBUF prima del prossimo trasferimento, anche se il dato ricevuto non interessa?
Mostra risposta
Il flag BF (Buffer Full) in SSPSTAT viene settato quando un byte è stato completamente ricevuto in SSPBUF. L’unico modo per azzerare BF e permettere un nuovo trasferimento è leggere SSPBUF. Se non si legge, BF rimane alto e il prossimo tentativo di scrittura in SSPBUF verrà ignorato (oppure il flag WCOL di SSPCON viene settato, segnalando una collisione scrittura). Ignorare la lettura di SSPBUF causa quindi il blocco silenzioso del modulo SPI dopo il primo byte.
2. Il MCP3204 supporta anche la modalità differenziale (non solo single-ended). Come cambia il byte di comando per leggere CH0+ vs CH0-?
Mostra risposta
Il bit SGL/DIFF (bit7 del secondo byte) seleziona la modalità: SGL=1 → single-ended (misura vs GND), SGL=0 → differenziale. In modalità differenziale i canali sono accoppiati: CH0/CH1 formano una coppia differenziale, dove CH0 è IN+ e CH1 è IN−. Il byte di comando differenziale per CH0+/CH1− è 0x00 (SGL=0, D2=D1=D0=0), contro 0x80 per single-ended CH0. Il risultato può essere positivo o negativo — il MCP3204 restituisce un valore in complemento a due se IN+ < IN−.
3. Con SSPM=0000 (Fosc/4) a 4MHz la velocità SPI è 1MHz. Quanto tempo impiega un trasferimento completo di 3 byte (MCP3204)?
Mostra risposta
A 1MHz ogni bit richiede 1µs, quindi 8 bit = 8µs per byte. Tre byte = 24µs di clock SPI. A questo vanno aggiunti: il tempo di abbassamento e rialzamento del CS (2 istruzioni × 1µs = ~2µs), il tempo di propagazione tra un byte e il successivo (lettura SSPBUF + scrittura nuovo valore = ~2-4 istruzioni = ~2-4µs). Totale stimato: ~30µs per una lettura completa. A confronto, la stessa lettura via I²C a 100kHz richiederebbe circa 240µs (3 byte × 9 bit × 10µs/bit) — 8 volte più lento. Con Fosc/4 e 20MHz di oscillatore si scende a 7.5µs totali.
Prossima lezione →
Lezione 30 — Comparatori e tensione di riferimento interna
I moduli comparatori C1/C2 del PIC16F628A: CMCON, VRCON, tensione di riferimento programmabile (0–Vdd in 32 passi), interrupt su cambio comparatore.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 29