STM32F1xx: Brauche mal Hilfe mit DMA

Gast #4518862
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
}
#4518897
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.
Gast #4519114
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);
Gast #4519164
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);
Gast #4519197
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
Gast #4519201
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.
Gast #4519206
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....
Angehängte Dateien:
Gast #4519222
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?
Gast #4519283
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
}
Gast #4519518
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.
Gast #4519573
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.
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren