Forum: Mikrocontroller und Digitale Elektronik STM32F1xx: Brauche mal Hilfe mit DMA


von Christian J. (Gast)


Lesenswert?

Hallo,

klappt leider noch nicht so, wie gewünscht, bräuchte da mal Hilfe.

Der ADC1 Kanal 0 soll einfach stetig in ein Array kopiert werden. 
Allerdings nicht so schnell wie möglich, sondern soll das softwaremässig 
getriggert werden, zb aus einem zyklischen Timer Interrupt. Nur um das 
Einsortieren in den Array soll sich die Hardware kümmern.

Tut es aber nicht, es steht einmal Werte im Array, die dem AD WWert auch 
entsprechen und das war es dann auch.

Ich hoffe ich verstehe das richtig: Harware kann einen "DMA Request" 
einleiten, denn irgendwie muss ja ein Startsignal kommen. In meinem Fall 
soll dieses Startsignal allerdings immer manuell kommen. Nur wie weiss 
ich noch nicht.

Aktuell schreibt er 16 Mal das gleiche in den Array rein und bleibt dann 
stehen.

Hier mal mein Code
1
void Init_DMA_LDR()
2
{
3
    #define ADC1_DR_Address   0x4001244C                    // ADC Regular Data Register
4
5
    GPIO_InitTypeDef    GPIO_InitStructure;
6
    ADC_InitTypeDef     ADC_InitStructure;
7
    DMA_InitTypeDef     DMA_InitStructure;
8
9
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    // GPIOA Clock enable
10
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);    // ADC Clock enable
11
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
12
13
    /* ADC Pin konfigurieren */
14
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;               // AN1 Pin (PA0) aktivieren
15
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
16
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
17
    GPIO_Init(GPIOA, &GPIO_InitStructure);
18
19
    /* ADC1 configuration ------------------------------------------------------*/
20
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
21
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
22
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
23
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
24
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
25
    ADC_InitStructure.ADC_NbrOfChannel = 1;
26
    ADC_Init(ADC1, &ADC_InitStructure);
27
28
    /* DMA1 Channel1 configuration ----------------------------------------------*/
29
    DMA_DeInit(DMA1_Channel1);
30
    DMA_StructInit(&DMA_InitStructure);
31
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                           // Peripherie ist Datenquelle
32
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_BASE;              // Vom ADC Wandler....
33
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&LDR;                       // .....zum Array
34
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;             // Auf ADC keine Inkrementierung
35
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                      // Auf Array inkrementieren
36
    DMA_InitStructure.DMA_BufferSize = LDR_HIST;
37
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // ADC Wandler 16 Bit
38
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;          // Memory auch 16 Bit
39
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                              // Array circular füllen
40
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;                           // Niedrige Prio
41
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
42
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                 // Mache Einstellungen
43
44
    /* Enable DMA1 Channel1 */
45
    DMA_Cmd(DMA1_Channel1, ENABLE);
46
47
    /* ADC1 Kanal 1 configuration */
48
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // PA1
49
    ADC_Cmd(ADC1, ENABLE);
50
51
    /* Enable ADC1 reset calibaration register */
52
    ADC_ResetCalibration(ADC1);
53
54
    /* Check the end of ADC1 reset calibration register */
55
    while(ADC_GetResetCalibrationStatus(ADC1));
56
57
    /* Start ADC1 calibaration */
58
    ADC_StartCalibration(ADC1);
59
60
    /* Check the end of ADC1 calibration */
61
    while(ADC_GetCalibrationStatus(ADC1));
62
63
   /* Starte Wandlung */
64
   ADC_SoftwareStartConvCmd(ADC1, ENABLE);
65
66
}

von Jim M. (turboj)


Lesenswert?

Error is in Code not shown.
Zeige uns mal den vollständigen Code, insbesondere den Timer 
Interrupt.

von 900ss (900ss)


Lesenswert?

Du kannst (mußt?) einen Timer-IRQ zum Triggern des DMA-Requests 
benutzen. Hab ich schon gemacht, allerdings vom RAM zum DAQ. Ist aber so 
lange her, dass ich genaues grad nicht weiß. Es sind nicht alle Timer 
für alle DMA-Kanääle zu verwenden. Must mal das Manual wälzen.

von Christian J. (Gast)


Lesenswert?

Hat sich erledigt, klappt jetzt supi!

von Feedback (Gast)


