PI Pico UART IRQ mit Callback

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
DL3AD
User
Beiträge: 63
Registriert: Montag 31. August 2015, 19:03

Hallo in die Runde,
ich versuche seit Tagen einen UART IRQ mit Callback ans laufen zu bekommen.
Bei Datenempfang über den UART soll ein IRQ gefeuert werden um dann eine Callback oder ISR aufzurufen.

Hier https://docs.micropython.org/en/latest/ ... chine-uart habe ich nachgelesen und im Netz nach einem Beispiel gesucht
habe aber nicht brauchbares gefunden oder ans laufen bekommen.
Der µC ist ein original RP2040 board, UART an sich funktioniert tadellos.
Hat jemand vieleicht ein Beispiel oder ein Link wo diese Problematik behandelt wird ?

Frank
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DL3AD: Was ist denn das konkrete Problem?

Wie es aussieht kann man sich beim Pico nur einen IRQ generieren lassen wenn die RX-Leitung „idle“ wird, also wenn die Gegenseite aufhört Zeichen zu schicken oder zumindest eine Pause einlegt. Nicht für jedes einzelne empfangene Zeichen.

Welches Problem soll denn mit IRQs hier gelöst werden?
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
Benutzeravatar
DeaD_EyE
User
Beiträge: 1286
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Nicht jede Architektur unterstützt die IRQs. Hier ist eine Tabelle:
https://docs.micropython.org/en/latest/ ... T.html#id1

UART unterstützt asyncio.Stream beim rp2 leider noch nicht.
Beim esp32 ist es bereits implementiert.

Man kann sich aber einen eigenen Wrapper für asyncio bauen. Ist zwar nicht so effizient, aber funktioniert.


Die Frage ist, wie schnell willst du reagieren? Kommt es auf weniger als 10 ms an?
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
DL3AD
User
Beiträge: 63
Registriert: Montag 31. August 2015, 19:03

@__blckjack__,
genau den Fall wenn die RX-Leitung "idle" wird.
Ich möchte eine Funktion aufrufen und was mit den Daten machen.
Bisher habe ich es nicht hinbekommen und suche nach einem Beispiel andem ich mich orientieren kann.
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DL3AD: Was hast Du denn gemacht und was funktioniert daran nicht? Und was willst Du mit den Daten machen? Denn in einem ISR darf man ja so gut wie gar nichts machen. Alles was neuen Speicher dynamisch anfordern könnte ist Tabu und das ist in Python so gut wie alles.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
Benutzeravatar
DeaD_EyE
User
Beiträge: 1286
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Bei harten Interrupts kann man keinen Speicher zuweisen, d.h. man muss außerhalb der Funktion (z.B. auf Modulebene) einen Buffer (z.B. bytearray) anlegen und diesen dann beim Aufruf des IRQ befüllen.

Code: Alles auswählen

from machine import UART, Pin

BUFFER_SIZE = 256


def uart_rx_idle(uart: UART):
    if uart.any():
        uart.readinto(uart_rx_buffer)


uart_rx_buffer = bytearray(BUFFER_SIZE) 
uart = UART(1, baudrate=9600, rx=Pin(10), tx=Pin(11), rxbuf=BUFFER_SIZE)
uart.irq(handler=uart_rx_idle, trigger=UART.IRQ_RXIDLE, hard=False)
# handler is an optional function to be called when the interrupt event triggers.
# The handler must take exactly one argument which is the UART instance.
# kein harter Interrupt, aber es ist trotzdem besser, den Buffer zu verwenden
Code nicht getestet.

Wenn der IRQ RXIDLE kommt, prüft der handler, ob daten verfügbar sind und falls ja, werden die Daten vom rxbuffer des UART in den uart_rx_buffer (bytearray) kopiert.
Der Speicher für den Buffer ist aber bereits vergeben und wird nicht beim Aufruf des Interrupts zugewiesen. D.h. der Code sollte auch mit harten Interrupts funktionieren, sofern der RP2 das unterstützt.
Wenn man die Methode uart.read(xxx) nutzt, wird, soweit ich weiß, neuer Speicher zugewiesen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Frage an der Stelle ist warum man das so machen sollte, denn man kopiert hier ja nur aus dem Puffer den `UART` schon hat, in einen anderen Puffer um. Wirklich etwas interessantes kann man nur ausserhalb der ISR machen, da braucht man aber diesen zusätzlichen Puffer nicht wirklich.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
DL3AD
User
Beiträge: 63
Registriert: Montag 31. August 2015, 19:03

