Il protocollo seriale I2C con un PIC16F84
Link Articolo Originale: http://davbucci.chez-alice.fr/index.php?argument=elettronica/pic_i2c/pic_i2c.inc
Introduzione
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 sitema 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 comuncazione è 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. 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.
1
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 10kOhm è 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 PORTB, SDA ; The start condition on the I2C bus bsf PORTB, SCL ; An high to low transition when SCL is high call shortdelay bcf PORTB, SDA call shortdelay bcf PORTB, 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
1
Una condizione simmetrica è lo stop, che segnala la fine della trasmissione: una transizione da livello logico basso a livello logico alto sulla linea SDA metre 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 bcf I2CPORT, SCL ; Clock low bsf I2CPORT, SDA BANKSEL I2CTRIS bsf I2CTRIS, SDA ; SDA as input BANKSEL I2CPORT 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 I2CTRIS bcf I2CTRIS, SDA ; SDA as output BANKSEL I2CPORT ; Clock is left low bcf I2CPORT, SCL call shortdelay return ; This version of i2cwaitack can be a little bit too fast for some i2c devices ; such as some EEPROM's. ; It may be convenient to introduce a timeout mechanism when waiting for the ; acknowledge. ; Refer to your device datasheet for more details. ; 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 situazione seguente:
1
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 comuncazione. La mia esposizione non ha alcuna pretesa di essre 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.License:
--------
Copyright (C) 2004 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.