Der Elch als Weihnachtsschmuck ist ein kleines Projekt, welches mit einem Arduino Uno oder Klone, einer LED mit Vorwiderstand und einem kleinen Servo auskommt. Die Besonderheit liegt darin, dass der Elch mit dem USB-Kabel, mit dem er über die Arduino IDE programmiert wird, mit dem PC verbunden bleibt. Dann wird das Python3-Skript Elch_1.py gestartet, welches im aktuellen Verzeichnis die Datei Elch.png benötigt. Zwischen dem PC und dem Arduino wird nun eine serielle Verbindung aufgebaut, die als Beispiel für eine fehlertolerante und besonders zuverlässige serielle Datenübertragung herangezogen werden kann.
Jetzt kann man wunderschön ein Setup für den Elch finden, welches einem besonders gefällt: Man schaltet die Automatik aus, fährt den Kopf in die gewünschte obere Position und speichert diese ab und macht dasselbe mit der unteren Position. Anschließend werden noch die Nickfrequenz des Kopfes und die Blinkfrequenz der LED eingestellt. Wer will kann auch im Stillstand einfach nur die Helligkeit der LED regeln. Wenn alles gespeichert ist, dann läuft der Elch auch ohne das Python Skript mit jeder USB-Stromversorgung oder einem Arduino Uno geeigneten Netzteil.
Damit eine serielle Verbindung zustande kommt, muss immer zuerst der Elch eingesteckt sein und dann das Python Skript gestartet werden.



