Kommunikativer BanziMat

Arduino Yún – So gelingt der Einstieg ins „Cloud-Controlling”
Kommentare

Wer mit dem Arduino-Erfinder Massimo Banzi näheren Kontakt pflegt, beschreibt ihn unter der Hand als unkommunikativen Zeitgenossen. Seine neueste Kreation zeigt sich von einer besseren Seite: Dank eines eingebauten WiFi-Moduls nimmt der Mikrocontroller Arduino Yún bereitwillig Kontakt mit Smartphones, Tablets und Workstations auf.

Die in Arduinos für die Steuerung eingesetzten AVR-Mikrocontroller sind mit dem Handling von WLAN völlig überfordert. Zwecks Gewährleistung der Abwärtskompatibilität ist der Yún als „Zweichip-Planare“ aufgebaut: Ein unter Linux laufender ARM-Prozessor handhabt das WLAN-Modul, während ein dedizierter AVR für das Messen, Steuern und Regeln zuständig ist.

Ein frisch gekaufter Arduino Yún spannt ein WLAN-Netzwerk auf. Wer seine Workstation mit dem Netz verbindet, kann den Einplatinencomputer über einen Browser seiner Wahl konfigurieren. Die dazu notwendige Webseite lässt sich durch Eingeben der URL 192.168.240.1 aufrufen. Falls Ihr Yún einen Vorbesitzer hatte und Ihr WLAN nicht erkennt, so drücken Sie rund zehn Sekunden lang einen Button am Yún, der dafür sorgt, dass alle am Gerät befindlichen Netzwerkkonfigurationen verworfen werden.

Die Programmierung kann sowohl über ein Micro-USB-Kabel als auch per WLAN erfolgen. In der Praxis erweist sich die Arbeit mit einem Kabel als „friedfertiger“ – manche aus dem Internet übernommene Sketches haben bei Installation per WLAN Probleme.

Sprechen Sie Brücke?

Für die Kommunikation zwischen den beiden Systemen ist eine als Bridge bezeichnete Software zuständig. HTTP hat sich im Laufe der Jahre als Quasistandard in Sachen Kommunikation zwischen Geräten etabliert. Der Yún trägt diesem Aspekt durch in der Bridge implementierte Hilfsfunktionen Rechnung, die die Kommunikation zwischen AVR und Rechner über HTTP realisieren. Clients können Werte in den Yún schreiben, dieser kann andere Werte über ein Webinterface zum Abruf bereitstellen.

Wir wollen dies zur Erstellung eines kleinen Beleuchtungstrackers nutzen. Als Erfassungselement dient ein als LDR bezeichneter Widerstand: Er reduziert seinen Widerstand mit steigender Helligkeit. Das vergleichsweise preiswerte Bauteil arbeitet langsam (Reaktionszeit im Sekundenbereich), ist für unser vorliegendes Beispiel aber aufgrund seiner Einfachkeit gut geeignet. Schneller arbeitende Fototransistoren würden eine komplexere externe Schaltung erfordern.

In unserem Fall genügt es, den LDR wie in Abbildung 1, gezeigt zur Realisierung eines Spannungsteilers zu nutzen. Der genaue Wert des Vorwiderstands ist von Ihrem LDR abhängig: Sie sollten einen möglichst hohen „Spannungshub“ ermöglichen, um die Genauigkeit des AD-Wandlers besser auszunutzen.

Abb. 1: Der AD-Wandler liest die am LDR abfallende Spannung ein.

Abb. 1: Der AD-Wandler liest die am LDR abfallende Spannung ein.

Auf Softwareseite beginnen wir mit der Initialisierung der notwendigen Komponenten wie in Listing 1.

Listing 1
#include <Bridge.h>
#include <YúnServer.h>
#include <YúnClient.h>

YúnServer server;

void setup() 
{
  pinMode(13,OUTPUT);
  digitalWrite(13, LOW);
  Bridge.begin();
  digitalWrite(13, HIGH);
  server.listenOnLocalhost();
  server.begin();
}

Als „braver“ Arduino bringt der Yún eine LED mit, die am Port 13 hängt. Da das durch Aufrufen von Bridge.begin() eingeleitete Aufbauen einer Verbindung zwischen den beiden Prozessoren rund drei Sekunden in Anspruch nimmt, nutzen wir die LED zur Anzeige des Verbindungszustands. Die im nächsten Schritt folgenden Befehle weisen das Serverobjekt dazu an, einen HTTP-Server am Yún bereitzustellen.

In der regelmäßig ausgeführten Funktion loop fragen wir den Server, ob sich ein Client mit ihm verbunden hat. Wenn dies der Fall ist, so wird im nächsten Schritt die Methode process() ausgeführt. Sie bekommt als Parameter das Clientobjekt übergeben, das für die weitere Interaktion mit dem verbundenen Gerät zuständig ist (Listing 2).

