3.7 I2C Schnittstelle
[MezData: Links-I2C] [Portexpander HLF 8574] [Temperatursensor LM75A] [Druck- und Temperatursensor BMP280] [MezData: Discovery-Room][stm32-I2C-Lib][Elektrische Gedanken]
Einlesen in die Theorie der Schnittstelle
- Mit 2 Leitungen viele Komponenten anschließen. Lesen: [Wikipedia: I2C]
- Wozu und wer hat I2C-Schnittstelle “erfunden”?
- Wie lauten die Bezeichnungen der Bus-Leitungen und wofür stehen die Abkürzungen?
- Zeichnen Sie ein Blockschaltbild mit einem Controller und zwei Targets.
- Erklären Sie OpenCollector- bzw. OpenDrain-Ausgänge und warum PullUp-Widerstände nötig sind.
- Was ist ein “Wired-AND”?
- Bei welchen Zustand von SCL werden die Daten von SDA übernommen?
- Arduino-Wire-Operationen
Mit Portexpander HLF8574T zwei LED über I2C im Wechsel leuchten lassen
Die LED-Anoden sind VDD (5V) verbunden. P1 und P0 des PCF8574-Moduls sind über 330Ω Widerstände an die Kathoden angeschlossen. Somit leuchten die LED, wenn an den P-Ausgängen eine 0 anliegt. Hintergrund: Die Ausgänge des HLF8574T liefern nur kurz bei einem Schreibvorgang höhere Ströme und sonst nur 100µA bei log. 1 (siehe Datenblatt HLF8574T). An den Bus können mehrere Targets angeschlossen werden. Damit sie unterscheidbar sind bekommt jedes Target eine eigene Adresse [RN-Wissen: I2C-Adressierung]. Bei unserem Modul sind die Bit A6..A3 fest (0b0100000 = 0x20). Mit den Jumpern (die blauen Dinger unter A2) können die Bit A2..0 von uns eingestellt werden. Somit ist für uns die Targetadresse von 0b0100000 = 0x20 bis 0b0100111 = 0x27 wählbar. Zunächst sei sie 0x20.

#include <Wire.h> // I2C-Library
#define ADRESSE 0x20 // Adresse des Bausteins im I2C-Bus, siehe Vorgabe des Herstellers
void setup() {
Wire.begin(); //Alternatives PinMapping: Wire.begin(SDA, SCL);
}
void loop() {
Wire.beginTransmission(ADRESSE); // beginnt mit dem Slave-Gerät zu sprechen
Wire.write (0xfe); // 0b1111 1110 übertragen: Rote LED soll leuchten
Wire.endTransmission(); // hört auf, mit dem Gerät zu sprechen
delay(500);
Wire.beginTransmission(ADRESSE); // beginnt mit dem Slave-Gerät zu sprechen
Wire.write (0xfd); // 0b1111 1101 übertragen: Grüne LED soll leuchten
Wire.endTransmission(); // hört auf, mit dem Gerät zu sprechen
delay(500);
}
Bei der Übertragung mit Oszilloskop zuschauen

Die Geschichte zu dem Bild: SCL und SDA sind high. Ein Master will senden und zieht SDA auf GND, dies ist die Startbedingung. Um A6 zu übertragen zieht der Master SCL auf GND und legt den Wert von A6 auf SDA. Dann lässt er SCL los und dessen Pegel geht durch den PullUp-Widerstand auf high, jetzt wissen die Slaves, dass das Datenbit auf SDA gültig ist und übernehmen A6. Das Spiel wiederholt sich bis A0 und R/!W-Bit. Wenn bei einem Slave die gewünschte Adresse mit der eingestellen Adresse übereinstimmt zieht er die SDA-Leitung auf GND und gibt sein Ack=OK: Ich bin bereit für Daten. Der Master sendet nun 8 Bit Daten, der Slave quittiert dies mit weiteren Ack=OK Antworten. Hier wurde nur ein Byte zum Slave übertragen. Am Ende lässt der Master SCL und dann SDA los, damit zeigt er das Ende der Übertragung an.
Im nächsten Bild wird die Übertragung für grüne LED leuchten lassen gezeigt.

Was passiert, wenn kein Slave antwortet? Im Programm die Adresse von 0x20 auf 0x21 geändert. Kein Modul fühlt sich nun angesprochen und gibt sein Ack=OK:

Wird nun am Modul A0 auf + gejumpert fühlt es sich wieder angesprochen:

