Beginnend mit I2C auf PIC18s

8

Für ein Projekt möchte ich, dass drei PICs (zwei Slaves PIC18F4620, ein Master PIC18F46K22) über den I2C-Bus kommunizieren. Später können weitere Slaves hinzugefügt werden (wie EEPROM, SRAM, ...). Ich schreibe den Code für diese PICs in C mit dem C18-Compiler. Ich habe mich im Internet viel umgesehen, konnte aber keine Bibliotheken für das (M) SSP-Peripheriegerät finden. Ich habe das Datenblatt beider PICs auf dem (M) SSP-Peripheriegerät im I2C-Modus gelesen, konnte aber nicht herausfinden, wie der Bus angeschlossen werden soll.

Also brauche ich Master- und Slave-Bibliotheken.

Was empfehlen Sie? Hast du irgendwo eine solche Bibliothek? Ist es im Compiler eingebaut und wenn ja, wo? Gibt es irgendwo im Netz ein gutes Tutorial?


quelle
2
Ich hatte vor einigen Monaten ähnliche Probleme. Sie können über sie lesen hier . Hier sind Bibliotheken für C18, die mit I ^ 2C funktionieren, aber eines fehlt: Sie müssen die Busgeschwindigkeit manuell einstellen, indem Sie in das entsprechende Register schreiben. Dies wird in der Dokumentation der Bibliothek nirgends erwähnt.
AndrejaKo
Danke, das war hilfreich! Es wurde jedoch nur der Master-Teil, nicht der Slave-Teil.
Ja, ich musste damals nicht mit Sklaven arbeiten, also keine Sklavenbeispiele. Es tut uns leid.
AndrejaKo
2
Nein, das ist in Ordnung, es war nützlich für den Master-Teil! :-)
Bitte deaktivieren Sie auch analog an den Ports ANSELC = 0;

Antworten:

22

Microchip hat dazu Anwendungshinweise geschrieben:

  • AN734 zur Implementierung eines I2C-Slaves
  • AN735 zur Implementierung eines I2C-Masters
  • Es gibt auch einen theoretischeren AN736 zum Einrichten eines Netzwerkprotokolls für die Umgebungsüberwachung, der für dieses Projekt jedoch nicht benötigt wird.

Die Anwendungshinweise funktionieren mit ASM, können jedoch problemlos auf C portiert werden.

Die kostenlosen C18- und XC8-Compiler von Microchip verfügen über I2C-Funktionen. Weitere Informationen hierzu finden Sie in der Dokumentation zu Compiler-Bibliotheken , Abschnitt 2.4. Hier einige Informationen zum Schnellstart:

Einrichten

Sie haben bereits den C18- oder XC8-Compiler von Microchip. Beide verfügen über integrierte I2C-Funktionen. Um sie zu verwenden, müssen Sie Folgendes einschließen i2c.h:

#include i2c.h

Wenn Sie sich den Quellcode ansehen möchten, finden Sie ihn hier:

  • C18-Header: installation_path/vx.xx/h/i2c.h
  • C18-Quelle: installation_path/vx.xx/src/pmc_common/i2c/
  • XC8-Header: installation_path/vx.xx/include/plib/i2c.h
  • XC8-Quelle: installation_path/vx.xx/sources/pic18/plib/i2c/

In der Dokumentation finden Sie, in welcher Datei sich im /i2c/Ordner eine Funktion befindet.

Verbindung öffnen

Wenn Sie mit den MSSP-Modulen von Microchip vertraut sind, wissen Sie, dass sie zuerst initialisiert werden müssen. Mit der OpenI2CFunktion können Sie eine I2C-Verbindung an einem MSSP-Port öffnen . So wird es definiert:

void OpenI2C (unsigned char sync_mode, unsigned char slew);

