Forum: Mikrocontroller und Digitale Elektronik STM32 baremetal ADC


von Marc (Gast)


Lesenswert?

Nachdem die Baremetal-Implementierung auf einem Nucleo F303K8 so gut 
geklappt hat
Beitrag "STM32Duino DMA DAC"

Versuche ich mich an einer baremetal ADC Implementierung.

Leider tut sich bis jetzt da gar nichts.
Hat jemand eine Idee, woran es liegen könnte?
1
// F303 baremetal ADC
2
//
3
// References:
4
// https://www.youtube.com/watch?v=Zq4Ixz6mFDE
5
// https://github.com/DK7IH/STM32-F4-Demos/blob/main/_ADC/adc_port_pb1_as_analog_input.c
6
7
void initAdc()
8
{
9
  RCC->AHBENR  |= (1 << RCC_AHBENR_GPIOAEN); // enable clock for Port A
10
  GPIOA->MODER |= (3 << 0); //PA0 as analog
11
  //analogRead(A0);
12
  RCC->AHBENR  |= (1 << RCC_AHBENR_ADC12EN); // enable clock for ADC12
13
  ADC1->CFGR &= ~(0x07 << ADC_CFGR_RES); // 12bit resolution
14
  
15
  // disable voltage regulator
16
  ADC1->CR &= ~(0x07 << ADC_CR_ADVREGEN); // Intermediate state required when moving the ADC voltage regulator from the enabled to the disabled state or from the disabled to the enabled state.
17
  ADC1->CR |= (0x02 << ADC_CR_ADVREGEN); // ADC Voltage regulator disabled (Reset state)
18
  
19
  ADC1->CR |= (1 << ADC_CR_ADDIS); // ADC disable command
20
21
  ADC1->SQR1 &= ~(0x1F << ADC_SQR1_SQ1); // select channel 0 in seuqence 0
22
23
  // enable voltage regulator
24
  ADC1->CR &= ~(0x07 << ADC_CR_ADVREGEN); // Intermediate state required when moving the ADC voltage regulator from the enabled to the disabled state or from the disabled to the enabled state.
25
  ADC1->CR |= (0x01 << ADC_CR_ADVREGEN); // ADC Voltage regulator enabled
26
27
  delay(10);
28
29
  ADC1->CR |= (1 << ADC_CR_ADEN); // ADC enable command
30
}
31
32
uint16_t readAdc()
33
{
34
  uint16_t adcval;
35
  
36
  ADC1->CR |= (1 << ADC_CR_ADSTART); // start conversion
37
  
38
  while (!(ADC1->ISR & (1 << ADC_ISR_ADRDY))); //Wait until conversion is complete
39
  adcval = ADC1->DR;             //Read value from register
40
  return adcval;
41
}
42
43
void setup()
44
{
45
  Serial.begin(115200);
46
  delay(3000);
47
  Serial.println("Hello");
48
  delay(1000);
49
  Serial.println("initADC");
50
  initAdc();
51
  delay(1000);
52
  Serial.println("done .. start sampling");
53
}
54
55
void loop()
56
{
57
  Serial.println(readAdc());
58
  delay(100);
59
}

von N. M. (mani)


Lesenswert?

Vergleich doch einfach Mal mit anderen wo es zu funktionieren scheint.

Hier ein Beispiel:
http://stefanfrings.de/stm32/stm32f3.html#analog

von M. Н. (Gast)


Lesenswert?

Marc schrieb:
> (1 << RCC_AHBENR_GPIOAEN);

und Konsorten sehen falsch aus. Wenn deine Header aussehen, wie üblich, 
sind das bereits "Bitmasken" und du müsstest
1
RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

schreiben. Selbiges gilt für die anderen Felder.

von Marc (Gast)


Lesenswert?

>Wenn deine Header aussehen, wie üblich,
>sind das bereits "Bitmasken" und du müsstest

Oha, du hast recht .. das geht natürlich gewaltig schief.

