diff --git a/cpu/nrf51/include/periph_cpu.h b/cpu/nrf51/include/periph_cpu.h index e4eac37287..bc368152fd 100644 --- a/cpu/nrf51/include/periph_cpu.h +++ b/cpu/nrf51/include/periph_cpu.h @@ -60,12 +60,12 @@ typedef enum { */ #define HAVE_ADC_RES_T typedef enum { - ADC_RES_6BIT = 0xf0, /**< ADC resolution: 6 bit */ + ADC_RES_6BIT = 0xf0, /**< ADC resolution: 6 bit (not supported) */ ADC_RES_8BIT = 0x00, /**< ADC resolution: 8 bit */ ADC_RES_10BIT = 0x02, /**< ADC resolution: 10 bit */ - ADC_RES_12BIT = 0xf1, /**< ADC resolution: 12 bit */ - ADC_RES_14BIT = 0xf2, /**< ADC resolution: 14 bit */ - ADC_RES_16BIT = 0xf3 /**< ADC resolution: 16 bit */ + ADC_RES_12BIT = 0xf1, /**< ADC resolution: 12 bit (not supported) */ + ADC_RES_14BIT = 0xf2, /**< ADC resolution: 14 bit (not supported) */ + ADC_RES_16BIT = 0xf3 /**< ADC resolution: 16 bit (not supported) */ } adc_res_t; /** @} */ diff --git a/cpu/nrf52/Makefile.features b/cpu/nrf52/Makefile.features index 7d12728b90..56ecba5a41 100644 --- a/cpu/nrf52/Makefile.features +++ b/cpu/nrf52/Makefile.features @@ -1 +1,4 @@ +# The ADC does not depend on any board configuration, so always available +FEATURES_PROVIDED += periph_adc + -include $(RIOTCPU)/nrf5x_common/Makefile.features diff --git a/cpu/nrf52/include/periph_cpu.h b/cpu/nrf52/include/periph_cpu.h index 9b8adc33ab..bac6b6487e 100644 --- a/cpu/nrf52/include/periph_cpu.h +++ b/cpu/nrf52/include/periph_cpu.h @@ -40,6 +40,26 @@ extern "C" { #define SPI_MISOSEL (dev(bus)->PSEL.MISO) /** @} */ +/** + * @brief The nRF52 family of CPUs provides a fixed number of 9 ADC lines + */ +#define ADC_NUMOF (9U) + +/** + * @brief nRF52 specific naming of ADC lines (for convenience) + */ +enum { + NRF52_AIN0 = 0, /**< Analog Input 0 */ + NRF52_AIN1 = 1, /**< Analog Input 1 */ + NRF52_AIN2 = 2, /**< Analog Input 2 */ + NRF52_AIN3 = 3, /**< Analog Input 3 */ + NRF52_AIN4 = 4, /**< Analog Input 4 */ + NRF52_AIN5 = 5, /**< Analog Input 5 */ + NRF52_AIN6 = 6, /**< Analog Input 6 */ + NRF52_AIN7 = 7, /**< Analog Input 7 */ + NRF52_VDD = 8, /**< VDD, not useful if VDD is reference... */ +}; + /** * @brief Override ADC resolution values * @{ @@ -50,20 +70,11 @@ typedef enum { ADC_RES_8BIT = 0x00, /**< ADC resolution: 8 bit */ ADC_RES_10BIT = 0x01, /**< ADC resolution: 10 bit */ ADC_RES_12BIT = 0x02, /**< ADC resolution: 12 bit */ - ADC_RES_14BIT = 0xf1, /**< supported with oversampling */ + ADC_RES_14BIT = 0xf1, /**< supported with oversampling, not implemented */ ADC_RES_16BIT = 0xf2 /**< not supported by hardware */ } adc_res_t; /** @} */ -/** - * @name ADC configuration, valid for all boards using this CPU - * - * The NRF52832 has a fixed mapping of ADC pins and a fixed number of ADC channels, - * so this ADC configuration is valid for all boards using this CPU. No need for - * any board specific configuration. - */ -#define ADC_NUMOF (8U) - #ifdef __cplusplus } #endif diff --git a/cpu/nrf52/periph/adc.c b/cpu/nrf52/periph/adc.c index 42b3043b7f..7ecd219784 100644 --- a/cpu/nrf52/periph/adc.c +++ b/cpu/nrf52/periph/adc.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2017 HAW Hamburg + * 2017 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 @@ -10,32 +11,11 @@ * @ingroup cpu_nrf52 * @{ * - * Any one of the available channels can be enabled for the ADC to operate in - * one-shot mode. If more than one CH[n] is configured, the ADC enters scan mode. - * - * Example of RAM placement (RESULT.MAXCNT = 8), all channels enabled: - * - * 31 16 15 0 - * RESULT.PTR = adc_val[0] | CH[1] | CH[0] | - * RESULT.PTR + 4 | CH[3] | CH[2] | - * RESULT.PTR + 8 | CH[5] | CH[4] | - * RESULT.PTR + 12 | CH[7] | CH[6] | - * - * Example of RAM placement (RESULT.MAXCNT = 4), channels 0, 3, 4 and 7 enabled: - * - * 31 16 15 0 - * RESULT.PTR = adc_val[0] | CH[3] | CH[0] | - * RESULT.PTR + 4 | CH[7] | CH[4] | - * RESULT.PTR + 8 | | | - * RESULT.PTR + 12 | | | - * - * Scan mode and oversampling cannot be combined. - * -> 8/10/12-bit resolution, 14-bit resolution only with oversampling - * * @file * @brief Low-level ADC driver implementation * * @author Dimitri Nahm + * @author Hauke Petersen * * @} */ @@ -45,16 +25,35 @@ #include "periph/adc.h" #include "periph_conf.h" -#ifdef ADC_NUMOF - -static int16_t adc_val[ADC_NUMOF]; -static uint8_t adc_ch_enabled = 0; -static uint8_t adc_return_idx; +/** + * @name Default ADC reference, gain configuration and acquisition time + * + * Can be overridden by the board configuration if needed. The default + * configuration uses the full VDD (typically 3V3) as reference and samples for + * 10us. + * @{ + */ +#ifndef ADC_REF +#define ADC_REF SAADC_CH_CONFIG_REFSEL_VDD1_4 +#endif +#ifndef ADC_GAIN +#define ADC_GAIN SAADC_CH_CONFIG_GAIN_Gain1_4 +#endif +#ifndef ADC_TACQ +#define ADC_TACQ SAADC_CH_CONFIG_TACQ_10us +#endif +/** @} */ /** * @brief Lock to prevent concurrency issues when used from different threads */ -static mutex_t lock; +static mutex_t lock = MUTEX_INIT; + +/** + * @brief We use a static result buffer so we do not have to reprogram the + * result pointer register + */ +static int16_t result; static inline void prep(void) { @@ -74,31 +73,33 @@ int adc_init(adc_t line) return -1; } - if ((adc_ch_enabled & (1 << line)) == 0) { - /* prepare device */ - prep(); + prep(); - /* set the number of enabled channels and the data pointer */ - NRF_SAADC->RESULT.MAXCNT += 1; - NRF_SAADC->RESULT.PTR = (uint32_t)&adc_val; + /* prevent multiple initialization by checking the result ptr register */ + if (NRF_SAADC->RESULT.PTR != (uint32_t)&result) { + /* set data pointer and the single channel we want to convert */ + NRF_SAADC->RESULT.MAXCNT = 1; + NRF_SAADC->RESULT.PTR = (uint32_t)&result; - /* set ADC channel and use VDD (+5V) as reference */ - NRF_SAADC->CH[line].PSELP = line + 1; - NRF_SAADC->CH[line].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_4 << SAADC_CH_CONFIG_RESN_Pos) | - (SAADC_CH_CONFIG_REFSEL_VDD1_4 << SAADC_CH_CONFIG_REFSEL_Pos); + /* configure the first channel (the only one we use): + * - bypass resistor ladder+ + * - acquisition time as defined by board (or 10us as default) + * - reference and gain as defined by board (or VDD as default) + * - no oversampling */ + NRF_SAADC->CH[0].CONFIG = ((ADC_GAIN << SAADC_CH_CONFIG_GAIN_Pos) | + (ADC_REF << SAADC_CH_CONFIG_REFSEL_Pos) | + (ADC_TACQ << SAADC_CH_CONFIG_TACQ_Pos)); + NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC; + NRF_SAADC->OVERSAMPLE = SAADC_OVERSAMPLE_OVERSAMPLE_Bypass; - /* calibrate the SAADC */ + /* calibrate SAADC */ NRF_SAADC->EVENTS_CALIBRATEDONE = 0; NRF_SAADC->TASKS_CALIBRATEOFFSET = 1; while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0) {} - - /* remember which ADC channel is enabled */ - adc_ch_enabled |= (1 << line); - - /* free device */ - done(); } + done(); + return 0; } @@ -116,19 +117,18 @@ int adc_sample(adc_t line, adc_res_t res) /* set resolution */ NRF_SAADC->RESOLUTION = res; + /* set line to sample */ + NRF_SAADC->CH[0].PSELP = (line + 1); /* start the SAADC and wait for the started event */ NRF_SAADC->EVENTS_STARTED = 0; NRF_SAADC->TASKS_START = 1; while (NRF_SAADC->EVENTS_STARTED == 0) {} - /* start conversions and wait for conversions to be complete */ - for (uint8_t i = 0; i < NRF_SAADC->RESULT.MAXCNT; i++) { - while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos)) {} - NRF_SAADC->EVENTS_DONE = 0; - NRF_SAADC->TASKS_SAMPLE = 1; - while (NRF_SAADC->EVENTS_DONE == 0) {} - } + /* trigger the actual conversion */ + NRF_SAADC->EVENTS_END = 0; + NRF_SAADC->TASKS_SAMPLE = 1; + while (NRF_SAADC->EVENTS_END == 0) {} /* stop the SAADC */ NRF_SAADC->EVENTS_STOPPED = 0; @@ -138,19 +138,9 @@ int adc_sample(adc_t line, adc_res_t res) /* free device */ done(); - /* return the ADC value for the given line */ - adc_return_idx = 0; - for (uint8_t i = 0; i < line; i++) { - if (adc_ch_enabled & (1 << i)) { - adc_return_idx++; - } - } - if (adc_val[adc_return_idx] < 0) { - return 0; - } - return adc_val[adc_return_idx]; + /* hack -> the result can be a small negative number when a AINx pin is + * connected via jumper wire a the board's GND pin. There seems to be a + * slight difference between the internal CPU GND and the board's GND + * voltage levels?! (observed on nrf52dk and nrf52840dk) */ + return (result < 0) ? 0 : (int)result; } - -#else -typedef int dont_be_pedantic; -#endif /* ADC_CONFIG */