Forum: Mikrocontroller und Digitale Elektronik ARM Cortex M4 mit LL Treiber DAC DMA Wave SD Card Playback


von Gendo I. (gendoikari)


Angehängte Dateien:

Lesenswert?

Hallo,

für das Problem brauche ich einen Cortex Experten, da ich hier selber 
mal nicht weiter komme.

Nach dem ich es mit HAL Treibern nur geschafft habe einen Sinus array 
abzuspielen bin ich für den DAC Trasfer eines WAV Files via DMA  Unit 
auf Low Laytreiber umgeschwenkt. Nach 3 Monaten lesen und durchforsten 
von ST Dokumenten, welche sich leider nur auf SPL Bibliotheken beziehen, 
hatte ich jetzt gedacht die richtige Lösung gefunden zu haben.

Soll Ablauf:

Der Timer6 triggert die DMA Unit, welche dann den DAC Transfer ausführt.
Bei halb Trasfer Flag und voll trasfer Flag werden je 256 byte aus dem 
WAV File nachgeladen. Ist das File am Ende wird der Timer6  gestoppt und 
die DMA Unit nicht mehr getriggert.

So viel zu Theorie.

Mein Problem ist jetzt, dass entweder die DMA Unit nicht getriggert 
wird, der Timer nicht richtig läuft (zu schnell für ein 48kHz WAVE 
File),Oder die Speicherzuweisung für die DMA unit nicht passt.

Das einlesen des Files geht.

Siehe DAC DMA Init in dermain der main.c

Das komplette Projekt ist .tar.gz und für ein Developmentboard mit 
STM32F407


Kann sich da jemand Reinfuchsen, dass Problem wird bei Initialisieren 
und starten der DMA liegen. Ich kom aber nicht drauf wo der Fehler 
ist...

von Stefan F. (Gast)


Lesenswert?

Gendo I. schrieb:
> brauche ich einen Cortex Experten

Das Problem ist eher spezifisch für die ST Bibliotheken.

> Mein Problem ist jetzt, dass entweder ... oder ....oder

Dann finde doch mal heraus, welche Vermutung zutrifft. Dafür gibt es 
doch Debugger. Immer schön einen Schritt nach dem anderen machen.

Falls sich dabei heraus stellt, dass du (wie ich) den Quellcode von ST 
nicht durchblickst, dann versuche es mal ohne.

von pegel (Gast)


Lesenswert?

Ich habe noch nichts mit LL gemacht, wenn ich aber deinen Code mit dem 
Beispiel vergleiche, fehlt das LL_DAC_EnableTrigger(DAC1, 
LL_DAC_CHANNEL_1);

zum Vergleich:
1
/**
2
  * @brief  Perform DAC activation procedure to make it ready to generate
3
  *         a voltage (DAC instance: DAC1).
4
  * @note   Operations:
5
  *         - Enable DAC instance channel
6
  *         - Wait for DAC instance channel startup time
7
  * @param  None
8
  * @retval None
9
  */
10
void Activate_DAC(void)
11
{
12
  __IO uint32_t wait_loop_index = 0;
13
  
14
  /* Enable DAC channel */
15
  LL_DAC_Enable(DAC1, LL_DAC_CHANNEL_1);
16
  
17
  /* Delay for DAC channel voltage settling time from DAC channel startup.    */
18
  /* Compute number of CPU cycles to wait for, from delay in us.              */
19
  /* Note: Variable divided by 2 to compensate partially                      */
20
  /*       CPU processing cycles (depends on compilation optimization).       */
21
  /* Note: If system core clock frequency is below 200kHz, wait time          */
22
  /*       is only a few CPU processing cycles.                               */
23
  wait_loop_index = ((LL_DAC_DELAY_STARTUP_VOLTAGE_SETTLING_US * (SystemCoreClock / (100000 * 2))) / 10);
24
  while(wait_loop_index != 0)
25
  {
26
    wait_loop_index--;
27
  }
28
  
29
  /* Enable DAC channel trigger */
30
  /* Note: DAC channel conversion can start from trigger enable:              */
31
  /*       - if DAC channel trigger source is set to SW:                      */
32
  /*         DAC channel conversion will start after trig order               */
33
  /*         using function "LL_DAC_TrigSWConversion()".                      */
34
  /*       - if DAC channel trigger source is set to external trigger         */
35
  /*         (timer, ...):                                                    */
36
  /*         DAC channel conversion can start immediately                     */
37
  /*         (after next trig order from external trigger)                    */
38
  LL_DAC_EnableTrigger(DAC1, LL_DAC_CHANNEL_1);
39
}

