Forum: Mikrocontroller und Digitale Elektronik stm32F4 + ADC + Timer + DMA


von frage (Gast)


Lesenswert?

Hallo,

Hardware: stm32F4discovery
---------

folgendes Szenario:
-------------------

Ein Timer triggert (mit 1000Hz) den ADC, welcher wiederrum einen 
DMA-Request auslöst, wodurch per DMA die gemessenen Werte (160 stück) in 
einen Buffer geschrieben werden. Wenn der Buffer beschrieben wurde hört 
der DMA auf Werte hineinzuschreiben und ich kann die Werte aus dem 
Buffer lesen.
Das funktioniert nun auch.

Frage:
------

Wie lasse ich nun das ganze abermals starten? Vermutlich trivial, aber 
ich bekomme es nicht sauber hin. DMA_ClearFlag(DMA2_Stream0, 
DMA_FLAG_TCIF0);
führt nicht zu einem Neustart. Ich möchte aber bei jedem 
Schleifendurchlauf wieder per Befehl einen Samplevorgang neu initiieren

Hier ein Auschnitt des Codes:
1
TIM2_Config()
2
{
3
TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
4
5
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
6
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
7
  TIM_TimeBaseStructure.TIM_Period = 84-1; // 1000Hz
8
  TIM_TimeBaseStructure.TIM_Prescaler = 1000-1; // 84000hz
9
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
10
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
11
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
12
  TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
13
//TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
14
}
15
16
adc_dma_init()
17
{
18
ADC_InitTypeDef       ADC_InitStructure;
19
ADC_CommonInitTypeDef ADC_CommonInitStructure;
20
  DMA_InitTypeDef       DMA_InitStructure;
21
  GPIO_InitTypeDef      GPIO_InitStructure;
22
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 | RCC_AHB1Periph_GPIOA, ENABLE);
23
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE);
24
  DMA_InitStructure.DMA_Channel = DMA_Channel_2;
25
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC3_DR_ADDRESS;
26
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&ADC3ConvertedValue;
27
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
28
  DMA_InitStructure.DMA_BufferSize = 160;
29
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
30
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
31
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
32
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
33
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
34
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
35
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
36
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
37
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
38
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
39
  DMA_Init(DMA2_Stream0, &DMA_InitStructure);
40
  DMA_Cmd(DMA2_Stream0, ENABLE);
41
42
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
43
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
44
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
45
  GPIO_Init(GPIOA, &GPIO_InitStructure);
46
47
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
48
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div2;
49
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
50
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
51
  ADC_CommonInit(&ADC_CommonInitStructure);
52
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
53
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
54
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
55
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
56
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T2_TRGO;
57
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
58
  ADC_InitStructure.ADC_NbrOfConversion = 1;
59
  ADC_Init(ADC3, &ADC_InitStructure);
60
  ADC_RegularChannelConfig(ADC3, ADC_Channel_1, 1, ADC_SampleTime_15Cycles);
61
  ADC_DMARequestAfterLastTransferCmd(ADC3, ENABLE);
62
  ADC_DMACmd(ADC3, ENABLE);
63
  ADC_Cmd(ADC3, ENABLE);
64
}
65
66
67
int main(void)
68
{
69
  
70
  SystemInit();
71
72
  TIM2_Config();
73
  adc_dma_init();
74
  TIM_Cmd(TIM2, ENABLE);
75
76
  while(1)
77
    {
78
79
    DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);
80
    while(!DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0));
81
82
  //... ab hier die gemessenen Werte aus dem Buffer lesen und
83
//verarbeiten...
84
    }
85
}

von adc (Gast)


Lesenswert?

Bei meinem STM32 F1 starte ich meine ADC-Conversion mit
1
ADC_SoftwareStartConvCmd(ADC1, ENABLE);

Dabei sind 4 ADC-Kanäle zu einer Regular Group zusammengefasst und per 
DMA-Controller werden die Daten in einen Buffer geschrieben.
Also vom Prinzip ähnlich wie deins. Vielleicht hilft dir das weiter.

von frage (Gast)


Lesenswert?

Danke für die Antwort!

Mein ADC läuft bereits ständig durch. Ich muss ihn nicht per Software 
triggern, da er schon vom Timer getriggert wird. Das Problem ist nur, 
dass die DMA nur genau einmal durchläuft und dann stoppt (Der ADC läuft 
derweil weiter). Ich möchte dann per Befehl sagen, wann die DMA wieder 
einen Durchlauf machen soll um einen Schwung Daten vom ADC abzuholen.

Ich könnte die DMA auch auf DMA_Mode_Circular stellen, dann würde sie 
aber automatisch immer wieder neu den Buffer beschreiben. Ich will es 
jedoch manuell per Befehl initiieren.

von Star K. (starkeeper)


Lesenswert?

Der ADC läuft koninuierlich aber der DMA nicht? Was soll mit den Werten 
passieren, die der ADC misst während der DMA keine Daten mehr kopiert?

