diff --git a/boards/saml21-xpro/Makefile.features b/boards/saml21-xpro/Makefile.features index 41f035174f..7e71364551 100644 --- a/boards/saml21-xpro/Makefile.features +++ b/boards/saml21-xpro/Makefile.features @@ -1,4 +1,5 @@ # Put defined MCU peripherals here (in alphabetical order) +FEATURES_PROVIDED += periph_adc FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_rtc diff --git a/boards/saml21-xpro/include/periph_conf.h b/boards/saml21-xpro/include/periph_conf.h index 6f8c3cdb28..03e9d383af 100644 --- a/boards/saml21-xpro/include/periph_conf.h +++ b/boards/saml21-xpro/include/periph_conf.h @@ -118,6 +118,32 @@ static const spi_conf_t spi_config[] = { #define RTT_NUMOF (1) /** @} */ +/** + * @name ADC Configuration + * @{ + */ +#define ADC_NUMOF ADC_0_CHANNELS +#define ADC_0_EN 1 +#define ADC_MAX_CHANNELS 14 +/* ADC 0 device configuration */ +#define ADC_0_DEV ADC +#define ADC_0_IRQ ADC_IRQn +#define ADC_0_CHANNELS (3U) + +/* ADC 0 Default values */ +#define ADC_0_CLK_SOURCE 0 /* GCLK_GENERATOR_0 */ +#define ADC_0_PRESCALER ADC_CTRLB_PRESCALER_DIV256 +static const adc_channel_t adc_channels[] = { + /* port, pin, muxpos */ + {GPIO_PIN(PA, 10), ADC_INPUTCTRL_MUXPOS(ADC_INPUTCTRL_MUXPOS_AIN18)}, + {GPIO_PIN(PA, 11), ADC_INPUTCTRL_MUXPOS(ADC_INPUTCTRL_MUXPOS_AIN19)}, + {GPIO_PIN(PA, 2), ADC_INPUTCTRL_MUXPOS(ADC_INPUTCTRL_MUXPOS_AIN0)} +}; + +#define ADC_0_NEG_INPUT ADC_INPUTCTRL_MUXNEG(0x18u) +#define ADC_0_REF_DEFAULT ADC_REFCTRL_REFSEL_INTVCC2 +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/saml21/include/periph_cpu.h b/cpu/saml21/include/periph_cpu.h index 86d05f6124..3bdbeaf734 100644 --- a/cpu/saml21/include/periph_cpu.h +++ b/cpu/saml21/include/periph_cpu.h @@ -43,6 +43,14 @@ enum { */ #define GPIO_MODE(pr, ie, pe) (pr | (ie << 1) | (pe << 2)) +/** + * @brief ADC Channel Configuration + */ +typedef struct { + gpio_t pin; /**< ADC channel pin */ + uint32_t muxpos; /**< ADC channel pin multiplexer value */ +} adc_channel_t; + #ifndef DOXYGEN /** * @brief Override GPIO modes diff --git a/cpu/saml21/periph/adc.c b/cpu/saml21/periph/adc.c new file mode 100644 index 0000000000..0916f7e8eb --- /dev/null +++ b/cpu/saml21/periph/adc.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 Dylan Laduranty + * + * 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. + */ + +#include +#include "cpu.h" +#include "periph/gpio.h" +#include "periph/adc.h" +#include "periph_conf.h" +#include "mutex.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/* Only if we actually have any ADCs */ +#if ADC_NUMOF + +/* Prototypes */ +static void _adc_poweroff(void); +static int _adc_configure(uint32_t resolution); + +static mutex_t _lock = MUTEX_INIT; + +static inline void _prep(void) +{ + mutex_lock(&_lock); +} + +static inline void _done(void) +{ + mutex_unlock(&_lock); +} + +static void _adc_poweroff(void) +{ + /* Disable */ + ADC_0_DEV->CTRLA.reg &= ~ADC_CTRLA_ENABLE; + while (ADC_0_DEV->SYNCBUSY.reg); + /* Disable bandgap */ + if (ADC_0_REF_DEFAULT == ADC_REFCTRL_REFSEL_INTREF) { + SUPC->VREF.reg &= ~SUPC_VREF_VREFOE; + } +} + +static int _adc_configure(uint32_t resolution) +{ + _adc_poweroff(); + if (ADC_0_DEV->CTRLA.reg & ADC_CTRLA_SWRST || + ADC_0_DEV->CTRLA.reg & ADC_CTRLA_ENABLE ) { + _done(); + DEBUG("adc: not ready\n"); + return -1; + } + /* GCLK Setup */ + GCLK->PCHCTRL[ADC_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0; + /* Set Voltage Reference */ + ADC_0_DEV->REFCTRL.reg = ADC_0_REF_DEFAULT; + /* Configure CTRLB & CTRLC Register */ + ADC_0_DEV->CTRLB.reg = ADC_0_PRESCALER; + ADC_0_DEV->CTRLC.reg |= resolution; + /* Disable all interrupts */ + ADC_0_DEV->INTENCLR.reg = ADC_INTENCLR_WINMON |ADC_INTENCLR_OVERRUN | + ADC_INTENCLR_RESRDY; + /* Set default calibration from NVM */ + ADC_0_DEV->CALIB.reg = + ADC_FUSES_BIASCOMP(*(uint32_t*)ADC_FUSES_BIASCOMP_ADDR) | + ADC_FUSES_BIASREFBUF(*(uint32_t*)ADC_FUSES_BIASREFBUF_ADDR); + + while (ADC_0_DEV->SYNCBUSY.reg); + /* Enable bandgap if necessary */ + if (ADC_0_REF_DEFAULT == ADC_REFCTRL_REFSEL_INTREF) { + SUPC->VREF.reg |= SUPC_VREF_VREFOE; + } + /* Enable ADC Module */ + ADC_0_DEV->CTRLA.reg |= ADC_CTRLA_ENABLE; + /* Wait for sync */ + while (ADC_0_DEV->SYNCBUSY.reg); + return 0; +} + +int adc_init(adc_t line) +{ + _prep(); + gpio_init(adc_channels[line].pin, GPIO_IN); + gpio_init_mux(adc_channels[line].pin, GPIO_MUX_B); + MCLK->APBDMASK.reg |= MCLK_APBDMASK_ADC; + _done(); + return 0; +} + +int adc_sample(adc_t line, adc_res_t res) +{ + if (line >= ADC_0_CHANNELS) { + DEBUG("adc: line arg not applicable\n"); + return -1; + } + uint32_t resolution; + switch (res) { + case ADC_RES_8BIT: + resolution = ADC_CTRLC_RESSEL_8BIT; + break; + case ADC_RES_10BIT: + resolution = ADC_CTRLC_RESSEL_10BIT; + break; + case ADC_RES_12BIT: + resolution = ADC_CTRLC_RESSEL_12BIT; + break; + default: + resolution = 0xFFFFFFFF; + DEBUG("adc: Resolution arg not applicable\n"); + return -1; + } + _prep(); + if (_adc_configure(resolution) != 0) { + _done(); + DEBUG("adc: configuration failed\n"); + return -1; + } + ADC_0_DEV->INPUTCTRL.reg = adc_channels[line].muxpos | ADC_0_NEG_INPUT; + /* Wait for sync */ + while (ADC_0_DEV->SYNCBUSY.reg); + /* Start the conversion */ + ADC_0_DEV->SWTRIG.reg = ADC_SWTRIG_START; + /* Wait for the result */ + while (!(ADC_0_DEV->INTFLAG.reg & ADC_INTFLAG_RESRDY)); + int result = ADC_0_DEV->RESULT.reg; + _adc_poweroff(); + _done(); + return result; +} + +#endif /* #if ADC_NUMOF */