Lesenswert?

Und was hast du gemacht, wo steckte der Fehler?

von aSma>> (Gast)


Lesenswert?

Servus,
errata sagt, dass PA0 ungeeignet ist als ADC.

Weiterhin darf der ADC clock nicht mehr wie 14MHz sein.
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //hier bei 72Mhz hat man 12Mhz

RCC_APB2Periph_AFIO nicht aktiv ;(

Bitte, bitte.

PS: guck mal hier vorbei:
http://www.diller-technologies.de/stm32.html#dma

von Christian J. (Gast)


Lesenswert?

PA0 klappt ganz normal bei mir.

Man muss einfach den ADC auf Single Shot einstellen und ihn an den DMA 
koppeln, wenn er fertig gewandelt hat. Außerdem muss man ihn von allen 
Evenst abkoppeln, die ihmn starten könnten und das muss manuell 
passieren, im Struct aber auch per Befehl.

Der Befehl

ADC_SoftwareStartConvCmd(ADC1, ENABLE);

irgendwo plaziert startet dann jedesmal eine neue Wandlung die der DMA 
dann brav ablegt.

PS: Diller kenne ich auswendig, ausgedruckt als Heft :-)

1
/* Initiiert den Transfer zwischen ADC1
2
   Kanal und dem Array LDR              */
3
void Init_DMA_LDR()
4
{
5
    #define ADC1_DR_Address   0x4001244C    // ADC Regular Data Register
6
7
    GPIO_InitTypeDef    GPIO_InitStructure;
8
    ADC_InitTypeDef     ADC_InitStructure;
9
    DMA_InitTypeDef     DMA_InitStructure;
10
11
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);                        // GPIOA Clock enable
12
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);                        // ADC Clock enable
13
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
14
15
    /* ADC Pin konfigurieren --------------------------------------------------*/
16
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;                                   // AN1 Pin (PA0) aktivieren
17
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;                               // Analog Input
18
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
19
    GPIO_Init(GPIOA, &GPIO_InitStructure);
20
21
    /* ADC1 configuration ------------------------------------------------------*/
22
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                          // ADC1 werkelt allein
23
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;                               // Nur 1 Kanal, keine Scans
24
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;                         // Keine ständigen Wandlungen
25
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;         // Start mit Software, nicht durch Hardware
26
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                      // Bits rechts ausrichten im Half-Word
27
    ADC_InitStructure.ADC_NbrOfChannel = 1;                                     // 1 Kanal sampeln
28
    ADC_Init(ADC1, &ADC_InitStructure);
29
30
    ADC_Cmd(ADC1, ENABLE);
31
32
    /* DMA1 Channel1 configuration ----------------------------------------------*/
33
    DMA_DeInit(DMA1_Channel1);
34
    DMA_StructInit(&DMA_InitStructure);
35
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                           // Peripherie ist Datenquelle
36
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;        // ADC Basisadresse Datenregister
37
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)LDR;                        // Memory Array Adresse
38
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;             // Auf Source ADC keine Inkrementierung
39
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                      // Auf Target Array inkrementieren
40
    DMA_InitStructure.DMA_BufferSize = (uint16_t)LDR_HIST;                       // Groesse des Arrays
41
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // ADC Wandler 16 Bit
42
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;          // Memory auch 16 Bit
43
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                              // Array circular füllen
44
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                          // Hohe Prio
45
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
46
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                 // Mache Einstellungen
47
48
    /* Enable DMA1 Channel1 */
49
    DMA_Cmd(DMA1_Channel1, ENABLE);
50
51
    /* ADC1 Sequencer Kanal 1 auf Rang 1 configuration */
52
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // PA0
53
54
    /* Enable ADC1 reset calibaration register */
55
    ADC_ResetCalibration(ADC1);
56
57
    /* Check the end of ADC1 reset calibration register */
58
    while(ADC_GetResetCalibrationStatus(ADC1));
59
60
    /* Start ADC1 calibaration */
61
    ADC_StartCalibration(ADC1);
62
63
    /* Check the end of ADC1 calibration */
64
    while(ADC_GetCalibrationStatus(ADC1));
65
66
   /* Keine Triggerung durch Events etc */
67
   ADC_ExternalTrigConvCmd(ADC1, DISABLE);
68
69
   /* Erzeuge nach jeder Wandlung einen DMA Request */