Mit sync_modekönnen Sie auswählen, ob das Gerät Master oder Slave ist und ob es ein Slave ist, ob es eine 10-Bit- oder eine 7-Bit-Adresse verwenden soll. Meistens werden 7-Bit verwendet, insbesondere in kleinen Anwendungen. Die Optionen für sync_modesind:

  • SLAVE_7 - Slave-Modus, 7-Bit-Adresse
  • SLAVE_10 - Slave-Modus, 10-Bit-Adresse
  • MASTER - Master-Modus

Mit slewkönnen Sie auswählen, ob das Gerät die Anstiegsgeschwindigkeit verwenden soll. Mehr darüber, was es hier ist: Was ist die Anstiegsrate für I2C?

Zwei MSSP-Module

Geräte mit zwei MSSP-Modulen wie dem PIC18F46K22 haben etwas Besonderes . Sie haben zwei Funktionssätze, einen für Modul 1 und einen für Modul 2. Stattdessen OpenI2C()haben sie statt OpenI2C1()und openI2C2().

Okay, Sie haben alles eingerichtet und die Verbindung geöffnet. Lassen Sie uns nun einige Beispiele machen:

Beispiele

Master schreiben Beispiel

Wenn Sie mit dem I2C-Protokoll vertraut sind, wissen Sie, dass eine typische Master-Schreibsequenz folgendermaßen aussieht:

Master : START | ADDR+W |     | DATA |     | DATA |     | ... | DATA |     | STOP
Slave  :       |        | ACK |      | ACK |      | ACK | ... |      | ACK |

Zuerst senden wir eine START-Bedingung. Betrachten Sie dies als Abheben des Telefons. Dann die Adresse mit einem Schreibbit - Wählen der Nummer. Zu diesem Zeitpunkt weiß der Sklave mit der gesendeten Adresse, dass er angerufen wird. Er sendet eine Bestätigung ("Hallo"). Jetzt kann das Master-Gerät Daten senden - er beginnt zu sprechen. Er sendet eine beliebige Anzahl von Bytes. Nach jedem Byte sollte der Slave die empfangenen Daten bestätigen ("Ja, ich höre dich"). Wenn das Master-Gerät aufgehört hat zu sprechen, legt er mit der STOP-Bedingung auf.

In C würde die Master-Schreibsequenz für den Master folgendermaßen aussehen:

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 liest Beispiel

Die Master-Lesesequenz unterscheidet sich geringfügig von der Schreibsequenz:

Master : START | ADDR+R |     |      | ACK |      | ACK | ... |      | NACK | STOP
Slave  :       |        | ACK | DATA |     | DATA |     | ... | DATA |      |

Wieder leitet der Master den Anruf ein und wählt die Nummer. Jetzt möchte er jedoch Informationen erhalten. Der Slave nimmt zuerst den Anruf entgegen und beginnt dann zu sprechen (Daten senden). Der Master bestätigt jedes Byte, bis er genügend Informationen hat. Dann sendet er ein Not-ACK und legt mit einer STOP-Bedingung auf.

In C würde dies für den Hauptteil folgendermaßen aussehen:

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

Slave-Code

Für den Slave ist es am besten, eine Interrupt Service Routine oder ISR zu verwenden. Sie können Ihren Mikrocontroller so einrichten, dass er einen Interrupt empfängt, wenn Ihre Adresse angerufen wird. Auf diese Weise müssen Sie den Bus nicht ständig überprüfen.

