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
/v
x.xx
/h/i2c.h
- C18-Quelle:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- XC8-Header:
installation_path
/v
x.xx
/include/plib/i2c.h
- XC8-Quelle:
installation_path
/v
x.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 OpenI2C
Funktion 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_mode
kö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_mode
sind:
SLAVE_7
- Slave-Modus, 7-Bit-Adresse
SLAVE_10
- Slave-Modus, 10-Bit-Adresse
MASTER
- Master-Modus
Mit slew
kö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
GOTO
dem 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:
- Der Master schreibt, das letzte Byte war die Adresse
- Der Master schreibt, das letzte Byte waren Daten
- Der Master liest, das letzte Byte war die Adresse
- Der Master liest, das letzte Byte waren Daten
- NACK: Ende der Übertragung
Sie können überprüfen, in welchem Zustand Sie sich befinden, indem Sie die Bits im SSPSTAT
Register ü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 SSPSTAT
Register (zuerst UND-verknüpft, 0x2d
damit 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.