Bright Side of Arduino

Der Pragmatische Architekt  –  0 Kommentare

In dieser Sonderedition des Blogs geht es um die Programmiersprache Python. Genau genommen darum, wie sich Python nutzen lässt, um einen Arduino fernzusteuern. Oder um mit einem Arduino-Board zu kommunizieren.

Python als Programmiersprache hat speziell im naturwissenschaftlich-mathematischen Bereich viele Anhänger. Der Raspberry Pi verdankt den Zusatz "Pi" nicht zuletzt der Sprache Python, die bei den Erfindern des Raspberry seit jeher einen ähnlichen Kultstatus besitzt wie die Kultserie Monty Python. In dieser Folge wollen wir uns speziell mit der Frage beschäftigen, ob auch für Arduino-Liebhaber Python eine Option ist.

Eine Laufzeitumgebung für Python macht eigentlich nur auf Arduino-Boards mit zusätzlichem Linux-System Sinn, etwa auf den Distributionen Linino bzw. OpenWRT für den Yun (arduino.cc, arduino.org) oder den Tian (arduino.org).

Eingeweihte wissen zwar, dass es mit PyMite eine Python-Umgebung auch für leistungsstärkere Microcontroller-Arduino-Boards wie dem Mega gibt. Allerdings sehe ich persönlich darin keinen großen Mehrwert, da sich dort größenbedingt weder die wichtigen Python-Bibliotheken nutzen lassen noch genügend Platz für den eigenen Code bleibt.

Als eine zu JavaScript vergleichbare Fernsteuerungsmöglichkeit kommt Python aber auf jeden Fall in Frage.

Firmata-Sketch

Diesmal möchte ich zur Illustration mit einem Python-Programm starten, das auf den Arduino zugreift. Hier gibt es zwei grundlegenden Optionen, eine über die Bibliothek serial und eine über pyFirmata. Letztere Variante setzt Firmata auf dem Arduino-Board voraus.

Laden Sie also über das Menü File | Examples | Firmata den Sketch StandardFirmata auf Ihr Arduino-Board.

Python installieren

In diesem Beitrag nutze ich Python 2.7.x . Es gibt zwar neuere 3.y-Versionen, aber noch sind nicht alle relevanten Bibliotheken auch für die neueren Versionen verfügbar. Es ist aber garantiert, dass auch ein Python-2.7.x-Programm auf zukünftigen Sprachversionen läuft. Insofern ist das alles gut zu verschmerzen.

Die Python-Versionen ab 3.4 und ab 2.7.9 bringen das Programm pip mit, das Sie unbedingt installiert haben sollten. Hierbei handelt es sich um den für Python empfohlenen Installationsmanager für Pakete. Downloads von Python für Ihr Betriebssystem erhalten Sie über die Python.org-Webseite.

Um die für den vorliegenden Artikel vorausgesetzten Bibliotheken pyFirmata oder pySerial zu installieren, verwenden Sie pip auf der Kommandozeilenkonsole/dem Terminal wie folgt:

pip install pyserial

und

pip install pyfirmata

Das waren dann schon alle notwendigen Vorarbeiten.

Programmieren in Python

Im folgenden Python-Programm, das auf Mac, Linux oder Windows laufen kann, erfolgt ein Zugriff auf drei LEDs, die an den digitalen Ports 9, 10, 11 eines Arduino Uno angeschlossen sind. Das Python-Programm implementiert daher genau die Funktionen, die auch die früher vorgestellte Arduino-Bibliothek für die Implementierung eines binären Würfels offeriert.

Für die Erläuterung der Syntax von Python fehlt es hier an Platz. Interessierte seien daher an Tutorials wie dieses hier verwiesen. Nur ein Hinweis für Globetrotter aus der Welt der C-artigen Sprachen sei gestattet: Python benutzt statt mit Klammern oder Schlüsselwörtern begrenzten Blöcken Einrückung als Strukturierungsmittel. Eine if-Anweisung in C wie etwa

if (x > y)
....;
max = x;
} else {
....;
max = y;
}
printf("Hello World\n");

schreiben Python-Programmierer folgendermaßen:

if (x > y):
....
max = x
else:
....
max = y
print('Hello World');

Editieren können Sie ein Python-Programm zum Beispiel mit Ihrem Lieblingseditor. Der Start erfolgt auf der Kommandozeile dann über python myprogramm.py. Darüber hinaus enthalten neuere Python-Installationen IDLE (Integrated Development and Learning Environment). Wer es noch bequemer braucht, besorgt sich eine der folgenden Optionen:

Python ruft Arduino

Doch kommen wir nun zum eigentlichen Python-Programm. Erst erzeugt ein Zufallsgenerator in einer Endlosschleife Zahlen zwischen 1 und 6. Danach erfolgt die Ausgabe der gewürfelten Augenzahl als binärer dreistelliger Wert, wobei die LEDs den einzelnen Bits entsprechen. Als Goodie merkt sich das Python-Programm für alle möglichen Augenzahlen in einer Liste wie oft diese bisher vorgekommen sind.

