Forum: Mikrocontroller und Digitale Elektronik STM32F205 der DMA und der ADC


von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Hallo, bei mir verhält sich der DMA in Verbindung mit dem ADC1 etwas 
zickig.

Der ADC soll 4 Kanäle durchlaufen, also das SCAN Bit ist gesetzt und das 
soll er endlos tun -> CONT Bit ist auch gesetzt.
Wenn ich jetzt den IRQ auf den ADC draufloslasse, so bekomme ich auch 
jeden Wert zurück und kann den in ein Array einsortieren.

Jetzt soll aber der DMA das abholen, da der ADC doch recht schnell ist 
und die ganzen IRQs den CPU Kern zu sehr belasten.
Daher ist das DMA Bit gesetzt im ADC.
Der DMA selber ist auf den Circle Modus gestellt.
Beim ersten SCAN Durchlauf landen die Werte auch schön per DMA im Array, 
aber danach ist funkstille auf dem DMA Stream.
Ich weis nicht ob der ADC keinen DMA request mehr raushaut oder der DMA 
doch irgendwie meint, dass jetzt Schluss ist?

Als Code nutz ich nicht den Gencode vom STMCUBE, ich wollt da was selber 
häkeln ;)

Hier mal die wichtigen Codeschnipsel:

adc.c:
1
void adc_int_init(unsigned int adc_base){
2
3
  volatile struct adc * const adc = (struct adc *)adc_addrs[adc_base];
4
  volatile struct adcc * const adcc = (struct adcc *)ADCCOM_BASE;
5
6
  //ADC einschalten (RES = 12Bit -> 15ADCCLK cycles)
7
  adc->CR1 = 0;
8
  adc->CR2 = CR2_ADON;
9
  adc->SQR1 = 0;
10
  adc->SQR2 = 0;
11
  adc->SQR3 = 0;
12
  
13
  //ADC Takt setzen (1000x/s alles durchsamplen!) (APB/8)
14
  adcc->CCR = 0b11 << CCR_ADCPRE_SHIFT;
15
}
16
17
static void adc_int_xmode(volatile struct adc * const adc, enum adc_mode amode){
18
19
  //Modus setzen
20
  //adc->SR &=  ~SR_OVR;
21
  adc->CR1 &= ~CR1_SCAN;
22
  adc->CR2 &= ~CR2_CONT;
23
  
24
  switch (amode) {
25
    case ADCM_SINGLE:
26
      //nix setzen
27
      break;
28
    case ADCM_SCAN:
29
      adc->CR1 |= CR1_SCAN;
30
      break;
31
    case ADCM_SCANCONT:
32
      adc->CR1 |= CR1_SCAN;
33
      adc->CR2 |= CR2_CONT;
34
      break;
35
  }
36
  
37
  //fake abholen zum Flag löschen
38
  volatile unsigned int dr = adc->DR;
39
  (void)dr;
40
}
41
42
int adc_int_dmamode(
43
  unsigned int adc_base,               //Welcher ADC
44
  uint16_t *mem_buf,                 //ADC Daten wohin
45
  void (*callb_dma)(unsigned int, void*),     //IRQ Handler DMA
46
  void *callb_data_dma,               //IRQ Handler DMA Daten
47
  void (*callb)(unsigned int, uint16_t, void*),   //IRQ Handler ADc Err
48
  void *callb_data,                 //IRQ Handler ADc Err Daten
49
  enum adc_mode amode){              //ADC Mode
50
51
  volatile struct adc * const adc = (struct adc *)adc_addrs[adc_base];
52
  
53
  //Modus setzen
54
  adc_int_xmode(adc, amode);
55
  
56
  //es gibt 2 Versuche, denn jede Periph ist in 2 DMA Vetreten
57
  //beide belegt -> dammnit
58
  unsigned int circular = 0;
59
  if (ADCM_SCANCONT == amode){
60
    circular = 1;
61
  }
62
  unsigned int sequ_len = adc->SQR1 >> SQR1_SLEN_SHIFT;
63
  sequ_len &= 0b1111;
64
  sequ_len += 1;
65
  dprintf("DMA_ADC scanlen: %u\n", sequ_len);
66
  int i, dma_arr;
67
  for (i = 0; i < 2; i++){
68
    dma_arr = dma_periph(
69
      dma_streamchan[adc_base][i],    //Angabe der Peripherie
70
      (unsigned int)mem_buf,
71
      (unsigned int)&(adc->DR),
72
      sequ_len,      //Anzahl der zu übertragenden Elemente
73
      DMA_SIZE_HWORD,    //Größe der zu Übertragenden Daten
74
      circular,      //mem wird circulär befüllt = DMA geht nie zuende
75
      DMA_PERIPH_TO_MEM,  //in welche Richtung geht der DMA (mem to mem ist verboten)
76
      callb_dma,      //Callback be auftretendem IRQ
77
      callb_data_dma    //Daten für den Callback
78
      );
79
    if (0 == dma_arr){
80
      break;
81
    }
82
  }
83
  if (dma_arr){
84
    return -1;
85
  }
86
  
87
  callb_irq[adc_base] = callb;
88
  callb_irq_data[adc_base] = callb_data;
89
  
90
  //IRQ aktivieren
91
  nvic_enable_irq(IRQ_NBR_ADC, 0xF, 0xF);
92
  adc->CR1 |= CR1_OVRIE;
93
  
94
  //Leinen los!
95
  adc->CR1 &= ~CR1_EOCIE;
96
  adc->CR2 &= ~CR2_EOCS;
97
  adc->CR2 |= /*CR2_DDS |*/ CR2_DMA | CR2_SWSTART;
98
  
99
  return 0;
100
}