N. M. (mani)
08.11.2022 08:23
>Vergleich doch einfach Mal mit anderen wo es zu funktionieren scheint.
>Hier ein Beispiel:
>http://stefanfrings.de/stm32/stm32f3.html#analog

Super .. das geht fast "out of the box". Nur mein "Speed-Test" zeigt 
irgendwie Müll an. Ich will eigentlich wissen, wie schnell der ADC in 
der Schleife ist.
1
// F303 baremetal ADC
2
//
3
// References:
4
// http://stefanfrings.de/stm32/stm32f3.html#analog
5
6
// Initialize the ADC1 for single conversion mode
7
void init_analog()
8
{
9
  // Enable clock for ADC
10
  SET_BIT(RCC->AHBENR, RCC_AHBENR_ADC12EN);
11
12
  // Disable the ADC
13
  if (READ_BIT(ADC1->ISR, ADC_ISR_ADRDY))
14
  {
15
    SET_BIT(ADC1->ISR, ADC_ISR_ADRDY);
16
  }
17
  if (READ_BIT(ADC1->CR, ADC_CR_ADEN))
18
  {
19
    SET_BIT(ADC1->CR, ADC_CR_ADDIS);
20
  }
21
22
  // Wait until ADC is disabled
23
  while (READ_BIT(ADC1->CR, ADC_CR_ADEN));
24
25
  // Enable ADC voltage regulator
26
  MODIFY_REG(ADC1->CR, ADC_CR_ADVREGEN, 0);
27
  MODIFY_REG(ADC1->CR, ADC_CR_ADVREGEN, ADC_CR_ADVREGEN_0);
28
29
  // Delay 1-2 ms
30
  //delay_ms(2);
31
  delay(2);
32
33
  // ADC Clock = HCLK/4
34
  MODIFY_REG(ADC12_COMMON->CCR, ADC12_CCR_CKMODE, ADC12_CCR_CKMODE_0 + ADC12_CCR_CKMODE_1);
35
36
  // Single ended mode for all channels
37
  WRITE_REG(ADC1->DIFSEL, 0);
38
39
  // Start calibration for single ended mode
40
  CLEAR_BIT(ADC1->CR, ADC_CR_ADCALDIF);
41
  SET_BIT(ADC1->CR, ADC_CR_ADCAL);
42
43
  // Wait until the calibration is finished
44
  while (READ_BIT(ADC1->CR, ADC_CR_ADCAL));
45
46
  // Clear the ready flag
47
  SET_BIT(ADC1->ISR, ADC_ISR_ADRDY);
48
49
  // Enable the ADC repeatedly until success (workaround from errata)
50
  do
51
  {
52
    SET_BIT(ADC1->CR, ADC_CR_ADEN);
53
  }
54
  while (!READ_BIT(ADC1->ISR, ADC_ISR_ADRDY));
55
56
  // Select software start trigger
57
  MODIFY_REG(ADC1->CFGR, ADC_CFGR_EXTEN, 0);
58
59
  // Select single conversion mode
60
  CLEAR_BIT(ADC1->CFGR, ADC_CFGR_CONT);
61
62
  // Set sample time to 32 cycles
63
  MODIFY_REG(ADC1->SMPR1, ADC_SMPR1_SMP1, ADC_SMPR1_SMP1_2);
64
}
65
66
// Read from an analog input of ADC1
67
uint32_t read_analog(uint32_t channel)
68
{
69
  // Number of channels to convert: 1
70
  MODIFY_REG(ADC1->SQR1, ADC_SQR1_L, 0);
71
  // Select the channel
72
  MODIFY_REG(ADC1->SQR1, ADC_SQR1_SQ1, channel << ADC_SQR1_SQ1_Pos);
73
  // Clear the finish flag
74
  CLEAR_BIT(ADC1->ISR, ADC_ISR_EOC);
75
  // Start a conversion
76
  SET_BIT(ADC1->CR, ADC_CR_ADSTART);
77
  // Wait until the conversion is finished
78
  while (!READ_BIT(ADC1->ISR, ADC_ISR_EOC));
79
  while (READ_BIT(ADC1->CR, ADC_CR_ADSTART));
80
  // Return the lower 12 bits of the result
81
  return ADC1->DR & 0b111111111111;
82
}
83
84
#define CHANNEL 1 // PA0
85
86
void speedTest()
87
{
88
  uint32_t duration_ms = 1000;
89
  uint32_t endTime = millis() + 1000;
90
  uint32_t n = 0;
91
  uint32_t value;
92
  while (millis() < endTime)
93
  {
94
    value += read_analog(CHANNEL);
95
    n++;
96
  }
97
  //delay(1000);
98
  Serial.print('samples per second: '); 
99
  Serial.println(n);
100
  delay(1000);
101
  Serial.print('ADC mean value: '); Serial.println((float)value / n);
102
  delay(5000);
103
}
104
105
void setup()
106
{
107
  Serial.begin(115200);
108
  delay(3000);
109
  Serial.println("Hello");
110
  delay(1000);
111
  Serial.println("initADC");
112
  init_analog();
113
  delay(1000);
114
  Serial.println("done .. run speed test");
115
  speedTest();
116
}
117
118
void loop()
119
{
120
  Serial.println(read_analog(CHANNEL));
121
  delay(100);
122
123
}

