Serielle Kommunikation Raspi

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

Hallo,
ich versuche gerade das Modul "serial" am Raspi auszuprobieren. Habe auch die Baudrate usw. setzen können und kann auch vom Raspi zu einem PC senden und kann auch vom PC zum Raspi senden. Das Senden vom PC bzw, Empfangen am Raspi funktioniert aber nicht wie ich mir das vorstelle. Denn es funktioniert nur wenn ich "Daten = ser.readline()" in ein Programmteil welches mit "def Name():" angelegt wurde packe und mit z.B. einem Button aus Tkinter aufrufe. Frage ich aber in der Hauptschleife (also root) z.B. mit "datenanzahl = ser.inWaiting()" und dann mit "if Datenanzahl > 0." die Serielle Schnittstelle ab, so passiert Garnichts. Kann mir jemand erklären, wie ich die Serielle Schnittstelle am Raspi für die Eingehenden Daten in der Hauptschleife abfragen kann?
Hier mein Code zum besseren Verständnis was ich gemacht habe:

DAS FOLGENDE FUNKTIONIERT! (Sorry aber das Einrücken der Zeilen ist verloren gegangen)
from Tkinter import *
Import Serial
ser = Serial.Serial(port='7dev/ttyAMA0',baudrate = 9600, parity=Serial.PARITY_NONE, stopbits=Serial.STOPBITS_ONE,timeout=1)
ser.open()
root = Tk()
def empfangsprogramm():
datenanzahl = ser.inWaiting()
Daten = ser.readline()
print "Daten = ", Daten
print "Datenanzahl = ", datenanzahl
button_1 = Button(root, text="Empfang", command=empfangsprogramm).pack()
root.mainloop()

DAS FOLGENDE FUNKTIONIERT ABER NICHT!!!
from Tkinter import *
Import Serial
ser = Serial.Serial(port='7dev/ttyAMA0',baudrate = 9600, parity=Serial.PARITY_NONE, stopbits=Serial.STOPBITS_ONE,timeout=1)
ser.open()
root = Tk()
datenanzahl = ser.inWaiting()
if datenanzahl > 0:
Daten = ser.readline()
print "Daten = ", Daten
print "Datenanzahl = ", datenanzahl
root.mainloop()

Weil ich mich mit Python erst seit ein paar Tagen beschäftige, verstehe ich nicht, das die Leseanweisung unter "def" funktionert, im "root" aber nicht Funktioniert. Oder anders gefragt, wie Empfangt ihr denn die Daten welche in den Raspi eingehen? Ich denke mal das Ihr nicht immer auf irgend einen Button o.a. klickt um die empfangenen Daten in eine Stringvariable zu bekommen. Oder muß ich mit dem "time" Modul immer eine wiederkehrende Abfrage eines Programmteils, welches unter "def" angelegt wurde und welches die Daten mit ser.readline() einlesen tut, ständig erneut aufrufen?
Gruß Tom
Kann mir jemand sagen wie man das professionell löst?
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@tomw: im zweiten Versuch schaust Du, ob Daten da sind, wenn keine da sind, machst Du gar nichts. Das Programm verhält sich also genau so, wie Du es programmiert hast. Im ersten Versuch dagegen wartest Du, bis eine komplette Zeile gesendet wurde.
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

@sirius3: im zweiten Versuch schaust Du, ob Daten da sind, wenn keine da sind, machst Du gar nichts.

Mein Gedanke hierbei war der: Ich Frage mit "datenanzahl = ser.inWaiting()" ab, ob Daten empfangen wurden oder nicht. Wenn nicht, ist ser.inWaiting() = 0. Dann sollte auch wirklich nichts weiter passieren und die Hauptschleife einfach weiter laufen. Dafür habe ich die "if datenanzahl > 0. eingebaut. Wenn ich aber jetzt ein Zeichen vom PC zum Raspi sende, dann sollte in "ser.inWaiting()" eine "1" stehen. Diese 1 soll dann das Signal für das Programm sein, das Daten mit der Funktion "ser.readline()" abgeholt werden können. Aber die PRINT Funktion in der IF-Abfrage wird nicht durchgeführt. Entweder wird in der Hauptschleife (root) die Funktion "ser.inWaiting()" nicht gesetzt, oder die IF Anweisung funktioniert im root nicht. Das ist hier meine Frage: Warum funktioniert die IF-Anweisung bzw. die Funktion "ser.inWaiting()" nicht im "root" sondern bei mir nur unter "def Name():" usw.????
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@tomw: es gibt keine Hauptschleife, wo ein ser.inWaiting() aufgerufen wird. Das Programm ruft EINMAL am Anfang diese Funktion auf, und danach nie wieder. In der Funktion `Name` machst Du etwas ganz anderes. Du wartest, bis eine ganze Zeile gelesen werden kann.