dma.c
1
int dma_periph(
2
  enum dma_periph peri,    //Angabe der Peripherie
3
  unsigned int mem_addr,
4
  unsigned int per_addr,
5
  uint16_t data_items_nbr,  //Anzahl der zu übertragenden Elemente
6
  enum dma_size dsize,    //Größe der zu Übertragenden Daten
7
  unsigned int circular,    //mem wird circulär befüllt = DMA geht nie zuende
8
  enum dma_dir ddir,      //in welche Richtung geht der DMA (mem to mem ist verboten)
9
  void (*callb)(unsigned int, void*),  //Callback be auftretendem IRQ
10
  void *callb_data            //Daten für den Callback
11
  ){
12
13
  volatile struct dma * const dma = (struct dma *)dma_get_dma_addr(peri);
14
  unsigned int dma_nbr = dma_get_dma_nbr(peri);
15
  unsigned int stream = dma_get_dma_stream(peri);
16
  unsigned int channel = dma_get_dma_channel(peri);
17
  
18
  if (dma->stream[stream].SCR & SCR_EN){
19
    //Hier wohnt schon wer
20
    return -1;
21
  }
22
  
23
  callb_irq[dma_nbr][stream] = callb;
24
  callb_data_irq[dma_nbr][stream] = callb_data;
25
  
26
  //Statusflags für diesen Stream löschen, sonst geht ein Neustart daneben
27
  dma_clear_irq(dma, stream, ISR_TCIF | ISR_HTIF | ISR_TEIF | ISR_DMEIF | ISR_FEIF);
28
  
29
  dma->stream[stream].SNDTR = data_items_nbr;
30
  dma->stream[stream].SPAR = per_addr;
31
  dma->stream[stream].SM0AR = mem_addr;
32
  
33
  dprintf("DMA%u Stream %u, Channel %u\n", dma_nbr+1, stream, channel);
34
  
35
  //config setzen
36
  unsigned int config = 0;
37
  config |= channel << SCR_CHSEL_SHIFT;
38
  //KEIN MBURST -> kracht wenn man über ne 1kb Grenze schreibt!
39
  //KEIN PBURST
40
  //erstmal kein DBM
41
  config |= dsize << SCR_MSIZE_SHIFT;
42
  config |= dsize << SCR_PSIZE_SHIFT;
43
  config |= SCR_MINC;
44
  //Peripherie Pointer bleibt auf einem Register stehen -> SCR_PINC = 0
45
  if (circular){
46
    config |= SCR_CIRC;
47
  }
48
  if (ddir == DMA_MEM_TO_MEM){
49
    return -2; //Argument Error
50
  }
51
  config |= ddir << SCR_DIR_SHIFT;
52
  //DMA is Flowcontroller
53
  //IRQs sind immer wichtig!
54
  config |= SCR_TCIE | SCR_TEIE | SCR_DMEIE;
55
  
56
  //FIFO config: IRQ an und Viertelfoll
57
  dma->stream[stream].SFCR = SFCR_FEIE | SFCR_DMDIS;
58
  
59
  //IRQ am NVIC aktivieren
60
  nvic_enable_irq(dma_irq_nbrs[dma_nbr][stream], 0xF, 0xF);
61
  
62
  //erst Config schreiben und DANN aktivieren!
63
  dma->stream[stream].SCR = config;
64
  dma->stream[stream].SCR |= SCR_EN;
65
  
66
  dprintf("DMA_SCR (INI): %X\n", dma->stream[stream].SCR);
67
  
68
  return 0;
69
  
70
}

