Forum: Mikrocontroller und Digitale Elektronik FM auf STM32F767 demodulieren?


von Georg B. (Gast)


Lesenswert?

Hallo zusammen,

ich habe einen UKW-Superhet-Empfänger gebaut, welcher in das Basisband 
runtermischt (also wirklich auf 0Hz). Dieses Signal möchte ich mit einem 
ADC im Mikrocontroller abtasten und im Mikrocontroller demodulieren.
Das demodulierte Signal soll dann über einen DAC wieder ausgegeben 
werden und (verstärkt) auf einen Lautsprecher gegeben werden.

Die Hardware steht schon und funktioniert: Wenn ich statt ADC das Signal 
mit der PC-Soundkarte aufnehme, dann kann ich mit GNU-Radio demodulieren 
und es funktioniert!

Ebenso funktioniert der ADC und DAC schon: Wenn ich die Samples, die vom 
ADC kommen direkt über den DAC wieder ausgeben, dann liegen die Signale 
(im Oszi) schön übereinander. Der ADC läuft mit 1MSPS. Der 
Mikrocontroller ist ein STM32F767 der mit 216MHz läuft.

Nur mit dem FM-Demodulieren klappt es im Mikrocontroller nicht so.

Immer dann, wenn der ADC ein sample gesamplet hat, wird ein Interrupt 
aufgerufen, in welchem dieser Code läuft:
1
uint32_t adc_value_processed = (uint32_t)(((((int32_t)adc_value) - 1209) * 8) + 2048);    // remove offset and adjust scale
2
const int32_t K = 128;
3
int32_t new_arccos = K * arccos(adc_value_processed & 0xFFF);
4
int32_t dac_value_sig = new_arccos - old_arccos;
5
old_arccos = new_arccos;
6
uint32_t dac_value = (uint32_t)(dac_value_sig + ((int32_t)2048));
7
set_DAC1(dac_value & 0xFFF);

Die Funktion arccos() holt aus einer Lookup-Table die Arccosinus-Werte. 
Ihr Eingangsbereich ist 0..4095 und ihr Rückgabewert liegt im Bereich 
0..2047 (Datentyp int32_t).

Das Ergebnis ist ein Pfeifen (das schon mal zum Rythmus der Musik im 
Radio passt). Was ist das Problem, warum funktioniert dies hier nicht? 
Was könnte ich verbessern?

Vielen Dank schon mal für Hilfe!

von -gb- (Gast)


Lesenswert?

Das ist FM, eine bestimmte Frequenz am ADC soll also einer bestimmten 
Amplitude hinter deinem DAC entsprechen.

Du solltest also die Frequenz am Eingang bestimmen und dann da 
entsprechend eine Spannung am Ausgang ausgeben. Das ist ja linear, also 
kannst du sagen z. B. 0Hz am Eingang sind 0V am Ausgang. 1kHz am Eingang 
ist 1V am Ausgang. Eben je nach Frequenzbereich und Spannungsamplitude 
die du gerne hättest.

Ja wie misst man die Frequenz am Eingang? Z. B. indem man die Zeit 
zwischen zwei Nulldurchgängen misst.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Georg B. schrieb:
> (also wirklich auf 0Hz)

So richtig ganz echt wirklich - also mit Nachregelung des LO? Oder nur 
eher so ungefaehr 0Hz - dann musst du noch per "Derotator" das letzte 
bisschen Offsetfrequenz entfernen.

Gruss
WK

von ich hab gelernt (Gast)


Lesenswert?

Zum Nachdenken:

Ein HF-Träger der FM-moduliert ist und auf 0 Herz heruntergemischt
wird erzeugt als Augenblickswert auch negative Frequenzen, die
müssen auch irgendwo hin. Das ergibt beim Demodulieren dann was?

von W.S. (Gast)


Lesenswert?

Georg B. schrieb:
> in welchem dieser Code läuft:

Du erwartest nicht wirklich, daß ich mich da durch deine Quelle 
durchwurschtele - gelle?

Also, die NF bei FM ergibt sich durch die erste Ableitung des 
Empfangsvektors.

Du hast (hoffentlich) 2 Werte pro Sample vorrätig: I und Q.

Das ist dein Empfangsvektor in kartesischen Koordinaten. Jetzt mußt du 
selbigen in Polarkoordinaten wandeln. Dazu gibt es den CORDIC (hin und 
zurück, gelle..). Die Vektorlänge ist der AM-Anteil bzw. die Feldstärke 
und den Winkel mußt du nun differenzieren. Also die Differenz zwischen 
dem vorherigen Winkel und dem aktuellen Winkel bilden. Das ist erstmal 
deine NF - aber beaufschlagt mit dem DC-Anteil, den du als "AFC" 
benutzen könntest. Du mußt also deine Winkeldifferenz noch 
hochpaßfiltern, um den DC-Anteil rauszuwerfen.

W.S.

von H. Paul (Gast)


Lesenswert?

Es hat sich für mich, in einer ähnlichen Situation wie der TO, als 
nützlich erwiesen dieses Dokument zu lesen:

