Microchip ha scritto note applicative su questo:
- AN734 sull'implementazione di uno slave I2C
- AN735 sull'implementazione di un master I2C
- C'è anche un AN736 più teorico sulla configurazione di un protocollo di rete per il monitoraggio ambientale, ma non è necessario per questo progetto.
Le note sull'applicazione funzionano con ASM ma possono essere facilmente trasferite su C.
I compilatori C18 e XC8 gratuiti di Microchip hanno funzioni I2C. Puoi leggere di più a riguardo nella documentazione delle librerie del compilatore , sezione 2.4. Ecco alcune informazioni di avvio rapido:
Impostare
Hai già il compilatore C18 o XC8 di Microchip. Entrambi hanno funzioni I2C integrate. Per usarli, devi includere i2c.h
:
#include i2c.h
Se vuoi dare un'occhiata al codice sorgente, puoi trovarlo qui:
- Intestazione C18:
installation_path
/v
x.xx
/h/i2c.h
- Fonte C18:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- Intestazione XC8:
installation_path
/v
x.xx
/include/plib/i2c.h
- Sorgente XC8:
installation_path
/v
x.xx
/sources/pic18/plib/i2c/
Nella documentazione, è possibile trovare in quale file nella /i2c/
cartella si trova una funzione.
Apertura della connessione
Se hai familiarità con i moduli MSSP di Microchip, saprai che devono prima essere inizializzati. È possibile aprire una connessione I2C su una porta MSSP utilizzando la OpenI2C
funzione. Ecco come viene definito:
void OpenI2C (unsigned char sync_mode, unsigned char slew);
Con sync_mode
, è possibile selezionare se il dispositivo è master o slave e, se è uno slave, se deve utilizzare un indirizzo a 10 o 7 bit. Il più delle volte vengono utilizzati 7 bit, specialmente nelle applicazioni di piccole dimensioni. Le opzioni per sync_mode
sono:
SLAVE_7
- Modalità slave, indirizzo a 7 bit
SLAVE_10
- Modalità slave, indirizzo a 10 bit
MASTER
- Modalità master
Con slew
, è possibile selezionare se il dispositivo deve utilizzare la velocità di risposta. Maggiori informazioni su cosa si trova qui: che cos'è la velocità di risposta per I2C?
Due moduli MSSP
C'è qualcosa di speciale nei dispositivi con due moduli MSSP, come il PIC18F46K22 . Hanno due serie di funzioni, una per il modulo 1 e una per il modulo 2. Ad esempio, invece diOpenI2C()
, hanno OpenI2C1()
e openI2C2()
.
Ok, quindi hai impostato tutto e aperto la connessione. Ora facciamo alcuni esempi:
Esempi
Esempio di scrittura master
Se hai familiarità con il protocollo I2C, saprai che una tipica sequenza di scrittura principale è simile alla seguente:
Master : START | ADDR+W | | DATA | | DATA | | ... | DATA | | STOP
Slave : | | ACK | | ACK | | ACK | ... | | ACK |
Inizialmente, inviamo una condizione START. Considera questo sollevando il telefono. Quindi, l'indirizzo con un bit di scrittura - compone il numero. A questo punto, lo schiavo con l'indirizzo inviato sa che viene chiamato. Invia un riconoscimento ("Hello"). Ora, il dispositivo master può inviare dati - inizia a parlare. Invia qualsiasi quantità di byte. Dopo ogni byte, lo slave dovrebbe ACK i dati ricevuti ("sì, ti sento"). Quando il dispositivo principale ha terminato di parlare, riattacca con la condizione di STOP.
In C, la sequenza di scrittura principale sarebbe simile al master:
IdleI2C(); // Wait until the bus is idle
StartI2C(); // Send START condition
IdleI2C(); // Wait for the end of the START condition
WriteI2C( slave_address & 0xfe ); // Send address with R/W cleared for write
IdleI2C(); // Wait for ACK
WriteI2C( data[0] ); // Write first byte of data
IdleI2C(); // Wait for ACK
// ...
WriteI2C( data[n] ); // Write nth byte of data
IdleI2C(); // Wait for ACK
StopI2C(); // Hang up, send STOP condition
Master leggi l'esempio
La sequenza di lettura principale è leggermente diversa dalla sequenza di scrittura:
Master : START | ADDR+R | | | ACK | | ACK | ... | | NACK | STOP
Slave : | | ACK | DATA | | DATA | | ... | DATA | |
Ancora una volta, il master avvia la chiamata e compone il numero. Tuttavia, ora vuole ottenere informazioni. Lo slave risponde prima alla chiamata, quindi inizia a parlare (invio dati). Il master riconosce ogni byte fino a quando non ha abbastanza informazioni. Quindi invia un Not-ACK e riattacca con una condizione di STOP.
In C, questo sarebbe simile alla parte principale:
IdleI2C(); // Wait until the bus is idle
StartI2C(); // Send START condition
IdleI2C(); // Wait for the end of the START condition
WriteI2C( slave_address | 0x01 ); // Send address with R/W set for read
IdleI2C(); // Wait for ACK
data[0] = ReadI2C(); // Read first byte of data
AckI2C(); // Send ACK
// ...
data[n] = ReadI2C(); // Read nth byte of data
NotAckI2C(); // Send NACK
StopI2C(); // Hang up, send STOP condition
Codice slave
Per lo slave, è meglio usare una routine di servizio di interruzione o ISR. Puoi impostare il tuo microcontrollore in modo che riceva un interrupt quando viene chiamato il tuo indirizzo. In questo modo non è necessario controllare costantemente il bus.
Innanzitutto, impostiamo le basi per gli interrupt. Dovrai abilitare gli interrupt e aggiungere un ISR. È importante che i PIC18 abbiano due livelli di interrupt: alto e basso. Imposteremo I2C come interruzione ad alta priorità, perché è molto importante rispondere a una chiamata I2C. Quello che faremo è il seguente:
- Scrivi un ISR SSP, per quando l'interrupt è un interrupt SSP (e non un altro interrupt)
- Scrivi un ISR generale ad alta priorità, per quando l'interruzione ha la priorità alta. Questa funzione deve controllare quale tipo di interruzione è stata attivata e chiamare il giusto ISR secondario (ad esempio, ISP SSP)
- Aggiungere
GOTO
un'istruzione all'ISR generale sul vettore di interrupt ad alta priorità. Non possiamo mettere l'ISR generale direttamente sul vettore perché in molti casi è troppo grande.
Ecco un esempio di codice:
// Function prototypes for the high priority ISRs
void highPriorityISR(void);
// Function prototype for the SSP ISR
void SSPISR(void);
// This is the code for at the high priority vector
#pragma code high_vector=0x08
void interrupt_at_high_vector(void) { _asm GOTO highPriorityISR _endasm }
#pragma code
// The actual high priority ISR
#pragma interrupt highPriorityISR
void highPriorityISR() {
if (PIR1bits.SSPIF) { // Check for SSP interrupt
SSPISR(); // It is an SSP interrupt, call the SSP ISR
PIR1bits.SSPIF = 0; // Clear the interrupt flag
}
return;
}
// This is the actual SSP ISR
void SSPISR(void) {
// We'll add code later on
}
La prossima cosa da fare è abilitare l'interrupt ad alta priorità quando il chip si inizializza. Questo può essere fatto con alcune semplici manipolazioni del registro:
RCONbits.IPEN = 1; // Enable interrupt priorities
INTCON &= 0x3f; // Globally enable interrupts
PIE1bits.SSPIE = 1; // Enable SSP interrupt
IPR1bits.SSPIP = 1; // Set SSP interrupt priority to high
Ora, abbiamo interruzioni di lavoro. Se lo stai implementando, lo controllerei ora. Scrivi una baseSSPISR()
per iniziare a lampeggiare un LED quando si verifica un interrupt SSP.
Okay, quindi hai fatto funzionare i tuoi interrupt. Ora scriviamo del codice reale per la SSPISR()
funzione. Ma prima un po 'di teoria. Distinguiamo cinque diversi tipi di interrupt I2C:
- Il master scrive, l'ultimo byte era l'indirizzo
- Il master scrive, l'ultimo byte era dato
- Il master legge, l'ultimo byte era l'indirizzo
- Il master legge, l'ultimo byte era dato
- NACK: fine della trasmissione
Puoi verificare a che stato sei controllando i bit nel SSPSTAT
registro. Questo registro è il seguente nella modalità I2C (i bit non utilizzati o irrilevanti sono omessi):
- Bit 5: D / NOT A: Indirizzo dati / non: impostato se l'ultimo byte era dati, cancellato se l'ultimo byte era un indirizzo
- Bit 4: P: Bit di stop: impostato se si è verificata l'ultima condizione di STOP (non vi sono operazioni attive)
- Bit 3: S: bit di avvio: impostato se si è verificata l'ultima condizione di START (è presente un'operazione attiva)
- Bit 2: R / NON W: Lettura / Non scrittura: impostato se l'operazione è una lettura principale, azzerato se l'operazione è una scrittura principale
- Bit 0: BF: Buffer pieno: impostato se ci sono dati nel registro SSPBUFF, cancellato in caso contrario
Con questi dati, è facile vedere come vedere in quale stato si trova il modulo I2C:
State | Operation | Last byte | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 0
------+-----------+-----------+-------+-------+-------+-------+-------
1 | M write | address | 0 | 0 | 1 | 0 | 1
2 | M write | data | 1 | 0 | 1 | 0 | 1
3 | M read | address | 0 | 0 | 1 | 1 | 0
4 | M read | data | 1 | 0 | 1 | 1 | 0
5 | none | - | ? | ? | ? | ? | ?
Nel software, è meglio usare lo stato 5 come predefinito, che viene assunto quando i requisiti per gli altri stati non sono soddisfatti. In questo modo, non rispondi quando non sai cosa sta succedendo, perché lo schiavo non risponde a un NACK.
Comunque, diamo un'occhiata al codice:
void SSPISR(void) {
unsigned char temp, data;
temp = SSPSTAT & 0x2d;
if ((temp ^ 0x09) == 0x00) { // 1: write operation, last byte was address
data = ReadI2C();
// Do something with data, or just return
} else if ((temp ^ 0x29) == 0x00) { // 2: write operation, last byte was data
data = ReadI2C();
// Do something with data, or just return
} else if ((temp ^ 0x0c) == 0x00) { // 3: read operation, last byte was address
// Do something, then write something to I2C
WriteI2C(0x00);
} else if ((temp ^ 0x2c) == 0x00) { // 4: read operation, last byte was data
// Do something, then write something to I2C
WriteI2C(0x00);
} else { // 5: slave logic reset by NACK from master
// Don't do anything, clear a buffer, reset, whatever
}
}
Puoi vedere come puoi controllare il SSPSTAT
registro (prima ANDed con in 0x2d
modo che abbiamo solo i bit utili) usando le maschere di bit per vedere che tipo di interruzione abbiamo.
Il tuo compito è scoprire cosa devi inviare o fare quando rispondi a un interrupt: dipende dalla tua applicazione.
Riferimenti
Ancora una volta, vorrei menzionare le note sull'applicazione che Microchip ha scritto su I2C:
- AN734 sull'implementazione di uno slave I2C
- AN735 sull'implementazione di un master I2C
- AN736 sulla configurazione di un protocollo di rete per il monitoraggio ambientale
C'è documentazione per le librerie del compilatore: documentazione delle librerie del compilatore
Quando si imposta qualcosa da soli, controllare il foglio dati del proprio chip nella sezione (M) SSP per la comunicazione I2C. Ho usato PIC18F46K22 per la parte master e PIC18F4620 per la parte slave.