# Bibliotheken pyFirmata und random importieren
import pyfirmata
import random

RedPin = 9
YellowPin = 10
GreenPin = 11

Delay = 1 # Eine Sekunde Pause pro Iteration
Iteration = 0
Port = '/dev/cu.usbmodem143311' # Unter Windows und Linux ändern

# Neues Board anlegen
board = pyfirmata.Arduino(Port) # Mit Firmata verbinden

resList = [0,0,0,0,0,0] # Liste mit allen Vorkommen von Ergebnissen
while True:
Iteration += 1 # nächste Runde
dice = random.randrange(1,7) # Würfeln
resList[dice - 1] = resList[dice - 1] + 1
print('Wuerfel zeigt an: ' + str(dice))
if dice // 4 == 1:
board.digital[RedPin].write(1)
dice = dice % 4
else:
board.digital[RedPin].write(0)
if dice // 2 == 1:
board.digital[YellowPin].write(1)
dice = dice % 2
else:
board.digital[YellowPin].write(0)
board.digital[GreenPin].write(dice)
print(resList); # bisher gewürfelte Augenzahlen
board.pass_time(Delay)

Im Listing setze ich die Verfügbarkeit eines Arduino/Genuino Uno voraus:

board = pyfirmata.Arduino(Port)

Hätte ich einen Mega im Einsatz, müsste ich stattdessen schreiben:

board = pyFirmata.ArduinoMega(Port)

Zwischenstand des Programmdurchlaufs auf meinem PC nach geraumer Zeit:

....
Wuerfel zeigt an 3
[2188, 2234, 2126, 2180, 2203, 2251]
Wuerfel zeigt an 3
[2188, 2234, 2127, 2180, 2203, 2251]
Wuerfel zeigt an 3
[2188, 2234, 2128, 2180, 2203, 2251]
Wuerfel zeigt an 5
[2188, 2234, 2128, 2180, 2204, 2251]
Wuerfel zeigt an 5
[2188, 2234, 2128, 2180, 2205, 2251]

Die Zahlen in der Liste illustrieren, wie oft welche Augenzahl bereits vorgekommen ist. Sie können unschwer erkennen, dass sich die Verteilung der Augenzahlen dem statistischen Erwartungswert annähert.

Dies und das

Hier noch einige wichtige Eigenschaften von pyFirmata in aller Kürze. In pyFirmata gibt es die Funktion Iterator(). Diese öffnet einen Thread und verwaltet die über die serielle Leitung gelesenen Werte unter Nutzung eines Puffers. Ohne diese Funktion kann es zum Überlauf des seriellen Ports kommen:

import os #OS Funktionen einbinden
from pyfirmata import Arduino, util
from time import sleep

port = 'dev/ttyACM0' # Linux Port
board = Arduino(Port)
sleep(10)
it = util.Iterator(board)
it.start()
a0 = board.get_pin('a:0:i')
try:
while True:
p = a0.read()
print p
except: KeyboardInterrupt:
board.exit()
os._exit()

In diesem Beispiel findet sich auch eine andere Möglichkeit, um Pins des Boards zu spezifizieren.

a0 = board.get_pin('a:0:i')

bedeutet: a => Analog, 0 => Pin, i => Input. Daher ist ab diesem Zeitpunkt eine Anweisung wie a0.read() möglich. Auf diese Weise lassen sich auch PWM-Pins spezifizieren (PWM = Pulse With Modulation). Die Deklaration

pwmPort = board.get_pin('d:12:p')

hebt den Digitalpin 12 in den PWM-Modus.

Ein Aufruf von

pwmPort.write(0.30)

hat einen Duty-Cycle von 30 Prozent. Im Schnitt liegt an dem 5-V-Port daher eine Spannung von 1,5 V. Alternativ könnte man auch wie folgt vorgehen:

board.digital[13].mode = PWM
board.digital[13].write(0.30)

Zusätzlich erlaubt pyFirmata den SERVO-Modus. SERVO steht natürlich für Servo-Motoransteuerung. Folgende Anweisungssequenz definiert einen am Port 13 angeschlossenen Servo-Motor, der um 90° gedreht wird.

board.digital[13].mode = SERVO
angle = 90
board.digital[13].write(angle)

In pyFirmata verfügbare Boards werden über Verbundobjekte (Dictionary Objects) spezifiziert.

Ein Arduino ProMini etwa lässt sich wie folgt vereinbaren:

ProMini = {
'digital' : tuple(x for x in range(14)),
'analog' : tuple(x for x in range(8)),
'pwm' : (3, 5, 6, 9, 10, 11),
'use_ports' : True,
'disabled' : (0, 1) # Rx, Tx, Crystal
}

Nachdem wir dem System ein Arduino-Board bei der Initialisierung angegeben haben ...

port = 'dev/ttyACM0' # Linux Port
board = Arduino(Port)

... können wir in Firmata das Board-Layout des Uno mit dem Board-Layout des ProMini überschreiben:

board.setup_layout(ProMini)

Kommunikation zwischen Arduino und Python mittels pySerial