Lassen Sie uns zunächst die Grundlagen für die Interrupts einrichten. Sie müssen Interrupts aktivieren und einen ISR hinzufügen. Es ist wichtig, dass PIC18s zwei Ebenen von Interrupts haben: hoch und niedrig. Wir werden I2C als Interrupt mit hoher Priorität festlegen, da es sehr wichtig ist, auf einen I2C-Anruf zu antworten. Was wir tun werden, ist Folgendes:

  • Schreiben Sie einen SSP-ISR, wenn der Interrupt ein SSP-Interrupt ist (und kein anderer Interrupt).
  • Schreiben Sie einen allgemeinen ISR mit hoher Priorität, wenn der Interrupt hohe Priorität hat. Diese Funktion muss überprüfen, welche Art von Interrupt ausgelöst wurde, und den richtigen Sub-ISR (z. B. den SSP-ISR) aufrufen.
  • Fügen Sie GOTOdem allgemeinen ISR eine Anweisung für den Interruptvektor mit hoher Priorität hinzu. Wir können den allgemeinen ISR nicht direkt auf den Vektor setzen, da er in vielen Fällen zu groß ist.

Hier ist ein Codebeispiel:

// 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
}

Als nächstes muss der Interrupt mit hoher Priorität aktiviert werden, wenn der Chip initialisiert wird. Dies kann durch einige einfache Registermanipulationen erfolgen:

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

Jetzt arbeiten Interrupts. Wenn Sie dies implementieren, würde ich es jetzt überprüfen. Schreiben Sie eine Basis SSPISR(), um eine LED zu blinken, wenn ein SSP-Interrupt auftritt.

Okay, du hast deine Interrupts zum Laufen gebracht. Schreiben wir nun einen echten Code für die SSPISR()Funktion. Aber zuerst eine Theorie. Wir unterscheiden fünf verschiedene I2C-Interrupt-Typen:

  1. Der Master schreibt, das letzte Byte war die Adresse
  2. Der Master schreibt, das letzte Byte waren Daten
  3. Der Master liest, das letzte Byte war die Adresse
  4. Der Master liest, das letzte Byte waren Daten
  5. NACK: Ende der Übertragung

Sie können überprüfen, in welchem ​​Zustand Sie sich befinden, indem Sie die Bits im SSPSTATRegister überprüfen . Dieses Register ist im I2C-Modus wie folgt (nicht verwendete oder irrelevante Bits werden weggelassen):

  • Bit 5: D / NOT A: Daten / Not-Adresse: Wird gesetzt, wenn das letzte Byte Daten waren. Wird gelöscht, wenn das letzte Byte eine Adresse war
  • Bit 4: P: Stoppbit: Wird gesetzt, wenn zuletzt eine STOP-Bedingung aufgetreten ist (es gibt keine aktive Operation).
  • Bit 3: S: Startbit: Wird gesetzt, wenn zuletzt eine START-Bedingung aufgetreten ist (es ist eine aktive Operation vorhanden).
  • Bit 2: R / NOT W: Lesen / Nicht schreiben: Wird gesetzt, wenn die Operation ein Master-Lesevorgang ist. Wird gelöscht, wenn die Operation ein Master-Schreibvorgang ist
  • Bit 0: BF: Puffer voll: Wird gesetzt, wenn sich Daten im SSPBUFF-Register befinden. Wenn nicht, werden sie gelöscht

Mit diesen Daten ist leicht zu erkennen, in welchem ​​Zustand sich das I2C-Modul befindet:

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      | -         |   ?   |   ?   |   ?   |   ?   |   ?

In Software ist es am besten, Status 5 als Standard zu verwenden. Dies wird angenommen, wenn die Anforderungen für die anderen Status nicht erfüllt sind. Auf diese Weise antworten Sie nicht, wenn Sie nicht wissen, was los ist, weil der Sklave nicht auf einen NACK reagiert.

Schauen wir uns den Code an:

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
    }
}

Sie können sehen, wie Sie das SSPSTATRegister (zuerst UND-verknüpft, 0x2ddamit wir nur die nützlichen Bits haben) mit Bitmasken überprüfen können, um festzustellen, welchen Interrupt-Typ wir haben.

Es ist Ihre Aufgabe, herauszufinden, was Sie senden oder tun müssen, wenn Sie auf einen Interrupt reagieren: Dies hängt von Ihrer Anwendung ab.

Verweise

