Programmierkurs für PIC-Mikrocontroller
in C (CC5X Compiler)
Teil 4 I2C-Bus
Der I2C-Bus unterstützt
bidirektionalen Datenaustausch zwischen ICs, auf 2 Leitungen, mit großer Geschwindigkeit.
Hierbei werden die Daten seriell, asynchron übertragen. Es sind bis zu 127
Endgeräte adressierbar. Die Datenleitung trägt den Namen SDA (SerialDAta),
die Taktleitung den Namen SCL (SerialCLock).
Alle Teilnehmer teilen
sich in 2 Klassen: "Master" und "Slave". Der Master initialisiert Datenübertragungen
und erzeugt das dazugehörige Taktsignal. Der Slave ist passiv und wartet,
bis seine Adresse angewählt wird. Ein PIC mit softwaremäßigem I2C-Interface
kann nur Master sein, denn er ist zu langsam um auf einen fremden Takt zu
synchronisieren. Jede Datenübertragung startet mit einer Start-Bedingung und
endet mit einer Stop-Bedingung.
![]() |
Am Anfang sind beide Leitungen "1". Start-Bedingung ist, wenn während SCL "1" ist, SDA nach "0" wechselt. Stop-Bedingung ist, wenn SCL "1" ist und SDA nach "1" wechselt. Während SCL "1" ist, darf SDA sich nicht ändern, denn das ist der Zustand, in dem Daten gültig sind und übernommen werden. |
Nach der
Start-Bedingung wird Bauteiladresse übertagen.
|
Bits 7...4 der Adresse sind im IC festgelegt(beim seriellen EEPROM (24LC32) ist das beispielsweise "1010" . Bits 3...1 sind durch Spannungspegeln an den Pins 3,2,1 einstellbar. Bit 0 teilt dem IC mit, ob gelesen oder geschrieben wird: 1-Lesen, 0-Schreiben. Es können 8 seriellen EEPROM an einem Bus angeschlossen sein. |
![]() |
Seriellen EEPROM Beschreiben
![]() |
-Start -Bauteiladresse senden -Hochwertige Adresse senden -Niederwertige Adresse senden -Datenbyte senden -Stop. ACK(Bestätigungsbit) kommt von Slave |
Seriellen EEPROM Lesen
![]() |
-Start -Bauteiladresse senden -Hochwertige Adresse senden -Niederwertige Adresse senden -Bauteiladresse senden (Bit 0 = 1 zum lesen) -Datenbyte lesen -NO ACK senden -Stop. |
Ich stelle hier als Beispiel
die Kommunikation mit dem EEPROM(24LC32) vor :
|
#include <C:\cc5\16F84.H> #pragma config |= 0b.1111.1111.0010 //Konfigurations-Wort #pragma bit Opin @
PORTA.0 //Ausgang für serielle Daten
definieren #pragma bit Ipin @
PORTA.1 //Eingang für serielle Daten definieren #include <Seriel4.c> //Einbinden
Code zur seriellen Kommunikation #pragma bit sda @
PORTA.3 // Datenleitung SDA #pragma bit scl @
PORTA.2 // Taktleitung SCL #pragma bit tsda @
TRISA.3 // Datenleitung SDA IN/AUT - Umschalter #pragma bit tscl @
TRISA.2 // Taktleitung SCL
IN/AUT - Umschalter #include <I2C.c> //Einbinden Code
zur I2C Kommunikation void pausems(uns16 ms) //
Unterprogramm zum Abwarten angegebener Anzahl von Millisekunden { while(ms) // Schleife verlassen,
wenn ms=0 ist { OPTION = 2; // Vorteiler auf 8 einstellen TMR0 = 131; // 125 * 8 = 1000 (= 1
ms) while (TMR0); //Abwarten
einer Millisekunde ms--; // "ms" mit jeder Millisekunde erniedrigen } //Ende der Schleife
} void main(void) //Hauptprogramm { TRISA = 0b.1111.1110;
//Bit0 des PortA ist serielle Ausgang TRISB =
0b.0000.0000; //PortB eigentlich unbenutzt char d,komand,adrl,adrh; //Variablen definieren #asm BCF 0x03,RP0
//Speicherbank in Assemblermodus auf Bank 0 umschalten #endasm anfang: komand=empfang(); //Empfange seriellen Befehl switch(komand) //Befehl auswerten und
entsprechend handeln { case '1' : //
00 Adresse lesen ********************************** bstart(); // Start - Bedingung einstellen schreib(160); //
Bauteiladresse senden (zum Schreiben) schreib(0); //
Höhere Adresse übertragen (0..127) schreib(0); //
Niedrigere Adresse übertragen (0..255) bstart(); // Nochmals Start
- Bedingung einstellen schreib(161); //
Bauteiladresse senden (zum Lesen) d=lesen(); // Byte einlesen bitaus(1); //
NO ACK bstop(); // Stop - Bedingung einstellen senden(d); //Gelesene
Byte zum PC senden break; case '2' : //
00 Adresse beschreiben ****************************** d=empfang(); //Zu schreibendes Zeichen vom PC empfangen bstart(); // Start - Bedingung einstellen schreib(160); //
Bauteiladresse senden zum Schreiben schreib(0); //
Höhere Adresse übertragen (0..127) schreib(0); //
Niedrigere Adresse übertragen (0..255) schreib(d); //
Byte schreiben bstop(); // Stop - Bedingung einstellen break; case '3' : //
Auslesen 50-Byte ab bestimmter Adresse ***************** adrh=empfang(); // High –Adresse von PC empfangen adrl=empfang(); // Low –Adresse von PC empfangen bstart(); // Start - Bedingung einstellen schreib(160); //
Bauteiladresse senden zum Schreiben schreib(adrh); //
Höhere Adresse übertragen (0..127) schreib(adrl); //
Niedrigere Adresse übertragen (0..255) bstart(); // Nochmals Start
- Bedingung einstellen schreib(161); //
Bauteiladresse senden (zum lesen) d=lesen(); // Byte einlesen erstes Mal for (adrl=0;adrl<50;adrl++) // Schleife 50-mal { bitaus(0); // ACK senden(d); // seriell zum PC d=lesen(); // Byte
einlesen } bitaus(1); //
NO ACK (immer vor dem Stop) bstop(); // Stop - Bedingung einstellen break; case '4' : //
Schreiben 8-Byte an bestimmter Adresse ********** adrh=empfang(); // High –Adresse von PC empfangen adrl=empfang(); // Low –Adresse von PC empfangen bstart(); // Start - Bedingung einstellen schreib(160); //
Bauteiladresse senden zum Schreiben schreib(adrh); //
Höhere Adresse übertragen (0..127) schreib(adrl); //
Niedrigere Adresse übertragen (0..255) for (d=0;d<8;d++) schreib(d); // 8 Byte
schreiben in dem Fahl 0..8 bstop(); // Stop - Bedingung einstellen pausems(6); //
Schreibzyklus des EEPROMs 6ms, also warten break; case '5' : // Bauteiladressen
suchen ****************************** bit ACK;
// Bit Variable
d=2; // mit Adresse 2 anfangen while (d) // alle Bauteiladressen durchgehen { bstart(); // Start - Bedingung einstellen ACK=schreib(d); // Bauteiladresse
Übertragen, Bestätigungsbit aufnähmen if (!ACK) senden(d);// Wenn IC auf Adresse mit ACK antwortet, dann diese Adresse seriell senden bstop(); // Stop - Bedingung einstellen pausems(16); // nicht
unbedingt notwendig d
+=2; // Adresszähler um 2 erhöhen } break; } goto anfang; //
Endlosschleife(weitere Befehl holen) } |
| Include-datei I2C.c | Enthält Funktionen zum Kommunikation auf dem I2C-Bus als Master. |
| bstart(); | Erzeugt Startbedingung auf dem I2C-Bus, keine Parameter |
| bstop(); | Erzeugt Stopbedingung auf dem I2C-Bus, keine Parameter |
| schreib(10); | Schreibt ein Byte, erhält Zeichen zum Senden, gibt zurück Bestätigungsbit ACK des Slave-Geräts. |
| d=lesen(); | Liest ein Byte von I2C-Bus, gibt das eingelesene Zeichen zurück |
| bitaus(0); | Ein Bit ausgeben, dient zum Erzeugen des Bestätigungsbits (ACK) von Master. |
Der fünfte Abschnitt des Programms findet automatisch Bauteiladressen aller an I2C-Bus angeschlossener Geräte.
Schaltplan zu diesen Quellcode ist hier.
Anstatt 24LC32 kann auch 24L32, 24L64 verwendet werden. Wobei der 24L32 einen
schnelleren Schreibzyklus hat (1ms). Bei anderen EEPROMs mit I2C-Interface muss
man in den Datenblätter nachlesen, um die Adressbereiche und die Schreibzyklusdauer
einzustellen.
PCF8574 ist ein 8-Bit Port für I2C-Bus Mit diesem IC kann man 8-Bit ausgeben
oder einlesen. Zum Einlesen müssen alle Ausgänge auf 1 gesteuert sein. Wenn
ein Ausgang von aussen auf "0" gezogen wird, dann wird er als "0" eingelesen.
Die festgelegte Adresse des PCF8574 ist "0100". Das Schreiben und Lesen ist
noch einfacher als beim EEPROM.
| Schreiben | Lesen |
| -
Start - Bauteiladresse schreiben, Bit 0 ="0" - Datenbyte schreiben - Stop |
-
Start - Bauteiladresse schreiben, Bit 0 = "1" - Datenbyte lesen - NO ACK - Stop. |
LM75 (TCN75) ist
ein Temperatursensor mit I2C-Interface und einer Auflösung von 0,5 °C und einem
Messbereich von -55°C bis +125°C. Seine Bauteiladresse lautet "1001". Es werden
2 Bytes ausgelesen: das erste Byte enthält die ganze Zahl (zum Beispiel 22)
in Grad, das zweite Byte ist entweder "1" oder "0". Hierbei steht die "1" für
0,5°C und die "0" für 0°C. Der Temperatursensor hat eine Genauigkeit von +-3°C,
deswegen muss man im Programm eine Korrektur vorsehen. Der LM75 hat auch einen
Schaltausgang und die Möglichkeit die Einschalttemperatur und Ausschalttemperatur
einzustellen. Das ist aber bei der Arbeit mit einem Mikrocontroller überflüssig,
da diese Aufgaben von dem Mikrocontroller übernommen werden können.
Hier ist ein Beispiel einer Kommunikation mit 8-Bit Port (PCF8574) und Temperatursensor
(TCN75).
|
// Ausprobieren Kommunikation mit
I2C-Temperatursensor TCN75(LM75) bei 4 MHz // PORT-Expander #include <C:\cc5\16F84.H> #pragma config |= 0b.1111.1111.0010 //
Konfigurations Wort #pragma bit
Opin @ PORTA.0 //
Ausgang für serielle Daten definiren #pragma bit
Ipin @ PORTA.1 //
Eingang für serielle Daten definiren #include <Seriel4.c> #pragma bit sda @
PORTA.3 // Datenleitung SDA #pragma bit scl @
PORTA.2 // Taktleitung SCL #pragma bit tsda @
TRISA.3 // Datenleitung SDA IN/AUT - Umschalter #pragma bit tscl @
TRISA.2 // Taktleitung SCL
IN/AUT - Umschalter #include <I2C.c> // Einbinden
Code zur I2C Kommunikation void pausems(uns16 ms) //
Unterprogramm zum Abwarten angegebener Anzahl von Millisekunden { while(ms) // Schleife
verlassen, wenn ms=0 ist { OPTION
= 2; // Vorteiler auf 8 einstellen TMR0
= 131; // 125 * 8 = 1000 (= 1 ms) while (TMR0); //
Abwarten einer Millisekunde ms--; // "ms" mit jeder Millisekunde erniedrigen } } void main(void) { TRISA =
0b.1111.1110; TRISB =
0b.0000.0000; char d,komand,k; #asm BCF 0x03,RP0 #endasm anfang: komand=empfang(); switch(komand) { case '1'
: //
Temperatur lesen *********************** bstart(); // Start - Bedingung einstellen schreib(147); //
Bauteiladresse senden zum Lesen d=lesen();
// Byte einlesen, Temperatur in Grad bitaus(0); //
ACK k=lesen(); // Byte einlesen (0...0,5) 1 Bit bitaus(1); //
NO ACK bstop(); //
Stop - Bedingung einstellen senden(d); break; case '2' : d=empfang(); // Port-Expander beschreiben bstart(); // Start - Bedingung einstellen schreib(64); //
Bauteiladresse senden zum Schreiben schreib(d); //
Byte schreiben (0..255) bstop(); // Stop - Bedingung einstellen break; case '3' : //
Port-Expander lesen ******************* bstart(); // Start - Bedingung einstellen schreib(65); //
Bauteiladresse senden zum Lesen d=lesen(); // Port-Expander lesen bstop(); // Stop - Bedingung einstellen senden(d); break; } goto anfang; } |
Diese Quellkodes habe ich
getestet.
Schaltplan herunterladen zum Programm. Leider
nur der Teil mit I2C-ICs, Anschlussplan für PIC16F84 bitte im Schaltplan oben
entnehmen.
In allernächster Zukunft
plane ich "I2C.c" für Taktfrequenz 20Mhz einzustellen und mit PIC16F871 testen.
Auch sind Experimente geplant mit I2C-Chipkarte 256 Byte (2 kbit) (für 1,55
Euro bei Reichelt ).