von noreply@noreply.com (Gast)


Lesenswert?

Mal DMA-Sektion durchlesen, wenn es um Speedtest geht. Und die 
Implikationen der ADC-Realisierung.

von N. M. (mani)


Lesenswert?

Bis zu 4 Kanäle bekommt man glaube ich sogar noch ohne DMA hin. Der ADC 
hat meine ich 1 Result und 3 injected Result Register.

Wenn es mehr Kanäle oder spezielle Anforderungen gibt geht natürlich mit 
DMA noch viel mehr.

von Marc (Gast)


Lesenswert?

DMA wäre schon nicht schlecht, aber bis die Konfiguration steht, würde 
das vermutlich noch einmal ein paar Tag dauern.

Oben im Code im Code waren im "speedTest" kleine Fehler. Hier der 
richtige Code:
1
#define CHANNEL 1 // PA0
2
3
void speedTest()
4
{
5
  uint32_t n;
6
  float value;
7
  int32_t startTime = micros();
8
9
  for (n = 0; n < 1000; n++)
10
  {
11
    read_analog(CHANNEL);
12
  }
13
  
14
  int32_t duration1000samples_us = micros() - startTime;
15
  
16
  Serial.print("1000 samples duration: ");
17
  Serial.print(duration1000samples_us); Serial.println("us");
18
  Serial.print("samples per second: ");
19
  Serial.println((int)(1000 / ((float)duration1000samples_us/1e6)));
20
21
  delay(5000);
22
}

Ergebnis:
1
Hello
2
initADC
3
done .. run speed test
4
1000 samples duration: 3203us
5
samples per second: 312207

Laut Datenblatt sollten die Wandler ~5MSPs können. Mit der jetzigen 
Einstellung sind es aber nur ~300kSps. Immerhin .. das Original 
analogRead(A0) des STM32Duino (Arduino) Frameworks läuft mit 5kSps.
Da liegen als bei dieser "baremetal" Implementierung als Welten.
Als Desktop-Oscilloscope für Audiosignale würde das schon reichen.

von N. M. (mani)


Lesenswert?

Marc schrieb:
> Laut Datenblatt sollten die Wandler ~5MSPs können.

Das kann er auch. Deine Software ist schlicht zu langsam. Man könnte das 
jetzt noch optimieren mit Inline Funktion usw, aber es wird nichts 
ändern. Die 5 MSPS wirst du nur mit Hardware Unterstützung zuverlässig 
hinbekommen.

Marc schrieb:
> Als Desktop-Oscilloscope für Audiosignale würde das schon reichen.