70
   ADC_DMACmd(ADC1, ENABLE);
71
72
   /* Starte Wandlung */
73
   ADC_SoftwareStartConvCmd(ADC1, ENABLE);

von aSma>> (Gast)


Lesenswert?

>Servus,
>errata sagt, dass PA0 ungeeignet ist als ADC.
>
>Weiterhin darf der ADC clock nicht mehr wie 14MHz sein.
>RCC_ADCCLKConfig(RCC_PCLK2_Div6); //hier bei 72Mhz hat man 12Mhz
>
>RCC_APB2Periph_AFIO nicht aktiv ;(

Bitte wichtige Infos nicht überlesen, sondern anwenden!

Hier nochmals: 
http://www.st.com/st-web-ui/static/active/jp/resource/technical/document/errata_sheet/CD00190234.pdf

>providing that PA0 is driven with an impedance lower than 5kΩ

Und das hier:
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

Ist irgendwie ein widerspruch! Mache aus DMA_Mode_Circular ==> 
DMA_Mode_Normal. Und starte jedesmal eine neue Konversation mit:

ADC_SoftwareStartConvCmd(ADC1, DISABLE);
  DMA_Cmd(DMA1_Channel1, DISABLE);
  DMA1_Channel1->CNDTR = 2;
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
  DMA_Cmd(DMA1_Channel1, ENABLE);

von Christian J. (Gast)


Lesenswert?

aSma>> schrieb:
> Bitte wichtige Infos nicht überlesen, sondern anwenden!

Du, das steht drin .... und zwar direkt nach SysInit, ganz am Anfang :-) 
Und noch viele Male bei anderen Dingen, die das auch brauchen.

>>Und das hier:
>>ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
>>DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

Ist schon ok so. Dann läuft der Zeiger nämlich wieder auf LDR[0] nachdem 
er das letzte Element fertig hatte. Sonst wird der nicht zurück gesetzt 
auf 0.
Wenn Du

ADC_ContinuousConvMode = ENABLE

setzt rattert der DMA binnen Mikrosekunden das Array voll.

Und DMA wird manuell gestartet indem der ADC angestossen wird. Mit PA0 
kannste Recht haben aber bei mir passt es am 5kOhm LDR, der eh nur 
hell/dunkel merken muss.

1
/* System Grundeinstellungen, Clock, APB1,2 usw.  */
2
   SystemInit();
3
4
   /* Systick einrichten für Zeitmessung */
5
   RCC_GetClocksFreq(&RCC_Clocks);                      // Clocks holen
6
   SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
7
   SysTick_Config(RCC_Clocks.SYSCLK_Frequency/1000);
8
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
9
10
   /* JTAG abschalten (PB3, PB4 freimachen) */
11
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
12
   GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
13
14
   /* Busclocks setzen */
15
   RCC_HCLKConfig(RCC_SYSCLK_Div1);  // AHB Clock auf Maximum
16
   RCC_PCLK1Config(RCC_HCLK_Div2);   // APB1 Clock auf 36Mhz
17
   RCC_PCLK2Config(RCC_HCLK_Div2);   // APB2 Clock auf 36 Mhz

von Christian J. (Gast)


Lesenswert?

