1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/stm32l4: add adc support

This commit is contained in:
Michel Rottleuthner 2018-03-20 14:43:51 +01:00
parent 48c1c05297
commit e4c405daf3
4 changed files with 254 additions and 10 deletions

View File

@ -1,4 +1,5 @@
# Put defined MCU peripherals here (in alphabetical order)
FEATURES_PROVIDED += periph_adc
FEATURES_PROVIDED += periph_gpio
FEATURES_PROVIDED += periph_pwm
FEATURES_PROVIDED += periph_rtc

View File

@ -245,9 +245,20 @@ static const spi_conf_t spi_config[] = {
/**
* @name ADC configuration
*
* configure only ADC channels for the Arduino header pins A0-A5
*
* @{
*/
#define ADC_NUMOF (0)
#define ADC_NUMOF (6U)
#define ADC_CONFIG { \
{GPIO_PIN(PORT_A, 0), 0, 5}, /*< ADC12_IN5 */ \
{GPIO_PIN(PORT_A, 1), 0, 6}, /*< ADC12_IN6 */ \
{GPIO_PIN(PORT_A, 4), 1, 9}, /*< ADC12_IN9 */ \
{GPIO_PIN(PORT_B, 0), 1, 15}, /*< ADC12_IN15 */ \
{GPIO_PIN(PORT_C, 1), 2, 2}, /*< ADC123_IN_2 */ \
{GPIO_PIN(PORT_C, 0), 2, 1}, /*< ADC123_IN_1 */ \
}
/** @} */
/**

View File

@ -45,6 +45,23 @@ enum {
PORT_H = 7, /**< port H */
};
/**
* @brief Available number of ADC devices
*/
#if defined(CPU_MODEL_STM32L476RG) || defined(CPU_MODEL_STM32L475VG)
#define ADC_DEVS (3U)
#elif defined(CPU_MODEL_STM32L452RE) || defined(CPU_MODEL_STM32L432KC)
#define ADC_DEVS (1U)
#endif
#if defined(CPU_MODEL_STM32L476RG) || defined(CPU_MODEL_STM32L475VG) || \
defined(CPU_MODEL_STM32L452RE) || defined(CPU_MODEL_STM32L432KC)
/**
* @brief ADC voltage regulator start-up time [us]
*/
#define ADC_T_ADCVREG_STUP_US (20)
#endif
#ifndef DOXYGEN
/**
* @brief Override ADC resolution values
@ -52,22 +69,23 @@ enum {
*/
#define HAVE_ADC_RES_T
typedef enum {
ADC_RES_6BIT = (0x3 << 3), /**< ADC resolution: 6 bit */
ADC_RES_8BIT = (0x2 << 3), /**< ADC resolution: 8 bit */
ADC_RES_10BIT = (0x1 << 3), /**< ADC resolution: 10 bit */
ADC_RES_12BIT = (0x0 << 3), /**< ADC resolution: 12 bit */
ADC_RES_14BIT = (0xfe), /**< not applicable */
ADC_RES_16BIT = (0xff) /**< not applicable */
ADC_RES_6BIT = (ADC_CFGR_RES), /**< ADC resolution: 6 bit */
ADC_RES_8BIT = (ADC_CFGR_RES_1), /**< ADC resolution: 8 bit */
ADC_RES_10BIT = (ADC_CFGR_RES_0), /**< ADC resolution: 10 bit */
ADC_RES_12BIT = (0x0), /**< ADC resolution: 12 bit */
ADC_RES_14BIT = (0x1), /**< not applicable */
ADC_RES_16BIT = (0x2) /**< not applicable */
} adc_res_t;
/** @} */
#endif /* ndef DOXYGEN */
/**
* @brief ADC line configuration values
* @brief ADC channel configuration data
*/
typedef struct {
gpio_t pin; /**< pin to use */
uint8_t chan; /**< internal channel the pin is connected to */
gpio_t pin; /**< pin connected to the channel */
uint8_t dev; /**< ADCx - 1 device used for the channel */
uint8_t chan; /**< CPU ADC channel connected to the pin */
} adc_conf_t;
#ifdef __cplusplus

214
cpu/stm32l4/periph/adc.c Normal file
View File