Ich möchte noch einmal die Anwendungshinweise erwähnen, die Microchip über I2C geschrieben hat:

  • AN734 zur Implementierung eines I2C-Slaves
  • AN735 zur Implementierung eines I2C-Masters
  • AN736 zum Einrichten eines Netzwerkprotokolls für die Umgebungsüberwachung

Es gibt Dokumentation für die Compiler-Bibliotheken: Dokumentation für Compiler-Bibliotheken

Wenn Sie selbst etwas einrichten, überprüfen Sie das Datenblatt Ihres Chips im Abschnitt (M) SSP auf I2C-Kommunikation. Ich habe den PIC18F46K22 für den Master-Teil und den PIC18F4620 für den Slave-Teil verwendet.

Gemeinschaft
quelle
3

Erstens würde ich empfehlen, auf den XC8-Compiler umzusteigen, nur weil er der neueste ist. Es gibt periphere Bibliotheken, aber ich habe sie nie viel benutzt. Weitere Informationen und die Dokumentation finden Sie auf der Microchips-Website.

Okay, ich habe hier einige sehr alte Grundroutinen für I2C-EEPROM-Kommunikation, die ich vor langer Zeit mit einem PIC16F und dem alten Microhip-Midrange-Compiler (möglicherweise der Hi-Tech-Compiler) verwendet habe, aber ich denke, sie funktionieren möglicherweise in Ordnung mit dem PIC18, da ich denke, dass das Peripheriegerät das gleiche ist. Sie werden sowieso sehr schnell herausfinden, ob alles anders ist.
Sie waren Teil einer größeren Datei, die mit einem Temperaturlogger-Projekt verwendet wurde, also habe ich schnell alle anderen nicht verwandten Funktionen herausgerissen und als die folgenden Dateien gespeichert. Es ist also möglich, dass ich ein bisschen durcheinander gebracht habe, aber hoffentlich Sie wird in der Lage sein, eine Vorstellung davon zu bekommen, was benötigt wird (es kann sogar nur funktionieren, man weiß es nie ;-))

Sie müssen sicherstellen, dass die Haupt-Header-Datei korrekt ist, und die Pins überprüfen / ändern, um sicherzustellen, dass es sich um die richtigen I2C-Peripherie-Pins handelt, sowie die Registernamen, wenn sie vom Hi-Tech-Compiler stammen, was die Dinge etwas anders gemacht hat bezüglich der Registernamenskonvention.

I2C.c-Datei:

#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
}

I2C.h Header-Datei:

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 );
Oli Glaser
quelle
Danke, das ist der Hauptteil und in der Tat wahrscheinlich das gleiche wie bei PIC18. Vielen Dank auch für den Compiler-Hinweis :-) Wenn ich ein bisschen nachgefragt habe, habe ich einige Anwendungshinweise erhalten, daher werde ich sie selbst als Antwort hinzufügen.
Sie sollten einen Abschnitt zum Einrichten des Baudratengenerators hinzufügen. Der Code sieht normalerweise aus wie SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;für 1 kHz usw.
Jesse Craig
1

XC8- und XC16-Compiler enthalten Bibliotheken für I2C.

Das Problem, auf das ich gestoßen bin, ist, dass die Dokumentation nicht sehr gut ist! Wenn Sie die Beispiele aus der Microchip-Dokumentation verwenden, haben Sie kein Glück. Selbst die Unterstützung von Microchip kann Ihnen nicht helfen. Ich war selbst dort.

Vor einiger Zeit habe ich mit einem Mikrocontroller der PIC24EP512GP-Serie gearbeitet, und die Bibliothek hat bei mir nicht funktioniert, wie von Microchip dokumentiert.

Chetan Bhargava
quelle
Ah, das ist eine Schande! Also, was hast du gemacht?
Mein eigenes leider improvisiert.
Chetan Bhargava
1
Sind sie auch für andere nützlich? Ich würde sie gerne sehen!