von Gendo I. (gendoikari)


Lesenswert?

Stefan F. schrieb:
> Gendo I. schrieb:
>> brauche ich einen Cortex Experten
>
> Das Problem ist eher spezifisch für die ST Bibliotheken.
>
>> Mein Problem ist jetzt, dass entweder ... oder ....oder
>
> Dann finde doch mal heraus, welche Vermutung zutrifft. Dafür gibt es
> doch Debugger. Immer schön einen Schritt nach dem anderen machen.
>
> Falls sich dabei heraus stellt, dass du (wie ich) den Quellcode von ST
> nicht durchblickst, dann versuche es mal ohne.

Den Quellcode wirst du nicht bei ST finden hab ja extra alles 
Kommentiert.

Logischerweise habe ich durch Debuggt, aber wenn ein Interrupt flag 
nicht gesetzt wird, komme ich damit auch nicht weiter.

Da ich mit Low layer nicht auskenne hab ich die Init für DMA und DAC 
zusammen kopiert und da ist auch das Problem. Die ging nicht eins zu 
eins. In der Vorlage wurde ein DMA Channel angegeben, Hier kann man aber 
nur eine Stream angegeben. Den DAC1 habe ich auf Stream5 und DMA Channel 
7 konfiguriert.

Wie gesagt die Stream Flags welche ich abfrage werden nicht ausgelößt.

von Gendo I. (gendoikari)


Lesenswert?

pegel schrieb:

>   LL_DAC_EnableTrigger(DAC1, LL_DAC_CHANNEL_1);
> }
> [/c]

Danke das war der Fehlende Punkt. :D Klingt zwar noch nicht richtig, 
aber der DMA läuft schon mal und der DAC macht krach.

Für alle dies nach probieren wollen:

Man muss natürlich die SD Karte noch mounten (hatte) ich bei "aufräumen" 
aus versehen gelöscht.

z.B:

    FATFS *fs;     /* Pointer auf das filesystem object */

    fs = malloc(sizeof (FATFS));

    f_mount(fs ,"", 1);


und den Interrupt muss man disablen sonst hängt man nur in Interrupt 
routine des streams.

__disable_irq();

von Johnny B. (johnnyb)


Lesenswert?

Gendo I. schrieb:
> und den Interrupt muss man disablen sonst hängt man nur in Interrupt
> routine des streams.
>
> __disable_irq();

Gleich so massiv die IRQ's zu deaktivieren sollte nicht nötig sein. 
Deaktiviere nur mal den betreffenden Timerinterrupt, denn wahrscheinlich 
wird nur der IRQ vom DMA benötigt.

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


Lesenswert?

Der DMA braucht nur die interne Triggerleitung.
Sobald der IRQ im Timer aktiviert ist löst der eben auch nen IRQ aus.

von Gendo I. (gendoikari)


Lesenswert?

Mw E. schrieb:
> Der DMA braucht nur die interne Triggerleitung.
> Sobald der IRQ im Timer aktiviert ist löst der eben auch nen IRQ aus.

Hatte ich eigentlich auch so raus gelesen aber der tut nix.


Hier mit rauscht zwar mein Wave File aber es wird erst mal durch 
gespielt.

würde lieber gleich HAL Treiber nehmen aber damit läuft es gar nicht.
Und ein Beispiel source hab ich auch niergends gefunden.


 Aus UM1725 Datenblatt

//LL_DMA_DeInit(DMA1, LL_DMA_STREAM_5);  //hier springt er immer in di 
IRQ von stream 5
  LL_DAC_EnableDMAReq(DAC1, LL_DAC_CHANNEL_1); // enable DMA for 
Channel1
  LL_DAC_Enable(DAC1, LL_DAC_CHANNEL_1); //enable Channel on DAC
  LL_DMA_ConfigAddresses(DMA1, LL_DMA_STREAM_5,(uint32_t) &DAC_Buff, 
LL_DAC_DMA_GetRegAddr(DAC1, LL_DAC_CHANNEL_1, 
LL_DAC_DMA_REG_DATA_12BITS_RIGHT_ALIGNED ), 
LL_DMA_DIRECTION_MEMORY_TO_PERIPH ); //Speicherbereich auf den die DMA 
Unit zugreift definieren
  LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_5, 16);     // die 
