Indice |
Premesse
Lo scopo di questo articolo è quello di riuscire a configurare e utilizzare correttamente la periferica MSSP del PIERIN PIC18 alfine di stabilire una comunicazione con il sensore di temperatura TCN75A. Il programma scritto per questa applicazione è stato sviluppato partendo dal codice presentato nell’articolo precedente (quello sulla USART): infatti il codice che vedrete tra poco, legge la temperatura dal sensore comunicando via I2C, esegue una conversione del dato e poi invia il risultato via UART al PC.
Cos’è il MSSP
Il MSSP (Master Synchronous Serial Port) è una periferica di comunicazione seriale con altri dispositivi quali EEPROMs, sensori, convertitori A/D o D/A , ecc. Questa periferica può operare in due modi:
-SPI(Serial Peripheral Interface)
-I2C(Inter-Integrated Circuit)
Quest’ultima modalità utilizza due cavi per la comunicazione: SDA (per I dati) e SCK (per il clock).Il clock viene generato esclusivamente dal master che controlla l’intera rete a cui si possono collegare uno o più slave. Ogni slave è dotato di un proprio indirizzo univoco che li contraddistingue nella rete e possono comunicare solo quando vengono “interrogati” dal master, inviando un byte alla volta. Una particolarità di questo bus è che sono necessarie due resistenze di pull-up per entrambi i cavi: questo perché le uscite dei vari dispositivi collegati sono open-drain, ovvero possono assumere due valori: 0V o impedenza infinita. Il perché di questa caratteristica lo si può dedurre dal protocollo di comunicazione.
Protocollo di comunicazione
La comunicazione inizia sempre con un segnale di start: il master porta la linea SDA al livello logico basso. Segue l’indirizzo del dispositivo con cui si vuole comunicare. Esso è composto da 7bit più un bit che indica il tipo di trasmissione (scrittura o lettura). Inviato l’indirizzo il dispositivo interrogato risponde con un segnale di acknowledge, ovvero portando a livello basso la linea dati. Ecco il perché delle uscite open-drain: in questo modo entrambi i dispositivi possono inviare dati sulla stessa linea senza causare problemi. In seguito, in base al tipo di trasmissione, il master invia o riceve byte: ogni volta che invia un byte, lo slave deve rispondere con un acknowledge e lo stesso deve fare il master quando riceve dati dagli slave.
Registri di configurazione
Bit | Funzione |
---|---|
SMP | Attiva lo slew rate. Va configurato solo quando la velocità non è standard (100kHz o 1MHz) |
CKE | Attiva gli input peril SMBus |
D/A | Bit di sola lettura che indica se il dato trasmesso è un indirizzo o un dato (solo modalità slave) |
P | Bit di sola lettura che indica se è stato inviato un segnale di stop |
S | Bit di sola lettura che indica se è stato inviato un segnale di start |
R/W | Bit di sola lettura che indica se la trasmissione è in progresso (modalità master) |
UA | Serve nella modalità slave con indirizzo a 10bit |
BF | Bit di sola lettura che indica se il registro SSPxBUF è pieno o vuoto |
Bit | Funzione |
---|---|
WCOL | Serve per le operazioni di rilevamento delle collisioni tra le trasmissioni |
SSPOV | Indica se è stato ricevuto un byte mentre il registro SPPxBUF era pieno |
SSPEN | Attiva le porte SDA e SCK |
CKP | In modalità master non serve |
SSPM<3:0> | Sono 4bit che configurano la modalità del modulo I2C |
Bit | Funzione |
---|---|
GCEN | Utilizzato solo nella modalità slave |
ACKSTAT | Indica se è stato ricevuto o meno l’acknolege dallo slave |
ACKDT | Imposta il tipo di acknolege da inviare |
ACKEN | Invia l’acknolege(in base a quanto impostato nel bit ACKDT) |
RCEN | Attiva la modalità ricezione del master |
PEN | Invia un segnale di stop |
SEN | Invia un segnale di start |
Oltre a questi registri di configurazione, c’è anche il SPPxBUF che contiene il dato da inviare o ricevuto e il registro SPPxADD che contiene il valore che configura la frequenza di trasmissione secondo questa formula:
clock = FOSC/(4 *(SSPxADD + 1))
Il Programma
Il programma sviluppato si compone di 6 file: header.h, main.c, configuration_bits.c, funzioni.c, i2c.h e i2c.c. Gli ultimi due file sono quelli che ci interessano maggiormente perché in essi è contenuto il codice che permette la comunicazione con il sensore.
void I2C_init(intfreq); void I2C_start(void); void I2C_write(unsigned char dato); unsigned charI2C_read(unsigned char ack); void I2C_rep_start(void); void I2C_stop(void);
Queste sono le varie funzioni che permettono di far funzionare il modulo I2C. Analizziamo la prima:
//Inizializza il modulo MSSP in modalità I2C master. La frequenza deve essere espressa in KHz void I2C_init(intfreq) { unsigned long tmp; //Pin SDA e SCKdel modulo I2C TRISB4=1; TRISB5=1; //Controlla se viene utilizzata una frequenza standard o meno, per attivare lo slew rate control if (freq==100 || freq==1000) SSP1STAT=0b10000000; else SSP1STAT=0; SSP1CON1=0b00101000; SSP1CON2=0; //Fout=Fosc/(4*(SPP1ADD+1)) ==>SPP1ADD=(Fosc/(4*Fout))-1 tmp=_XTAL_FREQ/1000; tmp/=4; tmp/=freq; SSP1ADD=tmp-1; }
Questa funzione inizializza il modulo MSSP in modalità master utilizzando le configurazioni standard. Passando il valore dellafrequenza in KHz, il codice calcola il valore da porre nel registro SPP1ADD secondo la formula indicata dal datasheet.
//Invio segnale di start void I2C_start(void) { SSP1CON2bits.SEN=1; //Invia il segnale di start while(!SSP1IF); //Attende cheil segnale sia stato inviato SSP1IF=0; } //Ripetizione del segnale di start void I2C_rep_start(void) { SSP1CON2bits.RSEN=1; //Ripetoil segnale di start while(!SSP1IF); //Attende che il segnale sia stato inviato SSP1IF=0; } //Invio segnale di stop void I2C_stop(void) { SSP1CON2bits.PEN=1; //Segnale di stop while(!SSP1IF); //Attende che il dato sia stato inviato SSP1IF=0; }
Con queste due funzioni è possibile mandare i segnali distart, re-start e stop . Come è possibile vedere a pagina 336-337 del datasheet, alla fine di ogni operazione (invio del segnale di stop/start, invio o ricezione di un byte) viene settato il pin SSP1IF del registro PIR1 che se abilitato genera un interrupt. Questo bit deve essere ogni volta resettato.
//Invio un byte void I2C_write(unsigned char dato) { SSP1BUF=dato; //Invio deldato via I2C while(!SSP1IF); //Attende che il dato sia stato inviato SSP1IF=0; }
Per inviare un dato basta semplicemente scrivere nel registro SSP1BUF e attende l’invio
Più complesso è ricevere un byte:
//Lettura di un byte ricevuto unsigned char I2C_read(unsigned char ack) { unsigned dato; SSP1CON2bits.RCEN=1; //Modalità ricezione while(!SSP1IF); //Attende che il dato sia stato ricevuto SSP1IF=0; dato=SSP1BUF; if (ack)SSP1CON2bits.ACKDT=1; //Setto che deve inviare un segnale di acknowledge elseSSP1CON2bits.ACKDT=0; //Setto che deve inviare un segnale di not-acknowledge SSP1CON2bits.ACKEN=1; //Invio il segnale while(!SSP1IF); //Attende che il segnale sia stato inviato SSP1IF=0; return dato; }
Innanzitutto bisogna abilitare la ricezione agendo sul bit RCEN. Arrivato il byte, lo si legge e si invia il segnale di acknoelege: questo segnale può essere di due tipi: acknowledge (stato logico 0) o not-acknowledge(stato logico 1).
Inviando il primo, si indica allo slave di inviare un altro byte, con il secondo invece indica che la ricezione è terminata.
Leggiamo la temperatura
Ora analizziamo le modalità di comunicazione per poter leggere la temperatura dal sensore in questione. Innanzitutto dobbiamo scoprire l’indirizzo del nostro slave. Queste sensore, come molti altri dispositivi I2C,hanno dei pin che in base allo stato logico in cui si trovano, modificano l’indirizzo dello slave. Questo risulta importante nel caso in cui si vogliano collegare più sensori uguali nella stessa rete. Il TCN75 ha 3 pin A0, A1 e A2 che permettono di modificare gli ultimi 3 bit dell’indirizzo a 7 bit dello slave:questo significa che possiamo collegare fino a 8 sensori sulla stessa linea.Nel nostro caso, mettiamo tutti i pin a massa in modo da avere il seguente indirizzo: 0b1001000.
Ci creiamo nel file header.h due bei define dove salviamo gli indirizzi di lettura e scrittura.
Il sensore è molto semplice da usare ed è infatti dotato di solo 4 registri: noi ne useremo solo i primi due.
Il primo registro è quello di configurazione. Noi configureremo solo la risoluzione, portandola da 9bit di default a 12bit.
Il secondo registro invece è a 16bit e serve per la lettura della temperatura. Nel primo byte è contenuta la parte intera, mentre nel secondo la parte decimale espressa come da schema.
Ora dobbiamo capire come inviare e ricevere le informazioni che ci servono.
Da questo schema possiamo dedurre le modalità di comunicazione: come descritto prima, bisogna inviare prima l’indirizzo di scrittura, e poi il dato che ci interessa. Nel nostro caso, inviamo l’indirizzo del registro di configurazione che si trova alla posizione 0x01. Poi inviamo le configurazioni da porre in quel registro. Questa configurazione deve essere fatto una volta sola all'inizio del programma. Ecco il codice:
//Configurazione iniziale: Risoluzione a 12 bit (massima), il resto è di default I2C_start(); I2C_write(tcn_W); I2C_write(1); I2C_write(0b01100000); I2C_stop();
Come vedete utilizzando le varie funzioni, il codice è molto semplice ed è immediata la comprensione. Più complesso è leggere il dato della temperatura.
Infatti bisogna come per la scrittura inviare l’indirizzo di scrittura seguito dal registro che vogliamo leggere, nel nostro caso è 0x00. Poi però bisogna ripetere il segnale di start: ecco che entra in scena il comando rep_start(); che pone a 1 il bit RSEN del registro SSP1CON2. Fatto ciò,si rinvia l’indirizzo dello slave, questa volta in modalità lettura. Si prepara il master alla ricezione e si attende che arrivi il primo byte. Per confermarel’arrivo del primo byte si deve dare un segnale di acknoledge, che indica allo slave che il byte è stato ricevuto e che può inviare il secondo. Arrivato anche il secondo, il master deve inviare un segnale di not-acknoledge che indica la fine della ricezione. Il tutto si conclude con il segnale di stop. Ecco il codice:
I2C_start(); I2C_write(tcn_W); I2C_write(0); I2C_rep_start(); I2C_write(tcn_R); delay_ms(300); //Attende che la lettura venga effettuata (il datasheet indica 240mS pag.22) temp1=I2C_read(0); temp2=I2C_read(1); I2C_stop();
Ora però non possiamo trasmettere i dati ricevuto così come sono, ma dobbiamo convertirli.
Per poter inviare il dato della temperatura via seriale,dobbiamo ottenere prima il numero decimale della temperatura e poi convertirlo in ASCII. Quindi dobbiamo passare da una variabile numerica a una stringa.Creiamo quindi una stringa di 5 caratteri (2 cifre delle unità, la virgola, la cifra decimale e il carattere terminatore) e poi otteniamo le decine e le unità della variabile che contiene la parte intera della temperatura. A questi due valori (che assumono valori da 0 a 9) sommiamo 48 che in ASCII corrisponde allo zero, e avendo poi tutti gli altri numeri in serie (49=”1”, 50=”2” ecc),otteniamo le cifre da 0 a 9. Nella terza posizione mettiamo il numero 44 che corrisponde alla virgola. Per i decimali la questione è diversa. Innanzitutto shiftiamo di 4 posizioni verso destra il nostro dato, in modo da avere un numero che va da 0 a 15. Quindi possiamo dire che i decimali sono espressi in sedicesimi. Quindi per ottenere il nostro decimale ci basta dividere per 16 e otterremmo un numero che va da 0 a 0,9375 (la risoluzione è di 0,125). Ma per non complicarci la vita con le variabili a virgola mobile, moltiplichiamo per 10 prima della divisione per 16, in modo da avere sempre numeri interi (da 0 a 9). Il valore ottenuto lo poniamo nella quarta posizione (sommando sempre 48) e concludiamo l’ultimo carattere con il valore zero (il terminatore). Dopo di che invio la stringa ottenuta al modulo USART.
Ecco il codice:
//Converto e invio della temperatura letta voidstampaTemp(unsigned char t1, unsigned char t2) { unsigned chars_temp[5]; s_temp[0]=(t1/10)+48; //Ottengo la cifra delle decine s_temp[1]=(t1%10)+48; //Ottengo la cifra delle unità s_temp[2]=44; //Corrisponde alla virgola t2=t2>>4; //Shifto di 4 posizioni il dato dei decimali t2*=10; //Moltiplico per dieci per non dover usare i decimali t2=t2>>4; //E divido per 16 (i decimali della temp sono espressi in 16simi digrado) s_temp[3]=t2+48; //Sommo 48 che corrisponde allo zero in ascii s_temp[4]=0; //Carattere di terminazione //Invio la tempertura letta stampaTesto("Temperatura misurata: "); stampaTesto(s_temp); stampaTesto("\n"); }
Collegamenti
Collegare il PicKit2, il PIERIN e il sensore come in figura. Per stabilire la comunicazione, dovete avviare il pickit2, nel menù Tool selezionare UART tool. In seguito settate il baud rate a 19200 espuntate la casella VDD. Premete su connect e collegate al PIERIN PIC18 il cavo USB per dargli alimentazione. Dovrebbe comparire una scritta iniziale e poi ad intervalli di 1sec la rilevazione della temperatura come in figura.
Conclusioni
Qui potete scaricare il programma completo.
Se avete domande o ho commesso qualche errore, non esitate a commentare l’articolo. Buona sperimentazione!