Nach Hause telefonieren ... mit dem Arduino

Der Pragmatische Architekt  –  0 Kommentare

Bisher kamen in der vorliegenden Serie Elektronikgrundlagen und Schaltungen mit Sensoren und Aktoren zur Sprache. Das verwendete Microcontroller-Board erlebte nur ein Inseldasein und war allenfalls über USB mit dem Host verbunden. Der vorliegende Beitrag lässt diese Beschränkung fallen. In dieser und den nächsten Folgen behandelt die Serie unterschiedliche Kommunikationsoptionen für Arduino-Boards.

Internet der Dinge bedeutet Kommunikation: Kommunikation zwischen Geräten, Kommunikation zwischen Geräten und Cloud-Diensten und Interaktion zwischen Geräten und Menschen. Wie aber kann sich Mensch, Mobilgerät oder PC mit dem Mikrocontroller unterhalten? Anders ausgedrückt: Wie kann ein Arduino-Board nach Hause telefonieren oder mit anderen Maschinen chatten?

Im vorliegenden Artikel stehen Ethernet-Shields im Vordergrund. In weiteren Folgen kommen auch WiFi und Bluetooth zur Sprache. Außerdem beschäftigen wir uns mit unterschiedlichen anwendungsspezifischen Kommunikationsprotokollen wie zum MQTT. Wir legen damit den Grundstein, um unsere IoT-Geräte mit dem Internet zu verbinden.

Bevor wir mit praktischen Beispielen starten, ist ein kleiner Ausflug in die Welt der Bus-Systeme notwendig. Ethernet-Shields kommunizieren mit dem Arduino über den SPI-Bus.

SPI – Serial Peripheral Interface

Bereits in früheren Folgen haben wir Bussysteme wie I2C und 1-Wire kennengelernt Über I2C erfolgte beispielsweise der Anschluss eines LCD-Displays, über 1-Wire kommunizierten der Temperatursensor DS18B20 und das Mikrocontroller-System miteinander. Doch es gibt noch weitere praxisrelevante Bussysteme.

Ein sehr verbreitetes Bussystem nennt sich SPI (Serial Peripheral Interface), stammt ursprünglich vom Motorola und dient zur synchronen Vollduplex-Kommunikation zwischen einem (!) Master und seinen Slaves. Zu den Slaves können zum Beispiel ein SD-Cardreader, Displays oder Kommunikationskomponenten gehören. Sie fragen sich eventuell, warum wir dafür nicht ebenfalls I2C verwenden. Die Antwort ist kurz. SPI-Kommunikation ist schlicht schneller als die über einen I2C-Bus.

Typische SPI-Konfiguration mit einem Master und drei Slaves (Bild: wikipedia.org)
Vier Leitungen

Wie aber funktioniert SPI im Detail? Eine charakteristische Eigenschaft von SPI besteht darin, dass das Protokoll vier Leitungen spezifiziert:

  • Bei asynchroner Kommunikation können Master und Slave unterschiedliche Taktungen haben. Beim synchronen Protokoll SPI hingegen gibt der Master den Takt über den SCLK-Ausgang (Serial Clock) vor.
  • Über individuelle SS-Leitungen (Slave Select) klopft er bei demjenigen Slave an, mit dem er zu kooperieren wünscht.
  • Über MOSI (Master Output, Slave Input) spricht der Master mit seinen Slaves.
  • Über MISO (Master Input, Slave Output) kann ein Slave mit seinem Master sprechen. MISO-Ausgänge von Slaves sind als Tri-State-Ausgänge ausgelegt, weshalb ihr MISO-Signal hohe Impedanz besitzt, sobald SS kein Signal trägt
Kommunikation von Master und Slave mit Hilfe von Shiftregistern (Bild: wikipedia.org)

Kurzer Prozess

Zu Beginn jeder SPI-Kommunikation konfiguriert der Master die Taktung so, dass der Slave mit dieser Frequenz arbeiten kann. Dann selektiert der Master das gewünschte Gerät über ein Low-Signal. Sollte eine Wartezeit erforderlich sein, wartet der Master die vorgegebene Zeit ab, bevor er die Kommunikation initiiert.

