Beginnend mit I2C auf PIC18s

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 viel im Internet umgesehen, konnte aber keine Bibliotheken finden, die mit dem (M)SSP-Peripheriegerät umgehen können. Ich habe das Datenblatt beider PICs auf dem (M)SSP-Peripheriegerät im I2C-Modus gelesen, konnte aber nicht herausfinden, wie der Bus angeschlossen wird.

Also brauche ich Master- und Slave-Bibliotheken.

Was empfehlen Sie? Hast du irgendwo so eine Bibliothek? Ist es im Compiler eingebaut und wenn ja, wo? Gibt es irgendwo im Netz eine gute Anleitung?

Ich hatte vor ein paar Monaten ähnliche Probleme. Sie können hier darüber lesen . Hier sind Bibliotheken für C18, die mit I^2C arbeiten, aber es fehlt eine große Sache: Sie müssen die Busgeschwindigkeit manuell einstellen, indem Sie in das entsprechende Register schreiben, und das wird nirgendwo in der Dokumentation der Bibliothek erwähnt.
Danke, das war hilfreich! Es hat jedoch nur den Master-Teil gemacht, nicht den Slave-Teil.
Ja, ich musste damals nicht mit Sklaven arbeiten, also keine Sklavenbeispiele. Es tut uns leid.
Nein, das ist in Ordnung, es war nützlich für den Meisterteil! :-)
bitte auch analog auf den Ports deaktivieren ANSELC=0;

Antworten (3)

Microchip hat dazu Application Notes geschrieben:

  • AN734 zur Implementierung eines I2C-Slaves
  • AN735 zur Implementierung eines I2C-Masters
  • Es gibt auch ein eher theoretisches AN736 zum Einrichten eines Netzwerkprotokolls für die Umgebungsüberwachung, aber es wird für dieses Projekt nicht benötigt.

Die Anwendungshinweise funktionieren mit ASM, aber das kann leicht nach C portiert werden.

Die kostenlosen C18- und XC8-Compiler von Microchip verfügen über I2C-Funktionen. Sie können mehr darüber in der Dokumentation der Compiler-Bibliotheken , Abschnitt 2.4, lesen. Hier einige Schnellstart-Infos:

Einrichten

Sie haben bereits den C18- oder XC8-Compiler von Microchip. Beide haben eingebaute I2C-Funktionen. Um sie zu verwenden, müssen Sie Folgendes angeben 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 welcher Datei im /i2c/Ordner sich eine Funktion befindet, können Sie der Dokumentation entnehmen.

Öffnen der Verbindung

Wenn Sie mit den MSSP-Modulen von Microchip vertraut sind, wissen Sie, dass sie zuerst initialisiert werden müssen. Mit der Funktion können Sie eine I2C-Verbindung auf einem MSSP-Port öffnen OpenI2C. So ist 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 falls es ein Slave ist, ob es eine 10-Bit- oder 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 Slew-Rate verwenden soll. Mehr dazu hier: Was ist die Anstiegsrate für I2C?

Zwei MSSP-Module

Geräte mit zwei MSSP-Modulen, wie der PIC18F46K22 , haben etwas Besonderes . Sie haben zwei Gruppen von Funktionen, eine für Modul 1 und eine für Modul 2. Anstelle von OpenI2C()haben sie beispielsweise OpenI2C1()und openI2C2().

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

Beispiele

Meisterschreibbeispiel

Wenn Sie mit dem I2C-Protokoll vertraut sind, wissen Sie, dass eine typische Master-Schreibsequenz wie folgt aussieht:

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

Zuerst senden wir eine START-Bedingung. Betrachten Sie dies, indem Sie zum Telefon greifen. Dann die Adresse mit einem Write-Bit - Anwahl der Nummer. An diesem Punkt weiß der Slave mit der gesendeten Adresse, dass er gerufen wird. Er sendet eine Bestätigung ("Hallo"). Jetzt kann das Master-Gerät Daten senden – er beginnt zu sprechen. Er sendet beliebig viele Bytes. Nach jedem Byte sollte der Slave die empfangenen Daten bestätigen ("ja, ich höre dich"). Wenn das Master-Gerät das Sprechen beendet hat, legt er mit dem STOP-Zustand auf.

In C würde die Master-Schreibsequenz für den Master so 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-Read-Beispiel

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

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

Auch hier leitet der Master den Anruf ein und wählt die Nummer. Allerdings will er sich jetzt informieren. Der Slave beantwortet zuerst den Anruf und beginnt dann zu sprechen (Daten zu senden). Der Master bestätigt jedes Byte, bis er genug Informationen hat. Dann sendet er ein Not-ACK und legt mit einer STOP-Bedingung auf.

In C würde das für den Master-Teil so 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 aufgerufen wird. So müssen Sie den Bus nicht ständig kontrollieren.

