diff --git a/boards/fox/include/periph_conf.h b/boards/fox/include/periph_conf.h index 9430cb95ac..e667cbef97 100644 --- a/boards/fox/include/periph_conf.h +++ b/boards/fox/include/periph_conf.h @@ -43,6 +43,13 @@ extern "C" { #define CLOCK_FLASH_LATENCY FLASH_ACR_LATENCY_2 /** @} */ +/** + * @name ADC configuration + * @{ + */ +#define ADC_NUMOF (0) +/** @} */ + /** * @brief Timer configuration * @{ diff --git a/boards/iotlab-m3/include/periph_conf.h b/boards/iotlab-m3/include/periph_conf.h index 03516983bf..4202c98d23 100644 --- a/boards/iotlab-m3/include/periph_conf.h +++ b/boards/iotlab-m3/include/periph_conf.h @@ -44,6 +44,18 @@ extern "C" { #define CLOCK_FLASH_LATENCY FLASH_ACR_LATENCY_2 /** @} */ +/** + * @name ADC configuration + * @{ + */ +#define ADC_CONFIG { \ + { GPIO_PIN(PORT_A,3), 0, 3 }, \ + { GPIO_UNDEF , 0, 16 }, \ + { GPIO_UNDEF , 0, 17 } } + +#define ADC_NUMOF (3) +/** @} */ + /** * @brief Timer configuration * @{ diff --git a/boards/nucleo-f103/include/periph_conf.h b/boards/nucleo-f103/include/periph_conf.h index 257cfd3d44..9acfd91fa4 100644 --- a/boards/nucleo-f103/include/periph_conf.h +++ b/boards/nucleo-f103/include/periph_conf.h @@ -45,6 +45,13 @@ extern "C" { #define CLOCK_FLASH_LATENCY FLASH_ACR_LATENCY_2 /* for >= 72 MHz */ /** @} */ +/** + * @name ADC configuration + * @{ + */ +#define ADC_NUMOF (0) +/** @} */ + /** * @brief Timer configuration * @{ diff --git a/boards/spark-core/include/periph_conf.h b/boards/spark-core/include/periph_conf.h index 551b26a5f3..b8f8ef10f2 100644 --- a/boards/spark-core/include/periph_conf.h +++ b/boards/spark-core/include/periph_conf.h @@ -43,6 +43,13 @@ #define CLOCK_FLASH_LATENCY FLASH_ACR_LATENCY_2 /** @} */ +/** + * @name ADC configuration + * @{ + */ +#define ADC_NUMOF (0) +/** @} */ + /** * @brief Timer configuration * @{ diff --git a/cpu/stm32f1/include/periph_cpu.h b/cpu/stm32f1/include/periph_cpu.h index e2a8284618..23306fbbba 100644 --- a/cpu/stm32f1/include/periph_cpu.h +++ b/cpu/stm32f1/include/periph_cpu.h @@ -25,6 +25,11 @@ extern "C" { #endif +/** + * @brief Available number of ADC devices + */ +#define ADC_DEVS (2U) + /** * @brief Overwrite the default gpio_t type definition * @{ @@ -101,6 +106,15 @@ typedef enum { GPIO_AF_OUT_OD = 0xf, /**< alternate function output - open-drain */ } gpio_af_out_t; +/** + * @brief ADC channel configuration data + */ +typedef struct { + 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; + /** * @brief Timer configuration */ @@ -121,6 +135,13 @@ typedef struct { */ void gpio_init_af(gpio_t pin, gpio_af_out_t af); +/** + * @brief Configure the given pin to be used as ADC input + * + * @param[in] pin pin to configure + */ +void gpio_init_analog(gpio_t pin); + #ifdef __cplusplus } #endif diff --git a/cpu/stm32f1/periph/adc.c b/cpu/stm32f1/periph/adc.c new file mode 100644 index 0000000000..b9bbc616a0 --- /dev/null +++ b/cpu/stm32f1/periph/adc.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2016 Engineering-Spirit + * + * 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_stm32f1 + * @{ + * + * @file + * @brief Low-level ADC driver implementation + * + * @author Hauke Petersen + * @author Nick van IJzendoorn + * + * @} + */ + +#include "cpu.h" +#include "mutex.h" +#include "periph/adc.h" +#include "periph_conf.h" + +/** + * @brief Maximum allowed ADC clock speed + */ +#define MAX_ADC_SPEED (14000000U) + +/** + * @brief Load the ADC configuration + * @{ + */ +#ifdef ADC_CONFIG +static const adc_conf_t adc_config[] = ADC_CONFIG; +#else +static const adc_conf_t adc_config[] = {}; +#endif + +/** + * @brief Allocate locks for all three available ADC devices + */ +static mutex_t locks[] = { +#if ADC_DEVS > 1 + MUTEX_INIT, +#endif +#if ADC_DEVS > 2 + MUTEX_INIT, +#endif + MUTEX_INIT +}; + +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]); + RCC->APB2ENR |= (RCC_APB2ENR_ADC1EN << adc_config[line].dev); +} + +static inline void done(adc_t line) +{ + RCC->APB2ENR &= ~(RCC_APB2ENR_ADC1EN << adc_config[line].dev); + mutex_unlock(&locks[adc_config[line].dev]); +} + +int adc_init(adc_t line) +{ + uint32_t clk_div = 2; + + /* check if the line is valid */ + if (line >= ADC_NUMOF) { + return -1; + } + + /* lock and power-on the device */ + prep(line); + + /* configure the pin */ + gpio_init_analog(adc_config[line].pin); + /* set clock prescaler to get the maximal possible ADC clock value */ + for (clk_div = 2; clk_div < 8; clk_div += 2) { + if ((CLOCK_CORECLOCK / clk_div) <= MAX_ADC_SPEED) { + break; + } + } + RCC->CFGR &= ~(RCC_CFGR_ADCPRE); + RCC->CFGR |= ((clk_div / 2) - 1) << 14; + + /* enable the ADC module */ + dev(line)->CR2 |= ADC_CR2_ADON; + + /* resets the selected ADC calibration registers */ + dev(line)->CR2 |= ADC_CR2_RSTCAL; + /* check the status of RSTCAL bit */ + while (dev(line)->CR2 & ADC_CR2_RSTCAL) {} + + /* enable the selected ADC calibration process */ + dev(line)->CR2 |= ADC_CR2_CAL; + /* wait for the calibration to have finished */ + while (dev(line)->CR2 & ADC_CR2_CAL) {} + + /* set all channels to maximum (239.5) cycles for best accuracy */ + dev(line)->SMPR1 |= 0x00ffffff; + dev(line)->SMPR2 |= 0x3fffffff; + /* we want to sample one channel */ + dev(line)->SQR1 = ADC_SQR1_L_0; + /* start sampling from software */ + dev(line)->CR2 |= ADC_CR2_EXTTRIG | ADC_CR2_EXTSEL; + + /* check if this channel is an internal ADC channel, if so + * enable the internal temperature and Vref */ + if (adc_config[line].chan == 16 || adc_config[line].chan == 17) { + /* check if the internal channels are configured to use ADC1 */ + if (dev(line) != ADC1) { + return -3; + } + + dev(line)->CR2 |= ADC_CR2_TSVREFE; + } + + /* free the device again */ + done(line); + return 0; +} + +int adc_sample(adc_t line, adc_res_t res) +{ + int sample; + + /* check if the linenel is valid */ + if (line >= ADC_NUMOF) { + return -1; + } + + /* check if resolution is applicable */ + if (res != ADC_RES_12BIT) { + return -2; + } + + /* lock and power on the ADC device */ + prep(line); + + /* set conversion channel */ + dev(line)->SQR3 = adc_config[line].chan; + /* start conversion and wait for results */ + dev(line)->CR2 |= ADC_CR2_SWSTART; + while (!(dev(line)->SR & ADC_SR_EOC)) {} + /* finally read sample and reset the STRT bit in the status register */ + sample = (int)dev(line)->DR; + + /* power off and unlock device again */ + done(line); + + return sample; +} diff --git a/cpu/stm32f1/periph/gpio.c b/cpu/stm32f1/periph/gpio.c index 4d18dff35a..209402e73c 100644 --- a/cpu/stm32f1/periph/gpio.c +++ b/cpu/stm32f1/periph/gpio.c @@ -150,6 +150,16 @@ void gpio_init_af(gpio_t pin, gpio_af_out_t af) port->CR[pin_num >> 3] |= (af << ((pin_num & 0x7) * 4)); } +void gpio_init_analog(gpio_t pin) +{ + /* enable the GPIO port RCC */ + RCC->APB2ENR |= (RCC_APB2ENR_IOPAEN << _port_num(pin)); + + /* map the pin as analog input */ + int pin_num = _pin_num(pin); + _port(pin)->CR[pin_num >= 8] &= ~(0xfl << (4 * (pin_num - ((pin_num >= 8) * 8)))); +} + void gpio_irq_enable(gpio_t pin) { EXTI->IMR |= (1 << _pin_num(pin));