beschreibung in UM1725 ist heir etwas dürftig ich hab einfach mal 16 für 
16bit genommen
  LL_DMA_EnableIT_HT(DMA1,LL_DMA_STREAM_5);    // Halb Transfer 
Interrupt für Sream5 aktivieren
  LL_DMA_EnableIT_TC(DMA1,LL_DMA_STREAM_5);     // Complet Transfer 
Interrupt für Sream5 aktivieren
  LL_DAC_EnableTrigger(DAC1, LL_DAC_CHANNEL_1);  // ohne geht garnix, 
aber wenn an muss man __disable_irq machen
  LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_5);    //Stream5 DAC1 Starten

von Gendo I. (gendoikari)


Lesenswert?

Also wenn jemand ein funktionierenden (am besten selber getestet), auf 
das wesentliche reduzierte,  Bespielsource hat wie man ein file Stream 
(16bit wave file) mit einem DMA auf dem DAC ausgeben kann wäre ich sehr 
dankbar. HAL oder LL.

Bin schon seit Monaten am tüfteln.

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


Lesenswert?

Solch einen Code hätte ich für dich, allerdings noch total unaufgeräumt 
und er gibt nicht über den internen DAC aus, sondern per I2S auf den DAC 
eines F411 Discoverys.
Das hat aber eher was von Radio Erewan, denn der Codeteil deienr 
Verzweiflung ist ja der DMA zum internen DAC per IRQ trigger.

Aber bei meinem LED Panel projekt habe ich per Timer den DMA getriggert.

Gendo I. schrieb:
> Mw E. schrieb:
>> Der DMA braucht nur die interne Triggerleitung.
>> Sobald der IRQ im Timer aktiviert ist löst der eben auch nen IRQ aus.
>
> Hatte ich eigentlich auch so raus gelesen aber der tut nix.

Doch das geht ;)
DIER Register zwar den IRQ einschalten, aber schaltest ihn für den Timer 
insgesamt nicht beim NVIC ein.

Natürlich muss der Timer am DMA als Source nutzbar sein.

Guck bei LL_DMA_SetDataLength mal in den Sourcecode+Header, das sind 
alles nur Wrapper um einen Registerzugriff und da muss sicher der 
Registerwert als enum/define rein.

Zudem ist DMA1, Stream5, Channel1 kein Timertrigger, sondern das ist der 
I2C.
Das kann schonmal nich funzen.
Bitte mal ins Refman bei "Table 42. DMA1 request mapping" reingucken.
Dabei achten ob da CH1/2 oder UP steht.
Dann gehen nur die angebeben Timer Channel als Trigger oder das 
Updateevent.

von Gendo I. (gendoikari)


Lesenswert?

Hallo,

meinst du die declaration? Dort steht nämlich auch nicht ob es sind um 
Byte  oder Bit handelt.

Wenn ich hier was änder klingt es aber immer anderes.  Also hat es ja 
eine wichtigen Einfluss, nur halt welchen. 1- 32 klingt nicht richtig 
und 256 oder 512 auch nicht.

/**
  * @brief Set Number of data to transfer.
  * @rmtoll NDTR          NDT           LL_DMA_SetDataLength
  * @note   This action has no effect if
  *         stream is enabled.
  * @param  DMAx DMAx Instance
  * @param  Stream This parameter can be one of the following values:
  *         @arg @ref LL_DMA_STREAM_0
  *         @arg @ref LL_DMA_STREAM_1
  *         @arg @ref LL_DMA_STREAM_2
  *         @arg @ref LL_DMA_STREAM_3
  *         @arg @ref LL_DMA_STREAM_4
  *         @arg @ref LL_DMA_STREAM_5
  *         @arg @ref LL_DMA_STREAM_6
  *         @arg @ref LL_DMA_STREAM_7
  * @param  NbData Between 0 to 0xFFFFFFFF
  * @retval None
  */
__STATIC_INLINE void LL_DMA_SetDataLength(DMA_TypeDef* DMAx, uint32_t 
Stream, uint32_t NbData)
{
  MODIFY_REG(((DMA_Stream_TypeDef*)((uint32_t)((uint32_t)DMAx + 
STREAM_OFFSET_TAB[Stream])))->NDTR, DMA_SxNDT, NbData);
}

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


