Il protocollo seriale I2C con PIC 16F84
Nota del 2013: ho scritto quest'articolo nel 2004 e da allora alcune cose sono un po' cambiate. Prima di tutto, Philips non vende più semiconduttori e la divisione che si occupava di queste cose ora si chiama NXP. Poi, il PIC16F84 era già obsoleto allora, oggi è diventato poco più di una curiosità. Ciononostante certe idee non sono cambiate e la lettura può esser utile ancora oggi.
Nota del 2016: Sembrerebbe che questa paginetta sia ancora letta da qualcuno! Ringrazio Tom Lillevig per avermi segnalato un errore nella funzione "i2cwaitack".
Nota del 2020: Ebbene sì, questa pagina è ancora letta oggi! Sentiti ringraziamenti a István Sándor per avermi indicato una piccola correzione di stile che era opportuna da fare nel codice.
Cosa si può fare
A livello hardware
A livello software
Il protocollo
Se qualcosa va storto...
In conclusione
Introduzione
Nell'elettronica
attuale, i circuiti digitali tendono a diventare progressivamente
più complessi e divengono disponibili circuiti integrati in
grado di svolgere i compiti più vari. D'altro canto, la
disponibilità di microcontrollori di basso costo alla portata
dei comuni mortali (i PIC, la serie ST6 e 7, il Motorola 68HC11, in una
lista non esaustiva) consente di ideare circuiti relativamente piccoli
in grado di gestire funzioni abbastanza articolate.
Uno dei fattori che più incidono nel costo di un
microcontrollore o una logica programmabile (Altera, Lattice, Maxim,
etc...) è il numero complessivo di piedini di ingresso e di
uscita. In altre parole, a parità di prestazioni e diffusione
sul mercato, un microcontrollore in grado di gestire 24 ingressi/uscite
costa generalmente di più di un altro che ne ha solamente 13.
Il bus I2C è un sistema messo a punto dalla Philips
nella metà degli anni ottanta che consente di pilotare una
famiglia molto vasta di circuiti integrati utilizzando solamente due
linee I/O più la massa.
Si tratta dunque di un economico protocollo di comunicazione seriale a bassa o
media velocità (100kbit/s, 400kbit/s o più recentemente
3,4Mbit/s) il quale consente tuttavia di indirizzare un numero molto
grande di dispositivi sullo stesso bus, grazie ad un codice d'indirizzo proprio a
ciascun dispositivo.
In questo documento, sono presentate delle routine per PIC16F84 (penso
utilizzabili con poche modifiche anche su altri microcontrollori della
medesima famiglia non dotati di USART hardware) capaci di alleggerire il compito del programmatore
per la gestione a basso livello dello standard.
Cosa si può fare
Molti dei dispositivi che adottano il bus I2C
sono costruiti
dalla Philips, ma anche aziende indipendenti adottano quello che
è ormai diventato uno standard molto diffuso. Fra i modelli
forniti dalla Philips, troviamo diversi orologi/calendari (PCF8573,
PCF8583), memorie RAM statiche (PCF8570), memorie EEPROM (PCF8582,
24C01), convertitori analogico/digitali (PCF8591) e molto altro.
Con un integrato di tipo PCF8574, è possibile aggiungere 8 porte
bidirezionali in un sol colpo al microcontrollore. Se si tiene conto
che di integrati di questo tipo se ne possono utilizzare fino ad 8,
otteniamo un totale di 64 piedini di ingresso/uscita controllati con
solo due linee, ovviamente ad una velocità non elevatissima. Se
avete un plastico ferroviario per esempio, potete pensare di
controllare localmente il sistema di scambi o l'illuminazione delle
case facendo scorrere solo due fili di controllo.
Con il PCF8575, le porte a disposizione diventano 16...
Il sito della Philips contiene una vasta sezione dedicata allo standard I2C che consiglio di visitare per avere un'idea delle possibilità offerte da questa soluzione semplice ed efficace.
In questo articolo, vedremo come si può pilotare in maniera semplice il bus I2C
utilizzando un PIC tipo 16F84. La velocità di comunicazione
è quella più bassa (100kbit/s) e ci troveremo in una
situazione semplice in cui il PIC controlla da solo il clock della
trasmissione. Non si vuole fare una rivista completa delle numerose
caratteristiche offerte dallo standard (il meglio da fare è
quello di scaricarsi il il documento I2C bus specification sul sito della Philips).
A livello hardware
Il bus I2C è composto, come si è detto, da due
sole linee bidirezionali più la massa. La prima linea,
denominata SCK è il clock della trasmissione e la seconda,
denominata SDA è la linea su cui transitano i dati al ritmo
scandito da SCK. Il protocollo in questo modo è sincrono (a
differenza, per esempio del protocollo RS232 che è asincrono e
più complesso da gestire).
Data la possibilità di avere più dispositivi presenti
sulle linee, normalmente esse sono gestite con una logica a drain
aperto e richiedono una resistenza di pull-up collegata con il positivo
di alimentazione, come mostrato in Fig.1. Questo vuol dire che ogni dispositivo può
imporre un livello logico 0 sulla linea cortocircuitandola con la massa
(presumibilmente con un MOSFET, da cui il nome drain aperto), oppure un
livello logico 1 semplicemente senza fare nulla. In questo modo, un
dispositivo il quale voglia rimanere assolutamente inerte sulla linea
senza perturbare altre comunicazioni in corso non deve fare altro che
lasciare scollegate da massa le due linee.
Fig. 1: connessione di diversi dispositivi con un OR cablato, dal datasheet Philips.
Ma noi immagineremo di trovarci in una situazione semplice in cui vi sia un
solo trasmettitore ed un solo ricevitore sul bus I2C.
Si può distinguere tra dispositivo master
e dispositivo slave
a seconda di chi genera il clock, in altre parole a seconda di chi
impone la cadenza con cui i dati vengono inviati sulla linea, sia in un
senso che nell'altro. In questo modo, il dispositivo master
potrà essere sia un trasmettitore o un ricevitore, in modo
complementare rispetto al dispositivo slave. Come regola generale, ad
un istante prefissato, sul bus I2C vi può essere un solo
master ed un numero anche rilevante di slave.
Ritorniamo a noi. Nel nostro caso abbiamo un solo dispositivo da
gestire con un microcontrollore. Nella stragrande maggioranza dei casi,
il microcontrollore funge da master ed il dispositivo da gestire da
slave. In altre parole, il clock SCK sarà sempre gestito dal
microcontrollore mentre la linea SDA è generalmente
bidirezionale.
Ecco le definizioni che useremo nel seguito per migliorare la leggibilità:
; **************************************************************** ; I2C routines developed by Davide Bucci, version 1.0, August 2004 ; **************************************************************** ; Control lines of the I2C interface SCL equ 00 SDA equ 01 I2CPORT equ PORTB I2CTRIS equ TRISB ; Variables, substitute adresses of free RAM bytes TMP equ 0C ; Dummy variable COM equ 10 ; I2C Communication Register
In questo caso, le linee SCL e SDA sono da collegare con i bit 0 e 1 della porta B, con una resistenza di pull-up verso il positivo di alimentazione (per il valore c'è ampia scelta; valori più elevati penalizzano l'immunità ai disturbi ma riducono il consumo. Una scelta di valori intorno ai 10 kΩ è valida nella maggior parte dei casi).
A livello software
A livello software, le cose si complicano in quanto bisogna gestire il
livello logico delle linee in modo da pilotare il dispositivo
desiderato sul bus I2C. Durante la trasmissione, i bit sono
inviati in maniera sequenziale incominciando da quello più
significativo e la linea SDA può essere cambiata di stato
solamente quando il segnale SCK è alto. Vi sono due importanti
eccezioni a questa regola. Nei periodi di inattività, entrambe
le linee sono mantenute a livello logico alto tramite le resistenze di
pull-up; il microcontrollore agente come master segnala l'inizio di una
trasmissione proprio abbassando la linea SDA mentre SCK è a
livello 1. Ecco il codice necessario:
i2cstart ; Send a start on the I2C bus BANKSEL I2CTRIS bcf I2CTRIS, SDA ; SDA as output bcf I2CTRIS, SCL ; SCL as output BANKSEL I2CPORT bsf I2CPORT, SDA ; The start condition on the I2C bus bsf I2CPORT, SCL ; An high to low transition when SCL is high call shortdelay bcf I2CPORT, SDA call shortdelay bcf I2CPORT, SCL call shortdelay ; Leave SDA and SCL low return
Nel codice, si nota una chiamata ad una funzione di nome shortdelay, la
quale si occupa di gestire correttamente la temporizzazione in modo da
rispettare le specifiche per il bus. Nel caso di un quarzo a 4MHz, una
versione adeguata (anche un po' più lenta del necessario)
è la seguente:
shortdelay ; A short delay ;-) nop nop nop return
Fig. 2: la condizione di start, dai datasheet Philips.
Una condizione simmetrica è lo stop, che segnala la fine della
trasmissione: una transizione da livello logico basso a livello logico
alto sulla linea SDA mentre SCK è alta.
i2cstop ; Send a stop on the I2C bus BANKSEL I2CTRIS bcf I2CTRIS, SDA ; SDA as output BANKSEL I2CPORT bcf I2CPORT, SCL bcf I2CPORT, SDA ; The stop condition on the bus I2C call shortdelay bsf I2CPORT, SCL ; A low to high transition when SCL is high call shortdelay bsf I2CPORT, SDA call shortdelay ; SCL and SDA lines are left high return
A questo punto, si entra nel vivo del discorso ed avviene la trasmissione vera e propria che avviene byte per byte partendo da quello maggiormente significativo (MSB). Vengono inviati otto bit dal trasmettitore (che qui supporremo essere il microcontrollore), dopodiché la linea verra lasciata alta di modo che il ricevitore possa dire se tutto è andato bene o no. Questo segnale si chiama Acknowledgment e permette di indicare al trasmettitore la buona riuscita della trasmissione. In questo caso, la linea SDA è lasciata in ricezione e il microcontrollore deve verificare che il ricevitore la ponga a livello basso. Il codice è semplice da usare e la routine va richiamata con nel registro w il byte da inviare:
i2csend ; Send a byte over the I2C interface, movwf COM ; return 0x00 if ACK movlw 0x08 movwf TMP ; TMP is used as a counter BANKSEL I2CTRIS bcf I2CTRIS, SDA ; SDA as output BANKSEL I2CPORT icloops bcf I2CPORT, SCL ; Clock low: change of SDA allowed rlf COM,f bcf I2CPORT, SDA btfsc STATUS, C ; Test the carry bit bsf I2CPORT, SDA call shortdelay bsf I2CPORT, SCL ; Clock high call shortdelay decfsz TMP,f goto icloops ; i2cwaitack follows directly i2cwaitack bsf I2CPORT, SDA BANKSEL I2CTRIS bsf I2CTRIS, SDA ; SDA as input BANKSEL I2CPORT bcf I2CPORT, SCL ; Clock low call shortdelay bsf I2CPORT, SCL ; Clock high call shortdelay movlw 0x00 ; Ox00 in w means ack btfsc I2CPORT, SDA ; SDA low means ack movlw 0xFF ; 0xFF in w means no ack BANKSEL I2CPORT ; Clock is left low bcf I2CPORT, SCL BANKSEL I2CTRIS bcf I2CTRIS, SDA ; SDA as output BANKSEL I2CPORT call shortdelay return ; Questa versione di i2cwaitack pu essere un po' sbrigativa per certi ; dispositivi i2c come certe EEPROM. ; Pu essere conveniente introdurre un meccanismo di timeout ed incrementare ; l'attesa per l'acknowledge fino ad un tempo limite prefissato. ; Si faccia riferimento al datasheet del dispositivo per maggiori dettagli.
All'interno del registro w, una volta ritornati alla funzione chiamante
sarà contenuto il valore 0x00 se tutto è andato bene ed
abbiamo ricevuto il segnale di risposta dal dispositivo, oppure 0xFF se
tale segnale non è stato inviato.
Abbiamo visto come inviare un byte, adesso vediamo come riceverlo:
i2creceive clrf COM ; Receive a byte over the I2C interface movlw 0x08 movwf TMP ; TMP is used as a counter BANKSEL I2CTRIS bsf I2CTRIS, SDA ; SDA as input BANKSEL I2CPORT icloopr bcf I2CPORT, SCL ; Clock low: change of SDA allowed call shortdelay bsf I2CPORT, SCL ; Clock high call shortdelay bcf STATUS, C ; Clear the carry rlf COM,f btfsc I2CPORT, SDA ; Test the bit being received bsf COM,0 ; Stock the bit read in COM and rotate decfsz TMP, goto icloopr movf COM,w bcf I2CPORT, SCL ; Clock is left low call shortdelay return
Una volta ricevuto il byte dalle linee I2C, l'esecuzione passa al programma chiamante con li byte ricevuto nel registro w.
A questo punto, dato che chi riceve è il microcontrollore (come master receiver, dato che genera il clock), si tratta se scegliere se inviare il segnale di Acknowledgement o no:
i2csendack BANKSEL I2CTRIS bcf I2CTRIS, SDA ; SDA as output BANKSEL I2CPORT bcf I2CPORT, SCL ; Clock low: change of SDA allowed call shortdelay bcf I2CPORT, SDA ; SDA low means ack call shortdelay bsf I2CPORT, SCL ; Clock high call shortdelay bcf I2CPORT, SCL ; Clock is left low return i2cnoack BANKSEL I2CTRIS bcf I2CTRIS, SDA ; SDA as output BANKSEL I2CPORT bcf I2CPORT, SCL ; Clock low: change of SDA allowed call shortdelay bsf I2CPORT, SDA ; SDA high means no ack call shortdelay bsf I2CPORT, SCL ; Clock high call shortdelay bcf I2CPORT, SCL return ; Clock is left low
Normalmente, il ricevitore segnala Ack se ha correttamente ricevuto il byte per passare al successivo, oppure NoAck se si sono verificati problemi o per segnalare la fine della trasmissione.
Il protocollo
Abbiamo qui visto quali sono i mattoni di base che vengono utilizzati nella comunicazione. Si tratta adesso di analizzare come essi vengano utilizzati in sequenza in modo da mettere in piedi la comunicazione vera e propria. Attenzione! In questo caso, molte cose dipendono dal dispositivo che è stato scelto e la cosa migliore da fare è far riferimento alla documentazione ufficiale ed al datasheet. Ad ogni modo, certe caratteristiche sono più o meno costanti. Si parte con uno start che segnala l'inizio della trasmissione, il primo byte è sempre l'indirizzo su 7 bit del dispositivo slave da indirizzare sulla linea. Per il PCF8573, avremo qualcosa di simile alla quanto mostrato in Fig. 3.
Fig. 3: il primo byte di comunicazione, dai datasheet Philips.
Questi 7 bit sono composti da
due parti. La prima è fissa e determinata dal dispositivo che si
sta utilizzando. La seconda, composta da due, tre o quattro bit
solitamente, è decisa dal livello logico di altrettanti piedini
appositamente predisposti dall'integrato. Questo vuol dire che è
possibile indirizzare sulla medesima linea più dispositivi
uguali aventi indirizzi diversi.
Un esempio è rappresentato dal caso dell'orologio/datario
Philips PCF8573, la cui parte fissa è formata dai bit 1101; ad
essi segue un bit sempre a zero e poi due bit il cui livello logico
dipende da come sono collegati i due piedini A0 ed A1 sull'integrato.
In questo modo, senza complicazioni aggiuntive, è possibile
utilizzare separatamente fino a 4 orologi PCF8573 sulla medesima linea.
L'ultimo bit è quello di direzione. Nel caso in cui si desideri
trasmettere dei dati (il microcontrollore funziona quindi da master
transmitter generando il clock), lo si deve lasciare a 1. Se invece si
vogliono ricevere dei dati (come master receiver, quindi occupandosi
sempre del clock), va messo a 0.
La condizione di start può esser ripetuta all'interno della
trasmissione per più volte, solitamente per separare parti
logicamente diverse della comunicazione, come il passaggio da lettura a
scrittura o viceversa. In questo caso (il codice è identico),
basta richiamare la routine i2cstart.
Se qualcosa va storto...
Anche dopo aver studiato con attenzione i datasheet, è molto
difficile che un programma funzioni perfettamente al primo colpo... Per
effettuare un controllo diagnostico sul bus I2C, esistono
dei tester che si collegano a massa ed alle due linee SCK e SDA e
mostrano su uno schermo cosa viene inviato sulle linee.
Una soluzione più economica che fa uso di un oscilloscopio a
doppia traccia è quella di osservare sui due canali le linee SCK
e SDA utilizzando una terza linea del microcontrollore per controllare
il trigger dell'oscilloscopio (che quindi dovrà essere impostato
su esterno). Il programma dovrà essere modificato per ripetere a
loop la sequenza che pone problemi, di modo da poterla disegnare in
maniera continua sullo schermo dell'oscilloscopio.
Una soluzione alternativa per chi non avesse a disposizione un
oscilloscopio è quella di utilizzare due sonde logiche o, in
mancanza di esse, due semplici LED pilotati da un transistor a mo' di
buffer che segnalino lo stato logico sulle linee SCK e SDA. Dato che lo
standard I2C non pone limiti inferiori alla velocità
di trasmissione, si può rallentare di qualche ordine di
grandezza la frequenza della linea SCK semplicemente modificando la
routine shortdelay di modo che la sua chiamata duri circa un secondo.
In questo modo, armati di penna e molta pazienza, è possibile
seguire bit per bit quello che succede sulle linee ed individuare
eventuali errori.
In conclusione
In questo intervento, ho voluto fare un'introduzione ad uno standard che è molto utilizzato in tantissime applicazioni. Probabilmente, il vostro videoregistratore, il vostro televisore ed il vostro calcolatore contengono fra gli altri degli integrati che adottano tale sistema di comunicazione. La mia esposizione non ha alcuna pretesa di essere completa e neppure le routine presentate sono a prova di bomba (per ora le ho usate su un PCF8573); esse sono presentate così come sono, senza nessuna garanzia di buon funzionamento, sotto licenza GNU versione 2. Vi invito a provarle ed a farmi avere le vostre impressioni in merito, di modo da migliorarle se risulta opportuno.
Log:
October 30, 2020 - Corrected a minor discrepancy (reference to PORTB instead of I2CPORT in i2cstart).
January 17, 2016 - Corrected the end of i2cwaitack and some typos in the Italian version of the page.
January 10, 2016 - Added Tom Lillevig's correction in the code.
May 26, 2013 - Review and English translation.
August 2004 - First version of the page.
License
License:
--------
Copyright (C) 2004-2020 Davide Bucci davbucci at tiscali dot it
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.