Forum: Mikrocontroller und Digitale Elektronik stm32 bester Weg Daten aus verschiedenen Speicherbereichen mittels DMA auszulesen


von Marcus P. (spell)


Lesenswert?

Hallo, ich möchte gern spezielle Speicherbereichen auslesen bzw. es 
sollen nur ein paar Stellen übersprungen werden oder ab und zu mal die 
selbe doppelt ausgegeben werden. Was wäre die beste Methode dafür?
Eine Art Pointer auf die Database Adresse der DMA?
Das ständige Abändern der Database Adresse im DMA Interrupt?
Oder das Neubeschreiben des Zielspeichers im Interrupt?

Bestimmt gibt es noch bessere Methoden, eventuell kennt jemand ein 
Beispiel?

Gruß Marcus

von Sven Wagner (Gast)


Lesenswert?

Marcus P. schrieb:
> Hallo, ich möchte gern spezielle Speicherbereichen auslesen bzw. es
> sollen nur ein paar Stellen übersprungen werden oder ab und zu mal die
> selbe doppelt ausgegeben werden.
Das klingt irgendwie ganz schön konfus.

> Was wäre die beste Methode dafür?
Ich nehme mal an, daß Du in C programmierst?!?
Warum definierst Du Dir nicht einen Zeiger und liest die Speicherstelle, 
die Dir gefällt?

DMA brauchst Du dafür nicht. DMA nimmt man, wenn viele Daten am Stück 
übertragen werden sollen. Wobei bei den meisten DMA-Controllern sowohl 
der Quellspeicher (zum Füllen) als auch der Zielspeicher (für FIFOs) 
eine konstante Adresse haben kann.

Grüße
Sven

von Marcus P. (spell)


Lesenswert?

Hallo Sven,
Ok, ich habe mich etwas dämlich ausgedrückt. Ich möchte ein 
freqenzvariables Signal mittels DAC ausgeben und für höhere Frequenzen 
sollen z.B. einige Speicherstellen der LookUP übersprungen werden. Ich 
arbeite in C und mit Zeigern scheint sich bei der DMA nichts reißen zu 
lassen, scheinbar wird die Basis Adresse des Speichers nur einmal 
eingelesen und von da aus über andere Funktionen weiter hochgezählt und 
nicht wieder jedes mal auf die angegebene Adresse geschaut,so dass man 
hier leider eine Zeiger definieren kann. Klar gibt es die direkten DAC 
Befehle, aber die DMA scheint mir für dieses Vorhaben angebrachter. 
Momentan versuche ich im DMA Interrupt diese Zeilen unterzubringen, aber 
das scheint auch nicht von Erfolg gekrönt zu sein:

DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)neues_Lookuptable_Feld;
DMA_Init(DMA2_Channel4, &DMA_InitStructure);

Gruß Marcus

von 900ss (900ss)


Lesenswert?

Ich würde die Kurvenform im Flash ablegen und je nachdem wie sie dann 
gebraucht wird, aufbereitet einmal ins Ram kopieren. Die DMA dann aus 
dem Ram laufen lassen. OK das kostet Ram aber vielleicht paßt das ja.
Eine andere Möglichkeit sehe ich gerade nicht bei dem STM32.

von Marcus P. (spell)


Lesenswert?

Scheinbar gehts doch in dem ich die oben genannten Zeilen im Interrupt 
verwende, ich hatte vergessen den Buffer der DMA auf 1 zurück zu setzen, 
da ich vorher das automatische Auslesen der Lookup-table genutzt habe. 
Über die Qualität des Signals kann ich mich jetzt noch nicht wirklich 
auslassen, zumal ich noch einen Sprung drin habe. Ich muss wohl noch 
etwas an der Hochzählung arbeiten.

Ich kenne mich nicht so gut aus, aber wenn ich ständig den Ram neu 
beschreibe, frisst das nicht mehr als ich am Ende durch die DMA gut 
mache? Bzw. wahrscheinlich ist auch bei meiner Variante der DMA schon 
wieder ziemlich sinnfrei geworden.