https://dspguru.com/files/QuadSignals.pdf

von Detlef _. (detlef_a)


Lesenswert?

>>>Das Ergebnis ist ein Pfeifen (das schon mal zum Rythmus der Musik im
Radio passt). Was ist das Problem, warum funktioniert dies hier nicht?
<<<<

Das funktioniert nirgendwo, weil die Umkehrfunktion des cos, arccos, 
nicht eindeutig ist. Du schaust für jeden Abtastwert was der zugehörige 
Winkel ist, da gibts aber unendlich viele und Du weißt nicht welcher 
paßt.

>>>Was könnte ich verbessern?

Wende ein Standardverfahren zur FM Demodulation an.
https://en.wikipedia.org/wiki/Demodulation#FM_radio

erste Möglichkeit: Du mischt mit einem Sinus und einem Cosinus herunter 
( Quadratur Demodulator), die I/Q Komponenten sind eine komplexe Zahl, 
die Demodulation ist die Bestimmung des Winkels zwischen zwei 
aufeianderfolgenden, das ist der Inhalt von Quadsignals.pdf oder von 
https://mriquestions.com/uploads/3/4/5/7/34572113/quad_signals_tutorial-lyons.pdf 
.

Zur Not kann man das einfach runtergemischte Signal durch ein 
Hilbert-Filter ( Quadratur Filter ) schicken, das eine 90° 
phasenverschobene Komponente nachträglich erzeugt, war hier auch schon 
mal fett Thema.

zweite und dritte Möglichkeit sind eine PLL und ein digitales Filter auf 
der Flanke, so haben das die Alten noch analog gemacht.

math rulez!
Cheers
Detlef

: Bearbeitet durch User
von Markus (Gast)


Lesenswert?

Wie wär's die Frequenz über die Nulldruchgänge zu bestimmen und diese an 
den DAC auszugeben?

von -gb- (Gast)


Lesenswert?

Na du misst die Zeit zwischen zwei Nulldurchgängen. Oder aber zu zählst 
die Anzahl der Nulldurchgänge die in einem bestimmten Zeitintervall 
auftreten.

Ich würde ersteres machen.

von Markus (Gast)


Lesenswert?

Wobei er in dem Fall natürlich nicht auf 0Hz runtermischen und 
zentrieren darf.

von Georg B. (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank für die Antworten und nach einiger Recherche der angegebenen 
Links bin ich nun auch soweit, dass ich es mit I und Q versuchen will.

Dazu habe ich meine Hardware so geändert, dass der 2. LO nicht mehr auf 
0Hz herunter mischt, sondern auf 60kHz.

Im Mikrocontroller habe ich einen Timer, der mir ein Signal theta 
liefert, sodass sin(theta) und cos(theta) verwendet werden können, um 
das vom ADC kommende Signal auf I und Q herunterzumischen.

Der Code sieht nun so aus:
1
int32_t adc_value_processed = (uint32_t)((((int32_t)adc_value) - ((int32_t)1209)) * ((int32_t)1));
2
3
// get sine and cosine values
4
int32_t sine_value, cosine_value;
5
sine_cosine(&sine_value, &cosine_value);    // -2047..2048
6
7
// downsample (which yields I and Q)
8
int32_t I = cosine_value * adc_value_processed;    // -4192256..4194304
9
int32_t Q = sine_value * adc_value_processed;      // -4192256..4194304
10
11
// demodulate FM
12
const int32_t K = 128;
13
int32_t I_diff = (I - I_old) * K;
14
int32_t Q_diff = (Q - Q_old) * K;
15
int32_t delta_theta = (((I*K) * Q_diff) - ((Q*K) * I_diff)) / ((I*I) + (Q*Q));
16
I_old = I;
17
Q_old = Q;
18
19
// adjust scale and offset and send out the sample using the DAC
20
int32_t M = 1;
21
int32_t P = 128;
22
uint32_t dac1_value = (uint32_t)(((P * delta_theta) / M) + ((int32_t)2048));
23
set_DAC1(dac1_value & 0xFFF);

Folgendes habe ich mir dabei gedacht:
Per DMA wird das ADC-Sample in die Variable "adc_value" geschrieben. 
Einen vorhandenen Offset entferne ich darauf hin und bringe das Signal 
auf den Bereich -2047..2048, damit ich mit Integern rechnen kann.
Die Funktion sine_cosine(...) holt aus einer Lookup-Tabelle die zum 
aktuellen theta passende Werte für Sinus und Cosine. (Das funktioniert 
auch, habe mal den Sinus und Cosinus über den DAC ausgegeben und das 
passt sogar).

Danach wird per Multiplikation heruntergemischt sodass man I und Q 
erhält.

Nun versuche ich nach diesem Algorithmus: 
https://www.embedded.com/dsp-tricks-frequency-demodulation-algorithms/ 
die FM zu demodulieren... Leider funktioniert das nun gar nicht mehr, 
deshalb habe ich mit ein paar Faktoren (K, P und M) herumgespielt, aber 
es kommt nun nur noch Rauschen aus dem Lautsprecher...

Hat jemand einen Tipp, was ich noch versuchen könnte?

Viele Grüße,
Georg

von W.S. (Gast)


Lesenswert?

Georg B. schrieb:
> Folgendes habe ich mir dabei gedacht:
> Per DMA...

Solange du kein wirklich tragfähiges Konzept hast, ist es zwecklos, 
Geschütze wie DMA aufzufahren.

Georg B. schrieb:
> Hat jemand einen Tipp, was ich noch versuchen könnte?

Ja.

Dazu brauchst du nicht einmal zu löten, sondern kannst das am PC in 
aller Ruhe per Software tun: das Ganze simulieren.

1. Senderseite:
a) Nimm dir ein nicht allzu langes Stück WAV her, irgend ein Jingle 
deiner Wahl, ca. 1..3 Sekunden lang und der Einfachheit halber nur mono 
in 22.050 kHz Samplerate.
b) Schreib dir ein Stückchen Programm, das WAV Input auf ne HF per FM 
aufmoduliert, also wieder der Einfachheit halber auf 1 MHz (also 
wenigstens 2 MHz Samples). Jaja, das gibt ne schön große Datei - 
entweder als schlichter Text von Samplewerten im Wertebereich -0.99999.. 
bis +0.99999.. oder z.B. als 16 Bit Words. Ist deine Wahl. Der Hub, den 
du anwenden willst, ist auch deine Wahl.

