A partire da I2C su PIC18s


8

Per un progetto vorrei che tre PIC (due slave PIC18F4620, un master PIC18F46K22) per comunicare tramite il bus I2C. Più tardi, è possibile aggiungere altri slave (come EEPROM, SRAM, ...). Sto scrivendo il codice per questi PIC in C usando il compilatore C18. Mi sono guardato molto in giro su Internet, ma non sono riuscito a trovare librerie per gestire la periferica (M) SSP. Ho letto il foglio dati di entrambi i PIC sulla periferica (M) SSP in modalità I2C ma non sono riuscito a scoprire come interfacciare il bus.

Quindi ho bisogno di librerie master e slave.

Che cosa mi consiglia? Hai una biblioteca del genere da qualche parte? È integrato nel compilatore e, se sì, dove? Esiste un buon tutorial da qualche parte in rete?


2
Ho avuto problemi simili qualche mese fa. Puoi leggere su di loro qui . Qui ci sono librerie per C18 che funzionano con I ^ 2C, ma manca una cosa grande: è necessario impostare manualmente la velocità del bus scrivendo nel registro appropriato e che non è menzionato da nessuna parte nella documentazione della libreria.
AndrejaKo

Grazie, è stato utile! Tuttavia, ha fatto solo la parte master, non la parte slave.

Sì, allora non avevo bisogno di lavorare con lo schiavo, quindi nessun esempio di schiavo. Scusate.
AndrejaKo

2
No va bene, è stato utile per la parte principale! :-)

disabilitare anche analogico sulle porte ANSELC = 0;

Risposte:


22

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/vx.xx/h/i2c.h
  • Fonte C18: installation_path/vx.xx/src/pmc_common/i2c/
  • Intestazione XC8: installation_path/vx.xx/include/plib/i2c.h
  • Sorgente XC8: installation_path/vx.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 OpenI2Cfunzione. 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_modesono:

  • 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 GOTOun'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:

  1. Il master scrive, l'ultimo byte era l'indirizzo
  2. Il master scrive, l'ultimo byte era dato
  3. Il master legge, l'ultimo byte era l'indirizzo
  4. Il master legge, l'ultimo byte era dato
  5. NACK: fine della trasmissione

Puoi verificare a che stato sei controllando i bit nel SSPSTATregistro. 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 SSPSTATregistro (prima ANDed con in 0x2dmodo 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.


3

In primo luogo, consiglierei di passare al compilatore XC8 semplicemente perché è l'ultimo. Ci sono librerie periferiche disponibili, ma non le ho mai usate molto. Controllare sul sito Web di Microchips per i dettagli e la documentazione.

Ok, ho alcune routine di base molto vecchie qui per le comunicazioni eeprom I2C che ho usato molto tempo fa con un PIC16F e il vecchio compilatore di fascia media Microhip (potrebbe essere stato quello Hi-Tech), ma penso che possano funzionare bene con il PIC18, poiché penso che la periferica sia la stessa. Scoprirai comunque molto rapidamente se è tutto diverso.
Facevano parte di un file più grande che è stato utilizzato con un progetto di registrazione della temperatura, quindi ho strappato rapidamente tutte le altre funzioni non correlate e salvato come i file seguenti, quindi è possibile che abbia fatto un po 'di confusione, ma spero che tu sarà in grado di avere un'idea di ciò che è necessario (potrebbe anche funzionare, non si sa mai ;-))

Sarà necessario assicurarsi che il file di intestazione principale sia corretto e controllare / modificare i pin per assicurarsi che siano i pin periferici I2C corretti e i nomi dei registri se provengono dal compilatore Hi-Tech, che ha fatto le cose in modo leggermente diverso per quanto riguarda la convenzione di denominazione dei registri.

File I2C.c:

#include "I2C.h"
#include "delay.h"
#include <pic.h>

#define _XTAL_FREQ 20000000


