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!
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 | }
|
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 | }
|
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.