Forum: Mikrocontroller und Digitale Elektronik Sumd (Graupner) mit Micropython auswerten


von Reiner D. (dollreiner)


Lesenswert?

Hallo zusammen.

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.

An UART1 des ESP32 empfängt Micropython :

wert :  b'\xa8'
wert :  b'\x01'
wert :  b'\x06'
wert :  b'.'
wert :  b'\xd8'
wert :  b'.'
wert :  b'\xc8'
wert :  b'3'
wert :  b'('
wert :  b'/'
wert :  b'\x08'
wert :  b'.'
wert :  b'\xe0'
wert :  b'.'
wert :  b'\xe0'
wert :  b'\xfb'
wert :  b'\xe8'

168, 1, 6 : das passt! Aber was kommt denn nun daher???

Hier mein Code :
1
import machine
2
3
uart1 = machine.UART(1, baudrate=115200, tx=12, rx=13)
4
uart1.init(115200, bits=8, parity=None, stop=1) 
5
6
while 1:
7
    chari = uart1.read(1)    
8
    if chari is not None:
9
        print("wert : ",chari)

..ich komme einfach nicht weiter. Ich schätze, ich kapier einfach die 
Codierung nicht ;-(

von Stephan S. (uxdx)


Lesenswert?

mach mal in Dein uart1.init(...) ein timeout rein und dann

string = uart1.read()

Quelle https://docs.micropython.org/en/latest/library/machine.UART.html

von Walter T. (nicolas)


Lesenswert?

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.

: Bearbeitet durch User
von Reiner D. (dollreiner)


Lesenswert?

Walter T. schrieb:
> 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 ??

von Norbert (der_norbert)


Lesenswert?

Reiner D. schrieb:
> Walter T. schrieb:
>> 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.

von Walter T. (nicolas)


Lesenswert?

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.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Norbert schrieb:
> pack und unpack basteln alle mögliche Zeug zusammen und wieder
> auseinander.
1
#!/python
2
import struct
3
a,b = 0x33,0x44
4
# Konvertiert zwei unsigned 8bit in ein unsigned 16bit
5
c = struct.unpack('H', struct.pack('2B', a,b))[0]
6
# c=17459

Ausgabe noch vergessen:
1
#!/python
2
print(f'{c:05} {a:02x} {b:02x}')

: Bearbeitet durch User
von Robert M. (r_mu)


Lesenswert?

Das "b" vorne heisst so viel dass das eine byte-wurscht ist und kein 
(UTF-8) string. in eine zahl bekommt man mas mit int.from_bytes(bytes, 
byteorder) https://docs.micropython.org/en/latest/library/builtins.html

von Stephan S. (uxdx)


Lesenswert?

aus b'.' machst Du folgendes:
1
a = b'.'
2
int(a.hex(), 16)

von Stephan S. (uxdx)


Lesenswert?

>> 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 ??
1
a = b'.'
2
c = int(a.hex(), 16)

von Norbert (der_norbert)


Lesenswert?

Ernsthaft?

Welche Kapriolen denn noch?
1
#!/python
2
# Das ist empfangen worden:
3
ba = bytes((b'\xa8\x01\x06.\xd8.\xc83(/\x08.\xe0.\xe0\xfb\xe8'))
4
# Und nu schau'n mer mal:
5
for index,byte in enumerate(ba):
6
    print(f'{index:2}  0x{byte:02x} ({byte})')
7
#  0  0xa8 (168)
8
#  1  0x01 (1)
9
#  2  0x06 (6)
10
#  3  0x2e (46)
11
#  4  0xd8 (216)
12
#  5  0x2e (46)
13
#  6  0xc8 (200)
14
#  7  0x33 (51)
15
#  8  0x28 (40)
16
#  9  0x2f (47)
17
# 10  0x08 (8)
18
# 11  0x2e (46)
19
# 12  0xe0 (224)
20
# 13  0x2e (46)
21
# 14  0xe0 (224)
22
# 15  0xfb (251)
23
# 16  0xe8 (232)
24
25
# Und da das erste Servo bei index 3 & 4 liegt:
26
servopos = struct.unpack('H', struct.pack('2B', ba[4],ba[3]))[0]
27
# oder wie robert vorschlug:
28
servopos = int.from_bytes(ba[3:4+1], 'big')

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


Lesenswert?

Norbert schrieb:
> servopos = struct.unpack('H', struct.pack('2B', ba[4],ba[3]))[0]

Oder kurz:
1
servopos, = struct.unpack('>H', ba[3:5])

von Norbert (der_norbert)


Lesenswert?

Yalu X. schrieb:
> Norbert schrieb:
>> servopos = struct.unpack('H', struct.pack('2B', ba[4],ba[3]))[0]
>
> Oder kurz:
> servopos, = struct.unpack('>H', ba[3:5])

Na dann aber wenigstens: servopos,_ = …

Irgendein PEP (war's 8?)
Da wird sofort ersichtlich, dass ein Teil des Tupels brachial 
weggeworfen wird. ;-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Norbert schrieb:
> Yalu X. schrieb:
>> Norbert schrieb:
>>> servopos = struct.unpack('H', struct.pack('2B', ba[4],ba[3]))[0]
>>
>> Oder kurz:
>> servopos, = struct.unpack('>H', ba[3:5])
>
> Na dann aber wenigstens: servopos,_ = …

Nein, struct.unpack liefert im konkreten Fall ein Tupel mit 1 Element,
weswegen man auch nur eines extrahieren kann.

Norbert schrieb:
> Irgendein PEP (war's 8?)
> Da wird sofort ersichtlich, dass ein Teil des Tupels brachial
> weggeworfen wird. ;-)

Es muss auch kein Teil des Tupels brachial weggeworfen werden.

: Bearbeitet durch Moderator
von Norbert (der_norbert)


Lesenswert?

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.

von Reiner D. (dollreiner)


Lesenswert?

Yalu X. schrieb:
> n = uart1.read(1)[0] # Anzahl Servos

Was macht das [0]?

von Norbert (der_norbert)


Lesenswert?

Reiner D. schrieb:
> Was macht das [0]?

Das erzeugt eine Exception wenn innerhalb des Timeouts keine Daten 
eintrudeln.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Norbert schrieb:
> Ich formuliere aber gerne um: Durch x,_ = … wird erkennbar dass man nur
> am ersten Wert des Tupels interessiert ist.

Genauer gesagt: ..., dass man nur am ersten Werte eines Tupels mit genau
2 Elementen interessiert ist.

Das Muster auf der linken Seite der Zuweisung muss immer exakt zur
Listen-/Tupel-Struktur auf der rechten Seite passen.

> (auch wenn in diesem Fall das Tupel nur einen Wert enthält)

Das genau ist das Problem. Hat das Tupel nur 1 Element oder mehr als 2
Elemente, gibt es Schelte:
1
>>> x,_ = (11, 22)
2
>>> x
3
11

Soweit ok. Aber:
1
>>> x,_ = (11,)
2
Traceback (most recent call last):
3
  File "<stdin>", line 1, in <module>
4
ValueError: not enough values to unpack (expected 2, got 1)

Und:
1
>>> x,_ = (11, 22, 33)
2
Traceback (most recent call last):
3
  File "<stdin>", line 1, in <module>
4
ValueError: too many values to unpack (expected 2)

Das ist hier also keine Frage des Stils und hat deswegen nichts mit PEP
8 zu tun.

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


Lesenswert?

Norbert schrieb:
> Reiner D. schrieb:
>> Was macht das [0]?
>
> 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.

von Norbert (der_norbert)


Lesenswert?

Yalu X. schrieb:
> 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.

von Reiner D. (dollreiner)


Lesenswert?

Vielen Dank für eure Mühe zunächst!

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 :
1
import machine
2
3
uart1 = machine.UART(1, baudrate=115200, tx=12, rx=13)
4
uart1.init(115200, bits=8, parity=None, stop=1) 
5
6
while 1:
7
    chari = uart1.read(1)
8
    while(chari != b'\xa8'):    # warte auf die 168
9
        chari = uart1.read(1)        
10
    uart1.read(1)               # 0x01 lesen
11
    n=uart1.read(1)             # Anzahl Servos (=6)
12
    if(n is not None):
13
        #print(n)
14
        h,l = uart1.read(2)         # 
15
        wert=h*256+l
16
        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.

von Norbert (der_norbert)


Lesenswert?

Reiner D. schrieb:
> Vielen Dank für eure Mühe zunächst!
>
> 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.

: Bearbeitet durch User
von Reiner D. (dollreiner)


Lesenswert?

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.

von Norbert (der_norbert)


Lesenswert?

Reiner D. schrieb:
> Also versuche ich die "main" mit der Uart
> zu "synchronisieren".

Schlechte Idee. Wenn keine Daten kommen, was dann?

Reiner D. schrieb:
> 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.

PS.

Reiner D. schrieb:
> In der main haben ich einen 100ms-Zyklustimer,

Warum so lahmarschig? Für 100ms brauchst du keinen Timer sondern einen 
Abreißkalender.

: Bearbeitet durch User
von Reiner D. (dollreiner)


Lesenswert?

Norbert schrieb:
>>> 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.

: Bearbeitet durch User
von Reiner D. (dollreiner)


Lesenswert?

Das Decodieren mit der UART klappt prima, dank eurer Hilfe !
So sieht das jetzt aus:
1
import machine
2
import config
3
4
uart1 = machine.UART(1, tx=12, rx=13)
5
uart1.init(115200, bits=8, parity=None, stop=1, timeout=10) 
6
7
oszi = machine.Pin(27, machine.Pin.OUT)  #für testmessungen
8
9
def read_sumd():
10
    
11
    cha = uart1.read(1)
12
    while(cha != b'\xa8'):    # warte auf die 168
13
        cha = uart1.read(1)
14
    
15
    eins=uart1.read(1)        # 0x01 lesen
16
    zahl=uart1.read(1)        # Anzahl Servos (=6)
17
18
    oszi.on()
19
    
20
    sumdbytes=uart1.read(12)
21
    config.kanal[0] = sumdbytes[0]*256 + sumdbytes[1]
22
    config.kanal[1] = sumdbytes[2]*256 + sumdbytes[3]
23
    config.kanal[2] = sumdbytes[4]*256 + sumdbytes[5]
24
    config.kanal[3] = sumdbytes[6]*256 + sumdbytes[7]
25
    config.kanal[4] = sumdbytes[8]*256 + sumdbytes[9]
26
    config.kanal[5] = sumdbytes[10]*256 + sumdbytes[11]        
27
28
    oszi.off()

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?

von Norbert (der_norbert)


Lesenswert?

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 = zahl*2 == uart.readinto(sumdbytes, 
zahl*2), hast du direkt ein Flag ob's gut ging.

**Korrektur: Das war mit dem RP2350
Mit dem RP2040 dauert's 57.5µs

: Bearbeitet durch User
von Reiner D. (dollreiner)


Lesenswert?

Wow, danke für den Haufen Info.
Da brauch ich jetzt ein bisschen ...

von Reiner D. (dollreiner)


Lesenswert?

CRC ist klar, schon der "Eleganz" wegen ;-)

Array auch.

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.

Beitrag #7772223 wurde vom Autor gelöscht.
von Reiner D. (dollreiner)


Lesenswert?

Noch Lust, über Micropython zu plaudern ?

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 ?

von Norbert (der_norbert)


Lesenswert?

Reiner D. schrieb:
> Noch Lust, über Micropython zu plaudern ?
>
> 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)
1
#!/python
2
# -*- coding: utf-8 -*-
3
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
4
5
import uasyncio
6
from array import array
7
import sys
8
9
async def task(counters, index, wait_ms):
10
    while True:
11
        await uasyncio.sleep_ms(wait_ms)
12
        counters[index] += 1
13
14
async def main(counters):
15
    uasyncio.create_task(task(counters, 0, 0))
16
    uasyncio.create_task(task(counters, 1, 1))
17
    uasyncio.create_task(task(counters, 2, 5))
18
    uasyncio.create_task(task(counters, 3, 10))
19
    await uasyncio.sleep_ms(5_000)
20
21
counters = array('I', (0,0,0,0))
22
uasyncio.run(main(counters))
23
print(counters)
24
#   array('I', [13708, 4999, 999, 499])

: Bearbeitet durch User
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
Noch kein Account? Hier anmelden.