Da habe ich mir auch den Affen dran gesucht :-((( Ein Blick ins Errata 
hätte mir Stunden erspart..grr....

Debugging Stop mode and system tick timer
Description If the system tick timer interrupt is enabled during the 
Stop mode debug (DBG_STOP bit set in the DBGMCU_CR register ), it will 
wakeup the system from Stop mode. Workaround To debug the Stop mode, 
disable the system tick timer interrupt.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

aSma>> schrieb:
> ADC_SoftwareStartConvCmd(ADC1, DISABLE);
>   DMA_Cmd(DMA1_Channel1, DISABLE);
>   DMA1_Channel1->CNDTR = 2;
>   ADC_SoftwareStartConvCmd(ADC1, ENABLE);
>   DMA_Cmd(DMA1_Channel1, ENABLE);

Was soll das bringen? Faktisch spielt er den Array jedesmal dann genauso 
voll wie vorher. >Ich brauche DMA nur 1 Mal zu starten und er kreist 
endlos herum, triggert jedesmal wenn ich eine Wandlung anstosse. Du 
lädst es händisch nach, ich automatisch. Dafuer ist es ja da....

von aSma>> (Gast)


Lesenswert?

Christian J. schrieb:
> Was soll das bringen? Faktisch spielt er den Array jedesmal dann genauso
> voll wie vorher. >Ich brauche DMA nur 1 Mal zu starten und er kreist
> endlos herum, triggert jedesmal wenn ich eine Wandlung anstosse. Du
> lädst es händisch nach, ich automatisch. Dafuer ist es ja da....

Das ist ein Trugschluß, denn die DMA brauch auch ein wenig CPU! K.A aber 
sagen wir so 20-30%. Durch das automatischen Reloaden läuft das Ding 
ohne Ende.

WOZU?

von aSma>> (Gast)


Lesenswert?

Genau du musst das Ding triggern und nicht sich totlaufen lassen. Das 
wäre schon mal geklärt.

Ja, dann mach das mal :).

von Christian J. (Gast)


Lesenswert?

aSma>> schrieb:

> Das ist ein Trugschluß, denn die DMA brauch auch ein wenig CPU! K.A aber
> sagen wir so 20-30%. Durch das automatischen Reloaden läuft das Ding
> ohne Ende.

Ähm .... gibt es irgendwo Beweise für diese Behauptung? Dass die DMA 
20-30% "CPU braucht"? Klingt völlig aus der Luft gegriffen, so wie "ADC 
Wandler immer abschalten nach Gebrauch, geht sonst schneller kaputt."

Und ja, es ist Sinn der Sache, dass das Ding ohne Ende läuft. Die DMA 
schiesst die Daten in den Array rein und der Timer INT rechnet nebenbei 
laufende die Mittelwerte usw aus. Naja, Spass am programmieren, ne 
ernsthafte Anwendung steckt da nicht hinter.

Rrrrrrrrr......

1
/* ---- Timer 3 Time Base  ------
2
3
   Holt die AD Werte ab fuer die LED Steuerung
4
5
*/
6
void  TIM3_IRQHandler()
7
{
8
    static uint16_t cnt = 0;
9
    static uint32_t sum = 0;
10
11
    /* Fehlerhafte IRQ Anforderungen ausblenden */
12
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) == RESET)
13
       return;
14
15
    /* Clear IRQ Flag */
16
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
17
18
    /* Mittelwert des gesamten Arrays bilden, bei jedem Durchlauf
19
       aber nur 1 Element addieren */
20
21
    sum += LDR[cnt++];
22
    if (cnt >= LDR_HIST)
23
    {
24
        LDR_Value = 0.02417 * (sum / LDR_HIST);
25
        sum = 0;
26
        cnt = 0;
27
    }
28
29
    /* Neue ADC Wandlung anstossen + DMA */
30
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
31
32
}

von aSma>> (Gast)


Lesenswert?

Servus,
die DMA läuft bekannter maßen durch einen Bus. Wenn dieser Bus voll ist, 
weil die DMA die ganze Zeit rumeiert, dann läuft auch die Anwendung 
langsamer ab:
http://www.embedds.com/using-direct-memory-access-dma-in-stm23-projects/

Du machst das ja richtig. Die DMA läuft nur kurz, durch einen timer 
triggerung.

Hier habe ich auch was gelernt, dass mit:
>ADC_SoftwareStartConvCmd(ADC1, ENABLE);

und
>ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
>DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

Sich ja 5 Zeilen Code sparen kann. Das teste ich später dann.
Danke.

Aber es gibt bekannter maßen mehrere Wege nach Rom.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

aSma>> schrieb:
> Servus,
> die DMA läuft bekannter maßen durch einen Bus. Wenn dieser Bus voll ist,
> weil die DMA die ganze Zeit rumeiert, dann läuft auch die Anwendung
> langsamer ab:

Natürlich teilen sich CPU und DMA den Bus. Aber wo bei mir der 
Flasschenhals sein soll weiss ich nicht. Der ADC wird mit einer Rate von 
200ms per Hand getriggert in einem der Rundlauf Timer ISR's. Und dann 
schnubbeln die Daten direkt in den Array rein, sieht man schön im 
Debugger. Die ISR bildet dann auch einen fliessenden Mittelwert. Auch 
wenn man fliessende Mittelwerte mit einer Formel berechnen kann, die den 
neuen Wert gegen die alten gewichtet, so brauche ich doch kein 
Fliesskomma.

Schön, schön..... tolles Steinchen und gegenüber den F429 noch 
übersichtlich.

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.