SPI überträgt Daten bitweise in Vollduplex. Sendeeinheit sind Bytes oder Maschinenworte. Der Master sendet Bit für Bit über seinen MOSI-Ausgang, wo der Slave die Daten entgegennimmt. Parallel dazu verschickt der Slave Bits über seinen MISO-Ausgang, die seinerseits der Master empfängt.

Diese bidirektionale Kommunikation findet selbst dann statt, wenn Nutzdaten nur in eine Richtung transportiert werden. Jeder Kommunikationspartner enthält zwei 8-Bit-Shiftregister, eines zum Versenden, das andere zum Empfangen von Daten.

SPI-Polaritäten und Phasen

Damit sich Master und Slave erfolgreich verständigen können, bedarf es einer Festlegung nicht nur der Taktfrequenz, sondern auch der verwendeten Polarität (CPOL) und Phase (CPHA) des Takts.

Von der Polarität hängt ab, welcher Signal-Wert den aktiven und welcher den inaktiven Zustand repräsentiert. Von der Phase hängt ab, ob bei steigender Flanke gelesen und bei fallender Flanke geschrieben wird, oder umgekehrt.

Timing Diagram mit Polaritäten und Phasen (Bild: wikipedia.org)
  • Gilt CPOL = 0, ist der aktive Zustand 1, und der Idle-Zustand 0.
    • Phase 0 => Daten lesen bei steigender Flanke, schreiben bei fallender Flanke.
    • Phase 1 => Daten lesen bei fallender Flanke, schreiben bei steigender Flanke.
  • Gilt GPOL = 1, ist der aktive Zustand 0, und der Idle-Zustand 1.
    • Phase 0 => Daten lesen bei fallender Flanke, schreiben bei steigender Flanke.
    • Phase 1 => Daten lesen bei steigender Flanke, schreiben bei fallender Flanke.

Zuordnung der Arduino Pins

Pin-Zuordungen für SPI bei Arduino Boards (Bild: arduino.cc)

Die Preisfrage lautet, wo sich bei den einzelnen Arduino-Boards die genannten SPI-Pins tatsächlich befinden. Eine SPI-Seite auf Arduino.cc gibt darüber Aufschluss. Sie finden dort zumindest die Belegungen für die Boards von arduino.cc. Bei Nachbauten und anderen Boards sollten Sie die Dokumentation des Herstellers konsultieren.

Was ist ein UART

Offensichtlich gibt es innerhalb Microcontroller-Boards sowohl serielle als auch parallele Kommunikation.

  • Parallele Kommunikation ist schnell, funktioniert aber nur über kurze Strecken.
  • Serielle Kommunikation funktioniert über längere Strecken, ist dafür deutlich langsamer.

Wir benötigen eine Schnittstelle, die beide Varianten zusammenführt.

Der Universal Asynchronous Receiver/Transmitter hat die Aufgabe, Daten zwischen serieller und paralleler Kommunikation zu übersetzen. Beispielsweise kommen über eine parallele Schnittstelle Bytes an, die der Sender-UART zerlegt und über eine serielle Leitung Bit für Bit an den Empfänger weiterverschickt, wo ein Empfänger-UART die Bits wieder zu Bytes zusammensetzt. Als wichtigste Ingredienz enthält das UART Shiftregister – tatsächlich sind es zwei. Wir hatten im Beitrag über LEDs gelernt, wie das Shiftregister 74HC595 ein Byte empfängt und an seinen Ausgängen bitweise zur Verfügung stellt. Die serielle Kommunikation zwischen UARTs wird mittels Start- und Stopbits synchronisiert. Sie kann im Vollduplex- oder Simplex-Modus erfolgen.

Ein UART ist nicht für die Logik-Level der angeschlossenen Komponenten verantwortlich. Um das spannungsabhängige Signalisieren kümmern sich stattdessen Standards wie RS-232, RS-422 und RS-485.

Arduino Ethernet Shield

Das Arduino-Ethernet-Shield in seiner aktuellen Version basiert auf dem W5100-Chip von Wiznet. Die wichtigsten Leistungsdaten sind schnell aufgezählt. Der Baustein bietet bis 100 MByte/s Datenübertragung, unterstützt die Standards IEEE 802.3/10BASE-T, IEEE 802.3u/100BASE-TX besitzt einen internen Puffer von 16 KBytes und erlaubt bis zu vier parallele Socketverbindungen.

