DW EditSeite anzeigenÄltere VersionenLinks hierherAlles aus-/einklappenNach oben Diese Seite ist nicht editierbar. Sie können den Quelltext sehen, jedoch nicht verändern. Kontaktieren Sie den Administrator, wenn Sie glauben, dass hier ein Fehler vorliegt. CKG Editor ====== 10 I2C Schnittstelle ====== <WRAP group> <WRAP column 30%> ==== Ziele ==== Nach dieser Lektion sollten Sie: - wissen wie die Kommunikation zwischen I2C Master und Slave funktioniert </WRAP> <WRAP column 65%> ==== Video ==== {{youtube>7aZ43cl7o_w?size=700x400}} </WRAP> </WRAP> ==== Statemachine für Datenpaket ==== ++++ Statemachine der I2C Kommunikation | === Statemachine der I2C Kommunikation === <WRAP> <imgcaption BildNr3 | Statemachine der I2C Kommunikation > </imgcaption> {{drawio>StatemachinederI2CKommunikation.svg}} </WRAP> ++++ ==== I2C in Kürze und Zeitverlaufsdiagramm ==== <WRAP> <imgcaption BildNr09 | Zusammenspiel der TWI-Register> </imgcaption> {{drawio>TWIRegister.svg}} </WRAP> **Übertragung**\\ 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. <WRAP> <imgcaption BildNr4 | Zeitverlaufsdiagramm der I2C Kommunikation > </imgcaption> {{drawio>ZeitverlaufsdiagrammderI2CKommunikation.svg}} </WRAP> === Startbedingung === <WRAP group><WRAP column 30%> 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). <WRAP> <imgcaption BildNr5 | Startbedingung> </imgcaption> {{drawio>Startbedingung.svg}} </WRAP> </WRAP><WRAP column 65%> \\ \\ \\ \\ \\ \\ Für eine Startbedingung werden die Bits innerhalb des TWCR wie folgt gesetzt: <WRAP> <sxh c; first-line: 1> 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. </sxh> </WRAP> </WRAP></WRAP> === Übertragung === <WRAP group><WRAP column 30%> 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. <WRAP> <imgcaption BildNr6 | Übertragung> </imgcaption> {{drawio>Uebertragung.svg}} </WRAP> </WRAP><WRAP column 65%> \\ \\ \\ \\ \\ \\ Für die Übertragung eines Bytes muss TWDR und TWCR wie folgt gesetzt werden. \\ Zunächst wird die Übertragung der Adresse (''SLA_W'') betrachtet: <WRAP> <sxh c; first-line: 1> TWDR = SLA_W; // Load SLA_W into TWDR TWCR = (1<<TWINT)|(1<<TWEN); // Setting TWINT clears interupt flag // to start transmission of address </sxh> </WRAP> \\ \\ \\ Die Daten (''DATA'') werden in gleicher Weise übertragen: <WRAP> <sxh c; first-line: 1> TWDR = DATA; // Load DATA into TWDR TWCR = (1<<TWINT)|(1<<TWEN); // Setting TWINT clears interupt flag // to start transmission of address </sxh> </WRAP> </WRAP></WRAP> === Stoppbedingung === <WRAP group><WRAP column 30%> Die Stoppbedingung beendet die Übertragung. SCL geht auf HIGH (c), anschließend wechselt die SDA-Leitung von LOW nach HIGH (d). <WRAP> <imgcaption BildNr7 | Stoppbedingung > </imgcaption> {{drawio>Stoppbedingung.svg}} </WRAP> </WRAP><WRAP column 65%> \\ \\ \\ \\ \\ \\ Für eine Stoppbedingung werden die Bits innerhalb des TWCR wie folgt gesetzt: <WRAP> <sxh c; first-line: 1> 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. </sxh> </WRAP> </WRAP></WRAP> ===== Software ===== ==== einfache Anwendung ==== Im ersten Schritt ist im folgenden eine einfache Anwendung dargestellt. \\ Bei dieser werden Schalterstellungen vom Master an den Slave übertragen. --> I. Vorarbeiten Laden Sie die Datei {{microcontrollertechnik:simple_i2c.zip}} herunter und entpacken Sie diese. \\ In ihr finden Sie die Simulation als ''simple_I2C.sim1'' und das Microchip Solution File ''simple_I2C.atsln''. <-- --> II. Analyse des fertigen Programms # - Initialisieren des Programms - Öffnen Sie SimulIDE und öffnen Sie dort mittels {{microcontrollertechnik:simulide_open.jpg?25}} die Datei ''simple_I2C.sim1'' - In der Simulation finden Sie unten links 8 Dip-Schalter, welche durch Klick auf eine der acht kleinen Quadrate aktiviert (Wechsel auf grün)bzw deaktiviert werden kann. - Die hex Files sollten bereits in der simulation verlinkt sein, sodass diese nach dem Start auch lauffähig sei soll. \\ Falls nicht finden Sie die hex Files unter ''..\simple_I2C_Master\Debug'' bzw ''..\simple_I2C_Slave\Debug''. - Wenn Sie die Simulation starten, sehen Sie oben rechts im Logic Analyzer den Verlauf der Signale auf SDA (Daten) und SCL (Clock, also Takt). - Sobald die Stopp-Bedingung erfüllt ist (positive Flanke auf SDA, wenn SCL bereits High ist), wird die Simulation automatisch gestoppt. \\ Dies geschieht, da im Logic Analyzer als Trigger ''Ch1R & Ch2H''eingetragen wurde. - Sie können hier einige Dinge analysieren: - Was passiert in den I2C Registern (''TWAR'', ''TWBR'', ''TWCR'', ''TWDR'', ''TWSR'') auf beiden Seiten? - Was wird übertragen? - Das Programm zu diesem Hexfile soll nun erstellt werden <-- --> III. Code in Microchip Studio # <fs x-large>**TWI Master**</fs> <WRAP group><WRAP column 40%><sxh c; first-line: 1> /* ---------------------------------------------------------------------------- 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: AtmelStudio 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 // ggf. kann am Ende der Übertragung ein _delay_us(1) zur Synchronisierung helfen } } ///////////////////////////////////////// // 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) } </sxh> </WRAP><WRAP column 55%> \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ ''Deklarationen ==================================='' \\ \\ - Hier wird die Frequenz des Quarz direkt eingestellt. - Weiterhin wird eine Konstante für die I2C Frequenz definiert \\ \\ - Die Header-Dateien und die Bit-ändernden Makros entsprechen denen der letzten Programme. \\ \\ \\ \\ \\ \\ - Die weiteren Konstanten sind: - Kostante für die Pullups - Kostante für die Invertierung der SCcalterstellungen - Konstante für die I2C Adresse \\ \\ \\ \\ \\ - Zwei globale Variablen beinhalten die I2C Adresse und die Daten \\ \\ ''Hauptprogramm ========================='' - es wird zunächst die Pull-up Widerstände aktiviert, um die Schalter einlesen zu können - in der Haupschleife läuft: - Zu Beginn eine Iitialisierung der I2C Schnittstelle. Die wiederholte Initialisierung vermeidet Probleme der I2C Zustandsmaschine. - Als nächstes wird die startende Flanke gesendet. - Das erste Byte auf dem I2C Bus ist die Adressebyte. Dieses setzt sich aus der Adresse und einem Bit zusammen, welches angibt, ob der Slave nur zuhören (''0'') oder antworten (''1'') soll. - Nun wird der Zustand das Port B als I2C Daten eingelesen. - Diese werden per I2C übertragen. - Und zum Schluss wird das Stopp Bit gesendet \\ \\ \\ \\ '' I2C Initialisierung ========================='' - Durch das Zurücksetzen der Bits ''TWPS0'' und ''TWPS1'' wird kein Prescaler (bzw. einer von 1) gewählt - Auch das Kontrollregister wird zurückgesetzt - im Bitraten Register wird die I2C Frequenz eingestellt \\ \\ \\ \\ '' I2C Startbit senden =============='' - Es soll die I2C Schnittstelle aktiviert (''TWEN'' setzen) und das Startbit gesendet (''TWSTA'' setzen) werden. Das Schreiben einer ''1'' in ''TWINT'' löscht dieses Interruptflag - Nach dem Ändern des Konrollregisters muss die Abarbeitung abgewartet werden. Dies ist daran zu erkennen, das ''TWINT'' gleich ''1'' wird \\ \\ \\ \\ '' I2C Adressbyte/Daten senden ========================='' - Bevor Daten übertragen werden sollen, müssen diese erst in das I2C Datenregister geschrieben werden. - Wieder die I2C Schnittstelle aktivieren (''TWEN'' setzen) und das Inrerruptflag löschen (''TWINT'' setzen) - auch hier muss wieder die Abarbeitung abgewartet werden \\ \\ \\ \\ '' I2C Stoppbit senden ========================='' - Das Stoppbit wird wieder im Kontrollregister aktiviert - Hier ist kein Warten notwendig </WRAP></WRAP> <fs x-large>**TWI Slave**</fs> <WRAP group><WRAP column 40%><sxh c; first-line: 1> /* ---------------------------------------------------------------------------- Experiment 10: I2C Kommunikation ============= =============================== Dateiname : I2C_SimpleMaster.c Autoren : Tim Fischer (Hochschule Heilbronn, Fakultaet T1) Datum : 23.06.2021 Version : 1.0 Hardware: Simulide 0.5.16-RC5 Software: Entwicklungsumgebung: AtmelStudio 7.0 C-Compiler: AVR/GNU C Compiler 5.4.0 Funktion: tbd // ----------------------------------------------------------------------------*/ // Deklarationen ============================================================== // Festlegung der Quarzfrequenz #define F_CPU 16000000UL #define F_SCL 10000L //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) //Funktionsprototypen void I2C_Init(); void I2C_setAddress(char Address); char I2C_readData(); uint8_t TWI_Address = 0b0001010; uint8_t TWI_AddressMask = 0b11111110; int main(void) { DDRD= 0xFF; // Auf DDRC die Daten ausgeben I2C_setAddress(TWI_Address); // eigene Adresse setzen while (1) { PORTD = 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 } </sxh> </WRAP><WRAP column 55%> \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ ''Deklarationen ==================================='' \\ \\ - Hier wird die Frequenz des Quarz direkt eingestellt. - Weiterhin wird eine Konstante für die I2C Frequenz definiert \\ \\ \\ \\ - Die Header-Dateien und die Bit-ändernden Makros entsprechen denen der letzten Programme. \\ \\ \\ \\ \\ - Die weiteren Konstanten sind: - Kostante für die Pullups - Kostante für die Invertierung der SCcalterstellungen - Konstante für die I2C Adresse \\ \\ \\ \\ \\ - Zwei globale Variablen beinhalten die I2C Adresse und die Daten ''Hauptprogramm ========================='' - es wird zunächst die Pull-up Widerstände aktiviert, um die Schalter einlesen zu können - in der Haupschleife läuft: - Zu Beginn eine Iitialisierung der I2C Schnittstelle. Die wiederholte Initialisierung vermeidet Probleme der I2C Zustandsmaschine. - Als nächstes wird die startende Flanke gesendet. - Das erste Byte auf dem I2C Bus ist die Adressebyte. Dieses setzt sich aus der Adresse und einem Bit zusammen, welches angibt, ob der Slave nur zuhören (''0'') oder antworten (''1'') soll. - Nun wird der Zustand das Port B als I2C Daten eingelesen. - Diese werden per I2C übertragen. - Und zum Schluss wird das Stopp Bit gesendet '' I2C Initialisierung ========================='' - im Statusregister werden die Bits ''TWSR''Mit dem Befehl ''ISR()'' wird eine Interrupt Service Routine für den __OV__er__F__low Interrupt für __TIMER2__ angelegt. - Der Überlauf-Interrupt durch den Timer2 wird erst bei Überlauf des 8-Bit Wert ausgeführt. Auch hier ergibt sich durch den Prescaler und Modus (''TCCR2A'' und ''TCCR2B'') eine Periode von $T_{\rm ISR}= 0,16\bar{6}~\rm ms$. - Auch das Kontrollregister wird zurückgesetzt - im Bitraten Register wird die I2C Frequenz eingestellt \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ '' I2C Startbit senden =============='' \\ \\ \\ - Es soll die I2C Schnittstelle aktiviert (''TWEN'' setzen) und das Startbit gesendet (''TWSTA'' setzen) werden. Das Schreiben einer ''1'' in ''TWINT'' löscht dieses Interruptflag - Nach dem Ändern des Konrollregisters muss die Abarbeitung abgewartet werden. Dies ist daran zu erkennen, das ''TWINT'' gleich ''1'' wird \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ '' I2C Adressbyte/Daten senden ========================='' - Bevor Daten übertragen werden sollen, müssen diese erst in das I2C Datenregister geschrieben werden. - Wieder die I2C Schnittstelle aktivieren (''TWEN'' setzen) und das Inrerruptflag löschen (''TWINT'' setzen) - auch hier muss wieder die Abarbeitung abgewartet werden \\ \\ \\ \\ \\ \\ '' I2C Stoppbit senden ========================='' - Das Stoppbit wird wieder im Kontrollregister aktiviert - Hier ist kein Warten notwendig </WRAP></WRAP> <-- ==== komplexere Anwendung ==== Als Beispiel wurde die Temperaturmessung gewählt * {{microcontrollertechnik:twi_slave_master.zip}} ==== Bibliotheken ==== * TWI Module as I2C Master (AVR315): * Application Note: {{microcontrollertechnik:doc2564.pdf}} * Library und Beispielcode: {{microcontrollertechnik:avr315.zip}} * TWI Module as I2C Slave (AVR311): * Application Note: {{microcontrollertechnik:doc2565.pdf}} * Library und Beispielcode: {{microcontrollertechnik:avr311.zip}} * alternative und schlanke Implementierung des Slaves von [[https://github.com/thegouger/avr-i2c-slave|The Gouger (GitHub)]] \\ (Kopie vom 16.01.22: {{microcontrollertechnik:avr-i2c-slave-master.zip}}) ===== Beispiele ===== * Simulide: ''...\share\simulide\examples\Arduino\sofware_i2c_lcd\i2c_lcd-arduino'' (hierbei wird Software I2C eingesetzt) * Software I2C: * Library von Peter Fleury: [[http://www.peterfleury.epizy.com/avr-software.html|library]], [[http://www.peterfleury.epizy.com/doxygen/avr-gcc-libraries/group__pfleury__ic2master.html|Dokumentation]] * [[Software I2C Slave]] ===== weiterführende Unterlagen ===== * Die "[[https://mu.microchip.com/practical-i2c-introduction-implementation-and-troubleshooting|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.