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.
1 | #!/usr/bin/env python3
|
2 | import tkinter as tk |
3 | import pigpio |
4 | |
5 | ...
|
6 | pi=pigpio.pi() |
7 | h=pi.spi_open(0, 500000, 0x100) |
8 | ...
|
9 | |
10 | #SPI-Zugriff
|
11 | resp2 = pi.spi_xfer(h, [0x10, 0,0, 0]) |
12 | time.sleep(0.002) |
13 | a = [0]*len |
14 | resp2 = pi.spi_xfer(h, a ) |
Kann morgen mehr Code-Stellen anbieten.
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?!
:
Bearbeitet durch User
>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.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.