Um das Ethernet-Shield anzusteuern, brauchen wir eine entsprechende Bibliothek. Die Arduino IDE bringt bereits eine entsprechende Bibliothek namens Ethernet mit.

Wir fangen mit einem kleinen Sketch an, der lediglich eine Ethernet-Verbindung öffnet. Sollte sich auf Ihrem Ethernet-Shield ein Aufkleber mit einer eindeutigen MAC-Adresse befinden, setzen Sie im unteren Sketch diese Adresse ein. Gibt es eine solche Information jedoch nicht, erfinden Sie eine MAC-Adresse. Am einfachsten ist es natürlich, wenn Sie die MAC-Adresse im Sketch unverändert lassen. Die Gefahr, dass sich in Ihrem Netz ein Gerät mit der gleichen MAC-Adresse befindet, ist ohnehin vernachlässigbar gering.

Das Programm versucht, über DHCP eine dynamische IP-Adresse zu erhalten, und gibt im Fehlerfall entsprechende Information aus. Falls Sie mit statischen Adressen arbeiten, verfahren Sie so, wie im Listing angegeben.

///////////////////////////////////////////////////////////////
//
// Arduino-Sketch: Über Shield an Ethernet anmelden
// a) mit statischer Adresse, oder
// b) mit dynamischer Adresse über DHCP
//
///////////////////////////////////////////////////////////////

#include <SPI.h>
#include <Ethernet.h>

///////////////////////////////////////////////////////////////
// Wir benötigen eine MAC-Adresse.
// Sofern das Ethernet Shield keine Adresse
// (meist als aufgeklebter Sticker)
// besitzt, müssen wir unsere eigene erfinden:
///////////////////////////////////////////////////////////////

byte mac[] = {
0x00, 0xCC, 0xBB, 0xAA, 0xDE, 0x02
};


// EthernetClient: API zum Zugriff auf das Ethernet Shield
EthernetClient client;

///////////////////////////////////////////////////////////////
// Der Sketch geht von dynamischen IP-Adressen per DHCP aus.
// Alternativ wäre die Angabe einer statischen Adresse möglich.
// Ersetzen Sie die nachfolgenden Angaben durch die Ihrer
// Systemumgebung:
//
// IPAddress ip(192, 168, 178, 72);
// IPAddress dns(192,168,178, 1);
// IPAddress gateway(192, 168, 178, 1);
// IPAddress subnet(255, 255, 0, 0);
// Ethernet.begin(mac, ip, dns, gateway, subnet);
//
// Ersetzen Sie ggf.in der Methode setup()
// das if-Statement durch
//
// if (Ethernet.begin(mac) == 0) {
// Ethernet.begin(mac, ip, dns, gateway, subnet);
// }
//
///////////////////////////////////////////////////////////////

void setup() {
Serial.begin(9600); // Serielle Verbindung öffnen

// Ethernet-Verbindung öffnen und DHCP um IP-Adresse bitten:
if (Ethernet.begin(mac) == 0) {
Serial.println("IP-Adressanfrage über DHCP fehlgeschlagen");

while (true) {} // Unendliche Warteschleife
}

// Adresse über seriellen Port ausgeben:
Serial.print("Meine IP Adresse lautet: ");
Serial.println(Ethernet.localIP());
}

void loop() {
//////////////////////////////////////////////////////////////
// IP-Adressen werden von DHCP nur bis zu einer
// "Haltbarkeitsfrist" vergeben. Daher ist nach
// einer bestimmten Zeit ein Neuabonnement nötig:
//////////////////////////////////////////////////////////////

switch (Ethernet.maintain())
{
case 1:
Serial.println("Fehler: Adresserneuerung abgelehnt");
break;
case 2:
Serial.println("Adresserneuerung erfolgreich");
break;
case 3:
Serial.println("Fehler: Rebinding fehlgeschlagen");
break;
case 4:
Serial.println("Rebinding erfolgreich");
break;
default: // Nichts ist passiert
break;
}
}

