Forum: Digitale Signalverarbeitung / DSP / Machine Learning I2S Frame-Fehler automatisch erkennen/korrigieren


von Burkhard K. (buks)


Angehängte Dateien:

Lesenswert?

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?

von Gustl B. (-gb-)


Lesenswert?

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.

von Burkhard K. (buks)


Lesenswert?

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.

von Gustl B. (-gb-)


Lesenswert?

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.

von yesitsme (Gast)


Lesenswert?

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.

von Gustl B. (gustl_b)


Lesenswert?

Er überträgt die Daten per UART zum Raspberry. Also weder USB noch I2S.

von Burkhard K. (buks)


Lesenswert?

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.

von Gustl B. (-gb-)


Lesenswert?

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):
10
        wert = (sr[0] >> 1)*128*128 + (sr[1] >> 1)*128 + (sr[2] >> 1)
11
        werte.append(wert)
12
      else:
13
        sr = sr[1:len(sr)]
14
        print ("RESYNC")

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
10
    data = ser.read(2**(N+1))
11
    uart_data.extend(data)
12
  for x in range(0,N):
13
    werte.append((uart_data[x*2])*256+(uart_data[x*2+1]))

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.

: Bearbeitet durch User
von Gustl B. (-gb-)


Lesenswert?

Statt

if len(sr) >= 3:

muss da natürlich

while len(sr) >= 3:

stehen.

von Burkhard K. (buks)


Lesenswert?

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.

: Bearbeitet durch User
von Gustl B. (gustl_b)


Lesenswert?

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.

Beitrag #6472849 wurde von einem Moderator gelöscht.
von Burkhard K. (buks)


Lesenswert?

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

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.