Nach dieser Lektion sollten Sie:
Im Video wird eine Library für die Kommunikation verwendet. Wir werden in untenstehenden Beispiel die Register selbst schreiben.
Statemachine der I2C Kommunikation
Für die I2C Übertragung „trommelt“ der Master-IC auf der Taktleitung (SCL). Bei jedem „Trommelschlag“ (SCL=High), darf der Slave die Datenleitung (SDA) lesen.
D.h. während der Datenübertragung bleibt die Datenleitung bei SCL=High konstant.
Eine Flanke (=Signalwechsel) auf der Datenleitung während SCL=High definiert Beginn und Ende der Kommunikation.
Eine fallende Flanke auf SDA bei SCL=High stellt das Startbit dar, eine steigende Flanke das Stoppbit.
Läuft keine Kommunikation sind Daten- und Tankleitung auf High.
Um die Übertragung zu beginnen muss die Startbedingung eingeleitet werden. Während SCL HIGH ist (a), geht SDA von HIGH auf LOW. Anschließend startet SCL mit LOW (b).
Für eine Startbedingung werden die Bits innerhalb des TWCR wie folgt gesetzt:
TWCR = (1<<TWINT)|(1<<TWEN); // Setting TWINT clears interupt flag // to set the following state: | (1<<TWIE ) // Enable TWI Interrupt. --> nur wichtig, falls der Interrupt geprüft wird! | (1<<TWSTA)|(0<<TWSTO); // Initiate a START condition.
Die entscheidende Voraussetzung für eine erfolgreiche Bitübertragung ist, dass sich der Zustand von SDA nur ändern darf solange SCL auf LOW ist. Allerdings ist der Zustand von SDA erst gültig, wenn SCL auf HIGH ist.
Für die Übertragung eines Bytes muss TWDR und TWCR wie folgt gesetzt werden.
Zunächst wird die Übertragung der Adresse (SLA_W
) betrachtet:
TWDR = SLA_W; // Load SLA_W into TWDR TWCR = (1<<TWINT)|(1<<TWEN); // Setting TWINT clears interupt flag // to start transmission of address
Die Daten (DATA
) werden in gleicher Weise übertragen:
TWDR = DATA; // Load DATA into TWDR TWCR = (1<<TWINT)|(1<<TWEN); // Setting TWINT clears interupt flag // to start transmission of address
Die Stoppbedingung beendet die Übertragung. SCL geht auf HIGH (c), anschließend wechselt die SDA-Leitung von LOW nach HIGH (d).
Für eine Stoppbedingung werden die Bits innerhalb des TWCR wie folgt gesetzt:
TWCR = (1<<TWINT)|(1<<TWEN); // Setting TWINT clears interupt flag // to set the following state: | (1<<TWIE ) // Enable TWI Interrupt. --> nur wichtig, falls der Interrupt geprüft wird! | (0<<TWSTA)|(1<<TWSTO); // Initiate a STOP condition.
Im ersten Schritt ist im folgenden eine einfache Anwendung dargestellt.
Bei dieser werden Schalterstellungen vom Master an den Slave übertragen.
simple_I2C.sim1
und das Microchip Solution File simple_I2C.atsln
...\simple_I2C_Master\Debug
bzw ..\simple_I2C_Slave\Debug
.Ch1R & Ch2H
eingetragen wurde.TWAR
, TWBR
, TWCR
, TWDR
, TWSR
) auf beiden Seiten?
/* ---------------------------------------------------------------------------- Experiment 10: I2C Kommunikation ============= =============================== Dateiname : I2C_SimpleMaster.c Autoren : Tim Fischer (Hochschule Heilbronn, Fakultaet TE) Datum : 18.11.2023 Version : 1.1 Hardware : Simulide 1.0.0 >R810 Software : Entwicklungsumgebung: Microchip Studio 7.0 C-Compiler: AVR/GNU C Compiler 5.4.0 Funktion : einfacher I2C Master, der Schalterwerte überträgt Displayanzeige : keine Tastenfunktion : Die Tastenstellung der Dip-Schalter an Port B werden als Wert über I2C weitergegeben. Dabei zählt ein gedrückter Schalter (= hellgrün) als logisches High Signal Jumperstellung : keine Fuses im uC : keine // ----------------------------------------------------------------------------*/ // Deklarationen ============================================================== // Festlegung der Quarzfrequenz #define F_CPU 8000000UL // CPU Frequenz von 8MHz #define F_SCL 100000L // Baudrate von 100 kHz // Include von Header-Dateien #include <avr/interrupt.h> #include <util/delay.h> // Konstanten #define SET_BIT(BYTE, BIT) ((BYTE) |= (1 << (BIT))) // Bit Zustand in Byte setzen #define CLR_BIT(BYTE, BIT) ((BYTE) &= ~(1 << (BIT))) // Bit Zustand in Byte loeschen #define TGL_BIT(BYTE, BIT) ((BYTE) ^= (1 << (BIT))) // Bit Zustand in Byte wechseln (toggle) #define SET_ALL_PULLUPS (0xFF) // Konstante für die Aktivierung der Pullup #define INVERING_MASK (0xFF) // Konstante für die invertierung der Schalterstellungen #define TWI_ADRESS (0b0001010) // Konstante für die I2C Adresse //Funktionsprototypen void I2C_Init(); void I2C_transmitStart(); void I2C_transmitDataOrAddress(char Data); void I2C_transmitStop(); uint8_t TWI_Address = TWI_ADRESS; // Variable der I2C Adresse uint8_t TWI_Data = 0b00000000; // Variable für die I2C Daten int main(void) { PORTB = SET_ALL_PULLUPS; // Pull-up Widerstände an Port B aktivieren while (1) { I2C_Init(); // Initialisierung von TWI anstoßen I2C_transmitStart(); // Startbit schreiben I2C_transmitDataOrAddress((TWI_Address<<1) + 0);// Adresse senden: LSB = 0, zeigt Slave an, dass dieser nur empfangen soll TWI_Data = PINB^INVERING_MASK; // Lese Tasterstellungen ein. Invertiere jedes Bit I2C_transmitDataOrAddress(TWI_Data); // Daten senden I2C_transmitStop(); // Stoppbit schreiben _delay_us(1); // erst durch den Delay ist ein Triggern im Simulide möglich } } ///////////////////////////////////////// // I2C Initialisierung ///////////////////////////////////////// void I2C_Init() { CLR_BIT(TWSR, TWPS0); // Es wird kein Prescaler verwendet: CLR_BIT(TWSR, TWPS1); // Deshalb TWPS0 = 0 und TWPS1 = 0 TWCR = 0; // Control Register zurücksetzen TWBR = ((F_CPU/F_SCL)-16)/2; // die Bitrate wird mittels CPU Frequenz und Serial Clock Frequenz ermittelt } ///////////////////////////////////////// // I2C Startbit senden ///////////////////////////////////////// void I2C_transmitStart() { TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTA); // TWSTA = Startbit aktivieren, TWEN = TWI starten (ENable), TWINT = Interrupt bit löschen (durch beschreiben) while (!(TWCR & (1<<TWINT))); // warten bis Übertragung erfolgreich, Trigger ist hier das Setzen von TWINT } ///////////////////////////////////////// // I2C Adressbyte/Daten senden ///////////////////////////////////////// void I2C_transmitDataOrAddress(char Data) { TWDR = Data; // Data Variabel in Daten Register schreiben TWCR = (1<<TWINT)|(1<<TWEN); // TWEN = TWI starten (ENable), TWINT = Interrupt bit löschen (durch setzen) while (!(TWCR & (1<<TWINT))); // warten bis Übertragung erfolgreich, Trigger ist hier das Setzen von TWINT } ///////////////////////////////////////// // I2C Stoppbit senden ///////////////////////////////////////// void I2C_transmitStop() { TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO); // TWSTO = Stopptbit aktivieren, TWEN = TWI starten (ENable), TWINT = Interrupt bit löschen (durch setzen) }
Deklarationen ===================================
Hauptprogramm =========================
0
) oder antworten (1
) soll.
I2C Initialisierung =========================
TWPS0
und TWPS1
wird kein Prescaler (bzw. einer von 1) gewählt
I2C Startbit senden ==============
TWEN
setzen) und das Startbit gesendet (TWSTA
setzen) werden. Das Schreiben einer 1
in TWINT
löscht dieses InterruptflagTWINT
gleich 1
wird
I2C Adressbyte/Daten senden =========================
TWEN
setzen) und das Inrerruptflag löschen (TWINT
setzen)
I2C Stoppbit senden =========================
I2C Slave
/* ---------------------------------------------------------------------------- Experiment 10: I2C Kommunikation ============= =============================== Dateiname : I2C_SimpleSlave.c Autoren : Tim Fischer (Hochschule Heilbronn, Fakultaet TE) Datum : 18.11.2023 Version : 1.0 Hardware : Simulide 0.5.16-RC5 Software : Entwicklungsumgebung: Microchip Studio 7.0 C-Compiler: AVR/GNU C Compiler 5.4.0 Funktion : TBD Displayanzeige : keine Tastenfunktion : Die Tastenstellung der Dip-Schalter an Port B werden als Wert über I2C weitergegeben. Dabei zählt ein gedrückter Schalter (= hellgrün) als logisches High Signal Jumperstellung : keine Fuses im uC : keine // ----------------------------------------------------------------------------*/ // Deklarationen ============================================================== // Festlegung der Quarzfrequenz #define F_CPU 8000000UL // CPU Frequenz von 8MHz #define F_SCL 100000L // Baudrate von 100 kHz // Include von Header-Dateien #include <avr/interrupt.h> // Konstanten #define SET_BIT(BYTE, BIT) ((BYTE) |= (1 << (BIT))) // Bit Zustand in Byte setzen #define CLR_BIT(BYTE, BIT) ((BYTE) &= ~(1 << (BIT))) // Bit Zustand in Byte loeschen #define TGL_BIT(BYTE, BIT) ((BYTE) ^= (1 << (BIT))) // Bit Zustand in Byte wechseln (toggle) #define SET_ALL_TO_OUT (0xFF) // Konstante für die Aktivierung der Port Ausgänge #define TWI_ADRESS (0b0001010) // Konstante für die I2C Adresse #define TWI_ADRESS_MASK (0b0001010) // Konstante für die I2C Adress-Maske //Funktionsprototypen void I2C_Init(); void I2C_setAddress(char Address); char I2C_readData(); uint8_t TWI_Address = TWI_ADRESS; // Variable der I2C Adresse uint8_t TWI_AddressMask = TWI_ADRESS_MASK; // Variable Zum maskieren der eingehenden Adresse int main(void) { DDRB= SET_ALL_TO_OUT; // Auf DDRC die Daten ausgeben I2C_setAddress(TWI_Address); // eigene Adresse setzen while (1) { PORTB = I2C_readData(); // Daten an PortC ausgeben } } ////////////////////////////////////////////////// // Setzen der I2C Adresse auf die der Slave hört ////////////////////////////////////////////////// void I2C_setAddress(char Address) { TWAR = (Address<<1); // Adresse in das Pseudoregister schreiben TWAMR= TWI_AddressMask; // Adressmaske in das Pseudoregister schreiben TWCR = (1<<TWEA)|(1<<TWEN); // Enable Ack, Enable Interupt und Enable TIW } ////////////////////////////////////////////////// // Auslesen der übermittelten Daten ////////////////////////////////////////////////// char I2C_readData() { while (!(TWCR & (1<<TWINT))); // warte solange bis TWINT gesetzt ist return TWDR; // übermittle Daten }
Deklarationen ===================================
Hauptprogramm =========================
Setzen der I2C Adresse auf die der Slave hört =========================
0
)TWEA
setzen) und das I2C Modul aktiviert (TWEN
setzen). Hier darf TWINT
nicht geändert werden!
Auslesen der übermittelten Daten ==============
TWINT
gleich 1
wird
Als Beispiel wurde hier die Temperaturmessung aus Lektion 8 gewählt.
Das Projekt und die Simulation ist hier zu finden ad_wandler_i2c.zip
Bitte nutzen Sie diese als Vorlage, wen Sie eine I2C Schnittstelle implementieren wollen. Da in diesem Programmstand alles über Interrupts läuft, können auch weitere Funktionen abgearbeitet werden.
Die detaillierte Beschreibung zu I2C findet sich in des I2C Bus Specification and User Manual des Herstellers (ehemls Phillips nun NXP).
…\share\simulide\examples\Arduino\sofware_i2c_lcd\i2c_lcd-arduino
Die „Microchip University“ hat auch eine schöne Einführung in I2C. Hier wird aber nicht die Implementierung in Microchip Studio auf einem AVR-Chip gezeigt, sondern in MPLAB X auf einem PIC. D.h. der Code ist nicht direkt übertragbar.