So, du hast nun eine Datei, die - wenn man sie senden würde, deinen 
Jingle auf 1 MHz abstrahlen würde.

2. Empfängerseite:
a) Schreib dir ein Stückchen Programm, das ein HF-Signal per I/Q-Mischer 
auf null herabmischt. Als LO-Frequenz solltest du aber nicht 1 MHz 
nehmen, das wäre unrealistisch. Kannst du für's erste machen, aber dann 
solltest du auch mal mit 0.99995 MHz und 1.00005 MHz mischen, um zu 
sehen, was dann passiert. Merke: einen absoluten Gleichlauf zwischen 
Sender und Empfänger gibt es nie wirklich. Also muß der Empfänger damit 
auch klar kommen. Diese Programmstück sollte auch gleich das Dezimieren 
erledigen. Wie weit du dezimieren mußt, wirst du ebenfalls ausprobieren 
müssen.

So, nun hast du eine weitere Datei mit I/Q-Wertepaaren.

b) Schreibe dir jetzt deine Programmstückchen, die das FM-Demodulieren 
bewerkstelligen sollen - zum Ausprobieren, bis du den richtigen 
Algorithmus gefunden hast.

W.S.

von Dergute W. (derguteweka)


Lesenswert?

Georg B. schrieb:
> Dazu habe ich meine Hardware so geändert, dass der 2. LO nicht mehr auf
> 0Hz herunter mischt, sondern auf 60kHz.

So richtig ganz echt wirklich - also mit Nachregelung des LO? Oder nur 
eher so ungefaehr 60kHz - dann musst du noch per "Derotator" das letzte 
bisschen Offsetfrequenz entfernen.

SCNR,
WK

von Georg B. (Gast)


Lesenswert?

Hallo,

das System holt seinen Takt zwar aus einem Quarzofen, aber nachgeregelt 
wird leider noch nicht. Ist das so kritisch?

Ok, ich werde das rausfinden, indem ich es so wie von W.S. vorgeschlagen 
mal simuliere. Vielen Dank für die Tipps schon mal!

von foobar (Gast)


Lesenswert?

Da wird gerade dabei sind, sei mir ein Frage zum Verständnis von 
I/Q-Signalen gestattet (bin ziemlicher Newbie in dem Bereich):

Hab ich das richtig verstanden, dass ich ein direkt gesampeltes Signal 
(z.B. mit 1MHz) in I- und Q-Signale splitten kann, indem ich aus jedem 
4er-Block den ersten Sample als I-Sample nehme, den zweiten als Q und 
den 3. und 4. wegschmeiße?  Die I/Q-Samples wären dann mit je 250kHz 
gesampelt (mit 90° Phasenverschiebung), die Nutzbandbreite halbiert 
(wegen der weggeschmissenen Samples).  Soweit korrekt?

Weiter: Kann ich die dritten und vierten Samples negieren und ebenfalls 
benutzen (also I,Q,-I,-Q), um die Bandbreite zu erhalten?

(Hab mir das aus der Hardware von einfachen SDR-Receivern hergeleitet; 
kann sein, dass ich total auf dem Holzweg bin.)

von Detlef _. (detlef_a)


Lesenswert?

>>>>>>>>>>>>>>>>>>>>>>
Der Code sieht nun so aus:

int32_t adc_value_processed = (uint32_t)((((int32_t)adc_value) - 
((int32_t)1209)) * ((int32_t)1));

// get sine and cosine values
int32_t sine_value, cosine_value;
sine_cosine(&sine_value, &cosine_value);    // -2047..2048