Lassen Sie uns zunächst die Grundlagen für die Interrupts einrichten. Sie müssen Interrupts aktivieren und eine ISR hinzufügen. Es ist wichtig, dass PIC18s zwei Interrupt-Ebenen 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 eine SSP-ISR, wenn der Interrupt ein SSP-Interrupt ist (und kein anderer Interrupt)
  • Schreiben Sie eine allgemeine ISR mit hoher Priorität, wenn der Interrupt eine hohe Priorität hat. Diese Funktion muss überprüfen, welche Art von Interrupt ausgelöst wurde, und die richtige Sub-ISR aufrufen (z. B. die SSP-ISR).
  • Fügen Sie eine GOTOAnweisung zu der allgemeinen ISR auf dem Unterbrechungsvektor mit hoher Priorität hinzu. Wir können die allgemeine ISR nicht direkt auf den Vektor setzen, da sie 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 müssen Sie den Interrupt mit hoher Priorität aktivieren, 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 wir mit Interrupts. Wenn Sie dies implementieren, würde ich es jetzt überprüfen. Schreiben Sie ein Basic SSPISR(), um eine LED blinken zu lassen, wenn ein SSP-Interrupt auftritt.

Okay, du hast also deine Interrupts zum Laufen gebracht. Lassen Sie uns nun echten Code für die SSPISR()Funktion schreiben. Aber zuerst etwas Theorie. Wir unterscheiden fünf verschiedene I2C-Interrupt-Typen:

  1. Master schreibt, letztes Byte war Adresse
  2. Master schreibt, letztes Byte waren Daten
  3. Master liest, letztes Byte war Adresse
  4. Master liest, letztes 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/Nicht-Adresse: gesetzt, wenn das letzte Byte Daten waren, gelöscht, wenn das letzte Byte eine Adresse war
  • Bit 4: P: Stoppbit: gesetzt, wenn zuletzt eine STOP-Bedingung aufgetreten ist (kein aktiver Betrieb)
  • Bit 3: S: Startbit: gesetzt, wenn zuletzt eine START-Bedingung aufgetreten ist (es gibt eine aktive Operation)
  • Bit 2: R/NOT W: Lesen/Nicht schreiben: gesetzt, wenn die Operation ein Master-Lesen ist, gelöscht, wenn die Operation ein Master-Schreiben ist
  • Bit 0: BF: Puffer voll: gesetzt, wenn es Daten im SSPBUFF-Register gibt, gelöscht, wenn nicht

Mit diesen Daten ist es 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, Zustand 5 als Standard zu verwenden, der angenommen wird, wenn die Anforderungen für die anderen Zustände nicht erfüllt sind. Auf diese Weise antwortest du nicht, wenn du nicht weißt, was los ist, weil der Slave nicht auf ein NACK antwortet.

Wie auch immer, 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 mit UND verknüpft 0x2d, damit wir nur die nützlichen Bits haben) mit Bitmasken überprüfen können, um zu sehen, welchen Interrupttyp wir haben.

Es ist Ihre Aufgabe, herauszufinden, was Sie senden oder tun müssen, wenn Sie auf einen Interrupt antworten: das 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 Umweltüberwachung

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

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

Erstens würde ich empfehlen, zum XC8-Compiler zu wechseln, einfach weil es der neueste ist. Es sind periphere Bibliotheken verfügbar, aber ich habe sie nie oft benutzt. Einzelheiten und die Dokumentation finden Sie auf der Microchips-Website.

Okay, ich habe hier einige sehr alte grundlegende Routinen für I2C-EEPROM-Kommunikation, die ich vor langer Zeit mit einem PIC16F und dem alten Microhip-Midrange-Compiler (es könnte der Hi-Tech-Compiler gewesen sein) verwendet habe, aber ich denke, sie könnten gut funktionieren mit dem PIC18, da denke ich das die peripherie gleich ist. Sie werden sowieso sehr schnell feststellen, 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, also ist es möglich, dass ich ein bisschen Chaos daraus gemacht habe, aber hoffentlich Sie wird sich ein Bild davon machen können, was benötigt wird (es kann sogar funktionieren, man weiß nie ;-) )

Sie müssen sicherstellen, dass die Hauptheaderdatei korrekt ist, und die Pins überprüfen/ändern, um sicherzustellen, dass es sich um die richtigen I2C-Peripherie-Pins handelt, und die Registernamen, wenn sie vom Hi-Tech-Compiler stammen, der 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 );
Danke, das ist der Masterteil und tatsächlich wahrscheinlich derselbe wie PIC18. Vielen Dank auch für den Compiler-Hinweis :-) Als ich ein bisschen herumgefragt habe, habe ich einige Anwendungshinweise erhalten, also werde ich sie selbst als Antwort hinzufügen.
Sie sollten erwägen, einen Abschnitt zum Einrichten des Baudratengenerators hinzuzufügen. Der Code sieht normalerweise so aus SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;für 1KHz usw.

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 Pech. Auch der Support von Microchip kann dir nicht helfen. Ich war selbst dort.

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

Ach, das ist schade! Also, was hast du getan?
Hab leider mein eigenes improvisiert.
Sind sie auch für andere nützlich? Ich möchte sie sehen!