Laut Dokumentation ist übrigens inWaiting veraltet und man sollte `ser.in_waiting` benutzen.
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

Danke Sirius3 für deine klasse Antwort.
Das bedeutet also, das der Programmcode, der in "root" steht, nicht immer wieder und ewig durchlaufen wird?
Dann ist mir auch klar, das sich nichts tut bzw. nur einmal beim Programmstart, wenn ich eine IF-Anweisung in das "root" platziere.

Wäre denn dann die folgende Überlegung sinnvoll:
Eine While Schleife in das "root" programmieren, die ständig am laufen ist und die dann die IF-Anweisung beinhaltet, welche ständig abfragt, ob Daten in ser.in_waiting() vorhanden sind oder nicht.
Würde dann aber das gesamte Programm nicht ständig in der while Schleife verharren und andere Dinge, die im "root" nach der while-Schleife stehen, garnicht beachtet werden????
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja, das waere dann so. Und das man while-Schleifen nicht mischen kann mit dem Mainloop von GUIs diskutieren wir hier wirklich fast jeden Tag ;)

Die Loesung fuer dein Problem ist createfilehandler in Tk. Deine serielle Verbindung hat eine fileno()-Methode, und damit kannst du sie fuer Lese-Vorgaenge registrieren. Wenn dann Daten anliegen, bekommst du eine Nachricht & kannst sie auslesen und verarbeiten.

Schau mal in meine Beitraege der letzten Tage, da habe ich das mit einem anderen User diskutiert, und der hat auch ein bisschen Code zum langhangeln gepostet.
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

Danke __deets__ für deinen Lösungsansatz. Jetzt habe ich wenigstens erst einmal eine Richtung wo ich hin muss und werde mich nun erst einmal mit dem createfilehandler aus Tk, sowie der Funktion fileno() vertraut machen. Dann werde ich das Ganze mal ganz von neuem angehen. Danke erst einmal für eure Hilfe.
P.S. so ein paar GUI Objekte aus Tk zu platzieren, gestalten und denen eine Funktion zu geben war da schon für mich wesentlich einfacher... als wie eingehende Daten der UART zu händeln. Aber, ich sehe schon, ich muss noch viel über die Grundlagen von Python lernen....
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das hat mit Python nur bedingt viel zu tun - die Programmierung von GUIs und IO ist in jeder Sprache anspruchsvoll. Oder sogar schlimmer, weil du zB keine garbage collection hast ;)
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

Also, ich habe es jetzt hinbekommen, das ich die UART ordentlich lesen kann und das eingelesene auch weiter verarbeiten kann. Schreibe hier nur noch, um anderen die es vielleicht lesen und das selbe Problem haben, eventuell helfen könnte.
Das wichtigste Kriterium hierbei war, zu begreifen, das der Programmcode welcher in der "main" bzw. "root" steht nicht ständig immer wieder wie eine Schleife abgearbeitet wird, sondern nur einmal beim Programmstart. Somit machte es auch keinen Sinn, eine IF Anweisung o.ä. in "main" (bzw.root) für die UART Datenempfangs- Abfrage einzubauen. Um das Einlesen der eingehenden Daten der UART zu machen, kommt man nicht driumherum, eine Funktion zu erschaffen, die diese UART Abfrage macht. Diese Funktion muss dann, durch was auch immer, aufgerufen werden. Meine Funktion schaut zuerst in "ser.inWaiting()" nach, ob überhaupt etwas empfangen wurde. Wenn nicht, dann wird die Variable "variable_b" mit dem Text "KEIN EMPFANG" geladen und dem Textinhalt eines Labels aus Tk übergeben. Wurde etwas empfangen, dann wird der String mit "ser.readline()" aus dem Buffer geholt und dem Textinhalt eines Labels aus Tk übergeben.