// downsample (which yields I and Q)
int32_t I = cosine_value * adc_value_processed;    // -4192256..4194304
int32_t Q = sine_value * adc_value_processed;      // -4192256..4194304
<<<<<<<<<<<<<<<<<<<<<<<<

So geht das nicht, Du muss zweimal runtermischen mit sin und cos und mit 
zwei ADCs sampeln. Wenn Deine hardware das nicht kann wirds delikat, wie 
ich schrub braucht man dann einen Hilbert Filter:

Beitrag "Breitband-NF-Phasenschieber, DSP mit AVR"
Beitrag "Phasenschieber 90 Grad"

Cheers
Detlef

: Bearbeitet durch User
von foobar (Gast)


Lesenswert?

Ich schrieb:
> ... Soweit korrekt?

Ich beantworte es selbst: Nein.  Habe nicht beachtet, dass die 
Mixer-/IQ-Demodulator-Frequenz nichts mit der Samplefrequenz zu tun hat. 
Was ich da mache, wäre ein weiteres Mischen mit Fsample/4 (mit 
IQ-Output).

von Rolf M. (rmagnus)


Lesenswert?

W.S. schrieb:
> Georg B. schrieb:
>> Folgendes habe ich mir dabei gedacht:
>> Per DMA...
>
> Solange du kein wirklich tragfähiges Konzept hast, ist es zwecklos,
> Geschütze wie DMA aufzufahren.

Das finde ich nicht. Denn das hier:

Georg B. schrieb:
> Der ADC läuft mit 1MSPS

in Kombination mit dem:

> Immer dann, wenn der ADC ein sample gesamplet hat, wird ein Interrupt
> aufgerufen

dürfte alleine durch die Interrupt-Last schon eine ganz ordentliche 
Prozessorlast erzeugen. Da erscheint mir DMA durchaus sehr sinnvoll.

von Markus (Gast)


Lesenswert?

>von foobar (Gast)
>29.03.2020 22:56

>Da wird gerade dabei sind, sei mir ein Frage zum Verständnis von
>I/Q-Signalen gestattet (bin ziemlicher Newbie in dem Bereich):

Lies mal das hier:
https://de.wikipedia.org/wiki/I%26Q-Verfahren

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

W.S. schrieb:
> Georg B. schrieb:
>> Folgendes habe ich mir dabei gedacht:
>> Per DMA...
>
> Solange du kein wirklich tragfähiges Konzept hast, ist es zwecklos,
> Geschütze wie DMA aufzufahren.

DMA ist kein Geschütz sondern Handwerkszeug wie ein UART.
Sowas muss sitzen ;)

von W.S. (Gast)


Lesenswert?

foobar schrieb:
> Hab ich das richtig verstanden, dass ich ein direkt gesampeltes Signal
> (z.B. mit 1MHz) in I- und Q-Signale splitten kann, indem ich aus jedem
> 4er-Block den ersten Sample als I-Sample nehme, den zweiten als Q und
> den 3. und 4. wegschmeiße?  Die I/Q-Samples wären dann mit je 250kHz
> gesampelt (mit 90° Phasenverschiebung), die Nutzbandbreite halbiert
> (wegen der weggeschmissenen Samples).  Soweit korrekt?

Nein.

Sondern nur so einigermaßen.

Der Knackpunkt ist, daß I und Q eigentlich zum exakt gleichen Zeitpunkt 
gehören sollen - und eben nicht zu zwei verschiedenen Zeitpunkten.

Wenn du also ein bereits digital vorliegendes Signal per I/Q-Mischer 
heruntermischen willst, dann multipliziere jedes Sample mit dem Sinus 
und dem Cosinus deines digitalen LO's und du erhältst damit deine beiden 
Ergebnisse I und Q. Da du für die weitere Verarbeitung die hohe 
Anfangs-Samplerate normalerweise nicht brauchst, kannst du dann I und Q 
gleichermaßen auf eine niedrigere Samplerate dezimieren. Z.B. per 
Hogenauer.

Rolf M. schrieb:
> dürfte alleine durch die Interrupt-Last schon eine ganz ordentliche
> Prozessorlast erzeugen. Da erscheint mir DMA durchaus sehr sinnvoll.

Das ist Quatsch. Bedenke doch mal, daß DMA lediglich Daten von A nach B 
schaufeln kann. Aber derartige Daten sollen nicht geschaufelt, sondern 
verarbeitet werden. Das kann DMA eben nicht, sondern nur ein 
Rechenwerk wie die CPU oder wenns für die zu schnell wird, dann eben ein 
Rechenwerk in einem FPGA, was dadurch schneller wird, daß man dort 
vieles parallel machen kann, was die CPU nur sequentiell tun kann.

Mich regen solche Gedankenlosigkeiten auf. Da hat jemand mal gehört, daß 
DMA eben schnell ist, also muß DMA auch die richtige Medizin sein, wenn 
es heißt, daß es hie und da eben schnell zugeht.

W.S.

von foobar (Gast)


Lesenswert?