I2C-LCD ansteuern
Auf unserem Multifunction-Board befindet sich ein LCD-Modul, das über I2C angesprochen wird, leider an PA11 und PA12, die kein I2C können, daher müssen Drahtbrücken gesetzt werden.
Wenn man das LCD-Modul umdreht findet sich ein Bekannter, der Portexpander HLF8574T. Was macht der da? Tatsächlich sind die meisten preiswerten LCD-Module mit dem Chip HD44780 ausgestattet und der braucht mindestens 6 Leitungen. Daher wird zum sparen gerne ein I2C-Port-Expander verwendet [BLIT2008-Board-LCD]. Beim LCD-Modul gibt es keine Jumper sondern Lötbrücken gegen GND. Sind sie alle offen ist die Target-Adresse 0x27.
Es gibt einen Unterschied bei der Festadresse zwischen HLF8574T und HLF8574A:
HLF8574T: 0x20 HLF8574A: 0x38
Um auf dem LCD was auszugeben gibt es praktische Librarys:
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_PCF8574.h>
LiquidCrystal_PCF8574 lcd (0x27); // LCD-Adresse auf 0x27
void setup() {
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.print("MezMedia.de");
}
void loop() {
}

Den I2C-Bus abscannen um alle Targets zu finden
Oft gibt es das Problem, dass man ein Target verbindet, es aber scheinbar nicht antwortet. Hier ein kurzes Programm, dass alle Adressen abscannt und alle Targets auflistet. Modifizierter Code von dieser Seite: [i2c_scanner]
#include <Wire.h>
void setup(){
Wire.begin();
Serial.begin(9600);
Serial.println("\nI2C Scanner");
}
void loop(){
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++ ){
Wire.beginTransmission(address); // Target anfunken
error = Wire.endTransmission(); // hat es geantwortet?
if (error == 0){ // kein Fehler also Antwort
Serial.print("I2C Target gefunden bei Adresse 0x");
if (address<16) Serial.print("0");
Serial.println(address,HEX);
nDevices++;
}
else if (error==4){ // Fehler
Serial.print("Unbekannter Fehler bei Adresse 0x");
if (address<16) Serial.print("0");
Serial.println(address,HEX);
}
}
if (nDevices == 0) Serial.println("Keine I2C Targets gefunden\n");
else Serial.println("fertig\n");
delay(5000);
}
Temperatur mit LM75A messen und auf LCD ausgeben
Nun habe ich die Formelsammlung gelesen und darin den LM75 entdeckt, dabei soll ein Byte übertragen und als Antwort zwei Byte empfangen werden. Also so ein Modul, allerdings mit einen LM75A bestellt: [amazon]
Angeschossen, den Scanner angeworfen und unter Adresse 0x48 antwortet es.
Allerdings wenn man auf der Unterseite mit dem Finger über die Kontakte für die einstellbare Adresse kommt ändert sie sich, die Eingänge A2..A0 hingen in der “Luft”. Ok, mit dem Lötkolben Lötbrücken setzen. Aus Neugier habe ich geprüft, ob die VCC-Kontakte der Brücken mit VCC verbunden sind: Leider nein, untereinander haben sie zwar Kontakt, aber eine Verbindung zu VCC konnte ich nicht feststellen. Die GNDs sind miteinander verbunden. Falls eine andere Adresse als 0x48 eingestellt werden sollte würde es mich nicht wundern, wenn es spinnt…
Gut zu erkennen sind die PullUp-Widerstände von 10kΩ für SDA und SCL auf der Platine.



