stm32/machine_adc: Fix ADC V2 CR register writes and timeouts.

Replace all |= on ADC CR with mask-and-set using ADC_CR_BITS_PROPERTY_RS
from the LL HAL. On ADC V2 the CR command bits are write-1-to-set, so |=
can propagate stale bits (e.g. ADSTP left by HAL_ADC_Stop), violating the
RM requirement that ADSTART and ADSTP must not be asserted simultaneously.

Add recovery logic in adc_config_channel for stuck ADSTART: stop the
conversion, disable, and re-enable the ADC before reconfiguring SQR1.

Replace unbounded while loops in ADC V2 paths with ADC_WAIT, a busy-wait
macro using mp_hal_ticks_ms with a 250ms timeout.

Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
This commit is contained in:
Andrew Leech
2026-04-20 15:43:35 +10:00
committed by Damien George
parent 06f27ec853
commit a23f69ea68
+41 -18
View File
@@ -28,6 +28,7 @@
// extmod/machine_adc.c via MICROPY_PY_MACHINE_ADC_INCLUDEFILE.
#include "py/mphal.h"
#include "py/runtime.h"
#include "adc.h"
#if defined(STM32F0) || defined(STM32G0) || defined(STM32G4) || defined(STM32H5) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32N6) || defined(STM32U5) || defined(STM32WB) || defined(STM32WL)
@@ -96,6 +97,17 @@
// Timeout for waiting for end-of-conversion
#define ADC_EOC_TIMEOUT_MS (10)
#if ADC_V2
// Busy-wait with timeout for ADC peripheral operations. 250ms is a generous
// upper bound; normal operations complete in microseconds.
#define ADC_WAIT_TIMEOUT_MS (250)
#define ADC_WAIT(cond) do { \
uint32_t _t0 = mp_hal_ticks_ms(); \
while ((cond) && (mp_hal_ticks_ms() - _t0 < ADC_WAIT_TIMEOUT_MS)) { \
} \
} while (0)
#endif
// Channel IDs for machine.ADC object
typedef enum _machine_adc_internal_ch_t {
// Regular external ADC inputs (0..19)
@@ -277,19 +289,18 @@ void adc_config(ADC_TypeDef *adc, uint32_t bits) {
#else
LL_ADC_StartCalibration(adc, LL_ADC_CALIB_OFFSET_LINEARITY, LL_ADC_SINGLE_ENDED);
#endif
while (LL_ADC_IsCalibrationOnGoing(adc)) {
}
ADC_WAIT(LL_ADC_IsCalibrationOnGoing(adc));
}
if (adc->CR & ADC_CR_ADEN) {
// ADC enabled, need to disable it to change configuration
if (adc->CR & ADC_CR_ADSTART) {
adc->CR |= ADC_CR_ADSTP;
while (adc->CR & ADC_CR_ADSTP) {
}
adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADSTP;
ADC_WAIT(adc->CR & ADC_CR_ADSTP);
}
adc->CR |= ADC_CR_ADDIS;
while (adc->CR & ADC_CR_ADDIS) {
if (!(adc->CR & ADC_CR_ADSTART)) {
adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADDIS;
ADC_WAIT(adc->CR & ADC_CR_ADDIS);
}
}
#endif
@@ -349,18 +360,26 @@ static int adc_get_bits(ADC_TypeDef *adc) {
return adc_cr_to_bits_table[res];
}
static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) {
static bool adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) {
#if ADC_V2
if (!(adc->CR & ADC_CR_ADEN)) {
if (adc->CR & 0x3f) {
// Cannot enable ADC with CR!=0
return;
if (!(adc->CR & ADC_CR_ADEN) || (adc->CR & ADC_CR_ADSTART)) {
if (adc->CR & ADC_CR_BITS_PROPERTY_RS) {
// Stop and disable before (re-)enabling.
adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADSTP;
ADC_WAIT(adc->CR & ADC_CR_ADSTP);
if (adc->CR & ADC_CR_ADSTP) {
return false;
}
adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADDIS;
ADC_WAIT(adc->CR & ADC_CR_ADDIS);
if (adc->CR & ADC_CR_ADDIS) {
return false;
}
}
adc->ISR = ADC_ISR_ADRDY; // clear ADRDY
adc->CR |= ADC_CR_ADEN;
adc->ISR = ADC_ISR_ADRDY;
adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADEN;
adc_stabilisation_delay_us(ADC_STAB_DELAY_US);
while (!(adc->ISR & ADC_ISR_ADRDY)) {
}
ADC_WAIT(!(adc->ISR & ADC_ISR_ADRDY));
}
#else
if (!(adc->CR2 & ADC_CR2_ADON)) {
@@ -508,6 +527,7 @@ static void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t samp
*smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time
#endif
return true;
}
static uint32_t adc_read_channel(ADC_TypeDef *adc) {
@@ -523,7 +543,8 @@ static uint32_t adc_read_channel(ADC_TypeDef *adc) {
#endif
{
#if ADC_V2
adc->CR |= ADC_CR_ADSTART;
adc->ISR = ADC_ISR_EOC | ADC_ISR_EOS | ADC_ISR_OVR; // clear stale flags
adc->CR = (adc->CR & ~ADC_CR_BITS_PROPERTY_RS) | ADC_CR_ADSTART;
#else
adc->CR2 |= ADC_CR2_SWSTART;
#endif
@@ -542,7 +563,9 @@ uint32_t adc_config_and_read_u16(ADC_TypeDef *adc, uint32_t channel, uint32_t sa
channel = adc_ll_channel(channel);
// Select, configure and read the channel.
adc_config_channel(adc, channel, sample_time);
if (!adc_config_channel(adc, channel, sample_time)) {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ADC not responding"));
}
uint32_t raw = adc_read_channel(adc);
// If VBAT was sampled then deselect it to prevent battery drain.