> Wenn du also ein bereits digital vorliegendes Signal per I/Q-Mischer
> heruntermischen willst, dann multipliziere jedes Sample mit dem Sinus
> und dem Cosinus deines digitalen LO's und du erhältst damit deine beiden
> Ergebnisse I und Q.

Was ich gemacht habe, war ein weiteres Mischen mit Fsample/4.  Dabei 
läuft der Sinus/Cosinus in 90°-Schritten und jeweils einer wird 0 (der 
andere 1 oder -1) - dadurch entsteht das [I,Q,-I,-Q]-Muster, also aus 
S=[a,b,c,d] wird I=[a,0,-c,0] Q=[0,b,0,-d].

Muß mir jetzt nur noch überlegen, was das für das Ergebnisspektrum 
bedeutet (ist für mich alles Neuland) ;-)

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

W.S. schrieb:
> Rolf M. schrieb:
>> dürfte alleine durch die Interrupt-Last schon eine ganz ordentliche
>> Prozessorlast erzeugen. Da erscheint mir DMA durchaus sehr sinnvoll.
>
> Das ist Quatsch. Bedenke doch mal, daß DMA lediglich Daten von A nach B
> schaufeln kann. Aber derartige Daten sollen nicht geschaufelt, sondern
> verarbeitet werden. Das kann DMA eben nicht, sondern nur ein
> Rechenwerk wie die CPU
>
> Mich regen solche Gedankenlosigkeiten auf. Da hat jemand mal gehört, daß
> DMA eben schnell ist, also muß DMA auch die richtige Medizin sein, wenn
> es heißt, daß es hie und da eben schnell zugeht.

Du schnallst es einfach nur wieder nicht.
So ein IRQ hat ordentlich was an Overhead.
Das senkt die Zeit, die die CPU für die Berechnung selbst hat.
Wenn die Daten auf "magische weise" im RAM per DMA auftauchen hat man 
diese Rechenzeit frei.
So kann man sich einen Chunk an Daten per DMA schaufeln und dann gibts 
nur einen IRQ "Chunk fertig".
Auf dem wird dann gerechnet.

Auf einem F405 auf 168MHz hab ich da mal experimentiert.
Eine LED blinkt vor sich hin im busyloop und bei 1MSPS per IRQ vom 
internen ADC blinkte die dann sichtbar langsamer.
Das ist nicht zu unterschätzen!

von foobar (Gast)


Lesenswert?

> Bedenke doch mal, daß DMA lediglich Daten von A nach B
> schaufeln kann. Aber derartige Daten sollen nicht geschaufelt, sondern
> verarbeitet werden. Das kann DMA eben nicht,

Ich denke, Rolf spielte eher auf den per-Datum-Overhead an.  Eine 
Millionen Interrupts pro Sekunde wird jede CPU an ihre Grenzen bringen 
(selbst ohne "Verarbeitung").  Üblicherweise kann man die Daten im Batch 
auch effizienter bearbeiten (gemeinsamer Setup, Vector-Units, etc). 
Deshalb sammelt man solch große Datenmengen per DMA ein und lässt sie 
dann blockweise von der CPU verarbeiten.

von Rolf M. (rmagnus)


Lesenswert?

W.S. schrieb:
> Rolf M. schrieb:
>> dürfte alleine durch die Interrupt-Last schon eine ganz ordentliche
>> Prozessorlast erzeugen. Da erscheint mir DMA durchaus sehr sinnvoll.
>
> Das ist Quatsch. Bedenke doch mal, daß DMA lediglich Daten von A nach B
> schaufeln kann. Aber derartige Daten sollen nicht geschaufelt, sondern
> verarbeitet werden. Das kann DMA eben nicht, sondern nur ein
> Rechenwerk wie die CPU oder wenns für die zu schnell wird, dann eben ein
> Rechenwerk in einem FPGA, was dadurch schneller wird, daß man dort
> vieles parallel machen kann, was die CPU nur sequentiell tun kann.

Natürlich verarbeitet DMA die Daten nicht. Wie kommst du darauf, ich 
würde so einen Blödsinn glauben? Aber DMA kann die Daten sammeln, so 
dass der Prozessor einen ganzen Block auf einmal verarbeiten kann, statt 
für jedes einzelne Sample eine Million mal pro Sekunde unterbrochen zu 
werden und jedesmal separat zur ISR springen und die ausführen zu 
müssen. Gerade bei so hohen Datenraten ist deshalb die Benutzung von DMA 
eigentlich Standard. Man will dabei möglichst viel der Rechenkapazität 
für die eigentliche Rechenaufgabe zur Verfügung haben, statt schon das 
meiste davon für das hin- und herspringen zwischen Interrupt und 
Hauptprogramm zu verpulvern.

von W.S. (Gast)


Lesenswert?

foobar schrieb:
> Ich denke, Rolf spielte eher auf den per-Datum-Overhead an.

Das denke ich auch - aber das ist eigentlich nur Kosmetik.