Der ADC wird dann feststellen das sein Register nicht gelesen wurde und 
einen Fehler setzen, eventuell dann auch aufhören zu konvertieren.

Also ich würde sagen entweder beiden auf circular modus oder beides 
händisch triggern. Macht ja sonst auch keinen Sinn, waum soll der ADC 
laufen wenn die Werte ohnehin verloren sind.

von frage (Gast)


Lesenswert?

Ich möchte die Samplerate ja per Timer genau einstellen können. Eine 
Möglichkeit wäre den ADC in einem Timerinterrupt zu triggern, aber ich 
möchte hier den Prozessor entlasten und die DMA würde dann auch keinen 
Sinn mehr machen.

Der Prozessor soll immer nur kurz einen Befehl geben, anschließend 
werden x Samplewerte gebuffert. Aber - und das ist der springende Punkt 
- vom Timer getriggert.

von hnun (Gast)


Lesenswert?

wie wär's mit einem

> DMA_Cmd(DMA2_Stream0, ENABLE);

pro Durchlauf?

von hnun (Gast)


Lesenswert?

... und möglicherweise müssen ja die DMA-Adressen und -Zähler
neu gesetzt werden vor jedem Neustart.

Was sagt das schlaue Datenblatt?

von DE (Gast)


Lesenswert?

Hi !

Es geht, wenn Du den DMA auf circular stellst.
1
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

Wie Du richtig schreibst, wird dann Dein Puffer überschrieben, das 
kannst Du aber verhindern, indem Du die DMA anhälst. Das machst Du am 
besten mit dem DMA2_Stream...IRQHandler.
1
void DMA2_Stream0_IRQHandler(void){
2
3
    // Interrupt service handler for full transfer
4
    if( DMA_GetITStatus( DMA2_Stream0, DMA_IT_TCIF0 ) != RESET )
5
    {
6
    ADC_DMACmd(ADC1, DISABLE);  // Disable DMA otherwise DMA data destroys array during computation
7
    }
8
9
    DMA_ClearFlag( DMA2_Stream0, DMA_IT_TCIF0 );
10
    // Will be enabled after calc.
11
12
}

Der muss natürlich konfiguriert werden:
1
   /* Enable DMA2 channel1 IRQ Channel */
2
  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
3
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
4
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
5
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
6
  NVIC_Init(&NVIC_InitStructure);

und eingeschaltet werden muss er auch
1
DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);

DMA_IT_TC triggert den IORQ nach einer komplett Füllung des Puffers, 
DMA_IT_HC triggert nach der Hälfte.

Gruß

DE

von Patrick B. (p51d)


Lesenswert?

Ich hatte ein ähnlisches Projekt:
256 analoge Pixelwerte eines Pixelarrays mussten ausgelesen werden. 
Hierzu wurde jeder neue Wert erst nach einem Shift-Impuls an den 
Analog-Ausgang gelegt.
Dies habe ich dann so gelöst:
- Ein Timer triggert mir den ADC (continuous mode) wobei der Timer 
selber als Repetition counter genutz wurde (256 Bit).
- Der Selbe Timer hat mir zugleich ein PWM Signal ausgegeben, welches 
als Shift genutzt wurde (Flankengetriggert). Der ADC wurde dann auf die 
negative Flanke gestartet und sobald fertig wurde der DMA request 
aufgerufen und der Wert in das Array kopiert.
- Der DMA ist ebenfalls im continuous mode und gibt mir jeweils ein 
complete interrupt, welches ich bequem abfagen kann.

In deinem Fall kannst du ja eventuell noch einen weiteren Timer nutzen 
um das ganze zu starten.
Die Timermodule sind sehr umfangreich, und man durchschaut nicht gleich 
alle Funktionen... Viel Erfolg

von Michael F. (startrekmichi)


Lesenswert?

Stichwort double buffering: Es gibt zwei Puffer, die abwechselnd vom DMA 
gefüllt werden. Sobald einer voll ist, wird ein Interrupt ausgelöst. Die 
CPU verarbeitet nun den ersten Puffer, während der DMA in den zweiten 
Puffer schreibt. Ist der zweite voll, wird wieder gewechselt. Du musst 
nur sicherstellen, dass die CPU mit der Verarbeitung eines Puffers 
fertig ist, bevor der andere voll ist.

Das habe ich glaube ich sogar schon mal mit dem F4 gemacht 
(ADC->DMA->zwei Puffer), aber den Code finde ich leider gerade nicht 
mehr. Sollte aber relativ einfach machbar sein.

von Michael (Gast)


Lesenswert?

Hallo zusammen,
ich habe ein ähnliches Problem.
Einen ADC Channel einlesen von 160 Werten hab auch ich problemlos 
geschafft. Nun möchte ich aber mehrere Eingänge gleichzeitig einlesen. 
Sozusagen ein mehrspur recorder.

Wie muss ich hierbei vorgehen? Was muss ich im Gegensatz zum ersten 
Beispiel hir oben ändern? Hat das schon wer gemacht?

Viele grüße

Michael

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.