Im ersten Sketch haben wir Firmata als Abstraktionsebene eingesetzt, um das Arduino-Board über Python zu steuern. Geht es um reine Kommunikation, etwa zwischen PC und Arduino, Arduino und Arduino oder einer anderen Hardware und dem Arduino, kommt die Bibliothek pySerial zum Einsatz. Diese implementiert serielle Kommunikation. Auf diese Weise können Sie zum Beispiel von einem Python-Programm mit einem über USB angeschlossenen Arduino "chatten".

Der Arduino-Sketch gestaltet sich wie folgt. Zunächst liest der Sketch Daten über einen Ringpuffer der Länge 64 vom seriellen Port ein. Die empfangene Zeichenkette sendet der Arduino dann rückwärts gelesen an den Absender zurück: reverseBuffer(index)

const int BUFSIZE = 64; // Größe des Puffers
char buffer[64]; // Puffer

void setup() {
Serial.begin(9600);
}

void loop() {
int index = 0;
// erst lesen ...
while (Serial.available() > 0) {
buffer[index++] = Serial.read();
index = index % BUFSIZE; // Ringpuffer Modus
}
// ... dann Nachricht rückwärts über die Leitung
// zurücksenden:

reverseBuffer(index);
delay(100); // Kleine Pause
}

// Wir lesen den Puffer rückwärts
// und senden Byte für Byte über die Leitung

void reverseBuffer(int len) {
int index = len - 1;
while (index >= 0) {
Serial.print(buffer[index--]);
}
}

Das Python-Programm ist nicht weiter komplex. Es baut die Verbindung zum Arduino auf, schreibt eine Zeichenkette an den Arduino, bekommt von dort eine Antwort zurück, die es auf dem Bildschirm ausgibt. Den Port des Arduino ersehen Sie in der Arduino IDE, wenn Sie auf dem Fenster des Arduino-Sketches den rechten unteren Rand betrachten.

import serial # pySerial importieren

# Ersetzen Sie den Port auf Ihrem Betriebssystem durch den vom
# Arduino genutzten Port


arduino = serial.Serial('/dev/cu.usbmodem143311', 9600, timeout=2)
while True:

# erst an den Arduino schreiben ...
arduino.write('This is a test string')

# dann zurückgeliefertes Ergebnis lesen

line = arduino.readline();
print(line) # Ausgabe der empfangenen Daten

Probleme mit dem seriellen Port vermeiden

Sollten Sie Sync-Fehler beim Upload des Arduino-Sketches erhalten oder Fehlermeldungen des Python-Programms über einen Fehler beim Zugriff auf den seriellen Port, dann sind Sie sehr wahrscheinlich auf die Problematik gestoßen, dass mehrere Anwendungen gleichzeitig auf den seriellen Port zugreifen wollen, und nur eine die Oberhand gewinnt. Um das zu verhindern, gehen Sie wie folgt vor.

Annahmen: Es ist weder der serielle Monitor in der Arduino IDE aktiv noch ein Sketch auf den Arduino hochgeladen/gestartet worden. Sie haben das Python-Programm ebenfalls noch nicht gestartet.

Schritte:

  1. Laden Sie den Sketch auf den Arduino, aber aktivieren Sie den seriellen Monitor nicht!
  2. Starten Sie das Python-Programm.

Sie können diese Schritte auch vertauschen.

Außerdem haben wir bislang in den Python-Programmen nichts weiter getan, um die Verbindung zum jeweiligen Arduino-Board zu schließen. Im Würfel-Programm kann der Anwender das Python-Programm durch Eingabe von Control-C jederzeit unterbrechen. Um in diesem Fall trotzdem für eine Freigabe der Verbindung zum Board zu bewerkstelligen, müssen wir die Endlosschleife des Würfelskripts mit einer try/except-Anweisung umklammern. Durch Aufruf von board.exit() geben wir die Verbindung explizit wieder frei.

try:
while True:
Iteration += 1
dice = random.randrange(1,7)
resList[dice - 1] = resList[dice - 1] + 1
print('Wuerfel zeigt an: ' + str(dice))
if dice // 4 == 1:
board.digital[RedPin].write(1)
dice = dice % 4
else:
board.digital[RedPin].write(0)
if dice // 2 == 1:
board.digital[YellowPin].write(1)
dice = dice % 2
else:
board.digital[YellowPin].write(0)
board.digital[GreenPin].write(dice)
print(resList);
board.pass_time(Delay)
except KeyboardInterrupt:
print('connection to board closed')
board.exit()

Zusammenfassung

In dieser kurzen Extra-Ausgabe meines Blogs ging es um die Erweiterung des Entwicklerhorizonts. Neben JavaScript ist Python eine gute Option, um auf Arduino-Boards zuzugreifen, sei es zur reinen Kommunikation oder zur Ansteuerung von Arduinos über StandardFirmata. Da Python eine sehr gängige Sprache auf dem Raspberry Pi darstellt, eröffnen sich hier neue Möglichkeiten zur Verbindung von Raspberry und Arduino. Ich hoffe, Ihnen macht diese neue Möglichkeit ebenso viel Spaß wie mir.