Denk doch mal an nen FIR-Filter mit - sagen wir mal - 200 Taps und das 
2x (I und Q), und anschließend noch der Demodulator, also oftmals ein 
Cordic.  Da fällt das Sichern der Register nicht ins Gewicht, wenn man 
keinen Unfug treibt, und wenn die Hardware ordentlich gestaltet ist.

An so einer Stelle kommt jedoch der Groll hoch, was die Chiphersteller 
sich  gelegentlich geleistet haben. Völlig hirnrissige Cores sieht man 
z.B. bei einigen STM32-Typen, wo zum I2S umgebaute SPI-Cores die Daten 
nur 16 bittig empfangen/senden und obendrein keinerlei Fifo haben. Ein 
Sample dort per CPU abzuholen oder zu senden, würde 4 Interrupts/Sample 
bedeuten. Grausam.

Nochwas: Um die Algorithmen (speziell Filter) zu beschleunigen, werden 
die Samples normalerweise 2x abgelegt. Der Ringpuffer für die Samples 
ist dabei 2x so lang wie das Filter. Jedes Sample wird dann bei Stelle x 
im Filter und zusätzlich bei x+Filterlänge abgelegt. Dadurch kann der 
Filter durchbrettern, ohne sich um das Umschlagen des Index im 
Samplepuffer kümmern zu müssen. Das ist wesentlich für die 
Signalverarbeitung, wenn man diese auf einem Cortex erledigen will. Bei 
den DSP von AD ist das kein Problem, die haben intelligentere 
Adreßgeneratoren für das Ausführen des Filters. Aber ob so etwas ein DMA 
Core leisten kann, ist ja doch recht fraglich.

W.S.

von Georg M. (georg22)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

bin nun einen etwas anderen Weg gegangen:
Per Ethernet habe ich die ADC-Samples an den PC geschickt und als 
.csv-Datei abgespeichert.

Die Samplerate musste ich auf 500kSamples/s reduzieren, aber dann habe 
ich immer noch ca. 8-fache Überabtastung.
Der zweite LO war wieder so eingestellt, dass ich auf 50kHz, nicht auf 
0Hz herunter mische.
Die Datei mit den Samples habe ich mal angehängt, kann mir vielleicht 
jemand helfen es zu demodulieren?

Das Problem das ich nämlich habe ist folgendes:
Ich bin nicht sicher, ob mein Demodulationsalgorithmus das Problem ist, 
oder ob mit meiner Hardware/Antenne, etc... etwas nicht stimmt.

Über Hilfe würde ich mich sehr freuen!

Vielen Dank an alle!

von Detlef _. (detlef_a)


Lesenswert?

'Wire to wire' von Razorlight sagt Shazam. Kenn ick nich.

Das ist quick and dirty über ein 'analytisches Signal', das würde auch 
mit einem Hilbert Filter gehen. Deine samples sind ok.

Cheers
Detlef

clear

%load c:\Matlab\MYFILES\adc_samples_final.csv
%save c:\interim\ma.mat
load c:\interim\ma.mat
sam =adc_samples_final;
sam=sam(2:end); %gerade Anzahl
sam=sam-mean(sam); % DC raus
spsam=fft(sam);
n=length(sam);
spsam((n/2+1):end)=0; % Analytisches Signal basteln
spsam(1)=0; %DC nochmal raus
anasam=ifft(spsam);
diffangle=angle(anasam(2:end)./anasam(1:end-1)); % Winkeldifferenzen
diffangle(find(diffangle> 1.5))=[]; %Artefakte raus
diffangle(find(diffangle<-1.5))=[];
diffangle=diffangle-mean(diffangle); %DC raus
ton=decimate(diffangle,round(500000/8192)); % resample auf 8192er Audio
return