Schlussendlich noch wie das Ganze initialisiert wird:
1
static volatile uint16_t adc_werte[ADC_VAL_COUNT];
2
3
void init_adc(void){
4
5
  //STM32 ADC IO Pins und Clocks initialisieren
6
  rcc_enable_clock(RCC_GPIOA);
7
  gpio_initpin(PORTA, 6, GP_ANALOG, 0, 0); //Temp3
8
  gpio_initpin(PORTA, 7, GP_ANALOG, 0, 0); //Temp4
9
  rcc_enable_clock(RCC_GPIOB);
10
  gpio_initpin(PORTB, 0, GP_ANALOG, 0, 0); //Lüfter_VIN
11
  gpio_initpin(PORTB, 1, GP_ANALOG, 0, 0); //Lüfter_VLFT
12
  rcc_enable_clock(RCC_GPIOC);
13
  gpio_initpin(PORTC, 4, GP_ANALOG, 0, 0); //Temp1
14
  gpio_initpin(PORTC, 5, GP_ANALOG, 0, 0); //Temp2
15
  
16
  //STM32 ADC init
17
  rcc_enable_clock(RCC_ADC1);
18
  adc_int_init(ADC1);
19
  //STM32 ADC Sequenz basteln und übergeben
20
  uint8_t adc_seq[6];
21
  adc_seq[ADC_TEMP_KK1]   = ADC_TEMP_KK1_ADCCH;
22
  adc_seq[ADC_TEMP_KK2]   = ADC_TEMP_KK2_ADCCH;
23
  adc_seq[ADC_TEMP_2]   = ADC_TEMP_2_ADCCH;
24
  adc_seq[ADC_TEMP_4]   = ADC_TEMP_4_ADCCH;
25
  adc_seq[ADC_VCC_LFT]   = ADC_VCC_LFT_ADCCH;
26
  adc_seq[ADC_U_LFT]     = ADC_U_LFT_ADCCH;
27
  adc_int_set_sequence(ADC1, adc_seq, 4);
28
  
29
  adc_int_set_sampleclocks_all(ADC1, ADCSCLK_480CLK);
30
  //adc_int_irqmode(ADC1, callb_adc_int_irq, NULL, ADCM_SCANCONT);
31
  
32
  rcc_enable_clock(RCC_DMA1);
33
  rcc_enable_clock(RCC_DMA2);
34
  int dma_res;
35
  dma_res = adc_int_dmamode(
36
    ADC1,         //Welcher ADC
37
    (uint16_t*)&adc_werte[0],     //ADC Daten wohin
38
    callb_dma,       //IRQ Handler DMA
39
    NULL,         //IRQ Handler DMA Daten
40
    callb_adc_int_irq,   //IRQ Handler ADc Err
41
    NULL,         //IRQ Handler ADc Err Daten
42
    ADCM_SCANCONT);
43
  if (dma_res){
44
    dprintf("ADC DMA ERR: %i\n", dma_res);
45
  }
46
    
47
}

