Eigentlich lautet die Frage nicht "wie decodiere ich SUMD?" von Graupner Hott, sondern "wieso kapiere ich diese verdammten Datenformate nicht?"
Also laut Graupner schickt der Sender:
o Format: 115200 Baud, 8 Bit, 1 Stop-Bit;
o Der Frame sieht wie folgt aus:
o byte 0 = always '168'
o byte 1 = seems to be a kind of error indicator (1 normally..
o byte 2 = count of following channels
o byte 3 = high byte of Servo 0
o byte 4 = low byte of Servo 0; raw min is 8000 max is 16000
o ... repeats over the channel count
o byte n-1 = High byte of CRC
o byte n = Low byte of CRC16, which is CRC-CCITT (XModem) with 0x1021 as polynomial and 0x0000 as starting value.
Servo 0 hat 11992, das ist zwischen 8000 und 16000. Sieht doch alles gut aus.
Print() versucht die Bytes als Zeichen zu interpretieren, wahrscheintlich als UTF-8. Schau mal, ob Du einen Weg findest, die Daten roh als HEX-Wert zu senden.
Servo 0 hat 11992, das ist zwischen 8000 und 16000. Sieht doch
alles gut > aus.
Mein Problem ist viel grundlegender:
Wie rechne ich HB*256 + LB mit den Werten: b'.' und: b'\xd8'.
Oder anders gefragt, wie mach ich da Dezimalwerte draus ??
Servo 0 hat 11992, das ist zwischen 8000 und 16000. Sieht doch
alles gut > aus.
Mein Problem ist viel grundlegender:
Wie rechne ich HB*256 + LB mit den Werten: b'.' und: b'\xd8'.
Oder anders gefragt, wie mach ich da Dezimalwerte draus ??
struct ist dein Freund.
pack und unpack basteln alle mögliche Zeug zusammen und wieder auseinander.
Ich bin mir gerade nicht 100%ig sicher, wie print() unter micropython funktioniert. Für mich sieht das so aus:
Der Komma-Operator concateniert den String "wert : " und das Byte chari in einen längeren String. Deswegen wird versucht, letzteres als Zeichen darzustellen. Für 46 funktioniert das, es ergibt einen Punkt. Für 216 nicht (weil >128 und deswegen in UTF-8 als einzelnes Byte ungültig), deswegen wird der Hex-Wert direkt zurückgegeben.
Wenn Du mit den Werten etwas anfangen will, solltest Du wohl die Bytes erst einmal in einen String mit den Hex-Werten umbauen, bevor Du es mit dem String davor zusammenpfropst.
Walter T. schrieb:
Mein Problem ist viel grundlegender:
Wie rechne ich HB*256 + LB mit den Werten: b'.' und: b'\xd8'.
Oder anders gefragt, wie mach ich da Dezimalwerte draus ??
b'...' ist vom Type bytes, d.h. ein gepacktes Read-Only-Array von
Integerwerten im Bereich von 0 bis 255. Auf die einzelnen Array-Elemente
kannst du mit [] zugreifen.
Beispiel:
1
>>> bs = b'\x06'
2
>>> b[0]
3
6
4
>>> bs = b'.\xd8'
5
>>> bs[0]
6
46
7
>>> bs[1]
8
216
Mit uart1.read(1) wird 1 Byte gelesen, d.h. der Rückgabewert ist ein
bytes mit der Länge 1. Wenn du zwei zusammengehörige Bytes (bspw. bei
den Servo-Werten) erwartest, kannst auch gleich beide in einem Aufwasch
mit uart1.read(2) lesen.
Für das Zusammensetzen von 2 Bytes zu einem 16-Bit-Integer gibt es
mehrere Möglichkeiten, von denen zwei (int.from_bytes und struct.unpack)
bereits genannt wurden. Eine dritte Möglichkeit ist die Verwendung von
<< und |.
Das ganze Telegramm kann also folgendermaßen gelesen werden:
1
uart1.read(2) # 0xa8 0x01
2
n = uart1.read(1)[0] # Anzahl Servos
3
for i in range(n):
4
# mit int.from_bytes
5
val = int.from_bytes(uart1.read(2)) # byteorder='big' (default)
6
7
# mit struct. unpack:
8
# val, = struct.unpack('>H', uart1.read(2))
9
10
# ohne int.from_bytes und struct.unpack:
11
# bs = uart1.read(2)
12
# val = bs[0]<<8 | bs[1]
13
14
# dasselbe etwas pythonischer:
15
# high, low = uart1.read(2)
16
# val = high<<8 | low
17
18
print(f'Servo {i+1}: {val}')
Hinzu kommt natürlich noch die Auswertung der ersten beiden Bytes und
der CRC.
Sind wir schon wieder an dem Punkt wo jeder unbedingt recht haben muss?
Ich formuliere aber gerne um: Durch x,_ = … wird erkennbar dass man nur am ersten Wert des Tupels interessiert ist. (auch wenn in diesem Fall das Tupel nur einen Wert enthält)
Da PEP aber sowieso nur /proposals/ liefert, machen wir's doch einfach beide so wie wir mögen.
Das erzeugt eine Exception wenn innerhalb des Timeouts keine Daten
eintrudeln.
Wenn Reiner beschließt, einen Timeout für uart1 festzulegen (was er
derzeit noch nicht tut), muss er ausgelöste Timeouts natürlich auch
adäquat behandeln. Das ist dann aber wieder ein anderes Thema.
Wenn Reiner beschließt, einen Timeout für uart1 festzulegen (was er
derzeit noch nicht tut), muss er ausgelöste Timeouts natürlich auch
adäquat behandeln. Das ist dann aber wieder ein anderes Thema.
Das trifft es aber nicht.
Wenn man den Rückgabeparameter blindlings als bytes() auswertet und sich sofort das erste Element nimmt, dann knallt es wenn man nur ein einfaches ›None‹ zurück bekommt.
Also muss man erst einmal auf ›is not None‹ testen und dann gegebenenfalls auseinander pflücken.
Das mit dem Timeout kommt noch, damit möchte ich die Abwesenheit des Senders detektieren. das mache ich (wie die gesamte Decodiererei) aktuell "analog" mit dem Impuls-SummenPPM (SumO). Und weil ich das unelegant finde, der ganze Kram mit dem seriellen Signal. Vielleicht kapier ich auch mal ein wenig mehr in der komplizierten Welt der Datenformate in Python ;-)
Zunächst das harte Tagesgeschäft.
Es läuft schon bissi was, aber so recht versteh ich's nicht :
File "<stdin>", line 14, in <module>
ValueError: need more than 1 values to unpack
Wenn ich die Zeile "print(n)" drin lasse, klappt alles. Ok, ohne das ich verstehe, was da passiert, ist das nix wert. Wie kann der Print-Befehl diese Auswirkung haben? Ein Timing-Problem? Das gibts doch gar nicht.
Das mit dem Timeout kommt noch, damit möchte ich die Abwesenheit des
Senders detektieren. das mache ich (wie die gesamte Decodiererei)
aktuell "analog" mit dem Impuls-SummenPPM (SumO). Und weil ich das
unelegant finde, der ganze Kram mit dem seriellen Signal. Vielleicht
kapier ich auch mal ein wenig mehr in der komplizierten Welt der
Datenformate in Python ;-)
Zunächst das harte Tagesgeschäft.
Es läuft schon bissi was, aber so recht versteh ich's nicht :
import machine
uart1 = machine.UART(1, baudrate=115200, tx=12, rx=13)
uart1.init(115200, bits=8, parity=None, stop=1)
while 1:
chari = uart1.read(1)
while(chari != b'\xa8'): # warte auf die 168
chari = uart1.read(1)
uart1.read(1) # 0x01 lesen
n=uart1.read(1) # Anzahl Servos (=6)
if(n is not None):
#print(n)
h,l = uart1.read(2) #
wert=h*256+l
print(wert)
Produziert den Fehler:
File "<stdin>", line 14, in <module>
ValueError: need more than 1 values to unpack
Wenn ich die Zeile "print(n)" drin lasse, klappt alles. Ok, ohne das ich
verstehe, was da passiert, ist das nix wert. Wie kann der Print-Befehl
diese Auswirkung haben? Ein Timing-Problem? Das gibts doch gar nicht.
Timeout!
Dein UART ist non-blocking (ohne timeout) initialisiert.
Wenn der print drin ist, hat der UART genug Zeit um schnell noch zwei oder mehr Werte zu empfangen.
Ohne print geht's zu schnell, da bekommt er einen oder keinen.
Folglich: ValueError: need more than 1 values to unpack
Also solltest du bei der UART Initialisierung einen kleinen Timeout spezifizieren.
uart1.init(115200, bits=8, parity=None, stop=1, timeout=20) # ms
Das hier: h,l = uart1.read(2)
heißt, lese wenn möglich und vorhanden zwei Bytes, aber nicht mehr.
Es heißt nicht, warte bis du zwei Bytes gelesen hast.
Ich möchte nicht sinnlos massig UART-Daten lesen, die ich in der Häufigkeit eh nicht brauche. Also versuche ich die "main" mit der Uart zu "synchronisieren".
In der main haben ich einen 100ms-Zyklustimer, der ein festes Zeitraster vorgibt (nötig z.b. für die Regler oder Filter usw.) Darin setze ich ein Flag ("Data new") auf 0, wenn ich die PPM-Daten in der main übernommen habe. Die UART-Funktion aktiviere ich nur, wenn "Data new" auf ß liegt, und nach dem ersten Durchlauf des Strings wird das wieder 1.
Habs probiert, das geht, 100ms ist zwar müde für eine Steuerfunktion, aber für ein Schiff langt das.
Was ich nicht ganz kapiere ist, wieso ein Timeout den UART blockierend macht.
Was ich nicht ganz kapiere ist, wieso ein Timeout den UART blockierend
macht.
Ein timeout von zB. 20ms blockiert den UART für bis zu 20ms während auf die gewünschte Anzahl Bytes gewartet wird.
Wenn die Daten schneller (oder sofort) da sind, muss nicht blockiert werden.
Solltest du mehrere Dinge quasi-parallel machen wollen, dann schau dir die Möglichkeiten von asyncio an. Das ist praktisch (und stark vereinfacht) kooperatives Multitasking.
Einer dieser Tasks könnte kontinuierlich die UART Daten entgegen nehmen und in Blöcke packen.
Ein weiterer könnte analysieren und ein dritter zB. eine Steuerung aktivieren.
Alles in den Docs.
Schlechte Idee. Wenn keine Daten kommen, was dann?
Das ist quasi das Funktionsprinzip dahinter. Irgendwie (beim SUMD noch nicht realisiert) überwache ich, ob der Sender noch da ist. Bisher, beim PPM-Impulssignal, habe ich das in der Interruptstruktur "nebenher" gemacht. Der weggefallene (ausgeschaltete) Sender ist der Auslöser für die autonome Fahrt (waypoints oder home)
Das ist praktisch (und stark vereinfacht) kooperatives Multitasking.
Das mach ich mit einem NPRTM-Scheduler. Feste Zeitschlitze für die verschiedenen Tasks (deren Laufzeiten ich kenne). Vermeidet alle Probleme von kooperativem MT.
Warum so lahmarschig? Für 100ms brauchst du keinen Timer sondern einen
Abreißkalender.
Das stimmt natürlich. Momentan hab ich wenige Prozent Auslastung, das geht locker auch viel schneller. Aber das mach ich, wenn überhaupt (es langt echt für ein Schiff..), erst später, wenn der Rest fertig ist.
Und, sehr schlimm?
Nachgemessen: das Lesen der 12 Kanaldaten-Bytes dauert gemessen rund 1ms.
(Gerechnet bei 120Bit und 115200 Baud: 1.03 ms)
Der Plan wäre jetzt, aus dem timeout der UART das Fehlen des Sendesignals abzuleiten. Wäre viel eleganter als irgendwelche Messungen von Interrupts.
Aber : ich krieg's nicht hin. Wie werte ich die Überschreitung der maximalen Lesezeit aus? "Flush" ist offenbar fürs Schreiben, ein "try/except" klappt auch nicht. Hat wer 'nen Tipp (einen "Spoiler" sozusagen?
Zunächst einmal, die CRC16 sollte unbedingt geprüft werden.
Das dauert für einen kompletten Datenblock um die 35µs…45µs **, stellt also praktisch keinerlei Overhead dar. Gibt aber so ein warmes Gefühl im Bauch.
sumdbytes = uart1.read(12)
sollte anstelle von 12 die vorher ermittelte Zahl*2 sein.
Ein vorkonfiguriertes Array kann mittels readinto() wiederverwendet werden.
Ungleich schneller und weniger GC.
Und wenn du testest auf flag = zahl2 == uart.readinto(sumdbytes, zahl2), hast du direkt ein Flag ob's gut ging.
**Korrektur: Das war mit dem RP2350
Mit dem RP2040 dauert's 57.5µs
Die Kanalzahl ist eigentlich egal, weil ich nur den einen Graupner-Sender habe, und der hat immer 6. Prinzipiell hast du aber natürlich recht.
Die Erkennung, ob der Sender noch da ist, kann auch elegant der Hott-Empfänger selber. Man kann ihn so einstellen, daß auch bei Senderausfall ein Summensignal ansteht (sonst wirds schwierig, dann bleibt z.b. meine Suchschleife nach "168" hängen), und dann wird ein Fail-Safe Signal abgegeben, oder noch einfacher ist's, die "0x01" im Kopf von Sumd auswerten : 1 = ok, 81 = Sender weg.
Ich grüble gerade ein wenig über die Taskumschaltung.
Der Vorschlag, asyncio zu benutzen, ist interessant.
Aktuell läuft es so, daß ein Timer einen festen Scheduling-Takt vorgibt. In 10ms-Raster wird die main "losgelassen". Damit werden die schnellsten Aufgaben in Funktionsaufrufen bearbeitet. Natürlich muß dazu die Laufzeit all dieser Aufgaben unter 10ms sein, weil ich eine Taskunterbrechnug im Sinne eines präemptiven MT nie selber hinkriege. Als Schicht drüber läuft in diesem Raster ein Zähler, der die 10 ms durch n teilt (aktuell n=5). In diesem 50ms-Raster werden dann weniger schnelle Aufgaben erledigt. Dieses System läßt sich beliebig komplex auslegen. Man muß dazu nur die Laufzeiten der Aufgaben und die nötige Häufigkeit ihres Aufrufs kennen. Ganz am Anfang der Prozessrechentechnik wurde das mal so gemacht, die Methode hies: "non-preemtive-realtime-multiplexing" (oder -multitasking) NPRM.
Alternative asyncio. Wenn ich das richtig verstehe macht es ein nicht viel anderes timing. Es wird nicht unterbrochen. Das beim "normalen" kooperativen MT erfolgende Scheduling meist bei i/o-Vorgängen ("blocked") wird hier im Normalfall durch Pause-Anweisungen erzwungen. (Damit könnte man sogar ein gezieltes Unterbrechen vor Taskende provozieren). Ist das besser als NPRM ?
Ich grüble gerade ein wenig über die Taskumschaltung.
Der Vorschlag, asyncio zu benutzen, ist interessant.
Ganz am Anfang der Prozessrechentechnik
wurde das mal so gemacht, die Methode hies:
"non-preemtive-realtime-multiplexing" (oder -multitasking) NPRM.
Alternative asyncio. Wenn ich das richtig verstehe macht es ein nicht
viel anderes timing. Es wird nicht unterbrochen. Das beim "normalen"
kooperativen MT erfolgende Scheduling meist bei i/o-Vorgängen
("blocked") wird hier im Normalfall durch Pause-Anweisungen erzwungen.
(Damit könnte man sogar ein gezieltes Unterbrechen vor Taskende
provozieren). Ist das besser als NPRM ?
Ganz ehrlich? Alles ist besser im Jahre 2024. ;-)
Selbst eine einzelne Zeile innerhalb eines asyncio Tasks:
1
await uasyncio.sleep_ms(0)
gibt die Kontrolle instantan an den asyncio Scheduler zurück und der lässt den nächsten Task weiter laufen.
Wenn kein anderer etwas zu tun hat (alle im Wartemodus, was fast immer der Normalzustand ist), kommt der Bumerang schneller zurück als du ›hi‹ sagen kannst. Wir reden von tausenden von Taskswitches pro Sekunde.
1
await uasyncio.sleep_ms(time_in_milliseconds)
erlaubt es natürlich auch, eine verabredete Zeit zu warten.
Und jeder separate Task kann sich ein Timestamp holen und die nächste Wartezeit für sich jeweils dynamisch berechnen.
asyncio ist aber nicht für Mikrosekunden-genaues Arbeiten. Dafür gibt es andere Techniken. (und fast immer Hardware welche das besser als jede CPU macht)