von 900ss (900ss)


Lesenswert?

Marcus P. schrieb:
> aber wenn ich ständig den Ram neu
> beschreibe

Du mußt das Ram doch nur einmal mit den gewünschten Werten aus der 
Lookup-Table (aus dem Flash) beschreiben. Solange kann dann die DMA 
diese Werte an den DAC senden. Erst wenn sich die Kurvenform ändert, 
dann benötigst du neue Werte aus der Lookuptable.

von holger (Gast)


Lesenswert?

Wenn das ein DDS Generator werden soll kannst du DMA vergessen.

von 900ss (900ss)


Lesenswert?

holger schrieb:
> Wenn das ein DDS Generator werden soll kannst du DMA vergessen.

Eine Begründung wäre jetzt schön.

Hmmm... ja wenn ich länger drüber nachdenke... falls die Schrittweite 
nicht genau wieder am Anfang der Lookup-Table landet funktioniert das 
nicht. Dann müßte man jedesmal wieder neue Werte ins Ram kopieren wenn 
der Buffer von der DMA abgearbeitet wurde.

von holger (Gast)


Lesenswert?

>> Wenn das ein DDS Generator werden soll kannst du DMA vergessen.
>
>Eine Begründung wäre jetzt schön.

>Hmmm... ja wenn ich länger drüber nachdenke... falls die Schrittweite
>nicht genau wieder am Anfang der Lookup-Table landet

Genau das ist das Problem. Bei ungeraden Frequenzen schwebt
der Samplezeiger durch das Array. Der nimmt nie die
gleichen Speicherzellen.

von 900ss (900ss)


Lesenswert?

Trotzdem wäre die DMA ein Vorteil denke ich. Sie bestimmt das Timing 
dann (also per Hardware). Dadurch ist das schon mal genau (wenn man die 
Priorität der DMA hoch genug hat). Dann braucht man nur mittels der DMA 
half and full Interrupts den Puffer aus der Lookup Table füllen.

Ich nutze das in meiner Scopeuhr so (OK ist kein DDS). Aber dort 
triggert der DMA IRQ eine Task, die dann die leer geworde Pufferhälfte 
mit neuen Daten für den DAC füttert (und vorher berechnet). Das klappt 
sehr gut.
Genauso könnte der DMA Puffer mit Werten aus der Lookup-Table gefüllt 
werden. Sollte gehen denke ich. Außerdem ist die Interruptrate dann auch 
recht gering. Je größer der RAM-Puffer desto geringer die Interrupts.

von Marcus P. (spell)


Lesenswert?

Wenn mir nicht was besseres einfällt, dann sollte das auf eine DDS 
rauslaufen. Was wäre so schlimm daran wenn ich nicht genau an den Anfang 
der LUT komme, ich würde einfach eine 16 oder 32 Bit Variable in 
Vielfachen von 2 hochlaufen lassen und mir die Bits greifen die ich 
brauche, oder über die Array Schreibweise gehen. Ich habe nur absolut 
keine Ahnung was der beste Weg ist dabei die DMA zu benutzen und ob die 
so noch sinnvoll ist, da ich ja eigentlich die CPU entlasten wollte, 
wenn ich aber ständig neue Adressen vergebe wird das sicher nicht 
sinnvoll sein. Ich versuche mich morgen noch mal an den Pointern, 
vielleicht bekommt man doch noch irgendwas hin. Ich hatte eigentlich 
gehofft fast die maximale Geschwindigkeit des DAC nutzen zu können und 
nebenbei noch Zeit für andere Dinge zu haben. Im übrigen ändert sich die 
Frequenz oft ständig, deswegen sollte es eben sehr dynamisch und nicht 
erst nach einer ganzen Periode veränderbar sein.