include
uint8_t A = 0, B = 0, Position, Helligkeit, Automatik = 1;
// Jetzt lesen wir aus dem EEPORM unsere beiden Automatikpositionen:
uint8_t Automatik_Helligkeit_0 = EEPROM[0];
uint8_t Automatik_Position_0 = EEPROM[1];
uint8_t Automatik_Helligkeit_1 = EEPROM[2];
uint8_t Automatik_Position_1 = EEPROM[3];
uint8_t Schrittweite_LED = EEPROM[4];
uint8_t Schrittweite_Kopf = EEPROM[5];
uint8_t anzufahrende_Helligkeit = Automatik_Helligkeit_1;
uint8_t anzufahrende_Position = Automatik_Position_1;
Servo Hals;
unsigned long Zeit = millis();
void fahre(uint8_t LED, uint8_t Halsposition) {
analogWrite(3, LED);
Hals.writeMicroseconds(1000 + 10 * Halsposition);
}
void setup() {
Serial.begin(19200);
Hals.attach(2);
// Pin 3 ist PWM, dafür muss nichts angegeben werden
Helligkeit = Automatik_Helligkeit_0;
Position = Automatik_Position_0;
fahre(Helligkeit, Position);
}
// Wir bekommen über die serielle Schnittstelle Befehle in Form von einzelnen Bytes, von denen die linken 4 Bits
// ein Befehl sind und die rechten vier Bits die dazugehörigen Daten.
// Wir benötigen zum Empfangen eines ganzen Bytes dann z.B. mindestens zwei Befehle um 8 Bits für ein ganzes Byte
// zu übertragen.
// Wir definieren jetzt selbst Befehle, die wir benoetigen, dafür haben wir nur vier Bits zur Verfuegung.
// Die Befehle haben also Nummern.
void loop() {
int i = Serial.read();
if (i != -1) {
uint8_t EGB = i; // EGB=eingelesenes Byte – die linken 4 Bit geben den Befehl, die rechten den Wert
switch (EGB >> 4) {// Jeder case ist ein Befehl – Wir lesen den in den linken vier Bits stehenden Befehl
case 0x0: // Befehl 0: Die rechten 4 Bit von A setzen
A = (A & 0xf0) | (uint8_t(EGB & 0x0f));
break;
case 0x1: // Befehl 1: Die linken 4 Bit von A setzen
A = (A & 0x0f) | (uint8_t(EGB & 0x0f)) << 4; break; case 0x2: // Befehl 2: Die rechten 4 Bit von B setzen B = (B & 0xf0) | (uint8_t(EGB & 0x0f)); break; case 0x3: // Befehl 3: Die linken 4 Bit von B setzen B = (B & 0x0f) | (uint8_t(EGB & 0x0f)) << 4; break; case 0x4: // Befehl 4: Automatik Ein/Aus Automatik = (uint8_t(EGB & 0x01)); if (Automatik == 1) { anzufahrende_Position = Automatik_Position_0; anzufahrende_Helligkeit = Automatik_Helligkeit_0; } break; case 0x5: // Befehl 5: Ausführen if (Automatik == 0) { Position = A; Helligkeit = B; fahre(Helligkeit, Position); } break; case 0x6: // Befehl: Derzeitige Position als Automatik_0 speichern if (Automatik == 0) { Automatik_Helligkeit_0 = B; Automatik_Position_0 = A; } break; case 0x7: // Wegen der Übertragung in 7bit ASCII geht bei den Befehlen nicht mehr als 0x7f // Daher werden beim Befehl 0x07 die unteren 4 Bits für weitere Befehlsvarianten // verwendet: switch (EGB & 0x0f) { case 0x00: // Befehl: Derzeitige Position als Automatik_1 speichern if (Automatik == 0) { Automatik_Helligkeit_1 = B; Automatik_Position_1 = A; } break; case 0x01: // Befehl: Automatikpositionen ins EEPROM schreiben EEPROM[0] = Automatik_Helligkeit_0; EEPROM[1] = Automatik_Position_0; EEPROM[2] = Automatik_Helligkeit_1; EEPROM[3] = Automatik_Position_1; EEPROM[4] = Schrittweite_LED; EEPROM[5] = Schrittweite_Kopf; break; case 0x02: // Befehl: Geschwindigkeitswerte schreiben Schrittweite_LED = B; Schrittweite_Kopf = A; break; } break; } } if (Automatik == 1) { if (millis() > Zeit + 20) {
Zeit = millis();
if (anzufahrende_Helligkeit > Helligkeit) {
if (anzufahrende_Helligkeit – Schrittweite_LED <= Helligkeit) Helligkeit = anzufahrende_Helligkeit; else Helligkeit += Schrittweite_LED; } else { if (anzufahrende_Helligkeit + Schrittweite_LED >= Helligkeit) Helligkeit = anzufahrende_Helligkeit;
else Helligkeit -= Schrittweite_LED;
}
if (anzufahrende_Helligkeit == Helligkeit) {
if (anzufahrende_Helligkeit == Automatik_Helligkeit_0) anzufahrende_Helligkeit = Automatik_Helligkeit_1;
else anzufahrende_Helligkeit = Automatik_Helligkeit_0;
}
if (anzufahrende_Position > Position) {
if (anzufahrende_Position – Schrittweite_Kopf <= Position) Position = anzufahrende_Position; else Position += Schrittweite_Kopf; } else { if (anzufahrende_Position + Schrittweite_Kopf >= Position) Position = anzufahrende_Position;
else Position -= Schrittweite_Kopf;
}
if (anzufahrende_Position == Position) {
if (anzufahrende_Position == Automatik_Position_0) anzufahrende_Position = Automatik_Position_1;
else anzufahrende_Position = Automatik_Position_0;
}
fahre(Helligkeit, Position);
}
}
}
–– coding: utf-8 ––
als root machen: chmod o+rw /dev/ttyUSB0
oder alternativ den benutzer in die gruppe dialout hinzufuegen
from tkinter import *
import serial
import sys
import time
Schnittstelle initialisieren
Schnittstelle_gefunden=False
print(„“)
for Schnittstelle in („/dev/ttyUSB0″,“/dev/ttyUSB1″,“/dev/ttyACM0″,“/dev/ttyACM1“):
print(„Ich versuche die Serielle Schnittstelle „+Schnittstelle)
try:
ser=serial.Serial(port=Schnittstelle,baudrate=19200,bytesize=serial.EIGHTBITS,parity=serial.PARITY_NONE,stopbits=1,
timeout=10,xonxoff=False,rtscts=False,dsrdtr=False,write_timeout=1,inter_byte_timeout=None)
Schnittstelle_gefunden=True
except:
pass
if Schnittstelle_gefunden:
break
if not Schnittstelle_gefunden:
sys.exit(„Ich konnte keine Verbindung zum Arduino aufbauen“)
else:
print(„Verbindung zum Arduino steht -> 2 Sekunden warten …“)
time.sleep(2)
die linken 4 Bits geben immer an, um was es sich handelt, die rechten sind die Daten
def writeA(i): # i ist ein 1-Byte Integer # wird verwendet, um das darzustellende Zeichen zu uebertragen
ser.write(chr(0x00 | (i & 0x0f)).encode(„ascii“)) # das ergibt für den Empfaenger den Befehl, die rechten vier Bits von A zu setzen)
ser.write(chr(0x10 | (i & 0xf0)>>4).encode(„ascii“)) # das ergibt für den Empfaenger den Befehl, die linken vier Bits von A zu setzen
def writeB(i): # i ist ein 1-Byte Integer # wird verwendet, um die Displayzeile anzugeben (0..1)
ser.write(chr(0x20 | (i & 0x0f)).encode(„ascii“)) # das ergibt für den Empfaenger den Befehl, die rechten vier Bits von A zu setzen)
ser.write(chr(0x30 | (i & 0xf0)>>4).encode(„ascii“)) # das ergibt für den Empfaenger den Befehl, die linken vier Bits von A zu setzen
def go():
ser.write(chr(0x50).encode(„ascii“)) # der Arduino soll das Zeichen darstellen
def move_head_with_mouse(*a):
writeA(Kopf_Schieber.get())
go()
def LED_Helligkeit(*a):
writeB(LED_Schieber.get())
go()
def Automatik(*a):
if v.get()==0:
ser.write(chr(0x40).encode(„ascii“))
writeA(Kopf_Schieber.get())
writeB(LED_Schieber.get())
go()
else:
ser.write(chr(0x41).encode(„ascii“))
def save_Point_0 (*a):
ser.write(chr(0x60).encode(„ascii“))
def save_Point_1 (*a):
ser.write(chr(0x70).encode(„ascii“))
def save_to_EEPROM (*a):
ser.write(chr(0x71).encode(„ascii“))
def speed_function(*a):
writeA(Kopf_speed.get())
writeB(LED_speed.get())
ser.write(chr(0x72).encode(„ascii“))
def Programm_Beenden(*a):
main.destroy()
main=Tk(className=“ die Elch Programmiersoftware“)
Anlegen der Widget-Objekte
Bild=PhotoImage(file=“./Elch.png“)
Bildobjekt=Label(main, image=Bild)
Kopf_Schieber = Scale(main, from_=100, to=0, orient=VERTICAL, length=200, resolution=1, command=move_head_with_mouse)
LED_Schieber = Scale(main, from_=0, to=255, orient=HORIZONTAL, length=200, resolution=1, command=LED_Helligkeit)
Kopf_speed = Scale(main, from_=1, to=16, orient=VERTICAL, length=100, resolution=1, command=speed_function)
LED_speed = Scale(main, from_=0, to=100, orient=VERTICAL, length=100, resolution=1, command=speed_function)
Kopf_Schieber.set(„100.0“)
LED_Schieber.set(„0“)
Kopf_speed.set(„4“)
LED_speed.set(„50“)
v= IntVar()
Automatikschalter=Checkbutton(main, text=“Automatik „, variable=v, command=Automatik)
Automatikschalter.var=v
Save_0 = Button(main, text=“save as Pos.0″, command=save_Point_0)
Save_1 = Button(main, text=“save as Pos.1″, command=save_Point_1)
save_to_EEPROM= Button(main, text=“save to EEPROM“, command=save_to_EEPROM)
Beenden = Button(main, text=“Beenden“, command=Programm_Beenden)
Seriellen Puffer löschen:
ser.read(ser.inWaiting())
Aufrufen der Widget-Objekte
Bildobjekt.pack()
Kopf_Schieber.place(x=435,y=30)
LED_Schieber.place(x=25,y=70)
Kopf_speed.place(x=80,y=280)
LED_speed.place(x=25,y=280)
Automatikschalter.place(x=200,y=365)
Automatikschalter.deselect()
Save_0.place(x=366,y=255, width=120)
Save_1.place(x=366,y=290, width=120)
save_to_EEPROM.place(x=366,y=325, width=120)
Beenden.place(x=366,y=360, width=120)
ser.write(chr(0x40).encode(„ascii“)) # Automatik ausschalten
move_head_with_mouse()
LED_Helligkeit()
go()
main.mainloop()