Hallo,
hier eine Minimalimplementierung um auf einem STM32F042 einen AD Pin und
die Die Temperatur auszulesen. Gegebenenfalls müssen die Pins, die
eingelesen werden sollen, noch auf Analogen Eingang gestellt werden.
Gestestet mit PCLK=12MHz.
1 | void initAdc(void) {
|
2 | __HAL_RCC_ADC1_CLK_ENABLE();
|
3 | ADC1->CFGR2 = ADC_CFGR2_CKMODE_1; //PCLK div by 4. Allowed range is 0.6MHz to 14MHz.
|
4 | ADC1->SMPR = ADC_SMPR_SMP_0 | ADC_SMPR_SMP_1 | ADC_SMPR_SMP_2; //slowest possible sampling time, 239.5 clock cycles
|
5 | ADC1_COMMON->CCR = ADC_CCR_TSEN; //temperature sensor enabled
|
6 | ADC1->CR |= ADC_CR_ADCAL; //start calibration
|
7 | while (ADC1->CR & ADC_CR_ADCAL); //wait for calibration to end
|
8 | HAL_Delay(1); //errata workaround
|
9 | ADC1->CR |= ADC_CR_ADEN; //can not be done within 4 adc clock cycles, due errata
|
10 | while ((ADC1->ISR & ADC_FLAG_RDY) == 0); //measured 8 loop cycles @12MHz until bit was set
|
11 | }
|
12 |
|
13 | uint16_t getAdc(uint32_t channel) {
|
14 | ADC1->ISR |= ADC_ISR_EOC; //clear end of conversion bit
|
15 | while (ADC1->CR & ADC_CR_ADSTART); //otherwise the channel can not be changed
|
16 | ADC1->CHSELR = (1 << channel);
|
17 | ADC1->CR |= ADC_CR_ADSTART;
|
18 | while ((ADC1->ISR & ADC_ISR_EOC) == 0);
|
19 | uint16_t val = ADC1->DR;
|
20 | return val;
|
21 | }
|
22 |
|
23 | void readTemperatureSensor(void) {
|
24 | int32_t tsCal1 = *((uint16_t*)0x1FFFF7B8); //30°C calibration value
|
25 | int32_t tsCal2 = *((uint16_t*)0x1FFFF7C2); //110°C calibration value
|
26 | int32_t temperature = getAdc(ADC_CHANNEL_TEMPSENSOR);
|
27 | int32_t temperatureCelsius = 80000 / (tsCal2 - tsCal1) * (temperature - tsCal1) + 30000;
|
28 | temperatureCelsius /= 1000;
|
29 | dbgPrintf("Temp:%i°C\n\r", temperatureCelsius);
|
30 | }
|
Der Code mag auf den ersten Blick trivial erscheinen und damit kaum ein
Posting wert. Nur habe ich am Ende drei Abende herumprobiert, bis die
Ansteuerung funktionierte.
Die Gründe dafür waren:
1. Alle online gefundenen Beispiele verwenden einfach die HAL.
2. Der ADC verhält sich nicht so wie im Datenblatt beschrieben und wenn
ein nicht funktionieren mehrere Ursachen hat, wird es plötzlich
schwierig. Was nicht wie erwarten funktioniert hat:
2.1. Startet man die Kalibrierung und aktiviert danach den ADC, wird das
Bit nicht gesetzt. Dies ist auch im Errata beschrieben.
2.2. Laut Datenblatt soll man beim Aktivieren des ADC warten, bis das
Ready Bit gesetzt ist. Bei den ersten Versuchen funktionierte das aus
unbekannten Gründen nicht.
2.3. Deaktivieren des ADC mit dem ADDIS Bit. Klappte ebenfalls nicht.
Wohl auch ein Fehler bei den ersten Versuchen einer Implementierung.
2.4. Überall in der ADC Lib hat man defines für passende Bitfelder, nur
für die Channels ist es die ID und nicht die Bitmaske.
Warum nicht einfach die HAL Lib verwenden?
Weil der Code vom CubeMX nicht funktionierte. Warum nicht? Der Cube baut
alle ausgewählten AD Eingänge in eine Sequenz die alle automatisch
nacheinander Konvertiert werden. Und ohne DMA war Pollen per Software
bei 12MHz schlicht zu langsam um die Daten rechtzeitig abzuholen. Erst
Heruntertakten des ADC und Auswählen der langsamsten Samplingtime machte
eine Verwendung möglich. Außerdem spart der Verzicht auf die HAL
Funktionen (Ok, HAL_Delay ist jetzt noch drin) für den ADC hier mal eben
1,4KiB Flash (-Og Optimierung).