Forum: Mikrocontroller und Digitale Elektronik STM32F4 / DMA und ADC


von Michael W. (Gast)


Lesenswert?

Hallo!

Ich möchte herausfinden, wie man den DAC zusammen mit DMA richtig 
verwendet, um eine Sequenz (=Gruppe) von Channels zu konvertieren.


Da ich DMA bisher noch nie verwendete, kann es sein, dass ich eine 
falsche Vorstellung davon habe, daher bitte ich, mich zu korrigieren.

Im Reference Manual steht im Kapitel ADC:

When the DMA mode is enabled (DMA bit set to 1 in the ADC_CR2 register), 
after each conversion of a regular channel, a DMA request is generated.


Angenommen ich habe in der Gruppe 3 Channels, CH0, CH1, CH3, und diese 
werden kontinuierlich konvertiert.

Wenn CH1 fertig ist, wird ein DMA Request gestartet, der die Daten in 
den Speicher an die Stelle MEM0 schreibt. Ist dann CH2 fertig, wird 
wiederum so ein Request gestartet, der Pointer der Speicherzelle wird 
aber incrementiert und somit auf MEM1 = MEM0 + 1 geschrieben. Gleiches 
für CH3. Was passiert dann aber, wenn wieder CH0 drankommt? Muss ich da 
den Circular Mode aktivieren, damit der DMA_SxNDTR Inhalt wieder mit 3 
beschrieben wird und alles von vorn losgeht?

Wie kann ich erreichen, dass der Beginn der DMA Übertragung mit CH0 
zusammenfällt, und CH0 wirklich auf MEM0 geschrieben wird. Der DMA 
Controller weiss ja nicht, ob CH0, CH1 oder CH2 gerade konvertiert 
worden ist. Oder gehe ich einfach davon aus, dass der Mechanismus nie 
außer Tritt fällt, und nach Millionen von Konversionen, die Reihenfolge 
immer noch stimmt?

Ich hoffe, der Kern meiner Frage ist "rübergekommen". Vielleicht kann 
mir jemand in kurzen Worten erklären, wie man DMA mit dem ADC richtig 
verwendet.

Danke!

von ttl (Gast)


Lesenswert?

in den STM libraries gibt es dazu ein Bespiel

von Michael W. (Gast)


Lesenswert?

ttl schrieb:
> in den STM libraries gibt es dazu ein Bespiel

Ja, danke, es mangelt ja nicht an Beispielen. Ich habe eine 2 Kanal ADC 
Konvertierung mit DMA anhand eines Beispiels implementiert, wo CH0/CH1 
auf Adresse AD0/AD1 geschrieben werden - und es funktioniert (bisher) 
makellos!

Trotzdem habe ich da irgendwie ein "flaues Gefühl im Magen", da die 
Abfolge

CH0, CH1, CH0, CH1, ...
AD0, AD1, AD0, AD1, ...

durch das erstmalige Auftreten eines fertigen Konversionszyklus 
zugeordnet wird. Sollte aus irgendeinem Grund (vielleicht bin ich hier 
übersensibel, aber was ist wenn die MCU monatelang läuft und ein 
interner HW Fehler oder sonst was auftritt...) die Zuordnung 
durcheinander kommen

CH0, CH1, CH0, CH1, ...
AD1, AD0, AD1, AD0, ...

würde ich das nicht merken!!!

1) Ist diese Angst unbegründet?
2) Oder gibt es dagegen ein Patentrezept?

Ich gebe mich nicht damit zufrieden, dass das Beispiel "halt 
funktioniert", sondern möchte wissen, was ich tun muss, um die Daten 
100% (und nicht 99.999%) zuordnen zu können.

Abgesehen davon glaube ich nicht, dass man diesen kontinuierlichen Modus 
wirklich benötigt. Bei allem was ich mir an sinnvollen Anwendungen 
vorstellen kann, möchte man Konversionen in einem bestimmten zeitlichen 
Abstand anstoßen, und dann würde ich Interrupts verwenden zusammen mit 
dem ADC single-shot Modus.

Danke, Michael