void write_ext_eeprom(unsigned int address, unsigned char data)
 {
    unsigned char a0 = ((address & 0x8000) >> 14);  
    unsigned char msb = (address >> 8);
    unsigned char lsb = (address & 0x00FF);


   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_write(data);
   i2c_stop();
   DelayMs(11);
}

/******************************************************************************************/

unsigned char read_ext_eeprom(unsigned int address)
{
   unsigned char a0 = ((address & 0x8000) >> 14);  
   unsigned char data;
   unsigned char msb = (address >> 8);
   unsigned char lsb = (address & 0x00FF);

   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_repStart();
   i2c_write(0xa1 | a0);
   data=i2c_read(0);
   i2c_stop();
   return(data);
}

void i2c_init()
{
 TRISC3=1;           // set SCL and SDA pins as inputs
 TRISC4=1;

 SSPCON = 0x38;      // set I2C master mode
 SSPCON2 = 0x00;

 //SSPADD = 9;          // 500kHz bus with 20MHz xtal 
 SSPADD = 49;           // 100kHz bus with 20Mhz xtal

 CKE=0;     // use I2C levels      worked also with '0'
 SMP=1;     // disable slew rate control  worked also with '0'

 PSPIF=0;      // clear SSPIF interrupt flag
 BCLIF=0;      // clear bus collision flag
}

/******************************************************************************************/

void i2c_waitForIdle()
{
 while (( SSPCON2 & 0x1F ) | RW ) {}; // wait for idle and not writing
}

/******************************************************************************************/

void i2c_start()
{
 i2c_waitForIdle();
 SEN=1;
}

/******************************************************************************************/

void i2c_repStart()
{
 i2c_waitForIdle();
 RSEN=1;
}

/******************************************************************************************/

void i2c_stop()
{
 i2c_waitForIdle();
 PEN=1;
}

/******************************************************************************************/

int i2c_read( unsigned char ack )
{
 unsigned char i2cReadData;

 i2c_waitForIdle();

 RCEN=1;

 i2c_waitForIdle();

 i2cReadData = SSPBUF;

 i2c_waitForIdle();

 if ( ack )
  {
  ACKDT=0;
  }
 else
  {
  ACKDT=1;
  }
  ACKEN=1;               // send acknowledge sequence

 return( i2cReadData );
}

/******************************************************************************************/

unsigned char i2c_write( unsigned char i2cWriteData )
{
 i2c_waitForIdle();
 SSPBUF = i2cWriteData;
//if(ACKSTAT)
{
//while(ACKSTAT);
}
 return ( ! ACKSTAT  ); // function returns '1' if transmission is acknowledged
}

File di intestazione I2C.h:

extern void i2c_init();
extern void i2c_waitForIdle();
extern void i2c_start();
extern void i2c_repStart();
extern void i2c_stop();
extern int i2c_read( unsigned char ack );
extern unsigned char i2c_write( unsigned char i2cWriteData );

Grazie, questa è la parte principale e probabilmente probabilmente la stessa di PIC18. Grazie anche per la nota del compilatore :-) Chiedermi un po 'mi ha dato alcune note sull'applicazione, quindi le aggiungerò come risposta.

Dovresti considerare di aggiungere una sezione sulla configurazione del generatore di baud rate. Il codice in genere sembra SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;1KHz, ecc.
Jesse Craig,

1

I compilatori XC8 e XC16 includono librerie per I2C.

Il problema che ho riscontrato è che la documentazione non è molto buona! Se usi gli esempi della documentazione di Microchip, sei sfortunato. Anche il supporto Microchip non può aiutarti. Ci sono stato anch'io.

Qualche tempo fa ho lavorato con il microcontrollore serie PIC24EP512GP e la libreria non ha funzionato per me come documentato da Microchip.


Ah, è un peccato! Allora cosa hai fatto?

Improvvisato mio purtroppo.
Chetan Bhargava,

1
Sono utili anche per gli altri? Mi piacerebbe vederli!
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.