(Man is das viel Code, scrollend im Editor sieht das nach weniger aus)

von Simon (Gast)


Lesenswert?

Du musst continuous requests für DMA aktivieren, damit das mit dem 
continuous conversion Modus funktioniert.

Ich weiß nicht, ob das im Datenblatt auch so heißt, aber so heißt es in 
CubeMX.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Das wäre das DDS bit.
Wie man sieht, hab ich das inzwischen wieder auskommentiert.
Wenns aktiviert ist dann landet der DMA nämlich andauernd (= 
Dauerschleife) im "DMA hat fertig" IRQ.

von Curby23523 N. (Gast)


Lesenswert?

Mw E. schrieb:
> Wenns aktiviert ist dann landet der DMA nämlich andauernd (=
> Dauerschleife) im "DMA hat fertig" IRQ

Ich hab es mir jetzt nicht den ganzen Sourcecode zugeführt, aber du 
musst das Interrupt-Flag vom DMA manuell wieder löschen. Siehe 
Datenblatt.

von Simon (Gast)


Lesenswert?

Dann kann ich dir leider nicht helfen. Ich arbeite immer mit cube und da 
funktioniert es jedes Mal sofort.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

@Simon,
Der Versuch zählt!

@Nils N.
Exakt und vor jedem INit eines DMA.

Den IRQ Handler hab ich ganz verpennt reinzuposten, ups!
Der macht nämlich genau das (Mitm Vorschlghammer alle Bits auf 0 
Klopfen):

dma.c:
1
static void dma_clear_irq(volatile struct dma * const dma, unsigned int stream, unsigned int bits){
2
  
3
  bits &= 0b111101;
4
  
5
  if (stream <= 3){
6
    dma->LIFCR = bits << (stream * 6);
7
  } else {
8
    stream -= 4;
9
    dma->HIFCR = bits << (stream * 6);
10
  }
11
}
12
13
//IRQ Callback bekommt Statusflags
14
void dma_irq (unsigned int dma_nbr, unsigned int stream){
15
16
  volatile struct dma * const dma = (struct dma *)dma_bases[dma_nbr];
17
  
18
  unsigned int errflags = dma_get_irq(dma, stream);
19
  dma_clear_irq(dma, stream, ISR_TCIF | ISR_HTIF | ISR_TEIF | ISR_DMEIF | ISR_FEIF);
20
  
21
  //wenn fertig, dann Stream abschalten
22
  /*if (errflags & ISR_TCIF){
23
    dma->stream[stream].SCR &= ~SCR_EN;
24
  }*/
25
  
26
  dprintf("DMA_SCR (IRQ): %X\n", dma->stream[stream].SCR);
27
  
28
  nvic_pending_clear(dma_irq_nbrs[dma_nbr][stream]);
29
  
30
  if (*callb_irq[dma_nbr][stream]){
31
    callb_irq[dma_nbr][stream](errflags, callb_data_irq[dma_nbr][stream]);
32
  }
33
};

von Curby23523 N. (Gast)


Lesenswert?

Ich finde es immer wieder klasse, wenn man erkennt, dass man HAL nicht 
braucht und Register viel praktischer sind. Lesbarer, kürzer und man 
lernt den Prozessor kennen und hat alles selber in der Hand.

Aber bitte, bitte benutze Registermasken.