Also den LM75A ohne Library selber auslesen können, damit wir uns intensiver mit I2C-Kommunikation beschäftigen -RTFM: [Temperatursensor LM75A]
In der Forsa wird der LM75 dokumentiert, der kann nur 9 Bit Auflösung der Temperatur, beim LM75A sind es 11 Bit.
Der LM75 hat interne Register?
Beim HLF8574 war es einfach, es gab nur ein 1 Byte Register, das gelesen oder beschrieben werden konnte: Target-Adresse auswählen und 1 Byte transferieren.
Beim LM75 gibt es 4 interne Register. Ein “Pointer”-Register merkt sich welches dieser Register gemeint ist. Beim Schreiben muss immer der Pointerwert für Zielregister 0..3 gesendet werden. Beim Lesen wird einfach das zuletzt eingestellte Register verwendet. Will man ein anderes Register lesen muss zuerst der Pointer geändert werden. Steht alles mit Beispielen in der Doku. Muss ich das alles wissen um einfach die Temperatur auslesen zu können? Nein!
Voreingestellt ist Register 0 und dort steht in zwei Bytes die Temperatur: Zwei Bytes lesen und gut ist. Hier die Kodierung der Temperatur in den zwei Bytes:
MSByte | LSByte | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
LM75 | D8 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | x | x | x | x | x | x | x |
LM75A | D10 | D9 | D8 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | x | x | x | x | x |
Es ist total einfach: Im MSByte steht die Temperatur mit Vorzeichen (Bit 7) in °C, will man es genauer haben kriegt man beim LM75 noch die halben Grade im LSByte und beim LM75A die achtel Grade geliefert. Hier mein Test-Programm:
#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
#define ADRESSE 0x48 //0b01001000
LiquidCrystal_PCF8574 lcd (0x27);
char byteH, byteL;
float temp;
void setup() {
Wire.begin(); //Alternatives PinMapping: Wire.begin(SDA, SCL);
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.print("LM75A TempSensor");
}
void loop() {
/*Wire.beginTransmission(ADRESSE); // Nicht notwendig, wenn man am Pointer nix verstellt hat
Wire.write(0x00); // Pointer auf 0 setzen um Temp auslesen zu können
Wire.endTransmission();
*/
Wire.requestFrom(ADRESSE, 2); // 2 Bytes lesen
Wire.available(); // MSByte
byteH = Wire.read(); // receive a byte
Wire.available(); // LSByte
byteL = Wire.read(); // receive a byte
Wire.endTransmission();
temp=byteH; // einfach nur Grad Celsius
//temp = ((byteH<<3) + (byteL>>5))*0.125; // LM75A mit 0.125 Grad Auflösung
lcd.setCursor(0, 1);
lcd.print(temp);
delay(1000);
}
Bei 0.125 °C Auflösung ist ein wenig Bit-Shift-Woodo notwendig 😎.
Hab noch ne Dose Kältespray gefunden und siehe da: Negative Temperaturen stimmen nicht! Ok also Hirn ganz einschalten:
#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
#define ADRESSE 0x48//0x92
LiquidCrystal_PCF8574 lcd (0x27);
signed char byteH; // wichtig ist das signed!
char byteL; // signed führt zu Fehler
float temp;
void setup() {
Wire.begin(); //Alternatives PinMapping: Wire.begin(SDA, SCL);
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
lcd.print("LM75A TempSensor");
}
void loop() {
Wire.requestFrom(ADRESSE, 2); // 2 Bytes lesen
Wire.available(); // MSByte
byteH = Wire.read(); // receive a byte
Wire.available(); // LSByte
byteL = Wire.read(); // receive a byte
Wire.endTransmission();
//byteH=0b11111111; // zum Testen ob -0.125 laut Doku rauskommt
//byteL=0b11100000;
temp = ((byteH<<3) + (byteL>>5))*0.125; // volle Auflösung bitte
//temp=byteH;
lcd.setCursor(0, 1);
lcd.print(temp);
delay(1000);
}
Zum Kältespray: Von wegen bis -50°C kam auf minimal -36 °C.
Zum Code: Ich hab an den Datentypen rumgespielt: char byteH ist offensichtlich unsigned, erst als ich signed davor schrieb ging es in die Negativen. byteL darf nicht signed sein, sonst stimmt der Wert für -0.125 °C nicht. Spannend wäre nun den Code mit anderen µC zu testen..
Luftdruck und Temperatursensor BPM280
Siehe –>3.5 Sensoren. Der Lerneffekt bzgl. I2C-Bus ist dabei nicht besonders hoch, der Sensor ist komplex und es wird auch geraten die Library dafür zu verwenden.
Für ein Projekt, das sich mit dem Sensor beschäftigt aber brauchbar.
Aufgaben und abprüfbares Wissen
Sobald man alte und einfache Module verlässt wird es kompliziert, Details werden in die Nutzung von Library zu den Sensoren verlagert.
Falls man eigene Targets entwickelt siehe MezData: Discovery-Game sieht es schon spannender aus.
Schnipsel und ToDo
- Liste von Fragen zu I2C-Bus erstellen.
- Grösse von PullUp und Serienwiderstand -> Link. Wann müssen wir uns um die PullUps selber kümmern?
- ¡Selbst gebaute Targets siehe Discovery-Game! Event-Programmierung bei Bus-Action.
- Alternative Ports für I2C verwenden.