Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cpu/stm32/periph_adc: fixes and improvements for L4 support #19571

Merged
13 changes: 8 additions & 5 deletions cpu/stm32/include/periph/l4/periph_cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ extern "C" {
/**
* @brief Available number of ADC devices
*/
#if defined(CPU_MODEL_STM32L476RG) || defined(CPU_MODEL_STM32L475VG) || \
defined(CPU_MODEL_STM32L496ZG)
#if defined(ADC3)
#define ADC_DEVS (3U)
#elif defined(CPU_MODEL_STM32L452RE) || defined(CPU_MODEL_STM32L432KC) || \
defined(CPU_MODEL_STM32L4R5ZI)
#elif defined(ADC2)
#define ADC_DEVS (2U)
#elif defined(ADC1)
#define ADC_DEVS (1U)
#else
#error "Can't determine the number of ADC devices"
#endif

#if defined(CPU_MODEL_STM32L476RG) || defined(CPU_MODEL_STM32L475VG) || \
defined(CPU_MODEL_STM32L452RE) || defined(CPU_MODEL_STM32L432KC) || \
defined(CPU_MODEL_STM32L496ZG) || defined(CPU_MODEL_STM32L4R5ZI)
defined(CPU_MODEL_STM32L496ZG) || defined(CPU_MODEL_STM32L4R5ZI) || \
defined(CPU_MODEL_STM32L496AG)
/**
* @brief ADC voltage regulator start-up time [us]
*/
Expand Down
75 changes: 50 additions & 25 deletions cpu/stm32/periph/adc_l4.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,33 +40,32 @@
#endif

/**
* @brief map CPU specific register/value names
* @brief map CPU specific register/value names valid for all STM32L4 MCUs
*/
#if defined(CPU_MODEL_STM32L476RG) || defined(CPU_MODEL_STM32L4R5ZI) || \
defined(CPU_MODEL_STM32L496ZG)
#define ADC_CR_REG CR
#define ADC_ISR_REG ISR
#define ADC_PERIPH_CLK AHB2
/* on stm32-l476rg all ADC clocks are are enabled by this bit
/* on STM32L4xx MCUs all ADC clocks are are enabled by this bit
further clock config is possible over CKMODE[1:0] bits in ADC_CCR reg */
#define ADC_CLK_EN_MASK (RCC_AHB2ENR_ADCEN)
/* referring to Datasheet Section 6.3.18 (ADC characteristics) the minimum
achievable sampling rate is 4.21 Msps (12 Bit resolution on slow channel)
we use that worst case for configuring the sampling time to be sure it
works on all channels.
TCONV = Sampling time + 12.5 ADC clock cycles.
TCONV = Sampling time + 12.5 ADC clock cycles (RM section 18.4.12)
At 80MHz this means we need to set SMP to 001 (6.5 ADC clock cycles) to
stay within specs. (80000000/(6.5+12.5)) = 4210526 */
stay within specs. (80000000/(6.5+12.5)) = 4210526 */
#define ADC_SMP_MIN_VAL (0x1)

/* The sampling time can be specified for each channel over SMPR1 and SMPR2.
This specifies the first channel that goes to SMPR2 instead of SMPR1. */
/* The sampling time width is 3 bit */
#define ADC_SMP_BIT_WIDTH (3)

/* The sampling time can be specified for each channel over SMPR1 and SMPR2.
This specifies the first channel that goes to SMPR2 instead of SMPR1. */
#define ADC_SMPR2_FIRST_CHAN (10)
#endif

#define ADC_CCR_CKMODE_HCLK_1 (ADC_CCR_CKMODE_0)
#define ADC_CCR_CKMODE_HCLK_2 (ADC_CCR_CKMODE_1)

/**
* @brief Default VBAT undefined value
Expand All @@ -80,6 +79,9 @@
*/
static mutex_t locks[ADC_DEVS];

/* count the periph_clk_en calls to know when to disable the clock in done() */
static uint8_t _clk_en_ctr = 0;

static inline ADC_TypeDef *dev(adc_t line)
{
return (ADC_TypeDef *)(ADC1_BASE + (adc_config[line].dev << 8));
Expand All @@ -89,16 +91,16 @@ static inline void prep(adc_t line)
{
mutex_lock(&locks[adc_config[line].dev]);
periph_clk_en(ADC_PERIPH_CLK, ADC_CLK_EN_MASK);
_clk_en_ctr++;
}

static inline void done(adc_t line)
{
/* on STM32L476RG (TODO: maybe true for other L4's? - haven't checked yet)
all adc devices are controlled by this one bit.
So don't disable the clock as other devices may still use it */
#if !defined(CPU_MODEL_STM32L476RG)
periph_clk_dis(ADC_PERIPH_CLK, ADC_CLK_EN_MASK);
#endif
/* All ADC devices are controlled by this one bit.
* So don't disable the clock if other devices may still use it */
if (_clk_en_ctr && --_clk_en_ctr == 0) {
periph_clk_dis(ADC_PERIPH_CLK, ADC_CLK_EN_MASK);
}
mutex_unlock(&locks[adc_config[line].dev]);
}

Expand All @@ -125,28 +127,40 @@ int adc_init(adc_t line)
return -1;
}

#if VREFBUF_ENABLE && defined(VREFBUF_CSR_ENVR)
/* enable VREFBUF if needed and available (for example if the board doesn't
* have an external reference voltage connected to V_REF+), wait until
* it is ready */
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
VREFBUF->CSR &= ~VREFBUF_CSR_HIZ;
VREFBUF->CSR |= VREFBUF_CSR_ENVR;
while (!(VREFBUF->CSR & VREFBUF_CSR_VRR)) { }
#endif

/* lock device and enable its peripheral clock */
prep(line);

/* set prescaler to 0 to let the ADC run with maximum speed */
ADC->CCR &= ~(ADC_CCR_PRESC);

/* Setting ADC clock to HCLK/1 is only allowed if AHB clock prescaler is 1*/
ADC->CCR &= ~(ADC_CCR_CKMODE);
if (!(RCC->CFGR & RCC_CFGR_HPRE_3)) {
/* set ADC clock to HCLK/1 */
ADC->CCR |= (ADC_CCR_CKMODE_0);
/* set ADC clock to HCLK/1, only allowed if AHB clock prescaler is 1 */
ADC->CCR |= ADC_CCR_CKMODE_HCLK_1 << ADC_CCR_CKMODE_Pos;
}
else {
/* set ADC clock to HCLK/2 otherwise */
ADC->CCR |= (ADC_CCR_CKMODE_1);
ADC->CCR |= ADC_CCR_CKMODE_HCLK_2 << ADC_CCR_CKMODE_Pos;
}

/* configure the pin */
if (adc_config[line].pin != GPIO_UNDEF) {
gpio_init_analog(adc_config[line].pin);
}
#if defined(CPU_MODEL_STM32L476RG) || defined(CPU_MODEL_STM32L475VG)
/* On STM32L475xx/476xx/486xx devices, before any conversion of an input channel coming
#if defined(CPU_LINE_STM32L486xx) || defined(CPU_LINE_STM32L485xx) || \
defined(CPU_LINE_STM32L476xx) || defined(CPU_LINE_STM32L475xx) || \
defined(CPU_LINE_STM32L471xx)
/* On STM32L47xx/48xx devices, before any conversion of an input channel coming
from GPIO pads, it is necessary to configure the corresponding GPIOx_ASCR register in
the GPIO, in addition to the I/O configuration in analog mode. */
_port(adc_config[line].pin)->ASCR |= (1 << _pin_num(adc_config[line].pin));
Expand All @@ -170,7 +184,7 @@ int adc_init(adc_t line)
/* configure calibration for single ended input */
dev(line)->ADC_CR_REG &= ~(ADC_CR_ADCALDIF);

/* ´start automatic calibration and wait for it to complete */
/* start automatic calibration and wait for it to complete */
dev(line)->ADC_CR_REG |= ADC_CR_ADCAL;
while (dev(line)->ADC_CR_REG & ADC_CR_ADCAL) {}

Expand All @@ -181,8 +195,8 @@ int adc_init(adc_t line)
dev(line)->ADC_CR_REG |= (ADC_CR_ADEN);
while ((dev(line)->ADC_ISR_REG & ADC_ISR_ADRDY) == 0) {}

/* set sequence length to 1 conversion */
dev(line)->SQR1 |= (0 & ADC_SQR1_L);
/* set sequence length to 1 conversion, set ADC_SQR1_L to 0 */
dev(line)->SQR1 &= ~ADC_SQR1_L_Msk;
}