von Georg B (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Detlef,

wow, ich bin beeindruckt! Vielen Dank für das Code-Snipet!
Es scheint mir so, dass ich bei DSP einiges nachzuholen habe, aber jetzt 
ist die Motivation da, es zu lernen.

Damit ich auch etwas beitragen kann, stelle ich meine "Firmware" für den 
Mikrocontroller im Anhang zur Verfügung, vielleicht hilft es jemandem, 
der auch etwas per Ethernet übertragen möchte. Ist komplett selbst 
gebastelt (und natürlich nur quick and dirty!). Aber als Anregung kann 
es vielleicht jemandem dienen. Das Programm sendet die Daten "einfach" 
in Ethernet-Frames, ohne TCP/IP oder UDP oder soetwas zu benutzen. 
Empfangen kann man die Frames mit tcpdump oder Wireshark (oder einem 
kleinen C-Programm). Das war für mich halt die einfachste Methode, die 
Samples so schnell aus dem Mikrocontroller in den PC zu bekommen, denn 
z.B. UART ist zu langsam und mit "nur" 512K RAM kann ich auch nicht 
viele Samples im Mikrocontroller halten.

Werde nun aber weiter versuchen, direkt auch dem Mikrocontroller zu 
demodulieren. Mal so ein Hilbert-Transformator versuchen zu verstehen 
und zu implementieren. Beruflich komme ich aus dem Analogbereich und 
Hobbymäßig mache ich manches mit FPGA und Mikrocontroller. Darum brauche 
ich bei DSP etwas Hilfe.

Vielen Dank an alle!

von DH1AKF W. (wolfgang_kiefer) Benutzerseite


Lesenswert?

Georg B schrieb:
> Es scheint mir so, dass ich bei DSP einiges nachzuholen habe, aber jetzt
> ist die Motivation da, es zu lernen.

Man kann auch von Anderen lernen! Zum Beispiel:

https://github.com/DD4WH/Teensy-ConvolutionSDR/wiki/Links-&-Resources

https://forum.pjrc.com/threads/40590-Teensy-Convolution-SDR-(Software-Defined-Radio)

http://openwebrx.org/bsc-thesis.pdf  Abschnitt 10.5 FM- Demodulation

von Georg B. (Gast)


Lesenswert?


von Georg B. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

jetzt geht es ein bisschen ;-)
1
// remove offset and adjust scaling
2
int32_t adc_value_processed = (((int32_t)adc_value) - 1209) * 4;
3
4
// push back and add new element
5
for(uint32_t i = (N_STORE - 1); i >= 1 ; i--)
6
{
7
    samp_store[i] = samp_store[i - 1];
8
}
9
samp_store[0] = adc_value_processed;
10
11
// perform hilbert transformation by convolution
12
int32_t Q = 0;
13
int32_t n = N_HILBERT - 1;    // or 4?
14
for(int32_t k = 0; k < N_HILBERT; k++)
15
{
16
    if(((n - k) >= 0) && ((n - k) < N_HILBERT))
17
    {
18
        Q += (samp_store[k] * hilbert_coeffs[n - k]) / 1024;
19
    }
20
}
21
int32_t I = samp_store[4];
22
23
// FM demodulation
24
int32_t K = 512;
25
int32_t diffangle = (    (K*Q*I_old) - (K*I*Q_old)    ) / ((I*I) + (Q*Q));
26
I_old = I;
27
Q_old = Q;
28
        
29
int32_t L = 2048;
30
if((diffangle > L) || (diffangle < (-L)))
31
{
32
    diffangle = 0;
33
}
34
35
// output the sample using the DAC
36
set_DAC1((uint32_t)(diffangle + ((int32_t)2048)));

Noch rauscht es aber ein bisschen, hat jemand eine Idee, was ich 
verbessern könnte?

Vielen Dank nochmals an alle für die Tipps!

von Markus (Gast)


Lesenswert?

Bringt es was, wenn du "diffangle" mit einer genaueren Winkelfunktion 
berechnest ? ( arctan ?)
Dein DAC hat 12 Bit? Vielleicht gibt's Quantisierungsfehler in deinen 
Rechnungen, die für das Rauschen sorgen.

von Georg B. (Gast)


Lesenswert?

Habe nun die Parameter K und L (und die anderen "magic numbers") in 
obigem Programm "optimiert" und nun klingt es ganz passabel...

von Detlef _. (detlef_a)


Angehängte Dateien:

Lesenswert?

Hi Georg,

die Demodulation mit einem Hilbert-Pärchen geht genausogut, siehe 
angehängtes Matlab script. Das Hilbert-Pärchen ist aus

www.mikrocontroller.net/topic/308292#new

Man benötigt 32 multiply/accumulate pro sample ( die beiden filter sind 
spiegelsymmetrisch), bei 500ks/s macht das 16MMACs/s, das sollte ein ARM 
schaffen. Ausserdem hast Du mit 8fach Überabtastung ja noch Luft.

Zu Deinem Program:

- Offline ausprobieren. Du hast die samples und kannst in C auf dem PC 
spielen.

- Die Koeffizienten Deines Filters sind nicht korrekt, woher hast Du 
die, Du teilst bei der Berechnung einmal durch 0.

Ein Hilbert Filter sind in der Regel zwei Filter, Inphase Anteil und 
Quadratur Anteil, deswegen Hilbert Pärchen.

-
for(uint32_t i = (N_STORE - 1); i >= 1 ; i--)
{    samp_store[i] = samp_store[i - 1]; }
Du hast keine Zeit samples im Speicher rumzurangieren. Du greifst über 
Pointer auf die samples zu, die bleiben am Platz. Die angegebene Filter 
sind symmetrisch, das nutzt Du auf jeden Fall aus, mit nem Pointer ist 
das Fummelei aber notwendig.

-
int32_t diffangle = (    (K*Q*I_old) - (K*I*Q_old)    ) / ((I*I) + 
(Q*Q));

Du brauchst den Quotienten (I+i*Q)/(I_old+i*Q_old), und davon nur den 
Winkel, also wie schnell sich der Zeiger zwischen zwei samples 
fortbewegt hat. Da muss irgendwo noch 'atan' auftauchen. Falle: I steht 
für Inphase (Realteil), i für sqrt(-1).

Ich werde die Demodulation mit einer PLL probieren.

math rulez!
Cheers
Detlef