Das
1
 if (stream <= 3){
2
    dma->LIFCR = bits << (stream * 6);
3
  } else {
4
    stream -= 4;
5
    dma->HIFCR = bits << (stream * 6);
6
  }

Verstehe ich nach 1 Monat nicht mehr. Z.B.
1
dma->LIFCR |= DMA_LIFCR_CTCIFx; //x = deine Stream-Nummer

Am besten packst du diese Zeile in ein Makro, dann wird es kurz und 
super lesbar! Und das Makro kommt natürlich in eine eigene Headerdatei 
und wird überall eingefügt, wo dus brauchst! Ist so natürlich noch nicht 
fertig das Makro, aber als Idee..
1
#define CLEAR_DMAINTFLAG(a) dma->LIFCR |= DMA_LIFCR_CTCIF(a)
2
3
CLEAR_DMAINTFLAG(x); //x = deine Stream-Nummer

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Is eben nur blöd, dass die 8 Streams über 2 Register verstreut sind.
Daher das ifelse, ne Maske gibts "obendrüber" und sie wird auch 
verwendet:
1
  //Statusflags für diesen Stream löschen, sonst geht ein Neustart daneben
2
  dma_clear_irq(dma, stream, ISR_TCIF | ISR_HTIF | ISR_TEIF | ISR_DMEIF | ISR_FEIF);

Die 4 und die 6 könnt man noch ersetzen durch defines.
"STREAM_PER_REG"
"BITS_PER_STREAM"

Ich wollt eben NICHT alle Bitnamen ins enum werfen wo sich nur ne Zahl 
ändert
.. mea culpa

Aber ich bin kuurz vorm umkippen meiner Meinung, gibts nochn starkes 
Argument ;)?

Der HAL für die STM32 ist mir zu viel Tipparbeit mit den ausschweifenden 
Elementnamen im struct und die passenden Values dazu.
Zudem versteht man dann auch nicht was es tut, genau richtig erkannt ;)
STMCube schießt dann noch den Vogel ab, dass es einen absoluten Pfas zum 
GCC will und der muss innerhalb des Projekts liegen.
(Ich kann den Pfad unter "Toolchain Folder Location" nur verlängern aber 
nicht kürzen)

Jetz funktioniert was nicht, da muss man sich mehr durch die Register 
wühlen.
Wenn mit dem HAL was nicht geht, dann muss man im HAL UND in den 
Registern nach dem Fehler wühlen.

von Curby23523 N. (Gast)


Lesenswert?

Nun, ich programmiere den STM32 auch nur über Register. Zum Clock 
einstellen brauche ich z.B. 11 Zeilen Code, alles nur einzelne 
OR-Anweisungen etc. PLL aktivieren. HSE ektiviert, PLL aktivieren und 
einstellen, HSI deaktivieren usw...

Um einen Timer einzustellen sind es meist ~5 Zeilen.

Die Peripherie der STM32 bietet oft sehr viele Zusatzfunktionen. Wenn 
man diese aber einfach ignoriert und aus lässt - und das kann man in der 
Regel - ist die Peripherie mit Registern nicht schwerer zu 
parametrieren, als auf einem XMEGA oder gar einem Atmega (z.B. SPI).

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

LÄUFT!

Was wars?
Ersteinmal das fehlende DDS Bit beim SCAN+CONT Mode.
Dann liefs auch eigentlich schon, das "unendliche Aufrufen" des Transfer 
End IRQs scheint wohl auch richtig zu sein obwohl der DMA eigentlich nie 
zuende geht wenn er auf Zirkulär geschalten ist.
-> Trotzdem wird der IRQ ausgelöst.
-> Schalten wir den im SCAN+CONT Mode eben ab und lauschen nurnoch auf 
die Fehler IRQ des ADC und des DMA.
Dadurch hat meine kleine Debugausgabe im IRQ den UART so vollgespammt, 
ass die Arrayausgabe nicht mehr stattfand.

Manchmal sieht sone Tischkante echt lecker aus :/

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.