Serial
ist asynchron, es wird also kein Takt übertragen. Dementsprechend müssen sich kommunizierende Geräte vor Beginn der Kommunikation auf eine Übertragungsgeschwindigkeit einigen. Außerdem müssen beide Geräte Taktgeber haben, die ähnlich geringe Abweichungen voneinander haben - zu große Abweichungen sorgen für Fehler bei der Datenübertragung.
Außerdem können mit serieller Kommunikation nur genau zwei Geräte miteinander kommunizieren. Möchte man mit mehreren Geräten kommunizieren, benötigt man ein anderes System, z.B. I²C.
Das Inter-Integrated Circuit (I²C) Protokolll ist dazu gedacht, mehrere Geräte über einen so genannten Bus zu verbinden. Hierbei wird zwischen Peripheriegeräten (Slaves) und Controllern (Master) unterschieden. Außerdem benötigt I²C genau wie serielle Kommunikation nur zwei Kabel, um Informationen auszutauschen. Dabei benutzt I²C ein bestimmtes Protokoll, in dem jeder Slave eine fixe Adresse (eine Zahl zwischen 1 und 127, mehr dazu) hat. Der Beginn der Kommunikation geht hierbei immer vom Master aus:
- Das Senden von Daten von einem Master an einen bestimmten Slave (Mehr dazu)
- Das Abfragen von Daten vom Master von einem bestimmten Slave (Mehr dazu)
Mehr zum Protokoll kann hier nachgeschlagen werden.
Genutzt wird das I²C-Protokoll hauptsächlich von OLED-Displays und von digitalen Sensoren (z.B. Potentiometer, Temperatur-, Luftdruck- und Luftfeuchtigkeitssensoren).
Unser Ziel ist es, eine Potentiometer-Eingabe von einem Arduino (Reader-Slave) abzufragen und diese an einen anderen Arduino (Blinker-Slave) zu senden, der die OnBoard-LED mit entsprechender Verzögerung blinken lässt. Das ganze soll durch einen dritten Arduino (Master) koordiniert werden. Dazu muss etwas an Vorarbeit geleistet werden.
a) Erstellt die Schaltung unten in TinkerCad unter dem Namen Eingebettete Systeme - 09 - I2C.
b) Programmiert zunächst den Blinker so, dass er die OnBoard-LED auf Pin 13 ein mal pro Sekunde blinkt.
c) Programmiert zunächst den Reader so, dass er das Potentiometer auf Pin A3 ausliest und den Wert auf der Serial-Konsole ausgibt.
Materialliste
- 3x Arduino Uno
- linker Arduino: Master
- rechter oberer Arduino: Slave 2 (Reader)
- rechter unterer Arduino: Slave 1 (Blinker)
- 1x Breadboard
- 1x Potentiometer
Verkabelung (Siehe Schaltplan rechts)
- Schwarz: GND auf allen Arduinos verbinden
- I²C-Verbindung herstellen:
- Blau: SDA (Serial Data) → A4 auf allen Arduinos verbinden
- Gelb: SCL (Serial Clock) → A5 auf allen Arduinos verbinden
- auf dem Reader Potentiometer anschließen
- Schwarz: Linker Pin zu GND
- Rot: Rechter Pin zu +5V
- Grün: Mittlerer Pin zu A3
Programmiert nun Master, Reader und Blinker so, dass:
- Der Reader vom Master nach dem Potentiometer-Wert abgefragt werden kann
- Verwendet hierzu
onRequest
, siehe unten. - Denkt an die Kodierung auf ein einzelnes Byte, siehe unten!
- Der Blinker vom Master die Blink-Wartezeit gesendet bekommen kann
- Verwendet hierzu
onReceive
, siehe unten. - Denkt an die Kodierung von 0-255 auf 100-1000, 100ms bis 1 Sekunde Wartezeit, siehe unten!
- Der Master immer wieder einen Wert vom Reader ausliest und diesen an den Blinker sendet.
- Verwendet hierzu
requestFrom
undbeginTransmission()
/endTransmission()
, siehe unten.
- Wir kodieren einen einzelnen Potentiometer-Wert als ein einziges Byte, obwohl das Potentiometer einen Wert zwischen 0 und 1024 zurückliefert (dafür werden 2 Bytes benötigt). Grund hierfür ist, dass das Übertragen mit I²C dann leichter wird, da wir nur einzelne Bytes übertragen müssen. Die "Übersetzung" von Werten übernimmt die map-Funktion.
byteWert = map(messWert, 0, 1023, 0, 255); // Für den Reader interessant warteIntervall = map(byteWert, 0, 255, 100, 1000); // Für den Blinker interessant
- Auf der Arduino-Plattform ist die Wire-Bibliothek für die Kommunikation mit I²C zuständig.
- Ähnlich zu Serial muss die Kommunikation mit
Wire.begin()
(für den Master) bzw.Wire.begin(adresse)
(für einen Slave mit einer bestimmten Adresse) in dersetup()
-Funktion initiiert werden. - Der Master kann mit
Wire.beginTransmission(address, num)
damit beginnen,num
Bits an Daten an den Slave mit der angegebeneaddresse
zu schicken. Nach Abschluss der Übertragung muss mitWire.endTransmission()
die Übertragung beendet werden. Das Senden eines einzelnen Bytes übernimmt dieWire.write(data)
-Funktion. Der Code unten sendet die beiden Bytes 255 und 0 nacheinander vom Master an den Slave mit der Adresse 123.
void setup() { Wire.begin(); } void loop() { Wire.beginTransmission(123, 2); Wire.write(255); Wire.write(0); Wire.endTransmission(); }
- Wenn der Master
num
Bytes an Daten von einem Slave mit der Adresseadress
abfragen will, muss dieses er mitWire.requestFrom(adress, num)
initiieren. Ähnlich wie beiSerial
kann dann mitWire.available()
abgefragt werden, ob noch ein Byte zum Lesen da ist. Dieses kann mitint byte = Wire.read()
eingelesen werden. Der Code unten liest auf dem Master ein Byte vom Slave mit der Adresse 123 aus.
void setup() { Wire.begin(); } void loop() { Wire.requestFrom(123, 1); if (Wire.available()) { int byte = Wire.read(); } }
- Slaves können mit
Wire.onReceive(receive)
eine Methode definieren, die das Empfangen von Daten abhandelt, und mitWire.onRequest(request)
eine, die das Abfragen von Daten abhandelt. Der Code unten liest die vom Master gesendeten Daten inreceive()
aus und sendet die vom Master abgefragten Daten inrequest()
zurück.
void setup() { Wire.begin(123); Wire.onReceive(receive); Wire.onRequest(request); } void receive(int num) { while (Wire.available()) { int byte = Wire.read(); } } void request() { Wire.write(data); }
#include <Wire.h> const int SLAVE1_ID = 0xF1; const int SLAVE2_ID = 0xF2; int last_data = -1; void setup() { // Serial.begin(9600); Wire.begin(); } void loop() { Wire.requestFrom(SLAVE2_ID, 1); if (Wire.available()) { int data = Wire.read(); // Serial.print("Reader("); // Serial.print(data); // Serial.print(")"); if (data != last_data) { Wire.beginTransmission(SLAVE1_ID); Wire.write(data); Wire.endTransmission(); // Serial.print(" -> Blinker("); // Serial.print(data); // Serial.print(")"); last_data = data; } // Serial.println(); } delay(100); }
#include <Wire.h> const int SLAVE_ID = 0xF2; const int POT_PIN = 3; int pot_value = 1000; void setup() { // Serial.begin(9600); Wire.begin(SLAVE_ID); Wire.onRequest(request); } void loop() { pot_value = abs(analogRead(POT_PIN)); delay(100); } void request() { int data = map(pot_value, 0, 1023, 0, 255); Wire.write(data); // Serial.print("Reader("); // Serial.print(pot_value); // Serial.print(" -> "); // Serial.print(data); // Serial.println(")"); }
#include <Wire.h> const int SLAVE_ID = 0xF1; const int LED_PIN = 13; int blink_delay = 1000; void setup() { // Serial.begin(9600); pinMode(LED_PIN, OUTPUT); Wire.begin(SLAVE_ID); Wire.onReceive(receive); } void loop() { digitalWrite(LED_PIN, HIGH); delay(blink_delay); digitalWrite(LED_PIN, LOW); delay(blink_delay); } void receive(int numBytes) { while (Wire.available()) { int data = Wire.read(); blink_delay = map(data, 0, 255, 100, 1000); // Serial.print("Blinker("); // Serial.print(data); // Serial.print(" -> "); // Serial.print(blink_delay); // Serial.println(")"); } }
Serial
zum Debuggen)Reader(255) -> Blinker(255) Reader(255) Reader(255) Reader(255) Reader(255) Reader(255) Reader(255) Reader(255) Reader(234) -> Blinker(234) Reader(219) -> Blinker(219) Reader(209) -> Blinker(209) Reader(178) -> Blinker(178) Reader(142) -> Blinker(142) Reader(142) Reader(101) -> Blinker(101) Reader(96) -> Blinker(96) Reader(96) Reader(137) -> Blinker(137) Reader(137) Reader(158) -> Blinker(158) Reader(209) -> Blinker(209) Reader(209) Reader(209)
Reader(1023 -> 255) Reader(1023 -> 255) Reader(1023 -> 255) Reader(1023 -> 255) Reader(1023 -> 255) Reader(1023 -> 255) Reader(1023 -> 255) Reader(1023 -> 255) Reader(941 -> 234) Reader(880 -> 219) Reader(839 -> 209) Reader(716 -> 178) Reader(573 -> 142) Reader(573 -> 142) Reader(409 -> 101) Reader(389 -> 96) Reader(389 -> 96) Reader(552 -> 137) Reader(552 -> 137) Reader(634 -> 158) Reader(839 -> 209) Reader(839 -> 209) Reader(839 -> 209)
Blinker(255 -> 1000) Blinker(234 -> 925) Blinker(219 -> 872) Blinker(209 -> 837) Blinker(178 -> 728) Blinker(142 -> 601) Blinker(101 -> 456) Blinker(96 -> 438) Blinker(137 -> 583) Blinker(158 -> 657) Blinker(209 -> 837)
Erweitert die Schaltung, indem ihr auf TinkerCad eine Kopie unter Eingebettete Systeme - 10 - I2C (eigene Schaltung) anlegt.
Ideen, die ihr umsetzen könntet:
- Statt des Potentiometers könnte auf dem Reader ein Button abgefragt werden, der die LED auf dem Blinker kontrolliert.
- Es könnte mehrere Blinker geben.
- … Denkt euch was aus!