Ich ziehe I2S Daten (16bit PCM, Big Endian, 2 Kanäle) mit 4 MBit/s über
USB via Raspi Zero vom FPGA auf eine SD-Karte, dabei verliert der
Empfänger gelegentlich die ersten Bytes. Resultiert daraus ein
Frame-Fehler, ist auf beiden Kanälen effektiv nur weißes Rauschen.
Der Fehler lässt sich manuell einfach beheben - ein oder drei Bytes
entfernen bis der Rahmen wieder stimmt. Schön wäre es, wenn das sich mit
irgendeiner Heuristik automatisieren ließe. Natürlich könnte ich eine
Präambel mitsenden, aber die müsste sendeseitig eingefügt werden. Gibt
es noch andere Möglichkeiten, weisses Rauschen in einer begrenzten
Anzahl führender Frames zu detektieren?
Burkhard K. schrieb:> Ich ziehe I2S Daten (16bit PCM, Big Endian, 2 Kanäle) mit 4 MBit/s über> USB via Raspi Zero vom FPGA auf eine SD-Karte, dabei verliert der> Empfänger gelegentlich die ersten Bytes.
Wie werden die Daten denn zwischen FPGA und Raspberry übertragen? Ich
würde da eher mal nach der Ursache gucken und die beheben.
Burkhard K. schrieb:> Schön wäre es, wenn das sich mit> irgendeiner Heuristik automatisieren ließe.
Klar, wenn der neu empfangene Abtastwert nicht zu den schon vorher
empfangenen passt. Wobei das halt auch schwierig wird wenn die
Abtastrate im Vergleich zur Frequenz gering ist.
Mit 16 Bits hast du leider keine freien Bits in zwei Bytes. Du könntest
aber für jeden Abtastwert 3 Bytes übertragen und die Bits dann so
verteilen:
00000DD0 DDDDDDD1 DDDDDDD1
Dann ist zumindest mit den 3 LSBs der Rahmen klar. Zuerst muss eines mit
0 hinten kommen und dann zwei mit 1 hinten.
Ich verwende UART als Übertragung zwischen FPGA und PC. Verlorene Bytes
hatte ich noch nie.
Gustl B. schrieb:> Ich würde da eher mal nach der Ursache gucken und die beheben.
Die ist bekannt: RaspianOS auf dem Zero ist kein RealTime-OS und das
Dateisystem liegt genau auf der SD-Karte, auf der die Daten landen.
Schau mir deshalb gerade Tiny Core
(http://tinycorelinux.net/11.x/armv6/releases/RPi/) an. Bei Übertragung
zum Laptop tritt das Problem nicht auf, aber den kann/will ich nicht
"ins Feld" mitnehmen.
Gustl B. schrieb:> für jeden Abtastwert 3 Bytes übertragen
12MBaud sind bereits das Limit meines UARTS (nicht 4MBaud wie
fälschlicherweise oben geschrieben). Deshalb (und aus weiteren Gründen)
will ich das Einfügen von zusätzlichen Bytes lieber vermeiden.
Gustl B. schrieb:> Klar, wenn der neu empfangene Abtastwert nicht zu den schon vorher> empfangenen passt.
Das war meine Frage - wie läßt sich weißes Rauschen, d.h. fehlende
Korrelation der Abtastwerte mit einem (z.B. numpy-)Skript möglichst
einfach diagnostizieren? Visuell ist die Situation ja eindeutig.
Inzwischen gefunden:
https://machinelearningmastery.com/white-noise-time-series-python/
Der Mittelwert tendiert gegen 0 und die Standardabweichung gegen 1.
Werde heute mal schauen, ob ich mit meinen Daten passende Schwellwerte
finde und wieviele Pakete ich anschauen muss, um eine sichere
Entscheidung zu treffen.
Burkhard K. schrieb:> RaspianOS auf dem Zero ist kein RealTime-OS und das> Dateisystem liegt genau auf der SD-Karte, auf der die Daten landen.
Das trifft aber auch auf den Laptop zu. Welchen UART Baustein verwendest
du? Vielleicht kannst du größere Puffer einstellen oder den häufiger
abfragen.
Burkhard K. schrieb:> Das war meine Frage - wie läßt sich weißes Rauschen, d.h. fehlende> Korrelation der Abtastwerte mit einem (z.B. numpy-)Skript möglichst> einfach diagnostizieren?
Tja ... finde ich nicht sehr zielführend. Du wirst da immer einige viele
Abtastwerte verlieren.
I2S hat mit Word-select eine Synchronisierung, USB ist auch eher
packetorientiert.
Ich denke dein Problem ist das Protokoll was du über USB schiebst.
Vielleicht solltest du da einen Frame-Sync einbauen.
BTW. I2S müsste auch auf der Stiftleiste des RPI liegen.
Ich danke Euch für Eure Bemühungen, mich auf den rechten bzw
zielführenden Weg zu bringen. Wie gesagt arbeite ich bereits daran, das
Problem der "dropped Bytes" zu eliminieren.
Das ist aber nicht Thema dieses Threads. (Das Kind liegt bereits im
Brunnen und ich habe eine Sammlung an "kaputten" Daten, die ich trotzdem
auswerten will.) Inzwischen habe ich ein bisschen mit numpys std(),
median() und histogram() herumgespielt. Damit lässt sich die Situation
tatsächlich recht gut erfassen, ich muss jetzt nur noch geeignete
Schwellwerte bestimmen.
@Gustl - tatsächlich ist die von mir ursprünglich gewählte Kombination
aus Zero, RaspianOS und SD-Karte der Grund für die "dropped Bytes":
* Single CPU
* 512 MByte RAM
* Filesystem auf SD-Karte
Der empfangende Prozess konkurriert mit dem Betriebssystem um die
einzige CPU, selbst wenn er mit RT-Priorität läuft. Hinzu kommen
Latenzen durch das Logging auf die SD-Karte bzw. Lesen von derselben.
Tiny Core sollte helfen, da es nach dem Booten nur noch vom RAM-FS
läuft. Wenn nicht, muss ich wohl auf ein Gerät mit mehr als einer CPU
(und höherem Stromverbrauch) ausweichen. Oder auf Ethernet.
yesitsme schrieb:> ist das Protokoll was du über USB schiebst
Das Protokoll (Seriell über USB) ist nicht das Problem, siehe oben.
Burkhard K. schrieb:> Der empfangende Prozess konkurriert mit dem Betriebssystem um die> einzige CPU, selbst wenn er mit RT-Priorität läuft.
Ja, genau. Verwendest du Python um vom UART aufzunehmen?
Wie machst du das? Das kann man blocking und nonblocking machen.
Welche Datenmenge musst du denn in welcher Zeit wegschaufeln? Der UART
kann 12 MBaud, aber wie viele Bytes/s müssen tatsächlich übertragen
werden?
Wie machst du das im FPGA, schreibt der stumpf Bytes raus sobald der
UART wieder Ready meldet oder sendet der nur auf Anfrage vom PC?
Ich habe für mich zwei Varianten gebaut:
1. Stumpfes Senden. Der FPGA sendet ohne Anfrage. Da lese ich dann am PC
immer. Allerdings weiß ich beim Lesen natürlich nicht wo im Paket ich
neue Daten bekomme. Da muss man dann also eine Art Schieberegister
implementieren. Ganz grob (mit 3 Bytes und LSB Framing):
1
ser = serial.Serial("\dev\gerät", 921600, timeout=0)
2
werte = []
3
sr = []
4
und dann regelmäßig:
5
data = ser.read(100000) #lese alles was im puffer steht
6
if len(data) > 0:
7
sr.extend(data)
8
if len(sr) >= 3:
9
if (sr[0] & 1 == 0) and (sr[1] & 1 == 1) and (sr[2] & 1 == 1):
2. Senden auf Anfrage. Im FPGA werden die Werte in einen FIFO
geschreben. Der PC fragt dann eine Anzahl an Werten an und leist so
lange bis er die auch bekommt.
Weil da bekannt ist wie viele Werte kommen und wo der Anfang ist braucht
es kein Framing.
1
ser = serial.Serial("\dev\gerät", 921600, timeout=0)
2
werte = []
3
und dann regelmäßig:
4
N = 10 # also 2**10 zu lesende Werte je Lesetransaktion
5
uart_data = bytearray()
6
ser.write(bytearray([10]))
7
ser.flushOutput()
8
ser.flushInput()
9
while ((len(uart_data) < 2**(N+1): # also 2**11 Bytes bei 2 Byte je Wert
Das Problem bei begrenzter Rechenleistung ist, dass wenn du jetzt Fehler
zu erkennen versuchst, dann braucht das auch Rechenleistung. Du brauchst
dann also noch mehr Rechenleistung oder es gibt mehr Fehler.
Manchmal hilft etwas Abstand zum Problem:
1. Die einfachste Heuristik, um Framing-Fehler zu erkennen, schaut auf
die Dateigröße in Bytes:
1
frame_offset=(len(fn)%4);
2
if(frame_offset){
3
remove_initial_bytes_from_file(fn,frame_offset);
4
}
Erkennt natürlich nicht alle Fehler, aber den größten Teil - und den
verbleibenden Rest kann ich manuell bearbeiten. Korrelation und weisses
Rauschen können in der Schublade bleiben. Merke: "Too much DSP can make
you short sighted" ;)
2. Das Schreiben einer mehrere MByte großen Datei auf SD kann ein paar
Sekunden dauern - in dieser Zeit entnimmt der empfangende Prozess keine
Bytes aus dem Puffer. Abhängig von der gewählten Puffergröße kann es zum
Overrun kommen. Um den zu vermeiden, muss entweder der Puffer vergrößert
werden (1 sec entspricht grob 1 MByte) oder aber das Schreiben vom
Empfang der Daten abgekoppelt werden (eigener Prozess, Datei zunächst im
RAM erstellen). Werd mal schauen, wie ich das am besten in Python
abfackele (z.B. SpooledTempFile)
@Gustl - mein Zero ist nicht nur "kopf-" sondern auch "handlos", d.h.
keine Eingabegeräte. Der Datentransfer wird vom FPGA angestoßen. Die
Daten kommen dort aus dem RAM. CTS ist FPGAseitig (Nexys4 DDR) ein
Ausgang, somit eine Flusssteuerung per Hardware damit nicht
realisierbar. (Habe es auch mit RTS probiert - das funktionierte aus
irgendwelchen Gründen nicht).
Auf jeden Fall danke für Eure Beiträge.
Auch ohne CTS oder Eingabegeräte geht das mit der Anfrage.
Der Raspberry schickt für jede Anfrage ein Byte zum FPGA. In dem Byte
steht drinnen wie viele Werte das FPGA senden soll. Und dann sendet das
FPGA die geforderte Anzahl. Dafür braucht man nur RX und TX.
Ja, Schreiben und Lesen kannst du in zwei Threads unterteilen. Oder du
schreibst jeweils nur wenige Byte auf Karte. Also z. B. 1kByte von UART
empfangen und sofort auf Karte schreiben. Dann die nächsten 1kByte
anfordern, der FPGA schickt die und dann schreibt man die weg.
Burkhard K. schrieb:> Das Schreiben einer mehrere MByte großen Datei auf SD kann ein paar> Sekunden dauern - in dieser Zeit entnimmt der empfangende Prozess keine> Bytes aus dem Puffer. Abhängig von der gewählten Puffergröße kann es zum> Overrun kommen.
Mit obiger Vermutung lag ich offenbar daneben - das eigentliche Problem
ist wohl die beschränkte Puffergröße des FT2232H internen FiFos: ~4000
bit. Jetzt lese ich häufig kleine Batches (<= 512 Byte) - seit dem
klappt es mit dem Datentransfer - auch ohne weiteren Protokolloverhead.
Den Hinweis auf die FiFo-Größe habe ich hier gefunden:
https://stackoverflow.com/questions/57592288/pylibftdi-device-read-skips-some-bytes