Hallo,
eine Kollegin hat ein Programm geschrieben, wo sie mit einem Raspberry
und Python-Programm über I2C und SPI mit anderen Mikrocontrollern
kommuniziert.
Dabei gibt es manchmal Unregelmässigkeiten im Betrieb, d.h. dass I2C/SPI
Kommandos nicht erkannt werden von den Mikrocontrollern.
Dabei verwendet sie
als OS das Standard-OS raspbian stretch,
und
https://pypi.org/project/pigpio/
für die HW-Schnittstellen.
und tkinter als Python-GUI
Dabei sind ihre Aufrufe verstreut im Code, meistens in den Callbacks von
Button-Clicks.
Die Aufrufe der I2C/SPI-Zugriffe sind auch mit Sleep-Befehlen versehen
innerhalb der Button-Callbacks.
Es schien zu funktionieren
Frühers habe ich mal gelernt, dass man in solche Callbacks nur Befehle
mit kurzer Ausführungsdauer schreibt. Längere Sequenzen müssten in einem
anderen Thread abgearbeitet werden.
Kann das die Ursache für den unzuverlässigen Programmablauf sein?
Wie könnte man das begründen?
Mehr Details. Was wird genau gemacht? Welche Funktionen von pigpio
werden wie für was genutzt. Einfach Mal einen Use-Case beschreiben, den
Source Code zeigen und das aufgetretene Problem. Schon mit eine LA
angeschaut?
Bis vor einem Jahr, als ich mit dem Rpi noch was gemacht hatte, gab es
von Joan (Autor von pigpio) schnelle und fundierte Hilfestellung bei
Problemen. Also mit den erforderlichen Details im raspberry.org Forum
posten (englisch, im Interfacing Subforum).
Der Source-Code ist 15.000 Zeilen lang und in einem 3/4 Jahr entstanden,
ich weiß nicht warum der so lang ist. Die Funktionalität ist nicht
sonderlich umfangreich.
Ich muss da noch exemplarisch paar typische Stellen raussuchen.
Die Fehler sehen z.B. so aus, dass der Raspberry einen Befehl über i2c
an einen STM32-Mikrocontroller schickt, eine LED anzumachen, aber die
LED geht manchmal nicht an. Manchmal reproduzierbar, manchmal nicht.
Wo genau das Problem auftritt ist unbekannt, und es ist sehr schwierig
aufgrund der Länge des Codes zu lokalisieren.
Hier ist der SPI Zugriff kurz skizziert.
oldtimer schrieb:> Der Source-Code ist 15.000 Zeilen lang und in einem 3/4 Jahr entstanden,> ich weiß nicht warum der so lang ist. Die Funktionalität ist nicht> sonderlich umfangreich.
Sowas kenne ich auch. Copy-and-paste-Grab.
Leider kann ich zum eigentlichen Problem nichts sagen.
Ich selber würde alle pigpio-Zugriffe von einem einzelnen Thread (d.h.
"worker-thread") ausführen. Und die tkinter-Button-Callbacks posten
asynchron entsprechende Nachrichten zu diesem worker-Thread.
Wenn ich diese vielen pigpio und sleep Funktionen in den
Button-Callbacks sehe, wird es mir ungemütlich.
So wie ich das sehe, startet Python für jeden Callback einen neuen
Thread, weil die GUI nie hängen bleibt bei einer längeren
Callback-Ausführung.
D.h. die meisten pigpio-Zugriffen geschehen von ständig von neuen
Threads(?)
Ich kann da keine Hilfestellung geben. Ich mache kein Python. Und bin
aus dem Rpi Thema auch raus.
Anyway, bei SPI kann der RPI nur Master. Da wird also in deinem Bsp. ein
Kommando gesendet (und nach >= 2ms die Antwort abgeholt). Bei den kurzen
Kommandos sollte es kein Problem geben (da die FIFO nicht vollläuft).
Ich weiß nicht, wie Joan das implementiert hat: Wenn die aus dem
Userspace gefüllt wird, könnte es potentiell (sehr unwahrscheinliche)
Probleme geben, wenn die FIFO nicht gefüllt ist, bevor der SPI startet.
Wenn der Kernel Treiber (oder DMA aus dem Userland) benutzt wird, sollte
es immer klappen. (Bei I2C nutzt Joan glaube ich immer den Kernel
Treiber.) Grundsätzlich ist die Lib von Joan ausgereift. Da sollte es
nie Probs geben.
Neben dem Rpi gibt es als Fehlerquellen noch die Leitung und die
Gegenseite. Am besten einen LA mitlaufen lassen und gucken ob das Signal
auf der Leitung ok ist. Vielleicht schaltet ihr auch selbst die LED
wieder aus?!
Ansonsten (wie immer bei Fehleranalyse) Problem auf einen Minibsp.
reduzieren (ihr habt da ja euer LED-Anschalten Use Case), Stress Test
machen (wenn es nicht immer reproduzierbar ist), und mit dem Ergebnis
in's Rpi Forum gehen (also zu Joan).
btw: Single, Multi whatever Thread sehe ich nicht als Problem, da es nur
eine 4-byte Kommandosequenz ist. Ob die LED-an geht oder nicht hängt ja
nicht von was anderem ab, oder?!
>Wo genau das Problem auftritt ist unbekannt, und es ist sehr schwierig>aufgrund der Länge des Codes zu lokalisieren.
1. Habt Ihr schon die Mikrocontroller ausgeschlossen?
2. Hast Du schon den RPI Code auf SPI/I2C reduziert und einen
Dauerlauftest getätigt?
> 1. Habt Ihr schon die Mikrocontroller ausgeschlossen?
Im Prinzip ja. Sie sind schon im Feld 1/2 Jahr, und damit Dauerbetrieb.
Gab schon ein paar Rückläufer, aber Hardware-Versager.
> 2. Hast Du schon den RPI Code auf SPI/I2C reduziert und einen Dauerlauftest
getätigt?
Nee. Die Kollegin hat die 15.000 Zeilen verfasst, und ich kann den Code
nicht in absehbarer Zeit auseinandernehmen und verstehen, sondern suche
strukturelle Fehler. D.h. ich lese das quer durch.
Ihr scheint das Konzept von Unterfunktionen nicht so vertraut zu sein,
weil sie von oberster Ebene auf die low-level Funktionen zugreift.
(Durch Unterfunktionen mit Parameterübergabe könnte man Code
übersichtlicher und wartbarer schreiben, weil man mögliche
Fehlerkorrekturen und Anpassungen nur an einer Stelle machen muss und
nicht an 15 ähnlichen.)
Mein Vorschlag an Projekt-Leitung war "Neu schreiben", aber wurde nicht
umgesetzt.
Denkbar wäre einen Parser zu schreiben, der die ähnlichen Stellen mit
diff-tools vergleicht.
Ich habe immer noch die vielen Threads im Verdacht, und vermute, dass
dann doch einmal zwei threads gleichzeitig auf die pigpio-Objekte
zugreifen, und dann Daten durcheinander würfeln.
Bisher habe ich immer den Einsatz eines "worker-threads" empfohlen, der
zentral alle I2C/SPI-Zugriffe erledigt.
> Die Fehler sehen z.B. so aus, dass der Raspberry einen Befehl über i2c> an einen STM32-Mikrocontroller schickt, eine LED anzumachen, aber die> LED geht manchmal nicht an. Manchmal reproduzierbar, manchmal nicht.
Die Funktionen werden doch bestimmt ACK vom I2C auswerten und dann eine
Fehlermeldung zurueckliefern oder? Wenn nicht dann alles neuschreiben.
Wenn ja dann wuerde da erstmal ein Leitung setzen und die als Postrigger
fuer den Oszi verwenden um sich mal so eine fehlerhafte
Datenuebertragung anzuschauen.
Olaf
Normalerweise kapselt man HW-Zugriffe in eigenen Klassen.
Dann kann man dort auch gezielt Debuggen und Loggen.
Wenn das nicht der Fall ist, dann sollte man das Programm neu
strukturieren. Und wenn das die Kollegin nicht kann, dann ist sie falsch
am Platz.
Vielleicht sollte man dem Management mitteilen, dass sie die richtigen
Leute für die richtigen Jobs nehmen sollten.
Die Kollegin hat schon einiges drauf, ich möchte sie nicht kritisieren.
In Sachen SW-Entwicklung und SW-Architektur fehlt ihr einfach die
Erfahrung. In der Firma gibt es keinen SW-Senior, der sie einweisen
könnte. Der Firma meint, ein Abgänger von der FH kann bereits alles, um
professionelle SW zu schreiben und braucht keine weiteren Einweisungen.
(Ich bin in der Firma teilzeit-Freiberufler)
Wie die Kollegin angefangen hat vor einem Jahr, hatte sie keine Ahnung
von Raspberry, von Linux und von Python glaube ich auch nicht.
Ich denke, sie hat weit mehr geschafft als der Durchschnitt.
Sie damit ziemlich weit gekommen.
oldtimer schrieb:> So wie ich das sehe, startet Python für jeden Callback einen neuen> Thread, weil die GUI nie hängen bleibt bei einer längeren> Callback-Ausführung.
Kenne mich mit Python nicht aus, aber das würde mir etwas zu denken
geben:
Wenn wirklich überlappende Zugriffe von verschiedenen Threads auf die
gleiche SPI-Schnittstelle stattfinden, könnte es Kauderwelsch
(Kollisionen) geben.
Normalerweise würde man versuchen, dies mit einem Semaphor oder einer
Lockdatei zu verhindern.
Würde allerdings auch nur auftreten, wenn der Anwender schon die nächste
Funktion auslöst, bevor die Erste abgearbeitet ist. In diesem Falle sind
absichtlich eingebaute Verzögerungen (Sleep/Delay) wohl auch eher
kontraproduktiv, das sie diese Gefahr erhöhen.
oldtimer schrieb:> Ich selber würde alle pigpio-Zugriffe von einem einzelnen Thread (d.h.> "worker-thread") ausführen. Und die tkinter-Button-Callbacks posten> asynchron entsprechende Nachrichten zu diesem worker-Thread.
Das wäre wohl sicher die bessere Lösung, da so sichergestellt ist, das
die zweite Aktion erst begonnen wird, wenn die erste abgearbeitet ist.
> Wenn wirklich überlappende Zugriffe von verschiedenen Threads
Sollte normalerweise nicht passieren, weil die callbacks nacheinander
gefeuert werden. Es ist aber keine Lock-Sicherung vorhanden.
Aber wenn z.B. System überlastet ist, ein Thread ist blockiert, oder
irgendwelche Nebeneffekte, dann könnte es eventuell schon passieren.
Ich hätte gedacht, dass bei GUI-Programmen alle Button-Callbacks von
einem einzigen GUI-Thread aufgerufen werden. So war das bei der
WIN32-API bis Windows XP, oder Linux-GTK.
Da wäre nichts passiert mit überlappenden Threads, nur dass die
Oberfläche unresponsive ist, solange der Callback in der Ausführung
hängt.
>Die Kollegin hat schon einiges drauf, ich möchte sie nicht kritisieren.
Machst Du indirekt schon. Lass am besten solche Informationen in einem
Forum weg, weil es nichts mit deinem Problem zu tun hat.
Beispiel:
Hallo,
wir haben ein Programm geschrieben, wo sie mit einem Raspberry
und Python-Programm über I2C und SPI mit anderen Mikrocontrollern
kommuniziert.
>Ich habe immer noch die vielen Threads im Verdacht, und vermute, dass>dann doch einmal zwei threads gleichzeitig auf die pigpio-Objekte>zugreifen, und dann Daten durcheinander würfeln.>Bisher habe ich immer den Einsatz eines "worker-threads" empfohlen, der>zentral alle I2C/SPI-Zugriffe erledigt.
Vermutungen und etwas verdächtigen sind keine Fakten, bringt ein paar
Logfunktionen rein oder teile den Code auf, ansonsten wird die
Fehlerursache zu finden nicht einfacher.
Ja, ein Daemon wäre besser gewesen, aber auch viel Arbeit und später
weiss man es immer besser, aber auch das hilft dir gerade null.