Gestione della comunicazione I2C.
Se riassumiamo quanto detto finora, osserviamo che la
comunicazione sul bus consiste in una serie di azioni a cui devono
corrispondere delle reazioni. Ne risultano sequenze ben definite. Ecco alcune
di quelle descritte nelle pagine precedenti:
Il Master scrive un byte verso uno SLave (7 bit)
-
il Master verifica il bus libero e invia uno Start
-
il Master invia un indirizzo in cui R/W=0 (scrittura)
-
lo Slave risponde con un ACK
-
il Master riconosce l' ACK e invia il byte di dati
-
lo Slave risponde con un ACK
-
il Master chiude la comunicazione con uno Stop
il Master legge un byte dallo Slave (7 bit)
-
il Master verifica il bus libero e invia uno Start
-
il Master invia un indirizzo in cui R/W=1 (scrittura)
-
lo Slave risponde con un ACK
-
il Master si predispone in ricezione
-
lo Slave invia un byte di dati
-
il Master risponde con un NACK fermando la trasmissione
-
il Master chiude la comunicazione con uno Stop
il Master legge un byte da uno Slave (10 bit)
-
il Master verifica il bus libero e invia uno Start
-
il Master invia il primo byte di indirizzo in cui R/W=0
(scrittura)
-
lo Slave risponde con un ACK
-
il Master riconosce l' ACK e invia il secondo byte di
indirizzo
-
lo Slave risponde con un ACK
-
il Master invia un Restart
-
il Master invia nuovamente il primo byte di indirizzo con
R/W=1 (lettura)
-
lo Slave invia un byte
-
il Master risponde con un NACK fermando la trasmissione
-
il Master chiude la comunicazione con uno Stop
Ne risulta che la struttura del software sarà analoga ad una
macchina a stati in cui si passa alla stato successivo solo quando il
precedente è concluso. Questo è un obbligo in quanto il modulo MSSP non può
accodare comandi.
La scelta del progettista non è limitante, ma logica, in quanto le risposte
possibili al comando possono essere diverse: ad esempio, la periferica potrà
rispondere NACK o ACK e le due situazioni non possono avere la stessa
soluzione automatica. Così pure sarà necessaria una consistente
complicazione del software se si prevede un sistema multi-master, con la
relativa gestione dei conflitti oppure si debba tenere conto di possibili
guasti nelle periferiche, se non di sistemi dinamici e auto-configuranti,
situazioni che richiedono un programma di controllo molto articolato.
Negli esempi che facciamo qui, prendiamo in considerazione
situazioni ideali, in sistemi a Master singolo, senza prevedere guasti alla
periferica, che richiederebbero strutture di timeout per risolvere il problema
e neppure situazioni di conflitto sul bus. Dovendo implementare bus con
caratteristiche superiori, sarà necessario studiare con cura tutte le
possibili configurazioni che si dovranno affrontare.
In ogni caso. la chiave della gestione del protocollo è l'
impiego corretto delle segnalazioni che il modulo MSSP fornisce attraverso i
vari flag.
Ci sono due possibili alternative:
Quale è la differenza di una gestione polling e di una in interrupt?
Semplicemente si tratta di valutare quale è il carico del processore, dato
che la necessità assoluta di rispettare le sequenze previste dal protocollo
del bus impegna in modo sensibile il tempo della CPU nei test sui flag.
Trattandosi di una comunicazione sincrona con il clock separato dai dati,
durante la trasmissione il clock può anche variare, tanto che è definito,
come abbiamo visto, un clock stretching per rallentare la velocità di
trasmissione. E' chiaro, però, che un forte rallentamento dovuto alla
necessità del firmware di espletare le varie fasi della comunicazione,
finirebbe per essere penalizzante.
Inoltre, se il processore deve svolgere anche altre operazioni i cui tempi
siano critici, una gestione in polling è improponibile.
Come sistemi in cui è possibile un polling, si possono esemplificare bus
costituiti dal PIC come Master, collegato a sensori (temperatura, pressione,
ecc), RTC, EEPROM, ecc.
Il compito svolto è quello di recuperare i dati dai sensori e collezionarli
in EEPROM e anche inviarli a un display o a una linea di comunicazione esterna
(RS-232 e simili).
In configurazioni simili il processore sarà impegnato a comunicare con una
periferica per volta, con il suo firmware che stabilisce le sequenze. Non
essendo critici i tempi, ogni operazione può essere svolta consecutivamente
con le prestazioni che potrà offrire la CPU e senza rischio di conflitti sul
bus, dato che sarà il PIC a decidere ogni manovra.
Per contro, se il PIC ha funzione di Slave e deve fornire ad un Master dati
provenienti da vari sensori con tempi critici, come ad esempio segnali
tachimetrici o di posizione, encoder e simili, o effettuare la funzione di
scambiatore di protocolli (ad esempio tra RS-232 e I2C o I2C
e USB), la gestione dei tempi costringe obbligatoriamente ad abbandonare il
polling e la soluzione, allora, sarà una gestione in interrupt, dove ogni
task chiama il processore al momento opportuno, lasciandolo libero per le
altre quando non è necessario il controllo della task stessa.
La struttura tipica di una gestione in polling è:
Una altra alternativa è questa:
-
verificare se ci sono comandi in corso; se sì, ritentare
più tardi
-
se il modulo MSSP è libero, lanciare il comando
Ovviamente, la gestione ideale è quella che sfrutta gli
interrupt e lascia libero il processore di effettuare altre attività:
-
interrupt da MSSP: verificare la causa
-
eseguire i passi necessari
-
cacellare il flag di interrupt e riprendere l' attività
corrente
In tutti i casi si utilizza per i test il flag di interrupt del modulo MSSP (SSPIF)
e quelli di condizione di SSPSTAT.
Nel primo caso di polling, sappiamo che l'evento viene rilevato alla fine del comando
in corso. La struttura tipica è un polling stretto del flag e la routine è
bloccata fino a che il comando è eseguito.
Nel secondo caso di polling, per prima cosa viene verificato se è possibile
lanciare il comando, poi questo viene eseguito e si abbandona la procedura per
passare ad altro. Prima del prossimo comando occorrerà verificare che il modulo
sia libero.
E' indispensabile in ogni caso essere consapevoli delle funzioni dei vari
flag e della loro gestione nelle varie fasi, esendoci flag che si cancella
automaticamente a seguito di operazioni di scrittura/lettura e altri che vanno
cancellati in software. Ad esempio, il bit UA viene cancellato automaticamente una volta
riscritto SSPADD, ma il flag SSPIF va cancellato da programma.
Anche perchè, come abbiamo visto, le situazioni non risolte portano ad un
fallimento della comunicazione, con la genrazione di NACK al posto di ACK, se
non con il blocco del bus (SCL=0 ) fino alla rimozione della causa.
Di conseguenza è necessario determinare tutti i casi possibili, ad esempio:
- Trasmissione in corso (indicato dal bit R/W Registro SSPSTAT)
- Condizione di Start in corso (indicato dal bit SEN in SSPCON2)
- Restart in corso (indicato dal bit di RSEN di SSPCON2)
- Condizione di Stop in corso (indicato dal bit PEN di SSPCON2)
- Ricezione in corso (indicato da CEN di SSPCON2)
- ACK in corso (indicato dal bit ACKEN di SSPCON2)
- Necessità di aggiornamento di SSPADD (indicato da UA)
- Dato ricevuto o in trasmissione (Buffer Full BF )
- Errori di Overflow (SSPOV) o collisioni (WCOL, BCLIF)
- Generico completamento di una azione (SSPIF)
Come consigli generici per la gestione del modulo MSSP in I2C,
si può sintetizzare quanto segue.
Per la modalità master o multi-master:
- Se il tempo non è un fattore critico, è preferibile inviare i comandi
ed attenderne la conclusione.
- Se il tempo è stringente, meglio effettuare la verifica prima di
lanciare il comando (è possibile che il comando precedente sia già
concluso)
- Se occorre effettuare altre attività assieme alla comunicazione, non
c'è altra via che implementare una gestione in interrupt, che risolve
ogni problema di tempo.
Per il modo Slave è preferibile in tutti i casi la gestione in interrupt,
in quanto la chiamata da parte del Master può avvenire in qualsiasi istante
in modo del tutto asincrono dalle attività svolte dallo Slave.
inoltre, come abbiamo visto, il clock di bus è pure fornito dal Master, il
che condiziona i tempi di risposta della Slave.
In ogni caso sarà assolutamente necessario prevedere ogni possibile
condizione anomala (flag SSPOV, BF, UA) per evitare di bloccare il bus con una
mancata risposta agli eventi critici.
Un po' di codice per l' MSSP in I2C.
Vediamo qualche esempio minimale di azioni su un bus ideale. Gestione in polling e con
routine bloccanti (non rilasciano fino a che la condizione richiesta è
completata).
Va osservato che gli esempi qui sopra sono strutture del tutto
elementari, che partono dalla condizione che le operazioni vadano tutte a buon
fine e non ci siano problemi sul bus, di cui manca la gestione. |
Un breve esempio di setup dell' MSSP per I2C con le seguenti
condizioni:
- modo Master
- Fosc 40 MHz
- baud rate 400 kHz
- in forma di subroutine
; configurazione MSSP
per I2C
movlw 0x18
; valore per 400
kHz
movwf SSPADD
; nel registro
clrf
SSPCON1 ;
azzera flag in SSPCON1
clrf
SSPCON2 ; e
in SSPCON2
bcf
SSPSTAT,SMP ;
disabilita Slew Rate
bcf SSPSTAT,CKE
; disabilita SMBus
bsf TRISC,SCL
; imposta SDA
bsf
TRISC,SDA ; e
SCL come input
movlw b'00001000' ;
I2C modo Master
iorwf
SSPCON1
bcf
PIR2, BCLIF ;
cancella flag interrupt
bcf
PIR1, SSPIF ;
cancella flag interrupt
MSSP_on
; accende MSSP (SSPEN=1)
return |
Se il modulo MSSP non è utilizzato, può essere spento per
risparmio di energia. Il modulo va spento anche quando si deve configurare in un
modo differente.
; spegni MSSP
MSSP_off MACRO
bcf
PSSPCON1, SSPEN
; spegne MSSP
ENDM ;
accendi MSSP
MSSP_on MACRO
bsf
PSSPCON1, SSPEN
; accende MSSP
ENDM |
Ecco, ad esempio, una delle possibili subroutine che attende che l'operazione precedente
sia completata:
; verifica per il bus idle
i2c_idle btfsc
SSPSTAT, R_W ; trasmissione in
corso?
bra
i2c_idle ;
si, attesa
12c_id1 movf
SSPCON2,W
; no, ACKEN,RCEN,PEN,RSEN, SEN
andlw 0x1F
; sono a 1 ?
bnz
i2c_id1
; si, attesa
return
; no, fine test |
Il Master genera una condizione di Start.
; Genera uno Start iniziale
i2c_s bsf SSPCON2,
SEN ; abilita condizione
di Start
i2c_s1 btfsc PIR1, SSPIF
; check per controllo bus acquisito
bra
i2c_s1 ; No,
attesa
bcf
PIR1, SSPIF ; Sì,
cancella SSPIF
return
; fine comando |
Il Master invia un byte in WREG sul bus.
; il Master invia un byte
i2c_wr movwf SSPBUF
; scrive in buffer
i2c_wr1 btfsc PIR1, SSPIF
; è stato ricevuto un ACK ?
bra
i2c_wr1 ;
no, attesa
bcf
PIR1, SSPIF ; sì, cancella SSPIF
return
; fine comando |
oppure, analogo, con il test sul bit di avviamento del
comando:
; Genera uno Start iniziale
i2c_s bsf SSPCON2,
SEN ; abilita condizione
di Start
i2c_s1 btfsc SSPCON2,
SEN ; check per controllo
di fine comando
bra
i2c_s1 ; No,
attesa
bcf
PIR1, SSPIF ; Sì,
cancella SSPIF
return
; fine comando |
O anche una sequenza dove il comando è lanciato previa
verifica del bus:
; Genera uno Start iniziale
i2c_s rcall Si2c_idle
; verifica bus libero
i2c_s1 bsf PSSPCON2, SEN
; avvia comando
return
; e ritorna |
Il Master genera una condizione di Stop.
; Genera uno Stop
i2c_p bsf SSPCON2,
PEN ; abilita condizione
di Stop
i2c_p1 btfsc PIR1, SSPIF
; check per controllo di fine comando
bra
i2c_s1 ;
no,
attesa
bcf
PIR1, SSPIF ; sì,
cancella SSPIF
return
; fine comando |
Il Master genera una condizione di Stop.
; Genera uno Stop
i2c_p bsf SSPCON2,
PEN ; abilita condizione
di Stop
i2c_p1 btfsc PIR1, SSPIF
; check per controllo di fine comando
bra
i2c_s1 ;
no,
attesa
bcf
PIR1, SSPIF ; sì,
cancella SSPIF
return
|
Il Master genera un ACK.
; Genera ACK
i2c_ak rcall i2c_idle
; verifica per il bus idle
bcf SSPCON2,
ACKDT ; livello aknowledge=0 - ACK
bsf
SSPCON2, ACKEN ; e avvia
return
; esci |
Il Master genera un NACK.
; Genera NACK
i2c_nak rcall i2c_idle
; verifica per il bus idle
bsf SSPCON2,
ACKDT ; livello aknowledge=1 - NACK
bsf
SSPCON2, ACKEN ; e avvia
return
; esci |
Se il test è al rilascio del comando:
; Genera NACK
i2c_nak bcf SSPCON2,
ACKDT ; lvello aknowledge=0 - ACK
bsf
SSPCON2, ACKEN ; e avvia
i2c_na1 btfsc
SSPCON2, ACKEN ;
completato ?
bra
i2c_na1 ;
no,
attesa
return
; si, fine comando
|
Il Master trasmette un byte allo Slave.
; trasmettere un byte
i2c_wr rcall
i2c_idle ;
verifica per il bus idle
movlw
dato ;
copia dato in W
movwf SSPBUF
; copia W in buffer trasmisione
return
; fine comando |
o, in alternativa, con il dato in WREG:
; trasmettere un byte
i2c_wr movwf SSPBUF
; copia W in buffer trasmisione
i2c_wr1
btfsc SSPSTAT, R_W
; trasmissione in corso?
bra
i2c_wr1 ;
si, attesa
return
; no, fine comando |
Il Master riceve un byte dallo Slave e ritorna con il dato in
WREG.
; Ricevere un byte
i2c_rd bsf SSPCON2, RCEN
; abilita la ricezione
i2c_r1 btfsc PIR1, SSPIF
; dati pronti ?
bra
i2c_r1 ; No,
attesa
bcf PIR1,
SSPIF ; Sì,
cancella SSPIF
movf
SSPBUF, W ;
copia buffer in WREG
return |
Il Master invia l'indirizzo allo Slave e riceve un byte di dati;
un concatenamento semplificato delle routines precedenti sotto forma di una
unica subroutine.
; Il master prende il controllo
del bus
bcf
PIR1, SSPIF ;
cancella SSPIF
i2c_s bsf
SSPCON2, SEN ; abilita
condizione di Start
rcall
i2c_wait ;
attesa completamento comando
; invia l' indirizzo da WREG
movlw
address ;
carica indirizzo con R/W=1
i2c_wr movwf SSPBUF
; scrive in buffer
rcall
i2c_wait ;
attesa completamento comando
; riceve un byte
i2c_rd bsf SSPCON2,
RCEN ; abilita la ricezione
rcall
i2c_wait ;
attesa completamento comando
; genera uno Stop per chiudere la
connessione
i2c_p bsf SSPCON2,
PEN ; abilita condizione
di Stop
; attesa per l' esecuzione del comando
i2c_wait
btfsc
PIR1,
SSPIF ; comando
eseguito ?
bra
i2c_wait ; No,
attesa
bcf PIR1,
SSPIF ; Sì, cancella SSPIF
return
|
L' operazione di formazione dell' indirizzo dello Slave
partendo da un valore assoluto può essere svolta dal calcolatore dell'
Assembler.
Ad esempio, se l' indirizzo è address = 19h (b'001 1001'), si dovrà
trasmettere b'0011 0011'.
*2 crea uno shift a sinistra di
una posizione e | 1 setta il bit
0.
; invia l' indirizzo da WREG
movlw
(address * 2) | 1 ;
carica indirizzo con R/W=1 |
Dove si desideri o sia necessario utilizzare l' interrupt per gestire questi eventi,
occorre impostare attiva la chiamata di MSSP col bit SSPIE in PIE1.
Questo fa si che ogni volta SSPIF sia posto a 1, si attivi la richiesta
di interrupt.
Da osservare che, se non è abilitata la modalità Slave con interrupt allo
Start e allo Stop, SSPIF viene settato alla fine di una sequenza che
deve essere poi determinata testando i vari flag.
Questo non costituisce un grosso problema se abbiamo impostato la gestione
della comunicazione con la macchina a stati prima consigliata, in quanto ad
uno stato potranno far seguito solo due situazione: il comando andato a buon
fine oppure no.
Una jump table con un contatore di stati può essere una soluzione rapida per
far passare da uno stato al successivo.
Per una gestione completa e ben organizzata, sono fondamentali
le Application Notes di Microchip:
per la gestione in interrupt con il supporto completo di MSSP.
Per l implementazione del Master in software:
E per il bit-banging in PIC privi del modulo MSSP:
|