Code: Alles auswählen


variable_b = StringVar()
#empfang:
def empfang():
    bytedaten = ser.inWaiting()
    if bytedaten > 0:
        variable_b = ser.readline()
        label_3.config(text=variable_b)
    else:
        variable_b = "KEIN EMPFANG"
        label_3.config(text=variable_b)
Sorry das ich noch immer "ser.inWaiting()" verwende und nicht wie vorgeschlagen die neuere Version mit "ser.in_waiting()", aber die neuere Version funktioniert bei mir nicht. Obwohl ich einen Raspi 3 und das neueste Raspian habe, scheint noch immer eine alte Version von der "serial" Bibliothek vorhanden zu sein. Aber für mich ist es nicht so tragisch, denn funktionieren muss es.

So, die Erkenntnis war nun, das man die Funktion, welche das Lesen der UART beinhaltet ja immer wieder durch irgend eine andere Funktion o.ä. aufrufen muss. Wenn man z.B. ein einfaches Textbearbeitungsprogramm schreiben möchte, welches einfach den Text (ASCII) welcher über die UART kommt darstellen soll, so müsste man dann die Einlesefunktion alle 100mS oder alle 200mS erneut aufrufen. Hierzu hätte ich noch eine Frage:
Gibt es denn in Python eine Funktion, wo man eine gewisse Zeit einstellen kann, die eine andere Funktion dann immer kontinuierlich aufruft?
Sirius3
User
Beiträge: 17712
Registriert: Sonntag 21. Oktober 2012, 17:20

@tomw: warum verwendest Du nicht das von __deets__ vorgeschlagene `createfilehandle`?

Zu Deiner Frage: Bei Tk gibt es `after`. `variable_b` ist ein schlechter Name, weil dass es eine Variable ist, ist klar, und `b` sagt gar nichts aus. Außerdem verwendest Du die `StringVar` überhaupt nicht, statt dessen gehst Du umständlich über label.config. `bytedaten` ist auch ein schlechter Name, weil es sich um eine Anzahl und nicht um die Daten selbst handelt. Der Kommentar ist mehr als sinnfrei.
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

@Sirius3: @tomw: warum verwendest Du nicht das von __deets__ vorgeschlagene `createfilehandle`?
Hallo Sirius3.
Ich bin noch ganz am Anfang mit der Programmierung in Python. Ich verstehe das "createfilehandle" als eine Funktion um Datenfiles zu managen. Meine Python Kenntnisse reichen leider noch nicht aus, zu verstehen wie ich dieses für meinen Zweck einsetzen sollte. Da ich aber nur erst einmal mit meinen ersten Programmierversuchen, die Hardwareschnittstellen GPIO, UART und I2C beherrschen möchte, habe ich jetzt im Moment noch kein Bedarf, ein File zu öffnen und das Empfangene da hinein abzulegen. Ich habe halt einfach ein Label aus Tk dazu "missbraucht" die empfangenen Zeichen anzuzeigen. Halt Schritt für Schritt sich in die Materie einarbeiten.
GPIO-Pins anzusteuern, war ein Kinderspiel. Aber die UART verlangt da schon wesentlich mehr... Hoffe bei I2C wird das einfacher. Das versuche ich aber erst später. Mit dem I2C Protokoll kenne ich mich bestens aus und habe in Assembler schon jede Menge damit gemacht. Also von der Seite aus, wird es da kein Problem geben.
Ja, die vergebenen Namen sind nicht gerade ein Beispiel, aber ich lerne gerne wie es besser gemacht werden sollte. Die Stringvariable hatte ich erstellt, weil ich gedacht hatte, diese Variable nach Abarbeitung der IF-Anweisung mit "return" als Textinhalt an das LABEL aus Tk in "main" bzw. "root" zu übergeben. "return" hatte sich dann aber als nicht benötigt herausgestellt, da die Übernahme mit "Label_3.config(...)" auch aus der Funktion "empfang" heraus funktioniert.
Jetzt werde ich mir die von dir vorgeschlagene Funktion "after" mal ansehen. Danke für deine Hilfe.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Unter Unix ist ALLES eine Datei. Bzw ein Datenstrom. Du öffnest eine Datei die /dev/ttyUSB0 oder /dev/ttyACM0 oder ähnliches heißt, um seriell zu kommunizieren.