Hallo,
Danke für eure Hilfe - nun funktioniert es.

Code: Alles auswählen

from machine import UART, Pin


def myuart_rx(myuart: UART):
    print(myuart.read().decode())

myuart = UART(0, baudrate=9600, rx=Pin(1), tx=Pin(0))
myuart.irq(handler=myuart_rx, trigger=UART.IRQ_RXIDLE, hard=False)
Benutzeravatar
DeaD_EyE
User
Beiträge: 1286
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__blackjack__ hat geschrieben: Dienstag 28. Oktober 2025, 16:09 Die Frage an der Stelle ist warum man das so machen sollte, denn man kopiert hier ja nur aus dem Puffer den `UART` schon hat, in einen anderen Puffer um. Wirklich etwas interessantes kann man nur ausserhalb der ISR machen, da braucht man aber diesen zusätzlichen Puffer nicht wirklich.
Das Modul machine ist in C implementiert. Der UART-Buffer ist dementsprechend nicht direkt von Micropython aus erreichbar. Der zweite Buffer ist unumgänglich, wenn man mit IRQs arbeitet. Die Frage ist halt immer noch, wozu? Interrupts verwendet man, wenn man sehr schnell reagieren muss. Wenn 10 ms reichen, sollte man eher Asyncio nutzen. Leider muss man seinen eigenen Wrapper beim RP2 konstruieren, da asyncio.Stream nur UART-Objekte unterstützt, die die ioctl Methode unterstützen. Soweit ich weiß ist es beim ESP32 implementiert.

Wenn man das z.B. mit pyserial für CPython vergleicht, so ist die Nutzung von asyncio mit Micropython und UART einfacher. Micropythons asyncio fehlen noch einige Funktionen/Klassen. Das ist ein minimalistisches asyncio.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DeaD_EyE: Im ISR wird man den UART-internen Puffer ja nur in den eigenen Puffer umkopieren damit der normale Code etwas damit machen kann und da braucht man dass dann aber nicht umkopieren, denn im normalen Code kann man ja einfach die Daten aus dem internen Puffer anfordern, und das ohne sich Gedanken darüber machen zu müssen welche Einschränkungen für ISRs gelten.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
Benutzeravatar
DeaD_EyE
User
Beiträge: 1286
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__blackjack__ hat geschrieben: Dienstag 28. Oktober 2025, 20:06 @DeaD_EyE: Im ISR wird man den UART-internen Puffer ja nur in den eigenen Puffer umkopieren damit der normale Code etwas damit machen kann und da braucht man dass dann aber nicht umkopieren, denn im normalen Code kann man ja einfach die Daten aus dem internen Puffer anfordern, und das ohne sich Gedanken darüber machen zu müssen welche Einschränkungen für ISRs gelten.
Während im MainThread nach der Unterbrechung gelesen wird, können sich schon wieder neue Daten im Puffer befinden. Man muss kopieren und auch etwas zur Synchronisation nutzen. Besser wäre eine Queue. Dann ist die Synchronisation kostenlos mit dabei.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DeaD_EyE: Aber genau das gleiche kann einem doch auch beim ISR passieren. Auch dort kann ein weiterer IRQ ausgelöst werden, bevor der normale Code dazu kommt den letzten kopierten Puffer zu verarbeiten. Diese angebliche Notwendigkeit läuft darauf hinaus, dass ein Protokoll verwendet wird, wo man aus den Nachrichten nicht erkennen kann wo sie anfangen/enden wenn man dazu den „idle“-Zustand des Senders missbraucht. Das wäre fehleranfällig und sollte beim Protokoll repariert werden.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
Benutzeravatar
DeaD_EyE
User
Beiträge: 1286
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