Ich denke was 900ss D. schreibt klingt doch interessant, irgendwie muss 
man die DMA doch auch in der IRQ füttern können, also Stück für Stück. 
Momentan schreibe ich dort halt jedesmal meine Basis Adresse neu.

Gibt es eigentlich eien Unterschied wenn man die Daten aus dem Ram ließt 
anstelle direkt vom Flash? Ram ist ja an sich schneller. Ich habe mir 
damals mal das ST Beispiel zum Dual Sinus angeschaut, was dort 
merkwürdig war: die schreiben den Wert doppelt in 32 Bit Variablen, so 
als wollten sie jeden Channel getrennt versorgen und lesen dann doch nur 
den Unteren Teil aus...

Gruß Marcus

von 900ss (900ss)


Lesenswert?

Marcus P. schrieb:
> irgendwie muss
> man die DMA doch auch in der IRQ füttern können

Die DMA wird nicht in dem IRQ gefüttert. Der IRQ setzt nur ein Flag 
welches eine Task anschmeißt, die dann den DMA Puffer füttert. Machst du 
das alles im Interrupt, dann läuft nichts mehr richtig. Dauert zu lange 
für den Interrupt. Warum möchtest du die DMA im IRQ füttern?
Die Basisadresse ständig zu ändern bringt dir rein garnichts.

Marcus P. schrieb:
> Ich hatte eigentlich
> gehofft fast die maximale Geschwindigkeit des DAC nutzen zu können und
> nebenbei noch Zeit für andere Dinge zu haben.

Genau das ist möglich. Der DAC braucht ja keine SW Unterstützung zum 
arbeiten. Nur hin und wieder, wenn sein Puffer leer läuft.

von Marcus P. (spell)


Lesenswert?

Doch, mit der Änderung der Basisadresse ging das auch, obwohl ich nicht 
glaube, dass es von der Performance her sinnvoll war. Ich versuche mich 
jetzt gerade mal an der alternativen leichteren Variante, dem Ausgeben 
des Wertes mittels DAC_SetChannel1Data(DAC_Align_12b_R , xxx);
Merkwürdigerweise funktioniert dies nicht, kannst du evetuell auf Anhieb 
sehen was mir fehlt, ich hatte es sogar schon mit einem deiner anderen 
Beiträge verglichen, wo sich herausstellte dass der Controller keine DAC 
hatte^^. (Meiner hat definitiv einen, da es ja mittels DMA-DAC 
funktioniert hat).


GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);


DAC_InitTypeDef            DAC_InitStructure;

DAC_DeInit();
DAC_InitStructure.DAC_Trigger = AC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_1, ENABLE);



int main(void)
{
    while (1)
    {
      testzaehler2++;

        if(sin_zaehler == 1)
          {
          DAC_SetChannel1Data(DAC_Align_12b_R , 2000);
          GPIO_WriteBit(GPIOE, GPIO_Pin_8, Bit_SET);
          }
          else
          {
          DAC_SetChannel1Data(DAC_Align_12b_R , 0);
          GPIO_WriteBit(GPIOE, GPIO_Pin_8, Bit_RESET);
          }




    }
}



Ich habe jetzt nur die für den DAC relevanten Einstellungen 
aufgeschrieben, das ganze sollte dieses Verfahren erst mal testen. Die 
if Bedingungen in der While Schleife werden aus dem Timer2 Interrupt 
erfüllt und die LED an Port E Pin 8 blinkt mit einer Frequenz von ca. 
2Hz, somit werden die Bedingungen auch wirklich angesprochen und der 
Fehler kann dort nicht liegen.
Wie gesagt mittles DMA hatte es funktioniert.

von 900ss (900ss)


Lesenswert?

