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?
Microchip hat dazu Application Notes geschrieben:
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:
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:
installation_path
/v
x.xx
/h/i2c.h
installation_path
/v
x.xx
/src/pmc_common/i2c/
installation_path
/v
x.xx
/include/plib/i2c.h
installation_path
/v
x.xx
/sources/pic18/plib/i2c/
In welcher Datei im /i2c/
Ordner sich eine Funktion befindet, können Sie der Dokumentation entnehmen.
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_mode
kö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_mode
sind:
SLAVE_7
- Slave-Modus, 7-Bit-AdresseSLAVE_10
- Slave-Modus, 10-Bit-AdresseMASTER
- Master-ModusMit slew
können Sie auswählen, ob das Gerät die Slew-Rate verwenden soll. Mehr dazu hier: Was ist die Anstiegsrate für I2C?
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:
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
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
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:
GOTO
Anweisung 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:
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):
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 SSPSTAT
Register (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.
Ich möchte noch einmal die Anwendungshinweise erwähnen, die Microchip über I2C geschrieben hat:
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 );
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.
AndrejaKo
Benutzer17592
AndrejaKo
Benutzer17592
Benutzer54853