@ -0,0 +1,214 @@
/*
* Copyright (C) 2014-2016 Freie Universität Berlin
* Copyright (C) 2018 HAW-Hamburg
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_stm32l4
* @ingroup drivers_periph_adc
* @{
*
* @file
* @brief Low-level ADC driver implementation
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
*
* @}
*/
#include "cpu.h"
#include "mutex.h"
#include "periph/adc.h"
#include "periph_conf.h"
#include "xtimer.h"
/**
* @brief map CPU specific register/value names
*/
#if defined(CPU_MODEL_STM32L476RG)
#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
further clock config is possible over CKMODE[1:0] bits in ADC_CCR reg */
#define ADC_CLK_EN_MASK (RCC_AHB2ENR_ADCEN)
/* refering 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.
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 */
#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. */
#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
/**
* @brief Load the ADC configuration
*/
static const adc_conf_t adc_config[] = ADC_CONFIG;
/**
* @brief Allocate locks for all three available ADC devices
*/
static mutex_t locks[ADC_DEVS];
static inline ADC_TypeDef *dev(adc_t line)
{
return (ADC_TypeDef *)(ADC1_BASE + (adc_config[line].dev << 8));
}
static inline void prep(adc_t line)
{
mutex_lock(&locks[adc_config[line].dev]);
periph_clk_en(ADC_PERIPH_CLK, ADC_CLK_EN_MASK);
}
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
mutex_unlock(&locks[adc_config[line].dev]);
}
/**
* @brief Extract the port base address from the given pin identifier
*/
static inline GPIO_TypeDef *_port(gpio_t pin)
{
return (GPIO_TypeDef *)(pin & ~(0x0f));
}
/**
* @brief Extract the pin number from the last 4 bit of the pin identifier
*/
static inline int _pin_num(gpio_t pin)
{
return (pin & 0x0f);
}
int adc_init(adc_t line)
{
/* check if the line is valid */
if (line >= ADC_NUMOF) {
return -1;
}
/* lock device and enable its peripheral clock */
prep(line);
/* set prescaler to 0 to let the ADC run with maximum speed */
ADC123_COMMON->CCR &= ~(ADC_CCR_PRESC);
/* Setting ADC clock to HCLK/1 is only allowed if AHB clock prescaler is 1*/
if (!(RCC->CFGR & RCC_CFGR_HPRE_3)) {
/* set ADC clock to HCLK/1 */
ADC123_COMMON->CCR |= (ADC_CCR_CKMODE_0);
}
else {
/* set ADC clock to HCLK/2 otherwise */
ADC123_COMMON->CCR |= (ADC_CCR_CKMODE_1);
}
/* configure the pin */
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
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));
#endif
/* init ADC line only if it wasn't already initialized */
if (!(dev(line)->ADC_CR_REG & (ADC_CR_ADEN))) {
/* reset state of bit DEEPPWD is 1 -> so first leave deep-power down mode */
dev(line)->ADC_CR_REG &= ~(ADC_CR_DEEPPWD);
/* enable ADC internal voltage regulator and wait for startup period */
dev(line)->ADC_CR_REG |= (ADC_CR_ADVREGEN);
xtimer_usleep(ADC_T_ADCVREG_STUP_US);
/* configure calibration for single ended input */
dev(line)->ADC_CR_REG &= ~(ADC_CR_ADCALDIF);
/* ´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) {}
/* clear ADRDY by writing it*/
dev(line)->ADC_ISR_REG |= (ADC_ISR_ADRDY);
/* enable ADC and wait for it to be ready */
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);
}
/* configure sampling time for the given channel */
if (adc_config[line].chan < ADC_SMPR2_FIRST_CHAN) {
dev(line)->SMPR1 = (ADC_SMP_MIN_VAL << (adc_config[line].chan *
ADC_SMP_BIT_WIDTH));
}
else {
dev(line)->SMPR2 = (ADC_SMP_MIN_VAL << ((adc_config[line].chan -
ADC_SMPR2_FIRST_CHAN)
* ADC_SMP_BIT_WIDTH));
}
/* free the device again */
done(line);
return 0;
}
int adc_sample(adc_t line, adc_res_t res)
{
int sample;
/* check if resolution is applicable */
if (res & 0x3) {
return -1;
}
/* lock and power on the ADC device */
prep(line);
/* first clear resolution */
dev(line)->CFGR &= ~(ADC_CFGR_RES);
/* then set resolution to the required value*/
dev(line)->CFGR |= res;
/* specify channel for regular conversion */
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;
while (!(dev(line)->ISR & ADC_ISR_EOC)) {}
/* read the sample */
sample = (int)dev(line)->DR;
/* free the device again */
done(line);
return sample;
}