Listing 2
void loop() {
  curVal=analogRead(A0);
  
  YúnClient client = server.accept();
  if (client) {
    process(client);
    client.stop();
  }
}

onClientConnected() beginnt mit der Ermittlung der im Rahmen des Verbindungsaufbaus übergebenen URL. Von Haus aus reagiert der in die Bridge integrierte „Server“ nur auf jene Anfragen, die sich auf den REST-Endpoint /arduino/ beziehen.

Wir stellen durch Nutzung der Stringbearbeitungsroutinen fest, ob der Benutzer http://arduino-ip/arduino/getLight eingegeben hat. Wenn dies der Fall ist, so folgt das Ausgeben der vom LDR angelieferten Helligkeitswerte. Diese erscheinen „direkt“ in der Webseite – sie könnten stattdessen natürlich auch etwas Mark-Up wie in Listin 3 zurückgeben.

Listing 3
void process(YúnClient aClient)
{
  String whatYouSay = aClient.readStringUntil('/');
  aClient.print("Hello Arduino!\n");
  aClient.print(whatYouSay);
  if (whatYouSay == "gettemp") 
  {
    aClient.print("Temp:");
    aClient.print(curVal);
  }
}

Nach getaner Arbeit folgt dann noch ein Aufruf von stop(), um die Verbindung zu beenden. Zur Reduktion des Stromverbrauchs kann es sinnvoll sein, loop() nach jedem Durchlauf 50 Millisekunden lang anzuhalten – wir gehen hier nicht weiter darauf ein.

REST ist Böse

Aus der Logik folgt, dass Sie die gerade am Arduino anliegende Temperatur durch Eingabe von http://<IP-Adresse>/arduino/gettemp in einem Webbrowser abrufen können. In der Praxis funktioniert dies auch nach dem Deaktivieren des REST-Passworts nicht: Der Grund ist in Abbildung 2 gezeigt.

Abb. 2: Internet Explorer zeigt den Grund für das seltsame Verhalten in der Codeansicht

Abb. 2: Internet Explorer zeigt den Grund für das seltsame Verhalten in der Codeansicht

Der einfachste Weg zur Lösung dieses Problems besteht darin, einen weiteren Parameter an die URL anzuhängen. Wer http://<IP-Adresse>/arduino/gettemp/x aufruft, sorgt dafür, dass gettemp ohne Return-Zeichen ausgeliefert wird.

Linuxoide Spielereien

Das Ansprechen per HTTP ist für komplexere Systeme nur eingeschränkt geeignet, denn wer die für das Funktionieren eines Prozessrechners notwendige Intelligenz am Client realisiert, bittet um Schmerzen.

Leider ist die Prozessorleistung des AVR für komplexere Rechenaufgaben nicht ausreichend. Zur Umgehung dieser Problematik bietet sich die Nutzung des ARM-Prozessors an, dessen Linux in Form eines OpenWRT-Derivats vorliegt.

Da die Erstellung eines eigenen Images den Rahmen dieses Artikels sprengt, beschränken wir uns auf das Abrufen von Daten per HTTP. Als Schaltungsbeispiel soll diesmal ein „arbitrary function generator“ dienen. Es handelt sich dabei um ein (im Labor hilfreiches) Bauteil, das beliebige Signalformen erzeugen kann.

Zur Lösung dieses Problems wird auf ein als Digital Direct Synthesis bezeichnetes Verfahren zurückgegriffen. Das eigentliche Ausgangssignal entsteht in einem DA-Wandler, der von einem prozessrechnerartigen System mit einer das Wunschsignal beschreibenden Datenfolge gefüttert wird. Ein daran angeschlossener Filter kompensiert die wandlerimmanente „Stufigkeit“ des Ausgangssignals.

Aus didaktischen Gründen verzichten wir in unserem Beispiel auf einen Rekonstruktionsfilter. Der als DA-Wandler verwendete Baustein vom Typ TL7528 löst nicht besonders fein auf, ist dafür aber leicht erhältlich und kostet nur wenige Euro pro Stück. Sie können natürlich auch ein beliebiges anderes Bauteil verwenden, sofern sein Eingangsbus acht Bit breit ist. Im Fall des TLC7528 sieht die resultierende Schaltung wie in Abbildung 3 gezeigt aus.

Abb. 3: Zum richtigen Funktionsgenerator fehlt hier – zumindest – ein Rekonstruktionsfilter

Abb. 3: Zum richtigen Funktionsgenerator fehlt hier – zumindest – ein Rekonstruktionsfilter

Tick, Tack, Tock!