__blackjack__ hat geschrieben: Mittwoch 29. Oktober 2025, 09:45 @DeaD_EyE: Aber genau das gleiche kann einem doch auch beim ISR passieren. Auch dort kann ein weiterer IRQ ausgelöst werden, bevor der normale Code dazu kommt den letzten kopierten Puffer zu verarbeiten. Diese angebliche Notwendigkeit läuft darauf hinaus, dass ein Protokoll verwendet wird, wo man aus den Nachrichten nicht erkennen kann wo sie anfangen/enden wenn man dazu den „idle“-Zustand des Senders missbraucht. Das wäre fehleranfällig und sollte beim Protokoll repariert werden.
Vielleicht kann das passieren. Typischerweise wird beim ISR der Code so minimal gehalten, dass es abgearbeitet werden kann, bevor die nächsten Daten kommen. Wie schnell der RX-Idle nacheinander ausgelöst werden kann, hängt dann von der eingestellten Baudrate ab. Der Code im MainThread wird solange unterbrochen, bis der ISR-Handler fertig ist. Was dann passieren kann, dass der nächste ISR ausgelöst wird, bevor der MainThread wieder dran ist.

Wie auch immer. Ich glaube, die Diskussion führt nicht weiter. Jedenfalls ist der ISR nicht aus Langeweile implementiert worden. AFIK war es eine Anforderung von jemanden und ist gesponsort worden. D.h. einen echten Anwendungsfall gibt es definitiv.

Und nochmal zur Klarstellung, ich würde asyncio nutzen, wenn 10ms in Ordnung sind. IRQs sind immer problematisch, aber oftmals benötigt.

Für ein zukünftiges Projekt habe ich geprüft, ob es machbar ist, Encoder-Signale via GPIO einzulesen, diese via CAN-Bus @500 kbit/s zu übertragen und auf der Gegenseite mit einem RPi Zero + USBCan auf den GPIO-Pins auszugeben. Der RPi war jetzt die denkbar schlechteste Lösung, weil 1. der USB-Port Latenzen hat, das OS dazwischen hängt und der ISR Python-Code war. Trotz dessen hatte ich nur eine Latenz von 500 bis 700 µS gemessen. Der Sender war ein ESP32S3 + Can-Transceiver. TWAI ist beim ESP32 integriert. Die nennen das so, weil nicht der komplette Can-Bus Standard unterstützt wird. Wenn ich später beide Seiten mit Mikrocontroller realisiert habe, werde ich sicherlich nochmal 100-300 µs einsparen können. Ein Byte @500kbit/s => 16 µs
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@DeaD_EyE: Das es echte Anwendungsfälle gibt will ich gar nicht in Frage stellen. Die sind aber schon recht speziell und mich würde halt schon interessieren was *hier* der konkrete Anwendungsfall ist, damit man da keine Zeit mit verschwendet wenn das kein sinnvoller oder machbarer Fall ist. Mir würden auch eher sinnvolle Fälle für IRQ nach jedem Zeichen statt wenn IDLE einfallen, aber das gibt der Pico ja nicht her.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
DL3AD
User
Beiträge: 63
Registriert: Montag 31. August 2015, 19:03

... in meinem Fall kommen von einem Solarregler alle 1000ms Statusdaten die sollen eingelesen einige aufgedröselt und auf einem Display ausgegeben werden.
Da ist massig Zeit zum auswerten.
Hatte zuvor eine Lösung über die Mainloop mit uart.any() gebaut - funktioniert auch tadellos - fand die Sache mit dem ISR interessant und das funktioniert dank Eurer Hilfe nun auch.
Es ist ja nicht falsch verschiedene Lösungen zu probieren - so hat man Ansätze for zukünftige Bastelprojekte
Benutzeravatar
__blackjack__
User
Beiträge: 14210
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Naja, ist halt nicht wirklich ein ISR. Alleine das simple Beispiel ginge schon nicht als ISR weil `decode()` für das Ergebnis neuen Speicher anfordert.
“Ich bin für die Todesstrafe. Wer schreckliche Dinge getan hat, muss eine angemessene Strafe bekommen. So lernt er seine Lektion für das nächste Mal.” — Britney Spears, Interview in der französischen Zeitung Libération, 2. April 2002
DL3AD
User
Beiträge: 63
Registriert: Montag 31. August 2015, 19:03

jaaaa - mir ging es um die Funktionalität mit dem IRQ :mrgreen:
Antworten