/* configure sampling time for the given channel */
Expand Down Expand Up @@ -217,6 +231,11 @@ int32_t adc_sample(adc_t line, adc_res_t res)
if (IS_USED(MODULE_PERIPH_VBAT) && line == VBAT_ADC) {
vbat_enable();
}
#ifdef VREFINT_ADC
if (line == VREFINT_ADC) {
ADC->CCR |= ADC_CCR_VREFEN;
}
#endif

/* first clear resolution */
dev(line)->CFGR &= ~(ADC_CFGR_RES);
Expand All @@ -225,7 +244,8 @@ int32_t adc_sample(adc_t line, adc_res_t res)
dev(line)->CFGR |= res;

/* specify channel for regular conversion */
dev(line)->SQR1 = (adc_config[line].chan << ADC_SQR1_SQ1_Pos);
dev(line)->SQR1 &= ~ADC_SQR1_SQ1_Msk;
dev(line)->SQR1 |= (adc_config[line].chan << ADC_SQR1_SQ1_Pos);

/* start conversion and wait for it to complete */
dev(line)->ADC_CR_REG |= ADC_CR_ADSTART;
Expand All @@ -238,6 +258,11 @@ int32_t adc_sample(adc_t line, adc_res_t res)
if (IS_USED(MODULE_PERIPH_VBAT) && line == VBAT_ADC) {
vbat_disable();
}
#ifdef VREFINT_ADC
if (line == VREFINT_ADC) {
ADC->CCR &= ~ADC_CCR_VREFEN;
}
#endif

/* free the device again */
done(line);
Expand Down