von Georg B. (Gast)


Lesenswert?

Detlef _. schrieb:
> die Demodulation mit einem Hilbert-Pärchen geht genausogut, siehe
> angehängtes Matlab script. Das Hilbert-Pärchen ist aus
>
> www.mikrocontroller.net/topic/308292#new
>
> Man benötigt 32 multiply/accumulate pro sample ( die beiden filter sind
> spiegelsymmetrisch), bei 500ks/s macht das 16MMACs/s, das sollte ein ARM
> schaffen. Ausserdem hast Du mit 8fach Überabtastung ja noch Luft.
Ok, werde ich mir mal anschauen.

>
> Zu Deinem Program:
>
> - Offline ausprobieren. Du hast die samples und kannst in C auf dem PC
> spielen.
Ja, so habe ich es nun gemacht.

>
> - Die Koeffizienten Deines Filters sind nicht korrekt, woher hast Du
> die, Du teilst bei der Berechnung einmal durch 0.
Oha, stimmt, das muss ich mir nochmals genau anschauen. Danke für den 
Hinweis.

>
> Ein Hilbert Filter sind in der Regel zwei Filter, Inphase Anteil und
> Quadratur Anteil, deswegen Hilbert Pärchen.
Ich habe es mir so gedacht: Durch die Faltung mit der Impulsantwort der 
Hilbert-Transformation bekomme ich die Q-Komponente. Aber die Berechnung 
hat ja ein paar Takte gedauert. Deshalb habe ich als I-Komponente nicht 
das aktuelle Sample, sondern "ein älteres", verzögertes Sample benutzt. 
Funktioniert ziemlich gut!?

>
> -
> for(uint32_t i = (N_STORE - 1); i >= 1 ; i--)
> {    samp_store[i] = samp_store[i - 1]; }
> Du hast keine Zeit samples im Speicher rumzurangieren. Du greifst über
> Pointer auf die samples zu, die bleiben am Platz. Die angegebene Filter
> sind symmetrisch, das nutzt Du auf jeden Fall aus, mit nem Pointer ist
> das Fummelei aber notwendig.
Das habe ich mir sogar auch schon gedacht. Die CPU ist aktuell ungefähr 
80% ausgelastet. Deshalb habe ich noch keine Optimierungen in diese 
Richtung unternommen, steht aber auch jeden Fall auf der TODO-Liste.

>
> -
> int32_t diffangle = (    (K*Q*I_old) - (K*I*Q_old)    ) / ((I*I) +
> (Q*Q));
>
> Du brauchst den Quotienten (I+i*Q)/(I_old+i*Q_old), und davon nur den
> Winkel, also wie schnell sich der Zeiger zwischen zwei samples
> fortbewegt hat. Da muss irgendwo noch 'atan' auftauchen. Falle: I steht
> für Inphase (Realteil), i für sqrt(-1).
Das "(    (K*Q*I_old) - (K*I*Q_old)    ) / ((I*I) + (Q*Q));" beinhaltet 
den atan, wie hier auf Seite 72, Formel 32 gezeigt: 
http://openwebrx.org/bsc-thesis.pdf


Danke nochmals für die Anregungen!

von Detlef _. (detlef_a)


Angehängte Dateien:

Lesenswert?

Hi,

Georg B. schrieb:
>> -
>> int32_t diffangle = (    (K*Q*I_old) - (K*I*Q_old)    ) / ((I*I) +
>> (Q*Q));
>>
>> Du brauchst den Quotienten (I+i*Q)/(I_old+i*Q_old), und davon nur den
>> Winkel, also wie schnell sich der Zeiger zwischen zwei samples
>> fortbewegt hat. Da muss irgendwo noch 'atan' auftauchen. Falle: I steht
>> für Inphase (Realteil), i für sqrt(-1).
> Das "(    (K*Q*I_old) - (K*I*Q_old)    ) / ((I*I) + (Q*Q));" beinhaltet
> den atan, wie hier auf Seite 72, Formel 32 gezeigt:
> http://openwebrx.org/bsc-thesis.pdf

Interessante Idee, kannte ich nicht. Funktioniert so mittelmäßig, habe 
ich ausprobiert. Vielleicht kan man da aber noch was dran drehen.

Die Demodulation mit einer PLL geht auch ganz gut, siehe angehängtes 
file, Shazam erkennt den Song immer noch. PLL wird die Möglichkeit mit 
der minimalen Anzahl Multiplikationen pro sample sein: eine für den 
Phasenvergleicher und eine für den VCO, der Rest sind shifts und 
Additionen.

Bei der PLL muss man bißchen fummeln, das ist nicht so unabhängig von 
den Amplituden und der ZF wie die anderen Methoden.

Cheers
Detlef

von Georg M. (georg22)


Lesenswert?

Hallo Detlef,

cool, Danke für den Beispielcode!
Jetzt habe ich ja viele Anregungen, um den Algorithmus noch weiter zu 
verbessern.

Ich melde mich, wenn sich noch etwas ergibt.

Gruß,
Georg

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.