Tag zusammen,
ich verwende ein STM32F4 Discovery und programmiere mit CooCox.
Ich möchte immer 1000 Werte des ADC1 per DMA2 einlesen und in zwei
Arrays abspeichern (Double Buffering).
Gleichzeitig soll DMA1 den externen AudioDAC befeuern (ebenfalls
double-buffer mit jeweils 1000 Werten).
Meine Konfigurationen sehen folgender Maßen aus:
EXTI_Line0 verwende ich als Software-Interrupt, hier sollen die
adc_buffer in die dac_buffer kopiert werden (jeweils die Buffer, die
nicht verwendet werden). Später wird hier noch ein bisschen
Signalverarbeitung betrieben.
SystemInit() wird in main() aufgerufen, SYSCLK ist 168MHz.
Folgende Probleme habe ich:
1. Die Frequenz des Audio-DAC ist auf 48kHz eingestellt, ich messe
allerdings nur ca 30kHz. Daraufhin habe ich "HSE_VALUE=16000000" als
define eingefügt-->nun passt die Frequenz (gemessen mit GPIOs und Oszi).
2. Die Abtastfrequenz des ADC passt nicht, geplant war (erst einmal)
fa=48kHz einzustellen (so wird die Variable uint16_t fa initialisiert
wird), ich messe allerdings ca 2kHz.
3. Der ADC liefert komische Werte, siehe Anhang. Ich habe ein
Frequenzgenerator angeschlossen, eingestellt ist ein Sinus-Signal, 1V
Spitze-Spitze, 1V Gleichanteil (da der ADC-Bereich zwischen 0V und 3V
liegt).
4. Durch das define ist der SYSCLK nun 107MHz.
Sieht jemand einen Fehler bei der Konfiguration oder bei den Interrupts
oder hat sonst irgend einen Tipp? Auch wie ich den Audio-DAC auf die
richtige Frequenz bringe und trotzdem mit 168MHz fahren kann?
ich bin chillking, jetzt nur angemeldet :)
Nachtrag:
Das Bild habe ich mit Excel gemacht, indem ich im Debug-Modus den
Buffer-Inhalt kopiert habe.
Der Ausgang was aus dem DAC rauskommt ist natürlich ebenfalls kein
schöner Sinus, sondern ähnlich dem, was mir der ADC liefert. Komisch
ist, dass bei Pausieren im Debug, der Ausgang des DAC ein reiner Sinus
ist (bis auf ein paar Werte die nicht ganz passen). Sobald ich weiter
laufen lasse kommt wieder Müll.
Ich denke das liegt auch an der Tatsache, dass die Frequenz des ADC
nicht passt (deutlich zu hoch).
Danke schonmal!!!!
Habe deinen source jetzt nur mal gaaanz grob überflogen, paar
Kleinigkeiten, Tipps, Gedanken und Ideen:
- Guck mal ins ReferenceManual unter RCC, die Timer haben je nach
APB-CLK-Prescaler eine Taktung von *2
- Timer Prescaler immer -1 nehmen, also falls du tatsächlich 1750
ausgerechnet hast, noch 1 abziehen
- Guck mal ins ProgrammingManual unter ADC, mit welchem Tempo du den
maximal extern triggern kannst
- Stell HSE auf 8M und überprüfe in der system_irgendwas.c alle Teiler
- Versuch erst mal nur den ADC mit DMA2 laufen zu lassen um zu schauen
ob da alles passt, double buffer, DAC, etc. alles mal weg lassen
Drück dir die Daumen!
EDIT: Sehe gerade du hast nen ADC-Prescaler von 2. Den ADC kannst
maximal mit 35MHz takten.
Hi!
Danke für die Antwort!!!
Ich hab jetzt nach weiterem langem Versuchen ein MinimalProgramm, in dem
ich einfach nur ein Timer initialisiere und darin ein GPIO toggle.
In system_stm32f4xx.c habe ich
1
#if !defined (HSE_VALUE)
2
#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
3
#endif /* HSE_VALUE */
geändert. Vorher stand da 25000000 drin.
Die Teiler
Und nicht mal jetzt passt der Takt!
Ich messe am GPIO per Oszi 320,5Hz.
Im Debug werden mir folgende Werte angezeigt:
Clocks {...}
SYSCLK_Frequency 53760000
HCLK_Frequency 53760000
PCLK1_Frequency 13440000
PCLK2_Frequency 26880000
Reginald L. schrieb:> Die Teiler stimmen nicht, guck mal hier:> http://stm32f4-discovery.com/2015/01/properly-set-...>> EDIT: Schaffst du mit C++ oder warum das extern "C"?
Okay top, nun bin ich mit dem SysClk auf 168MHz und der Ausgang passt
nun auch! Hab die Seite vorher schon durchgeschaut, aber hab es da wohl
falsch verstanden oder war zu blöd...Dank Dir!
Dann werde ich jetzt mal Schritt für Schritt weiter machen.
Ich sollte, ein paar Teile vom eigentlichen Code sind in C++, da musste
ich nur feststellen, dass ich mit der Laufzeit nicht hingekommen bin und
versuch mich nun an der DMA.
Reginald L. schrieb:> EDIT: Sehe gerade du hast nen ADC-Prescaler von 2. Den ADC kannst> maximal mit 35MHz takten.
In der stm32f4xx_adc.h ist ADC_Prescaler_Div2 der kleinste Wert oder
geht das noch irgendwie anders?
Mar K. schrieb:> Reginald L. schrieb:>> EDIT: Sehe gerade du hast nen ADC-Prescaler von 2. Den ADC kannst>> maximal mit 35MHz takten.>> In der stm32f4xx_adc.h ist ADC_Prescaler_Div2 der kleinste Wert oder> geht das noch irgendwie anders?
84 / 2 = 42 ; 84 / 4 = 21 ;)
Reginald L. schrieb:> Mar K. schrieb:>> Reginald L. schrieb:>>> EDIT: Sehe gerade du hast nen ADC-Prescaler von 2. Den ADC kannst>>> maximal mit 35MHz takten.>>>> In der stm32f4xx_adc.h ist ADC_Prescaler_Div2 der kleinste Wert oder>> geht das noch irgendwie anders?>> 84 / 2 = 42 ; 84 / 4 = 21 ;)
Aber direkt mit 84MHz ohne Teiler (bzw Teiler 1) geht nicht?
EDIT: ADC + TIM2 geht...
EDIT EDIT: ADC + TIM2 + DMA funktioniert glaube ich auch. Habe das
Transmission Complete-Bit abgefragt und darin wieder ein GPIO
getogglet-->48Hz, da jeweils 1000 Werte eingelesen werden komme ich bei
meinen gewünschten 48kHz raus (prescaler=35-1; period=50-1;
ClockDivision=TIM_CLK_DIV1).
Es werden auf jeden Fall beide Buffer beschrieben, allerdings verstehe
ich hier zwei Dinge noch nicht ganz.
Habe einen Ausschnitt der Daten aus einem Buffer wieder angehängt, hier
sieht die Kurve auch ganz gut aus (Frequenzgenerator Sinus, f=1kHz,
Amplitude=Offset=1V). Woher kommen die beiden Fehler am Ende bei den
Werten 766 und 892? (Habe die Werte während eines Breakpoints nachdem
das DMA_IT_TCIF0 kam ausgelesen)
Zweitens bin ich mir mit den Interrupts nicht ganz sicher. Ich dachte,
das TC kommt, wenn die DMA beide Buffer beschrieben hat und das HT wenn
die DMA mit dem ersten Buffer fertig ist. Nun sieht es im Debugger aber
so aus, dass beide Buffer sowohl bei HT als auch bei TC neu beschrieben
wurden. Hast du da eine Ahnung wann welches Flag kommt?
Vielen Dank für deine Hilfe!
Mar K. schrieb:> Aber direkt mit 84MHz ohne Teiler (bzw Teiler 1) geht nicht?
Der ADC Controller arbeitet mit maximal 35MHz, dh. bei nem APB von 84MHz
musst du mindestens nen Prescaler von 4 reinhaun. Zumindest, wenn man
nach Spezifikation geht.
Mar K. schrieb:> Habe einen Ausschnitt der Daten aus einem Buffer wieder angehängt, hier> sieht die Kurve auch ganz gut aus (Frequenzgenerator Sinus, f=1kHz,> Amplitude=Offset=1V). Woher kommen die beiden Fehler am Ende bei den> Werten 766 und 892? (Habe die Werte während eines Breakpoints nachdem> das DMA_IT_TCIF0 kam ausgelesen)
Fülle die Buffer vor jeder Aufnahme mal mit 0en und schau mal wie der
Buffer dann in Excel aussieht. Und wie gesagt, mach erst mal
SingleBuffer. Zur Fehlersuche würde ich persönlich den DMA nicht in
Circular laufen lassen sondern manuell zünden. Ach und ich lasse meine
ADCs in meinem Projekt durchgehend auf Enabled und steuere die Füllung
des Buffers mit dem DMA. Dürfte aber ansich eigentlich egal sein.
Mar K. schrieb:> Zweitens bin ich mir mit den Interrupts nicht ganz sicher. Ich dachte,> das TC kommt, wenn die DMA beide Buffer beschrieben hat und das HT wenn> die DMA mit dem ersten Buffer fertig ist. Nun sieht es im Debugger aber> so aus, dass beide Buffer sowohl bei HT als auch bei TC neu beschrieben> wurden. Hast du da eine Ahnung wann welches Flag kommt?
TC kommt nach jedem CounterReset. ReferenceManual.
Das HT-Flag habe ich bisher noch nie benutzt. Ausser in irgendwelchen
Sonderfällen (bspw. SingleBuffer und anderweitige Verwendung des Buffers
während der Aufnahme) brauchst das normalerweise nicht.
Reginald L. schrieb:> Der ADC Controller arbeitet mit maximal 35MHz, dh. bei nem APB von 84MHz> musst du mindestens nen Prescaler von 4 reinhaun. Zumindest, wenn man> nach Spezifikation geht.
Jetzt hab auch ich es verstanden, so gesehen macht das natürlich Sinn.
Reginald L. schrieb:> Fülle die Buffer vor jeder Aufnahme mal mit 0en und schau mal wie der> Buffer dann in Excel aussieht. Und wie gesagt, mach erst mal> SingleBuffer. Zur Fehlersuche würde ich persönlich den DMA nicht in> Circular laufen lassen sondern manuell zünden. Ach und ich lasse meine> ADCs in meinem Projekt durchgehend auf Enabled und steuere die Füllung> des Buffers mit dem DMA. Dürfte aber ansich eigentlich egal sein.
Das werde ich morgen früh testen und berichten, bin jetzt grad daheim
angekommen und hab hier kein Frequenzgenerator usw.
Zum weiteren Testen mach ich den double Buffer wieder aus, wollte nur
testen ob das noch funktioniert.
Heißt in der Konfiguration DMA_Mode = DMA_Mode_Normal und dann im
Interrupt bei Bedarf starten, nur wie startest den dann? Ich hab das mal
mit ADC_DMACmd(ADC1, DISABLE); und ADC_DMACmd(ADC1, ENABLE); versucht,
das hat aber nicht so richtig funktioniert. Hab mir dann gedacht (und
meine auch irgendwo gelesen), dass es dem ADC nicht gefällt, wenn er vom
Timer getriggert wird, der alter Wert aber noch nicht ausgelesen wurde.
Reginald L. schrieb:> TC kommt nach jedem CounterReset. ReferenceManual.
Okay, kommt also immer wenn ein Buffer vollständig beschrieben wurde.
Ist mir auch grad eingefallen, dass ich das auch schon einmal getestet
hatte. Ist für mich ziemlich verwirrend, dass der DMA auch weiterläuft,
wenn der µC eigentlich an einem Breakpoint hängt...das hat mich vorhin
dann wohl wieder erwischt :D
Ja dieses ReferenceManual und auch ProgrammingManual und die ganze
ApplicationManual...so richtig durchsteigen tu ich da noch nicht. Da hab
ich entweder das Gefühl, etwas total sinnloses und unwichtiges
durchzulesen, oder ich blicks nicht. Fällt mir sehr schwer da was
brauchbares rauszubekommen, fehlt mir wohl (hoffentlich) noch die Übung.
Mar K. schrieb:> Heißt in der Konfiguration DMA_Mode = DMA_Mode_Normal und dann im> Interrupt bei Bedarf starten, nur wie startest den dann? Ich hab das mal> mit ADC_DMACmd(ADC1, DISABLE); und ADC_DMACmd(ADC1, ENABLE); versucht,> das hat aber nicht so richtig funktioniert. Hab mir dann gedacht (und> meine auch irgendwo gelesen), dass es dem ADC nicht gefällt, wenn er vom> Timer getriggert wird, der alter Wert aber noch nicht ausgelesen wurde.
Alles, bis auf das DMA EN Bit auf Enabled setzen, mit dem EN Bit dann
starten und / oder stoppen. Ist vielleicht nicht die feine englische Art
aber funktioniert, zumindest bei mir, hervorragend. Bei mir ändern sich
so viele Variablen, von denen der DMA und der ADC abhängen, dass dies in
meinem Projekt, zumindest meiner Ansicht nach, den schnellsten
ausführbaren Code darstellt. Je nach Projekt / Anforderung ist die feine
Englische Art vllt eher zu bevorzugen. Dabei musst du noch beachten, wie
du schon gelesen hast, dass du die OVR Flags und / oder die DR-Register
ausließt, bevor du den DMA wieder startest, sonst könnte es sein, dass
der ADC den DMA sofort nach Aktivierung, noch mit dem alten DR-Wert,
triggert. Siehe hierzu ReferenceManual (das lesen nehme ich dir nicht
ab).
Hierzu sei noch gesagt: Ich hantiere immer wieder mal mit den OVR-Flags.
Ich habe gemerkt, dass man die nicht als "Error" sehen darf sondern
vielmehr als Information. Je nach Anwendung, vor allem, wenn man sehr
individuelle Algorithmen für eine Lösung schreibt, schießt einem gerne
das OVR-Flag um die Ohren.
Mar K. schrieb:> Okay, kommt also immer wenn ein Buffer vollständig beschrieben wurde.> Ist mir auch grad eingefallen, dass ich das auch schon einmal getestet> hatte. Ist für mich ziemlich verwirrend, dass der DMA auch weiterläuft,> wenn der µC eigentlich an einem Breakpoint hängt...das hat mich vorhin> dann wohl wieder erwischt :D
Ist dem so? Zu der Thematik konnte ich mich bisher noch nicht wirklich
einlesen, lediglich ein paar Abschnitte zu den Timern. Bei denen kann
man das Stoppen beim Debuggen aber über ein Register erzwingen.
Wobei, jetzt wo du es sagst, der LTDC läuft ja auch weiter beim
Debuggen, ist mir aufgefallen. Andererseits stoppt der DMA2D
(augenscheinlich) beim Debuggen.
Mar K. schrieb:> Ja dieses ReferenceManual und auch ProgrammingManual und die ganze> ApplicationManual...so richtig durchsteigen tu ich da noch nicht. Da hab> ich entweder das Gefühl, etwas total sinnloses und unwichtiges> durchzulesen, oder ich blicks nicht. Fällt mir sehr schwer da was> brauchbares rauszubekommen, fehlt mir wohl (hoffentlich) noch die Übung.
Wie lange bist du denn schon dabei? Ich habe vor etwa einem Jahr mit dem
STM und C angefangen. Es hat etwa 1-2 Monate gedauert bis ich die
Manuals zu schätzen und benutzen gelernt habe.
Wenn du mal was absolut individuelles mit dem STM anfangen willst,
kommst du um die Dinger nicht herum. Klar, für Standardzeugs kopiert man
sich meist von irgendwoher Code, bzw. tippt ab. So habe ich es ganz am
Anfang auch gemacht. Inzwischen schau ich mir kurz ein Beispiel zu einer
Peripherie an und gehe dann gleich in die Manuals über. Oft ist es dann
auch so, dass die SPL bestimmte Bits nicht so setzt wie du es möchtest,
oder du direkt auf Register zugreifen möchtest, zb. bei zeitkritischen
Anwendungen. Ohne Manual geht das eben nicht.
EDIT: Das hier habe ich vor kurzem mal hier im Forum gepostet, da hatte
ich ein Problem mit SPI ;)
"In den Interrupts
wird ausschließlich mit "DMAx_Streamx->CR |= (uint32_t)DMA_SxCR_EN"
gearbeitet. Mein Denkfehler war, dass SPI anfängt mit OVR's um sich zu
schmeißen und mich dann nicht mehr mag, wenn ich ihm den DMA wegnehme.
Mit OVR's schmeißt er zwar rum, wenn ihm keiner das DR-Register leert,
aber mögen tut er mich trotzdem :)"
Reginald L. schrieb:> Ist dem so? Zu der Thematik konnte ich mich bisher noch nicht wirklich> einlesen, lediglich ein paar Abschnitte zu den Timern. Bei denen kann> man das Stoppen beim Debuggen aber über ein Register erzwingen.> Wobei, jetzt wo du es sagst, der LTDC läuft ja auch weiter beim> Debuggen, ist mir aufgefallen. Andererseits stoppt der DMA2D> (augenscheinlich) beim Debuggen.
Also zumindest der DMA1, der bei mir die SPI3-Schnittstelle befeuert
arbeitet während eines Breakpoints weiter, ich seh am Oszi die selbe
Kurve wie im normalen Durchlauf.
Reginald L. schrieb:> Wie lange bist du denn schon dabei? Ich habe vor etwa einem Jahr mit dem> STM und C angefangen. Es hat etwa 1-2 Monate gedauert bis ich die> Manuals zu schätzen und benutzen gelernt habe.
Dann dauert das bei mir schon einmal länger. Ich mach meine
Projektarbeit mit dem Board, heißt im November15 hab ich mit Cortex-M4
angefangen. Im Labor hab ich eine Lib bekommen, die sie selbst
geschrieben haben, mit der der Einstieg recht einfach war (dass sich
überhaupt mal was getan hat). Sobald man aber ein bisschen was anderes
machen will, muss man wieder alles selbst machen, wodurch sich der
richtige Einstieg bei mir nur etwas verschoben hat.
Bin aber echt beeindruckt, wie viel Leistung einmal hier zur Verfügung
steht, hätte ich nicht gedacht. Aber war auch klar, dass man dafür eben
auch mehr Arbeit reinstecken muss. Aber irgendwann wird das schon.
Reginald L. schrieb:> Alles, bis auf das DMA EN Bit auf Enabled setzen, mit dem EN Bit dann> starten und / oder stoppen. Ist vielleicht nicht die feine englische Art> aber funktioniert, zumindest bei mir, hervorragend. Bei mir ändern sich> so viele Variablen, von denen der DMA und der ADC abhängen, dass dies in> meinem Projekt, zumindest meiner Ansicht nach, den schnellsten> ausführbaren Code darstellt. Je nach Projekt / Anforderung ist die feine> Englische Art vllt eher zu bevorzugen. Dabei musst du noch beachten, wie> du schon gelesen hast, dass du die OVR Flags und / oder die DR-Register> ausließt, bevor du den DMA wieder startest, sonst könnte es sein, dass> der ADC den DMA sofort nach Aktivierung, noch mit dem alten DR-Wert,> triggert. Siehe hierzu ReferenceManual (das lesen nehme ich dir nicht> ab).> Hierzu sei noch gesagt: Ich hantiere immer wieder mal mit den OVR-Flags.> Ich habe gemerkt, dass man die nicht als "Error" sehen darf sondern> vielmehr als Information. Je nach Anwendung, vor allem, wenn man sehr> individuelle Algorithmen für eine Lösung schreibt, schießt einem gerne> das OVR-Flag um die Ohren.
Also nur per DMA_Cmd(DMA2_Stream0, DISABLE); bzw ENABLE und ADC-Wert
auslesen funktioniert es schon mal nicht (Mode=Normal), werde mich da
mal durch das Manual wühlen.
Vielen Dank für deine Hilfe!
funktionierts und ich kann den Buffer überprüfen. Sieht gut aus würde
ich sagen (siehe Anhang). Die Unterbrechungen zwischendrin sind
lediglich ein Kopierfehler der Daten von Coocox in Excel (eine Leerzeile
immer nach 100 Werten).
Würdest du generell die Buffer vor dem neuen Beschreiben mit 0er füllen,
oder machst du das nur zum Testen?
Na also.
Noch was: Versuche in Abhängigkeit deiner Samplingfrequenz das Maximum
an ADC-Sampling-Time einzustellen. Je länger eine ADC Wandlung läuft
desto genauer ist sie (Mittelwert).
Die 0er waren nur eine Anregung zwecks Test.
Reginald L. schrieb:> Na also.> Noch was: Versuche in Abhängigkeit deiner Samplingfrequenz das Maximum> an ADC-Sampling-Time einzustellen. Je länger eine ADC Wandlung läuft> desto genauer ist sie (Mittelwert).>> Die 0er waren nur eine Anregung zwecks Test.
Ja das kommt jetzt noch dazu, die Samplingfrequenz soll nämlich
einstellbar sein. Hat (zumindest vor meine DMA-Exzess) auch
funktioniert, nur muss ich jetzt dann mal schauen wie ich das mit der
Anzahl der Werte mache. Denn die Frequenz des Audio-DACs sollte ja
konstant sein, bzw wäre auch nur in recht großen Schritten einstellbar.
Habe bis jetzt im Kopf dass ich z.B. bei fs=24kHz einfach jeden Wert
zwei mal verwende (dac_buffer[0]=dac_buffer[1]=adc_buffer[0])...
fa soll aber in 1kHz oder 0,5kHz Schritten einstellbar sein.
Reginald L. schrieb:> Das kannst du allein über die Periode des Timers bewerkstelligen.
Ja das hab ich auch so gemacht, nur weis ich noch nicht genau wie ich
das mit der Anzahl der Werte mache.
Wenn die Buffer des DAC 1000 Werte beinhalten sind das ja ca. 20ms. Und
wenn ich die Samplefrequenz des ADCs veränder, passt bei 1000 Werten die
Dauer ja nicht mehr. Also muss ich (glaub ich zumindest) so viel Werte
aufnehmen, dass ich wieder bei 20ms bin, und dann je nachdem ein paar
Werte wegschmeißen oder doppelt nehmen.
Oder hast du da eine andere Idee?
Mar K. schrieb:> Reginald L. schrieb:>> Das kannst du allein über die Periode des Timers bewerkstelligen.>> Ja das hab ich auch so gemacht, nur weis ich noch nicht genau wie ich> das mit der Anzahl der Werte mache.> Wenn die Buffer des DAC 1000 Werte beinhalten sind das ja ca. 20ms. Und> wenn ich die Samplefrequenz des ADCs veränder, passt bei 1000 Werten die> Dauer ja nicht mehr. Also muss ich (glaub ich zumindest) so viel Werte> aufnehmen, dass ich wieder bei 20ms bin, und dann je nachdem ein paar> Werte wegschmeißen oder doppelt nehmen.>> Oder hast du da eine andere Idee?
Sry, vllt kann dir da jemand anders helfen. Mein Projekt bereitet mir
schon genug Kopfschmerzen. Weisst ja, vom Denken bekommt man Kopfweh ;)
Reginald L. schrieb:> Sry, vllt kann dir da jemand anders helfen. Mein Projekt bereitet mir> schon genug Kopfschmerzen. Weisst ja, vom Denken bekommt man Kopfweh ;)
Okay klar, trotzdem vielen Dank für deine Hilfe!