Arduino spielt Webserver

Die einfachste Kommunikationsvariante, um mit dem Arduino über das Internet zu kommunizieren, ist HTTP. Der Benutzer wählt eine bestimmte Webseite an, worauf das Arduino-Board mit der Rückmeldung der aktuellen Sensordaten reagiert. Dazu müsste allerdings der Arduino als Webserver in Erscheinung treten. Zum Glück fällt es mit der Ethernet-Bibliothek nicht schwer, genau das in die Tat
umzusetzen.

///////////////////////////////////////////////////////////////
//
// Arduino-Sketch: Über Shield an Ethernet anmelden
// und als Webserver arbeiten.
// Client Nutzung: Browser-Seite http://<serverIP>:80 gehen
//
////////////////////////////////////////////////////////////////

#include <SPI.h> // Serial Peripheral Interface
#include <Ethernet.h> // Ethernet Shield

// Geben Sie hier die MAC-Adresse ihres Ethernet-Shields ein
byte mac[] = {
0xDE, 0xAA, 0xBB, 0xCC, 0xDD, 0xED
};

//////////////////////////////////////////////////////////////
// Nutzung der statischen IP-Adresse. Geben Sie hier
// Ihre eigene statische IP-Adresse ein.
// Alternative wäre dynamische IP-Adresse über DHCP
//////////////////////////////////////////////////////////////
IPAddress serverIP(192, 168, 178, 72);

//////////////////////////////////////////////////////////////
// Der Sketch implementiert einen Server
// mit der serverIP und Port 80:
//////////////////////////////////////////////////////////////
EthernetServer server(80);

void setup() {
// Seriellen Monitor starten
Serial.begin(9600);

// Server mit MAC und IP initialisieren:
Ethernet.begin(mac, serverIP);
server.begin();
Serial.print("Server gestartet. IP Adresse: ");
Serial.println(Ethernet.localIP());
}

//////////////////////////////////////////////////////////////
// Ereignisschleife des Arduino-basierten Webservers
//////////////////////////////////////////////////////////////
void loop() {
// Warten auf einen Client:
EthernetClient client = server.available();
if (client) {
Serial.println("Neue Verbindung");

// Eine leere Zeile markiert das Ende des HTTP Request:
boolean endOfRequestReached = true;

// Solange die Verbindung steht, werden Daten vom Client gelesen:
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
// Beim Erreichen des Endes des HTTP Request
// können wir die Antwort zusammenbauen.
       if (c == '\n' && endOfRequestReached) {
// Versenden der Antwort:
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
         // Es ist notwendig, die Verbindung zu kappen:
client.println("Connection: close");
          // Alle 10 Sekunden soll ein Refresh stattfinden:
client.println("Refresh: 10");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
         //  Wir geben einen Zufallswert von A0 zurück:
int val = analogRead(A0);
client.print("Analoger Port A0 ");
client.print("hat den Wert: ");
client.print(val);
client.println("<br />");
client.println("</html>");
}
endOfRequestReached = (c == '\n') || (c == '\r');
}
}

Laden Sie den Sketch auf Ihren Arduino, öffnen Sie Ihren Browser und geben unteres als URL ein (serverIP ist die vom Sketch über den seriellen Monitor ausgegebene IP-Adresse):

      http://serverIP:80

Sie sollten dann ähnliche Ausgaben wie die folgende zu sehen bekommen:

Analoger Port A0 hat den Wert: 434
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 390
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 322
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 314
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 286
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 395
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 296
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 276
HTTP/1.1 200 OK Content-Type: text/html Connection: close Refresh: 10 Analoger Port A0 hat den Wert: 432

Fazit

In dieser Folge haben wir begonnen, das Ethernet-Shield genauer zu betrachten. Zur Sprache kam die systemnahe Kommunikation über TCP/IP und HTTP. Des Weiteren beschränkten sich die Beispiele auf 1:1-Kommunikation. Damit lässt sich zwar schon einiges auf die Beine stellen, aber für IoT-Anwendungen wären anwendungsnahe Protokolle von Vorteil. Um ein entsprechendes Beispiel namens MQTT geht es in der nächsten Folge.