Naja, je nach dem auch nicht schön da du mit dieser Implementierung 
keine äquidistante Abtastung hast.
Da würde man eher einen Timer nehmen der dem ADC in einem festen Raster 
(z.B 48kHz) triggert. Ein DMA schaufelt dann die Daten vom ADC Register 
in einen Zwischenpuffer.
Oft nimmt man dabei 2 Puffer die man wechselt sobald der andere voll 
ist, damit man immer etwas Zeit hat den gerade voll gewordenen Puffer 
weiter zu verbreiten.
Ich kenne das unter "Ping Pong Buffer" Mehrfach-Puffer.

Der F3 hat glaube ich auch einstellbare analoge Komperatoren im Gepäck. 
Damit könnte man dann evtl noch den Triggerzeitpunkt in Hardware machen 
indem der DMA erst kopiert wenn der Interrupt des Komparator kam.

: Bearbeitet durch User
von Marc (Gast)


Lesenswert?

Dank eines kleine "Überspannungsexperimentes" ist mir der zweite DAC 
Kanal meines Nucleo F303 abgeraucht :-(

Aber .. ich habe noch ein Nucleo L432KC.

Ich habe jetzt schon eine Weile probiert, aber irgend etwas fehlt noch 
in der Initialisierung. Ich vermute, der ADC Block bekommt noch keine 
Takt o.ä. weil bei der folgenden Zeile das Enable Bit nicht gesetzt wird 
(sieht man im Debugger):
1
  SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_ADCEN);

Hat jemand eine Idee, woran es liegen könnte? Hier der noch nicht 
funktionierende Code. Er funktioniert, wenn man die HAL bassiert 
Funktion "analog.cpp" voranstellt und dort drinnen das 
"HAL_DAC_MspDeInit" auskommentiert, damit die ADC Initialisierung 
erhalten bleibt. Das ist aber natürlich nicht der Sinn der Sache.
1
#include <Arduino.h>
2
3
#include "analog2.h"
4
5
// /home/christoph/.arduino15/packages/STMicroelectronics/hardware/stm32/2.3.0/libraries/SrcWrapper/src/stm32
6
//  analog.cpp
7
// /home/christoph/.arduino15/packages/STMicroelectronics/hardware/stm32/2.3.0/system/Drivers
8
9
#ifdef __cplusplus
10
extern "C" {
11
#endif
12
uint16_t adc_read_value2(PinName pin, uint32_t resolution);
13
#ifdef __cplusplus
14
}
15
#endif
16
17
#define CHANNEL 5 // PA0 -> A0
18
//#define CHANNEL 6 // PA1 -> A1
19
//#define CHANNEL 8 // PA3 -> A2
20
21
// Initialize the ADC1 for single conversion mode
22
void init_analog()
23
{
24
  // something is missing in the following initialization
25
  // thererfore we need to call the HALL funktions
26
  // tbd: find the missing register 
27
  // hint: the error might be in a missing clock initialization of the ADC
28
  adc_read_value2(PA_0, 12); 
29
  // Enable clock for ADC
30
  //SET_BIT(RCC->AHBENR, RCC_AHBENR_ADC12EN);
31
  SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_ADCEN);
32
33
  // Disable the ADC
34
  if (READ_BIT(ADC1->ISR, ADC_ISR_ADRDY))
35
  {
36
    SET_BIT(ADC1->ISR, ADC_ISR_ADRDY);
37
  }
38
  if (READ_BIT(ADC1->CR, ADC_CR_ADEN))
39
  {
40
    SET_BIT(ADC1->CR, ADC_CR_ADDIS);
41
  }
42
43
  // Wait until ADC is disabled
44
  while (READ_BIT(ADC1->CR, ADC_CR_ADEN));
45
46
  // Enable ADC voltage regulator
47
  MODIFY_REG(ADC1->CR, ADC_CR_ADVREGEN, 0);
48
  //MODIFY_REG(ADC1->CR, ADC_CR_ADVREGEN, ADC_CR_ADVREGEN_0);
49
  MODIFY_REG(ADC1->CR, ADC_CR_ADVREGEN, ADC_CR_ADVREGEN);