Lesenswert?

Dann ist LL_DMA_SetDataLength die Anzahl der zu übertragenen 
Datenelemente.
Also du willst ja sicherlich einen uint16 pro DMA Trigger übertragen.
Daher ist das dann 1 und nicht die Anzahl an Bits.

Um die Transferbreite auf 16 Bit zu setzen brauchste dann noch:
LL_DMA_SetPeriphSize
LL_DMA_SetMemorySize

von Gendo I. (gendoikari)


Angehängte Dateien:

Lesenswert?

Also bei TIM6 kann man unter DMA Settings "TIM6_Up" adden und dann 
entwerde mem to per oder eben andersrum auswählen.

Dann hat man bei TIM6 unter NVIC Settings den DMA1 Steam1 als global 
interrupt und kann das bei DMA -> DMA1 Request auch adden.

Im Anhang habe ich mal paar Bilder dazu.

von Gendo I. (gendoikari)


Lesenswert?

Bloß für mein Verständniss:


Ich will ja das der TIM6 den DMA1 triggert und dieser dann von I2C (Den 
stream des files von der SD Card) die Daten zur DAC periferi schiebt und 
dann die half trasfer flag (stream5?) und complet trasfer flag 
(stream5?) abrufen.

Also ich habe ja jetzt den DMA1 stream1 bei TIM6_UP event eingetragen. 
Der Tim6 sollte ja jetzt den DMA triggern.

Ich Starte in meinem Unterprogram den TIM6 und warte darauf das es ein 
Flag durch den Stream5 gibt, aber da passiert noch nichts.

Die allgemeine Init hab ich jetzt mal so.

   LL_DAC_EnableDMAReq(DAC1, LL_DAC_CHANNEL_1); // enable DMA for 
Channel1
  LL_DAC_Enable(DAC1, LL_DAC_CHANNEL_1); //enable Channel on DAC
  LL_DMA_ConfigAddresses(DMA1, LL_DMA_STREAM_5,(uint32_t) &DAC_Buff, 
LL_DAC_DMA_GetRegAddr(DAC1, LL_DAC_CHANNEL_1, 
LL_DAC_DMA_REG_DATA_12BITS_RIGHT_ALIGNED ), 
LL_DMA_DIRECTION_MEMORY_TO_PERIPH ); //Speicherbereich auf den die DMA 
Unit zugreift definieren
  LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_1, 1);     // die 
beschreibung in UM1725 ist heir etwas dürftig ich hab einfach mal 16 für 
16bit genommen
  LL_DMA_EnableIT_HT(DMA1,LL_DMA_STREAM_5);    // Halb Transfer 
Interrupt für Sream5 aktivieren
  LL_DMA_EnableIT_TC(DMA1,LL_DMA_STREAM_5);     // Complet Transfer 
Interrupt für Sream5 aktivieren
 // LL_DAC_EnableTrigger(DAC1, LL_DAC_CHANNEL_1);  // ohne geht garnix, 
aber wenn an muss man __disable_irq machen

LL_DMA_SetPeriphSize(DMA1, LL_DMA_STREAM_1 , 0xFFFF);
LL_DMA_SetMemorySize(DMA1, LL_DMA_STREAM_1,0xFFFF );
  LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_5);    //Stream5 DAC1 Starten
  LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_1);    //Stream5 DAC1 Starten
//

: Bearbeitet durch User
von Gendo I. (gendoikari)


Lesenswert?

Jetzt passiert gar nix mehr. Das HT5 Flag wird nie ausgelöst.

Die Sache ist wirklich viel kniffliger als ich anfangs dachte.

Mich wundert es nur das ich anscheinend der Erste bin der das mal 
ausprobiert.




Hab noch mal bei der Init alles auf Stream5 geändert geht aber auch 
nicht.

Vom logischen hätte ich jetzt auch gedacht das geht. Der TIM6_UP 
triggert den DMA1 Stream1 peripheral to memory und der  DMA1 Stream5 
merory to peripheral den Speicherinhalt auf den DAC1. Welcher dann 
wieder mein Stream5 flags triggert.

Wenn es mal laufen sollte kann ich hier ja mal ein howto bereit stellen. 
:)

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