1
void DMA_Configuration(void)
2
{
3
  DMA_InitTypeDef DMA_InitStructure;
4
5
  /* DMA2 Stream0 channel0 */
6
  DMA_InitStructure.DMA_Channel = DMA_Channel_0;
7
8
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
9
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADC1ConvertedValue;
10
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
11
  DMA_InitStructure.DMA_BufferSize = 2;
12
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
13
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
14
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
15
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
16
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
17
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
18
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
19
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
20
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
21
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
22
  DMA_Init(DMA2_Stream0, &DMA_InitStructure);
23
  DMA_Cmd(DMA2_Stream0, ENABLE);
24
}
25
26
...
27
...
28
29
void ADC1_Configuration(void)
30
{
31
  ADC_InitTypeDef       ADC_InitStructure;
32
  ADC_CommonInitTypeDef ADC_CommonInitStructure;
33
34
35
  /* ADC Common Init */
36
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
37
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
38
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
39
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
40
  ADC_CommonInit(&ADC_CommonInitStructure);
41
42
  /* ADC1 */
43
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
44
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;
45
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
46
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
47
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
48
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
49
  ADC_InitStructure.ADC_NbrOfConversion = 2;
50
  ADC_Init(ADC1, &ADC_InitStructure);
51
52
  ADC_TempSensorVrefintCmd(ENABLE);
53
  ADC_EOCOnEachRegularChannelCmd(ADC1, DISABLE);
54
  ADC_RegularChannelConfig(ADC1, ADC_Channel_TempSensor, 1, ADC_SampleTime_28Cycles);
55
  ADC_RegularChannelConfig(ADC1, ADC_Channel_Vrefint, 2, ADC_SampleTime_28Cycles);
56
57
  /* Enable DMA request after last transfer */
58
  ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
59
60
  //ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
61
62
  /* Enable ADC1 DMA */
63
  ADC_DMACmd(ADC1, ENABLE);
64
65
  /* Enable ADC1 */
66
  ADC_Cmd(ADC1, ENABLE);
67
68
}

von Nico W. (nico_w)


Lesenswert?

Michael W. schrieb:
> Trotzdem habe ich da irgendwie ein "flaues Gefühl im Magen", da die
> Abfolge
>
> CH0, CH1, CH0, CH1, ...
> AD0, AD1, AD0, AD1, ...
>
> durch das erstmalige Auftreten eines fertigen Konversionszyklus
> zugeordnet wird. Sollte aus irgendeinem Grund (vielleicht bin ich hier
> übersensibel, aber was ist wenn die MCU monatelang läuft und ein
> interner HW Fehler oder sonst was auftritt...) die Zuordnung
> durcheinander kommen
>
> CH0, CH1, CH0, CH1, ...
> AD1, AD0, AD1, AD0, ...
>
> würde ich das nicht merken!!!

Etwas nekrophil, aber genau das Problem hatte ich gerade.
Wenn man das DDS bit ungesetzt lässt, läuft der DMA genau einmal durch.

Da ich in meinem Falle nur alle 10ms ein paar Messwerte benötige, habe 
ich die ADC-DMA-Geschichte wie im Reference Manual beschrieben neu 
gestartet.

Also:
1
// startet alle 10ms den ADC neu
2
void start_adc() {
3
  // DMA bit löschen
4
  // und wieder setzen für nächste Konvertierung
5
  // RM0383 11.8.1 Data management -> Using the DMA
6
  ADC1->CR2 &= ~(ADC_CR2_DMA);
7
  ADC1->CR2 |= ADC_CR2_DMA;
8
}

Und dort trat genau dieser Effekt auf. Also manchmal hatte ich AD0-AD1 
als Ergebnis, manchmal AD1-AD0.

Zudem, obwohl im RM genau das Gegenteil erzählt wird, sollte durch DDS 
== 0 nie ein Overrun entstehen. Aber das hatte ich jetzt auch.

Was also machen? Nach langem probieren kam ich letzendlich auf die 
einzige Lösung in diesem Falle:
1
// startet alle 10ms den ADC neu
2
void start_adc() {
3
  ADC1->CR2 &= ~(ADC_CR2_ADON); // A/D aus
4
  ADC1->CR2 &= ~(ADC_CR2_DMA);
5
  ADC1->CR2 |= ADC_CR2_DMA;
6
  ADC1->CR2 |= ADC_CR2_ADON;    // A/D an
7
  ADC1->CR2 |= ADC_CR2_SWSTART; // und nächste Runde starten
8
}

von Nico W. (nico_w)


Lesenswert?

Den ADC auszuschalten ist eigentlich keine gute Idee. Der braucht laut 
Datenblatt bis zu 3µs um genau zu laufen.

Um da keine Probleme zu bekommen habe ich jetzt den Transfer Interrupt 
Complete vom DMA eingeschaltet und im zugehörigen Interrupt dann das 
DMA_CR2_CONT-bit gelöscht. Das ganze funktioniert dann genau so.

Also CONT bit löschen im DMA Handler. Und dann im start_adc() einmal 
wieder das CONT bit setzen und den ADC über SWSTART anstupsen.

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.