50
  // Delay 1-2 ms
51
  //delay_ms(2);
52
  delay(2);
53
54
  // ADC Clock = HCLK/4
55
  //MODIFY_REG(ADC12_COMMON->CCR, ADC12_CCR_CKMODE, ADC12_CCR_CKMODE_0 + ADC12_CCR_CKMODE_1);
56
  MODIFY_REG(ADC1_COMMON->CCR, ADC_CCR_CKMODE, ADC_CCR_CKMODE_0 + ADC_CCR_CKMODE_1);
57
58
  // Single ended mode for all channels
59
  WRITE_REG(ADC1->DIFSEL, 0);
60
61
  // Start calibration for single ended mode
62
  CLEAR_BIT(ADC1->CR, ADC_CR_ADCALDIF);
63
  SET_BIT(ADC1->CR, ADC_CR_ADCAL);
64
65
  // Wait until the calibration is finished
66
  while (READ_BIT(ADC1->CR, ADC_CR_ADCAL));
67
68
  // Clear the ready flag
69
  SET_BIT(ADC1->ISR, ADC_ISR_ADRDY);
70
71
  // Enable the ADC repeatedly until success (workaround from errata)
72
  do
73
  {
74
    SET_BIT(ADC1->CR, ADC_CR_ADEN);
75
  }
76
  while (!READ_BIT(ADC1->ISR, ADC_ISR_ADRDY));
77
78
  // Select software start trigger
79
  MODIFY_REG(ADC1->CFGR, ADC_CFGR_EXTEN, 0);
80
81
  // Select single conversion mode
82
  CLEAR_BIT(ADC1->CFGR, ADC_CFGR_CONT);
83
84
  // Set sample time to 32 cycles
85
  MODIFY_REG(ADC1->SMPR1, ADC_SMPR1_SMP1, ADC_SMPR1_SMP1_2);
86
}
87
88
// Read from an analog input of ADC1
89
uint32_t read_analog(uint32_t channel)
90
{
91
  // Number of channels to convert: 1
92
  MODIFY_REG(ADC1->SQR1, ADC_SQR1_L, 0);
93
  // Select the channel
94
  MODIFY_REG(ADC1->SQR1, ADC_SQR1_SQ1, channel << ADC_SQR1_SQ1_Pos);
95
  // Clear the finish flag
96
  CLEAR_BIT(ADC1->ISR, ADC_ISR_EOC);
97
  // Start a conversion
98
  SET_BIT(ADC1->CR, ADC_CR_ADSTART);
99
  // Wait until the conversion is finished
100
  while (!READ_BIT(ADC1->ISR, ADC_ISR_EOC));
101
  while (READ_BIT(ADC1->CR, ADC_CR_ADSTART));
102
  // Return the lower 12 bits of the result
103
  return ADC1->DR & 0b111111111111;
104
}
105
106
void speedTest()
107
{
108
  uint32_t n;
109
  float value;
110
111
  Serial.print("channel: ");
112
  Serial.print((int)CHANNEL);
113
  Serial.println("");
114
115
  int32_t startTime = micros();
116
117
  for (n = 0; n < 1000; n++)
118
  {
119
    read_analog(CHANNEL);
120
  }
121
122
  int32_t duration1000samples_us = micros() - startTime;
123
124
  Serial.print("1000 samples duration: ");
125
  Serial.print(duration1000samples_us); Serial.println("us");
126
  Serial.print("samples per second: ");
127
  Serial.println((int)(1000 / ((float)duration1000samples_us / 1e6)));
128
129
  delay(5000);
130
}
131
132
void setup()
133
{
134
  Serial.begin(115200);
135
136
  init_analog();
137
  speedTest();
138
}
139
140
void loop()
141
{
142
  uint16_t v;
143
  // uint16_t adc_read_value2(PinName pin, uint32_t resolution);
144
  //v=adc_read_value2(PA_0, 12);
145
  v = read_analog(CHANNEL);
146
  Serial.println(v);
147
  delay(1);
148
149
}

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.