Und darum kannst du mir das durchaus glauben wenn ich dir als Antwort auf deine Frage createfilehandler empfehle. Das ist schon das richtige.

Und noch ein Nachtrag: man kann das mit after machen. Ist aber letztlich verschwendete Rechenzeit und weniger “genau” weil du ja gerade Daten bekommen kannst wenn after gerade schon durch war.
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

@__deets__: Und noch ein Nachtrag: man kann das mit after machen. Ist aber letztlich verschwendete Rechenzeit und weniger “genau” weil du ja gerade Daten bekommen kannst wenn after gerade schon durch war.

Ich habe das "after" jetzt mal eingebaut und es funktioniert auch, zumindest soweit das ich die empfangenen Daten "automatisch" anzeigen kann.
NATÜRLICH! Es werden halt mit "after" die einkommenden Daten immer nur in einer vorgegebenen Zeiteinheit abgefragt. Deshalb benutze ich auch ser.readline() und nicht ser.read(). Bei readline() wird alles gepuffert bis CR empfangen wird. So lange kein CR kommt, ist es auch Zeitunkritisch wann ich den Puffer leere. Bei Verwendung von read(), wird es wahrscheinlich Datenverlust geben, weil Daten wahrscheinlich überschrieben werden bevor ich diese "abholen" kann.
Deshalb muss ich mir für die Zukunft wirklich mal etwas anderes aneignen. Denn irgendwann kommt der Tag, wo es Zeitkritische Dinge zu managen gibt und "after" dann nicht mehr verwendet werden kann. Aber für das heutige Testprogramm ist es schon ok und ich komme erst einmal ein Stückchen weiter.

Danke __deets__ für die Erklärung das unter UNIX alles als ein Datenstrom anzusehen ist. Ist aber für mich, der niemals mit UNIX oder Linux vorab etwas zu tun gehabt hat, leider zur Zeit schwierig sich vorzustellen. Ich bin schon froh, das ich mit Linux soweit bin, das ich es ohne fremde Hilfe konfigurieren und einrichten kann. Ich glaube dir auch absolut, das "createfilehandler" das beste hierfür ist. Nur stehe ich ziemlich am Anfang von Python und alles sieht noch sehr undurchsichtig für mich aus. Es muss erst einmal im Kopf klick machen und der Aha- Effekt auftreten, bevor ich so etwas wie "createfilehandler" verstehen und erfolgreich anwenden kann.

Hättest du eventuell einen Link o.ä. wo das "createfilehandler" für Python- Anfänger wie mich, etwas ausführlicher beschrieben wird, oder sehr einfache Beispiele zu finden sind?
tomw
User
Beiträge: 8
Registriert: Mittwoch 28. Februar 2018, 16:34

Ich habe mir das createfilehandler mal angesehen und möchte hier mal fragen ob es für das lesen der UART so richtig wäre:

root.tk.createfilehandler("/dev/ttyAMA0", Tkinter.READABLE, empfang)

Also "/dev/ttyAMA0" wäre die UART, "Tkinter.READABLE" wäre dann die Instruktion zu lesen und "empfang" wäre dann das aufrufen der Funktion welche das eigentliche lesen mit ser.readline() macht und sollte dann so ähnlich aussehen:

empfang("/dev/ttyAMA0", Tkinter.READABLE)
--ZEILE EINÜCKEN-- variable = ser.readline()

Am Ende muß ich dann den createfilehandler wieder inaktivieren mit:

tkinter.deletefilehandler("/dev/ttyAMA0")

Wäre dieser Ablauf so im Grunde genommen richtig?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Fast. Du musst eine offene Verbindung reingeben. Also serial.Serial(...) wenn ich mich nicht vertue ( mobil unterwegs)
Antworten