Ich hab jetzt nichts entdecken können und meinen eigenen Test den ich 
erst auch so aufgebaut hatte, den finde ich jetzt nicht wieder :-(

Ich habe aber ein Beispiel von ST genommen. Das hat funktioniert.

Marcus P. schrieb:
> Doch, mit der Änderung der Basisadresse ging das auch

Wenn du in jedem Interrupt die Basisadresse änderst, dann kannst du auch 
gleich die Werte in den DAC schreiben.

von 900ss (900ss)


Lesenswert?

Ich hab es doch wieder gefunden. Hier der Init-Schnipsel und
dann die Ausgaberoutine eines Dreieck-Signals (für 8 bit).
Ist größtenteils geklaut aus den Beispielen von ST.

1
void  BSP_Init (void)
2
{    
3
  GPIO_InitTypeDef GPIO_InitStructure;
4
5
/*--- System Clocks Configuration **********************************************/
6
  /* Setup the microcontroller system. Initialize the Embedded Flash Interface,
7
     initialize the PLL and update the SystemFrequency variable. */
8
  SystemInit();
9
10
/*--- Configure all unused GPIO port pins in Analog Input mode (floating input
11
     trigger OFF), this will reduce the power consumption and increase the device
12
     immunity against EMI/EMC *************************************************/
13
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
14
                         RCC_APB2Periph_GPIOC , ENABLE);
15
16
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
17
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
18
  GPIO_Init(GPIOA, &GPIO_InitStructure);
19
  GPIO_Init(GPIOB, &GPIO_InitStructure);
20
  GPIO_Init(GPIOC, &GPIO_InitStructure);
21
22
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |
23
                         RCC_APB2Periph_GPIOC , DISABLE);
24
25
/*--- Configure IO connected to LD1, LD2, LD3 and LD4 leds *********************
26
  Enable GPIO_LED clock (GPIOC) */
27
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_LED, ENABLE);
28
29
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
30
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
31
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
32
  GPIO_Init(GPIO_LED, &GPIO_InitStructure);
33
34
/*--- DAC Periph clock enable */
35
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
36
37
  /* Setup SysTick Timer for 1 msec interrupts  */
38
  if (SysTick_Config(SystemFrequency / 1000))
39
  {
40
    /* Capture error */
41
    while (1);
42
  }
43
}

Hier die Ausgabe eines Dreiecksignales:
1
  /* DAC channel1 Configuration */
2
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
3
  DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
4
  DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_LFSRUnmask_Bits8_0;
5
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
6
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);
7
8
  /* Enable DAC Channel1: Once the DAC channel1 is enabled, PA.04 is
9
     automatically connected to the DAC converter. */
10
  DAC_Cmd(DAC_Channel_1, ENABLE);
11
12
  /* Set DAC Channel1 DHR12L register */
13
  DAC_SetChannel1Data(DAC_Align_12b_L, 0x7FF0);
14
15
    while (1) {
16
      if( flag )
17
      {
18
        GPIO_SetBits(GPIO_LED, GPIO_Pin_7);
19
20
        DAC_SetChannel1Data(DAC_Align_8b_R, val++);
21
        /* Start DAC Channel1 conversion by software */
22
        //DAC_SoftwareTriggerCmd(DAC_Channel_1, ENABLE);
23
        if( val == 255)
24
          flag = FALSE;
25
      }
26
      else
27
      {
28
        GPIO_ResetBits(GPIO_LED, GPIO_Pin_7);
29
        DAC_SetChannel1Data(DAC_Align_8b_R, val--);
30
        /* Start DAC Channel1 conversion by software */
31
        //DAC_SoftwareTriggerCmd(DAC_Channel_1, ENABLE);
32
        if( val == 0)
33
          flag = TRUE;
34
      }
35
      OSTimeDly( MSEC(1));  // wait 1ms
36
    }

von Marcus P. (spell)


Lesenswert?

Ich bin ja auch doof wie ne Tüte Suppenmehl und hab das "Oszi" an den 
GPIOE angeschlossen anstelle an den A -.- Da hattest du anhand des Codes 
wenig Chancen den Fehler zu finden ;-P
Meine Fresse, die ganze Zeit mach ichs richtig und dann steck ichs um.
Danke für deinen Code.

Gruß Marcus

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.