Mehrfach auszuführende Aufgaben sollten in Mikrocontrollern über einen der normalerweise in ausreichender Menge verfügbaren Timerbausteine angestoßen werden. Sie aktualisieren sich automatisch im Hintergrund; die eigentliche Payload wird in einer Interrupt-Serviceroutine abgearbeitet.

Der Vorteil dieser Vorgehensweise liegt darin, dass der Controller auf diese Art und Weise nicht durch die Pollingschleife ausgelastet wird. Zudem ist die Frequenz der Abarbeitung nicht von der Länge der Interruptroutine oder dem sonst abgearbeiteten Code abhängig: Wenn die Interruptroutine in der Zeit zwischen zwei Ereignissen vollständig durchläuft, ist alles in bester Ordnung.

Als ersten Gehversuch wollen wir die Interruptroutine im Rahmen von setup() konfigurieren. Dazu müssen wir direkt auf einige Register des AVR-Prozessors zugreifen, die über globale Variablen abgebildet werden (Listing 4).

Listing 4
void setup() 
{
    pinMode(13,OUTPUT);
  digitalWrite(13, HIGH);
  
  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  OCR1AL=50;
  ICR1L=50;
  TCCR1B |= (1 << CS11);
  TCCR1B |= (1 << WGM12);
  TIMSK1 |= (1 << OCIE1A);
  sei();
  . . .

Zum Verständnis dieser Routine ist es hilfreich, nebenbei das online einsehbare Datenblatt des Controllers bereitzuhalten. Unsere erste Amtshandlung besteht darin, alle Interrupts durch Aufruf von cli() zu deaktivieren. Dies ist insofern notwendig, als ein während des Umschreibens der Register eintreffender Interrupt das System in einem inkonsistenten Zustand zurücklassen kann. Nach getaner Arbeit wird sei() aufgerufen, womit das Interrupt-Subsystem wieder reaktiviert ist.

Der eigentliche Timer besteht aus einem Zähler, der über ein als Vorteiler bezeichnetes Bauteil mit der Taktquelle des Prozessors verbunden ist. Dieser dient zur Verlangsamung des Auftretens von Timerüberläufen. Durch das Setzen des Bits CS11 weisen wir eine Teilung durch den Faktor acht an.

Wenn der Timer nach einiger Zeit „doch“ überläuft, so wird der im Zähler gespeicherte Wert je nach Betriebsmodus mit den in ICR1 oder OPR1 befindlichen Konstanten verglichen.

Wir schreiben hier 50 in die Register, um 50 Durchläufe a je acht Clock-Ticks zu erhalten. Zu guter Letzt setzen wir das Bit OCIE1A, um bei Überläufen des Timers einen Interrupt hervorzurufen.

WGM12 sorgt dafür, dass der Timer im Interruptmodus arbeitet. Atmel-Microcontroller erlauben die Nutzung der Timerengine zur Generierung von PWM-Wellenformen – aus Platzgründen wird diese durchaus interessante Anwendung hier nicht weiter besprochen.

Daten holen

Unser DDS-Generator emittiert prinzipiell eine aus 256 Werten bestehende Signalfolge. Dies ist insofern von Vorteil, als wir die als Nächstes auszugebende Adresse durch primitive Inkrementierung einer char-Variable ermitteln können.

Die Implementierung der ISR bestimmt die auszugebenden Bits durch Maskierung und schreibt sie danach schrittweise an den AD-Wandler. Das schrittweise Erscheinen der Werte sorgt für eine gewisse Ungenauigkeit, der man durch ein als Puffer dienendes Latch abhelfen könnte (Listing 5).

Listing 5
ISR(TIMER1_COMPA_vect) 
{ 
  myIndex++; 
  
  digitalWrite(0, whatToSay[myIndex]&1); 
  digitalWrite(1, whatToSay[myIndex]&2); 
  digitalWrite(2, whatToSay[myIndex]&4); 
  digitalWrite(3, whatToSay[myIndex]&8); 
  digitalWrite(4, whatToSay[myIndex]&16); 
  digitalWrite(5, whatToSay[myIndex]&32); 
  digitalWrite(6, whatToSay[myIndex]&64); 
  digitalWrite(7, whatToSay[myIndex]&128); 
}

Kenner der AVR-Assemblersprache fragen sich an dieser Stelle, warum die in whatToSay enthaltenen Werte nicht en bloc in das für die Ausgabe zuständige Register geschrieben werden. Der Grund dafür ist das von Arduino  gezeigte Mapping: Massimo Banzi schaffte das (normalerweise unmögliche) Kunststück, keinen einzelnen Port 1:1 auf einen Postenstecker umzulegen.

Daten herbei

Im Moment nutzt unser Funktionsgenerator die Vorteile der Hardware des Yún nicht aus. Die auszugebende Signalfolge wandert im Rahmen der Programmierung in den Speicher. Es wäre amüsanter, wenn diese erst nach dem Start per HTTP in das Gerät wandern würde.

Aus technischer Sicht spricht nichts dagegen, das erneute Herunterladen der anzuzeigenden Daten durch einen Knopfdrück auszulösen. Da die Implementierung einer weiteren Interrupt-Service-Routine den Rahmen dieses Artikels sprengen würde, laden wir die Daten stattdessen im ersten Durchlauf von loop herunter (Listing 6).

Listing 6
void loop() { 
  if(val==true) 
  { 
    HttpClient client; 
    client.get("<URL>“); 
    int readHowMuch=0; 
    while (client.available() && readHowMuch<256)      {        digitalWrite(13, HIGH);        char c = client.read();        whatToSay[readHowMuch]=c;        readHowMuch++;      }      if(readHowMuch>255) 
    {    
      val=false;
      //ISR konfigurieren
    } 
  } 
}

Die in der Bridge-Klasse implementierte HttpClient-Funktion verhält sich im Großen und Ganzen wie ein serieller Port. Daten können auf Wunsch synchron oder asynchron heruntergeladen werden, das eigentliche Auslesen erfolgt danach Byte für Byte.

Leider verbirgt sich an dieser Stelle eine kleine Falle für Quereinsteiger: Die Bridge-Bibliothek dürfte selbst mit Interrupts arbeiten, weshalb sie sich mit unserer selbsterstellten ISR in die Haare gerät. Wir umgehen dieses Problem durch die weiter oben beschriebene Umsiedelung der ISR-Aktivierung. Ein angenehmer Nebeneffekt dieser Änderung ist, dass setup nun wesentlich kürzer ausfällt:

void setup() 
{ 
  val=true; 
  for(int i=0;i<=7;i++) 
    pinMode(i, OUTPUT);  
  Bridge.begin(); 
}

Mehr Bridge

Die Möglichkeiten der Bibliothek sind damit noch nicht ausgeschöpft. Ein im Linux-Teil des Rechners befindlicher Prozess lässt sich vom AVR aus starten, Ein- und Ausgabeströme werden zwischen den beiden Systemen synchronisiert. Zudem gibt es einen kleinen KV-Speicher, der auf Linaro-Seite per Python ansprechbar ist.

Die bei Arduino einsehbare Funktionsliste gibt einen Überblick der in der Bridge enthaltenen Features. Vor der praktischen Implementierung eigenen Codes sollten Sie einen Blick auf die bereitgestellten Beispiele werfen – die Dokumentation ist stellenweise eher mangelhaft.

Alternativen

Massimo Banzi ist nicht nur aufgrund seines eigenwilligen Charakters nicht jedermanns Sache: Die unklare Dokumentation der Bridge-Library samt Beißereien mit Interrupt-Serviceroutinen und die am Ende doch vergleichsweise geringe Rechenleistung des Messen-Steuern-Regeln-Bereichs lassen Raum für Verbesserungen.

Diverse Anbieter von Einplatinencomputern bieten mittlerweile Platinen mit integriertem WLAN-Radio an. Ein bulgarisches Unternehmen unterbietet mit seinem A13-OlinuXino sogar den Preis des wesentlich gemächlicher arbeitenden Yún. Zudem ist es immer möglich, einen preiswerten Einplatinencomputer per WiFi-Stick ins Netz zu bringen.

Diese Systeme haben im praktischen Einsatz zwei gravierende Nachteile. Erstens obliegt die Wartung der Software allein dem Entwickler; zweitens gibt es keine Garantie für Interoperabilität und langfristige Verfügbarkeit. Die enorm höhere Rechenleistung wird somit teuer erkauft – bei kleinen Projekten kann dieser Preis zu hoch sein.

Fazit

Der Arduino Yún lässt sich – anders als seine kleineren Konkurrenten – nicht einfach 1:1 in eine beliebige Platine „transplantieren“. Die von Massimo Banzi realisierte Schaltung mit zwei Prozessoren ist dazu schlichtweg zu kompliziert.

Trotz seines vergleichsweise hohen Preises ist er für Einzelanfertigungen und Kleinstserien attraktiv. Die Erzeugung von mit WLAN verbundenen Geräten geht mit dem Yún ohne großen Aufwand von der Hand. Gerade bei geringen Stückzahlen ist es unterm Strich mit Sicherheit teurer, eine dedizierte Platine samt Linux-SOC zu realisieren.

Entwickler Magazin

Entwickler Magazin abonnierenDieser Artikel ist im Entwickler Magazin erschienen.

Natürlich können Sie das Entwickler Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

 

Aufmacherbild: 3D Graphic Steel blue electric smart phone symbol in a dark background on a computer chip via Shutterstock.com / Urheberrecht: Thomas Reichhart

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -