From 2421919295e4d13fff2c9e2a8bca25d0769ddd0a Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 3 Nov 2023 15:21:28 +0100 Subject: [PATCH 1/3] periph/adc: introduce periph_adc_continous --- drivers/include/periph/adc.h | 27 +++++++++++++++++++++++++++ kconfigs/Kconfig.features | 5 +++++ 2 files changed, 32 insertions(+) diff --git a/drivers/include/periph/adc.h b/drivers/include/periph/adc.h index b6eb4de713..c91d05c12e 100644 --- a/drivers/include/periph/adc.h +++ b/drivers/include/periph/adc.h @@ -128,6 +128,33 @@ int adc_init(adc_t line); */ int32_t adc_sample(adc_t line, adc_res_t res); +/** + * @brief Configure the ADC with a given resolution for continuous sampling + * + * @note requires the `periph_adc_continuous` feature + * + * @param[in] res resolution to use for conversion + */ +void adc_continuous_begin(adc_res_t res); + +/** + * @brief Sample an ADC line without powering off the ADC afterward + * + * @note requires the `periph_adc_continuous` feature + * + * @brief Sample a value from the given ADC line + * + * @return the sampled value on success + */ +int32_t adc_continuous_sample(adc_t line); + +/** + * @brief Disable the ADC to save power + * + * @note requires the `periph_adc_continuous` feature + */ +void adc_continuous_stop(void); + #ifdef __cplusplus } #endif diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index ab84f587a1..cb824d97b1 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -188,6 +188,11 @@ config HAS_PERIPH_ADC help Indicates that an ADC peripheral is present. +config HAS_PERIPH_ADC_CONTINUOUS + bool + help + Indicates that an ADC peripheral can be left on between measurements. + config HAS_PERIPH_CAN bool help From b289d69b4f5cf2fbb2dda92b067761dbc12fa1be Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 3 Nov 2023 15:11:40 +0100 Subject: [PATCH 2/3] cpu/sam0_common: implement periph_adc_continous --- cpu/sam0_common/Kconfig | 1 + cpu/sam0_common/Makefile.features | 1 + cpu/sam0_common/periph/adc.c | 155 +++++++++++++++++++++++------- 3 files changed, 123 insertions(+), 34 deletions(-) diff --git a/cpu/sam0_common/Kconfig b/cpu/sam0_common/Kconfig index 2f9b333e95..fe4c1afc42 100644 --- a/cpu/sam0_common/Kconfig +++ b/cpu/sam0_common/Kconfig @@ -8,6 +8,7 @@ config CPU_COMMON_SAM0 bool select HAS_PERIPH_CPUID + select HAS_PERIPH_ADC_CONTINUOUS select HAS_PERIPH_FLASHPAGE select HAS_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE select HAS_PERIPH_FLASHPAGE_PAGEWISE diff --git a/cpu/sam0_common/Makefile.features b/cpu/sam0_common/Makefile.features index 2d2035c20d..2f01bb135e 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -7,6 +7,7 @@ ifeq (,$(filter $(CPU_MODELS_WITHOUT_DMA),$(CPU_MODEL))) FEATURES_PROVIDED += periph_dma endif +FEATURES_PROVIDED += periph_adc_continuous FEATURES_PROVIDED += periph_flashpage FEATURES_PROVIDED += periph_flashpage_in_address_space FEATURES_PROVIDED += periph_flashpage_pagewise diff --git a/cpu/sam0_common/periph/adc.c b/cpu/sam0_common/periph/adc.c index 59c236a7e8..cf65a41605 100644 --- a/cpu/sam0_common/periph/adc.c +++ b/cpu/sam0_common/periph/adc.c @@ -49,16 +49,6 @@ static int _adc_configure(Adc *dev, adc_res_t res); static mutex_t _lock = MUTEX_INIT; -static inline void _prep(void) -{ - mutex_lock(&_lock); -} - -static inline void _done(void) -{ - mutex_unlock(&_lock); -} - static inline void _wait_syncbusy(Adc *dev) { #ifdef ADC_STATUS_SYNCBUSY @@ -263,7 +253,7 @@ int adc_init(adc_t line) const uint8_t adc = 0; #endif - _prep(); + mutex_lock(&_lock); uint8_t muxpos = (adc_channels[line].inputctrl & ADC_INPUTCTRL_MUXPOS_Msk) >> ADC_INPUTCTRL_MUXPOS_Pos; @@ -284,34 +274,44 @@ int adc_init(adc_t line) gpio_init_mux(sam0_adc_pins[adc][muxneg], GPIO_MUX_B); } - _done(); + mutex_unlock(&_lock); return 0; } -int32_t adc_sample(adc_t line, adc_res_t res) +static Adc *_dev(adc_t line) { - if (line >= ADC_NUMOF) { - DEBUG("adc: line arg not applicable\n"); - return -1; - } - /* The SAMD5x/SAME5x family has two ADCs: ADC0 and ADC1. */ #ifdef ADC0 - Adc *dev = adc_channels[line].dev; + return adc_channels[line].dev; #else - Adc *dev = ADC; + (void)line; + return ADC; #endif +} - bool diffmode = adc_channels[line].inputctrl & ADC_INPUTCTRL_DIFFMODE; - - _prep(); - - if (_adc_configure(dev, res) != 0) { - _done(); - DEBUG("adc: configuration failed\n"); - return -1; +static Adc *_adc(uint8_t dev) +{ + /* The SAMD5x/SAME5x family has two ADCs: ADC0 and ADC1. */ +#ifdef ADC0 + switch (dev) { + case 0: + return ADC0; + case 1: + return ADC1; + default: + return NULL; } +#else + (void)dev; + return ADC; +#endif +} + +static int32_t _sample(adc_t line) +{ + Adc *dev = _dev(line); + bool diffmode = adc_channels[line].inputctrl & ADC_INPUTCTRL_DIFFMODE; dev->INPUTCTRL.reg = ADC_GAIN_FACTOR_DEFAULT | adc_channels[line].inputctrl @@ -319,7 +319,6 @@ int32_t adc_sample(adc_t line, adc_res_t res) #ifdef ADC_CTRLB_DIFFMODE dev->CTRLB.bit.DIFFMODE = diffmode; #endif - _wait_syncbusy(dev); /* Start the conversion */ @@ -331,9 +330,6 @@ int32_t adc_sample(adc_t line, adc_res_t res) uint16_t sample = dev->RESULT.reg; int result; - _adc_poweroff(dev); - _done(); - /* in differential mode we lose one bit for the sign */ if (diffmode) { result = 2 * (int16_t)sample; @@ -341,11 +337,102 @@ int32_t adc_sample(adc_t line, adc_res_t res) result = sample; } + return result; +} + +static uint8_t _shift_from_res(adc_res_t res) +{ /* 16 bit mode is implemented as oversampling */ if ((res & 0x3) == 1) { /* ADC does automatic right shifts beyond 16 samples */ - result <<= (4 - MIN(4, res >> 2)); + return 4 - MIN(4, res >> 2); + } + return 0; +} + +static void _get_adcs(bool *adc0, bool *adc1) +{ +#ifndef ADC1 + *adc0 = true; + *adc1 = false; + return; +#else + for (unsigned i = 0; i < ADC_NUMOF; ++i) { + if (adc_channels[i].dev == ADC0) { + *adc0 = true; + } else if (adc_channels[i].dev == ADC1) { + *adc1 = true; + } + } +#endif +} + +static uint8_t _shift; +void adc_continuous_begin(adc_res_t res) +{ + bool adc0, adc1; + _get_adcs(&adc0, &adc1); + + mutex_lock(&_lock); + + if (adc0) { + _adc_configure(_adc(0), res); + } + if (adc1) { + _adc_configure(_adc(1), res); } - return result; + _shift = _shift_from_res(res); +} + +int32_t adc_continuous_sample(adc_t line) +{ + int val; + assert(line < ADC_NUMOF); + + mutex_lock(&_lock); + val = _sample(line) << _shift; + mutex_unlock(&_lock); + + return val; +} + +void adc_continuous_stop(void) +{ + bool adc0, adc1; + _get_adcs(&adc0, &adc1); + + if (adc0) { + _adc_poweroff(_adc(0)); + } + if (adc1) { + _adc_poweroff(_adc(1)); + } + + mutex_unlock(&_lock); +} + +int32_t adc_sample(adc_t line, adc_res_t res) +{ + if (line >= ADC_NUMOF) { + DEBUG("adc: line arg not applicable\n"); + return -1; + } + + mutex_lock(&_lock); + + Adc *dev = _dev(line); + + if (_adc_configure(dev, res) != 0) { + DEBUG("adc: configuration failed\n"); + mutex_unlock(&_lock); + return -1; + } + + int val = _sample(line) << _shift_from_res(res); + + _adc_poweroff(dev); + mutex_unlock(&_lock); + + return val; } From d3dfd4baf064bcd0ea1440e5edfab89b67e3f07a Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 3 Nov 2023 15:16:01 +0100 Subject: [PATCH 3/3] periph/adc_continous: add test for ADC continous API --- tests/periph/adc_continuous/Makefile | 9 ++++ tests/periph/adc_continuous/Makefile.ci | 3 ++ tests/periph/adc_continuous/README.md | 13 ++++++ tests/periph/adc_continuous/main.c | 58 +++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 tests/periph/adc_continuous/Makefile create mode 100644 tests/periph/adc_continuous/Makefile.ci create mode 100644 tests/periph/adc_continuous/README.md create mode 100644 tests/periph/adc_continuous/main.c diff --git a/tests/periph/adc_continuous/Makefile b/tests/periph/adc_continuous/Makefile new file mode 100644 index 0000000000..136730c9e0 --- /dev/null +++ b/tests/periph/adc_continuous/Makefile @@ -0,0 +1,9 @@ +BOARD ?= same54-xpro +include ../Makefile.periph_common + +FEATURES_REQUIRED += periph_adc +FEATURES_REQUIRED += periph_adc_continuous +USEMODULE += ztimer +USEMODULE += ztimer_msec + +include $(RIOTBASE)/Makefile.include diff --git a/tests/periph/adc_continuous/Makefile.ci b/tests/periph/adc_continuous/Makefile.ci new file mode 100644 index 0000000000..72db76ccb5 --- /dev/null +++ b/tests/periph/adc_continuous/Makefile.ci @@ -0,0 +1,3 @@ +BOARD_INSUFFICIENT_MEMORY := \ + atmega8 \ + # diff --git a/tests/periph/adc_continuous/README.md b/tests/periph/adc_continuous/README.md new file mode 100644 index 0000000000..7ad2990acf --- /dev/null +++ b/tests/periph/adc_continuous/README.md @@ -0,0 +1,13 @@ +Expected result +=============== +When running this test, you should see the samples of all configured ADC lines +continuously streamed to std-out. + +Background +========== +This test application will initialize each configured ADC lines to sample with +10-bit accuracy. Once configured the application will continuously convert each +available channel and print the conversion results to std-out. + +For verification of the output connect the ADC pins to known voltage levels +and compare the output. diff --git a/tests/periph/adc_continuous/main.c b/tests/periph/adc_continuous/main.c new file mode 100644 index 0000000000..4799aa1c40 --- /dev/null +++ b/tests/periph/adc_continuous/main.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2014-2015 Freie Universität Berlin + * + * 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 tests + * @{ + * + * @file + * @brief Test application for peripheral ADC drivers + * + * @author Hauke Petersen + * + * @} + */ + +#include + +#include "ztimer.h" +#include "periph/adc.h" + +#define RES ADC_RES_10BIT +#define DELAY_MS 100U + +int main(void) +{ + int sample = 0; + + puts("\nRIOT ADC peripheral driver test\n"); + puts("This test will sample all available ADC lines once every 100ms with\n" + "a 10-bit resolution and print the sampled results to STDIO\n\n"); + + /* initialize all available ADC lines */ + for (unsigned i = 0; i < ADC_NUMOF; i++) { + if (adc_init(ADC_LINE(i)) < 0) { + printf("Initialization of ADC_LINE(%u) failed\n", i); + return 1; + } else { + printf("Successfully initialized ADC_LINE(%u)\n", i); + } + } + + adc_continuous_begin(RES); + while (1) { + for (unsigned i = 0; i < ADC_NUMOF; i++) { + sample = adc_continuous_sample(ADC_LINE(i)); + printf("ADC_LINE(%u): %i\n", i, sample); + } + ztimer_sleep(ZTIMER_MSEC, DELAY_MS); + } + + adc_continuous_stop(); + return 0; +}