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

cpu/qn908x: Implement ADC support

The ADC in the QN908x cpu offers multiple options for ADC conversion
using up to 8 external pins, one external reference pin and some
internal signals like a 1.2V reference, Vss, Vcc and an internal
temperature monitor.

This patch implements support for sampling ADC values from the ADC lines
defined in the board configuration. Some configurations are really
always present and don't require a board configuration, like the Vcc or
internal temperature monitor but to coexist with other board ADC line
options they are only set as part of the board configuration.
This commit is contained in:
iosabi 2020-12-22 03:45:58 +01:00
parent a61edaa32a
commit 965ebaa15b
3 changed files with 605 additions and 0 deletions

View File

@ -30,6 +30,83 @@ The GPIO driver uses the @ref GPIO_PIN(port, pin) macro to declare pins.
No configuration is necessary.
@defgroup cpu_qn908x_adc NXP QN908x ADC - Analog to Digital converter
@ingroup cpu_qn908x
@brief NXP QN908x ADC driver
This ADC is a differential sigma-delta ADC. There are 9 external signals
named ADC0 to ADC7 and ADC_VREFI that can be connected to specific external GPIO
pins. There are several combinations of measurements possible with the ADC
module using these external signals as well as some internal signals, but not
every combination is a possible input pair to the ADC.
The ADC block runs at either 4 MHz or 32 KHz from the high speed or low speed
clock sources respective. An additional divisor is available to select some
intermediate clock values. However, this is not the sample rate, since a single
sample from @ref adc_sample() requires multiple internal samples which are then
filtered and decimated by the hardware, giving about 128 slower sample rate than
the selected clock.
Each board-defined ADC line is configured with a single integer value which is
the logic or of the following values:
- The differential pair of signals among the options in @ref
qn908x_adc_channel_t,
- For those channels that use the "Vinn" signal, a value selecting
the Vinn signal in @ref qn908x_adc_vinn_t,
- The reference voltage "Vref" that the ADC will use for its full range
selected with @ref qn908x_adc_vref_t,
- The ADC input gain as selected by @ref qn908x_adc_gain_t, which will
multiply the differential input by a factor between 0.5 and 2, and
- An optional gain flag @ref ADC_VREF_GAIN_X15.
- An optional PGA enabled flag @ref ADC_PGA_ENABLE.
The hardware resolution of the ADC data is always 23-bits signed, but smaller
resolutions can be requested which will result in a smaller output value.
An internal temperature sensor is available and connected to the ADC when
selecting @ref ADC_CHANNEL_TEMP as the channel. In this case the returned
value is still a number that represents the temperature dependent voltage level
of the internal signal which then needs to be converted to a temperature by the
application using calibration parameters. When using the internal temperature
sensor, the 1.2V bandgap Vref is recommended with a Vinn of 1/2 Vref since the
measured voltage is about 800 mV at room temperature.
A special microvolts (ADC_RES_UV) resolution value for @ref adc_res_t is
supported when using the internal 1.2 V bandgap as the reference voltage, in
which case @ref adc_sample will return the measured value in microvolts as a
signed integer, with a max range of +/- 1.8 V when using the x1.5 Vref
multiplier.
This special resolution mode takes into account the factory calibration of
the internal reference voltage for more accurate readings. In any other case,
the return value is a signed integer with as many bits as resolution
requested not including the sign bit. Note that the return value may be a
negative when measuring a negative differential voltage between the plus and
minus side of the input.
For example, if 8-bit resolution is requested for an ADC line where the channel
connects the - side to Vinn configured as Vss, a maximum value of 255 can be
returned when the + side level is as high as the Vref signal. However, a
negative value of -255 is also possible if Vinn is configured as Vref and
the + side level is as low as Vss.
### ADC configuration example (for periph_conf.h) ###
@code
static const adc_conf_t adc_config[] = {
/* Pin A11 to Vss, 1.8v Vref. */
ADC_CHANNEL_ADC7_VINN | ADC_VREF_GAIN_X15,
/* Pin A10 to A11, 1.2V Vref. */
ADC_CHANNEL_ADC6_ADC7,
/* Temperature (in V) over to 0.6 V, 1.2 V Vref. */
ADC_CHANNEL_TEMP | ADC_VINN_VREF_2,
/* Internal "battery monitor", Vcc/4 to Vss, 1.2V Vref. */
ADC_CHANNEL_VCC4_VINN | ADC_VINN_AVSS,
};
#define ADC_NUMOF ARRAY_SIZE(adc_config)
#define QN908X_ADC_CLOCK ADC_CLOCK_500K
@endcode
@defgroup cpu_qn908x_i2c NXP QN908x I2C
@ingroup cpu_qn908x

View File

@ -141,6 +141,196 @@ enum {
GPIO_PORTS_NUMOF /**< overall number of available ports */
};
/**
* @name ADC CPU configuration
* @{
*/
#if DOXYGEN
/**
* @brief Define if ADC external capacitor is connected to PA06 pin.
*
* The ADC block can use an external capacitor to better stabilize the reference
* voltage. This capacitor is optional, but if it is present on the board this
* macro should be defined by the board to make the ADC block use it.
*/
#define BOARD_HAS_ADC_PA06_CAP
#endif
#ifndef DOXYGEN
/**
* @name ADC resolution values
* @{
*/
#define HAVE_ADC_RES_T
typedef enum {
ADC_RES_6BIT = 6u, /**< ADC resolution: 6 bit + sign */
ADC_RES_8BIT = 8u, /**< ADC resolution: 8 bit + sign */
ADC_RES_10BIT = 10u, /**< ADC resolution: 10 bit + sign */
ADC_RES_12BIT = 12u, /**< ADC resolution: 12 bit + sign */
ADC_RES_14BIT = 14u, /**< ADC resolution: 14 bit + sign */
ADC_RES_16BIT = 16u, /**< ADC resolution: 16 bit + sign */
/* Extra modes supported by this CPU. */
ADC_RES_MAX = 22u, /**< Full ADC resolution: 22 bit + sign */
ADC_RES_UV = 23u, /**< ADC resolution: signed int in uV */
} adc_res_t;
/** @} */
#endif /* ifndef DOXYGEN */
/**
* @brief ADC oversample clock configuration
*
* The ADC runs at a given ADC clock frequency which is derived from either the
* high frequency clock (16 or 32 MHz) or the low frequency one (32 or
* 32.768 KHz). Running the ADC from the 32 KHz source can be useful in low
* power applications where the high speed clock is not running.
*
* The ADC sample rate for adc_sample() will be about 128 times slower than the
* ADC clock, due to the decimation filter, meaning that the maximum sampling
* rate is 31.25 KHz.
*/
typedef enum {
ADC_CLOCK_4M, /**< 4 MHz from the high speed clock. */
ADC_CLOCK_2M, /**< 2 MHz from the high speed clock. */
ADC_CLOCK_1M, /**< 1 MHz from the high speed clock. */
ADC_CLOCK_500K, /**< 500 KHz from the high speed clock. */
ADC_CLOCK_250K, /**< 250 KHz from the high speed clock. */
ADC_CLOCK_125K, /**< 125 KHz from the high speed clock. */
ADC_CLOCK_62K5, /**< 62.5 KHz from the high speed clock. */
ADC_CLOCK_31K25, /**< 31.25 KHz from the high speed clock. */
ADC_CLOCK_32K, /**< 32 KHz or 32.768 KHz from the low speed clock. */
} qn908x_adc_clock_t;
#ifdef DOXYGEN
/** @brief Selected ADC oversample clock.
*
* Define to one of the qn908x_adc_clock_t values.
*/
#define QN908X_ADC_CLOCK
/** @} */
#endif /* ifdef DOXYGEN */
/**
* @brief ADC channel pair configuration
*
* The following are the possible combinations of + and - inputs to the ADC
* sigma delta. Some of these combinations reference the "Vinn" signal which can
* be independently selected, see @ref qn908x_adc_vinn_t for details.
*
* The first signal is connected to the positive side while the second one is
* connected to the negative side. For example, ADC_CHANNEL_ADC0_ADC1 will read
* a positive value if ADC0 voltage is higher than ADC1.
*
* The @ref ADC_CHANNEL_TEMP uses the internal temperature signal and
* @ref ADC_CHANNEL_VCC4_VINN connects the + side to Vcc/4, which is useful to
* measure the battery level when Vcc is directly connected to a battery.
*/
typedef enum {
ADC_CHANNEL_ADC0_ADC1 = 0u << 9u, /**< Sample ADC0 / ADC1 */
ADC_CHANNEL_ADC2_ADC3 = 1u << 9u, /**< Sample ADC2 / ADC3 */
ADC_CHANNEL_ADC4_ADC5 = 2u << 9u, /**< Sample ADC4 / ADC5 */
ADC_CHANNEL_ADC6_ADC7 = 3u << 9u, /**< Sample ADC6 / ADC7 */
ADC_CHANNEL_ADC0_VINN = 4u << 9u, /**< Sample ADC0 / Vinn */
ADC_CHANNEL_ADC1_VINN = 5u << 9u, /**< Sample ADC1 / Vinn */
ADC_CHANNEL_ADC2_VINN = 6u << 9u, /**< Sample ADC2 / Vinn */
ADC_CHANNEL_ADC3_VINN = 7u << 9u, /**< Sample ADC3 / Vinn */
ADC_CHANNEL_ADC4_VINN = 8u << 9u, /**< Sample ADC4 / Vinn */
ADC_CHANNEL_ADC5_VINN = 9u << 9u, /**< Sample ADC5 / Vinn */
ADC_CHANNEL_ADC6_VINN = 10u << 9u, /**< Sample ADC6 / Vinn */
ADC_CHANNEL_ADC7_VINN = 11u << 9u, /**< Sample ADC7 / Vinn */
ADC_CHANNEL_TEMP = 13u << 9u, /**< Sample internal temperature */
ADC_CHANNEL_VCC4_VINN = 14u << 9u, /**< Sample 1/4 Vcc / Vinn */
ADC_CHANNEL_VINN_VINN = 15u << 9u, /**< Sample Vinn / Vinn */
ADC_CHANNEL_VINN_VSS = 20u << 9u, /**< Sample Vinn / Vss */
} qn908x_adc_channel_t;
/**
* @brief ADC Vref configuration
*
* This value affects the reference voltage used by the ADC as the full range.
* It is also used in some cases to generate the Vinn signal are is only relevant for the channels that reference Vinn when it was
* set by @ref qn908x_adc_vinn_t to use Vref. The actual values match the field
* VREF_SEL in ADC CTRL register.
*/
typedef enum {
ADC_VREF_1V2 = 0x0000u, /**< Vref := internal 1.2V. */
ADC_VREF_VREF = 0x4000u, /**< Vref := external ADC_VREFI pin */
ADC_VREF_VEXT = 0x8000u, /**< Vref := external ADC_VREFI with the driver */
ADC_VREF_VCC = 0xC000u, /**< Vref := Vcc */
} qn908x_adc_vref_t;
/**
* @brief ADC Vref x1.5 multiplier flag
*
* Note, this is the same value as ADC_CFG_VREF_GAIN_MASK. When enabled the
* Vref voltage will be multiplied by 1.5x.
*/
#define ADC_VREF_GAIN_X15 (0x100u)
/**
* @brief ADC PGA Enabled flag
*
* Flag to enable the Programmable Gain Amplifier (PGA) with a gain of 1x. This
* is only useful if the source signal doesn't have any driving capability since
* the gain is set to 1x. The hardware supports other gain combinations but
* those are not supported by the driver.
*
* Note: this value is defined as the inverse of ADC_CFG_PGA_BP_MASK which is
* defined if the PGA is bypassed.
*/
#define ADC_PGA_ENABLE (0x08u)
/**
* @brief ADC Vinn configuration
*
* This value is only relevant for the channels that reference Vinn. The value
* is the same as the PGA_VINN in ADC CFG register with a logic xor 0x30u to
* make the default AVSS (analog Vss pad).
*/
typedef enum {
ADC_VINN_VREF = 0x30u, /**< Use Vinn := Vref */
ADC_VINN_VREF_3_4 = 0x20u, /**< Use Vinn := 3/4 * Vref */
ADC_VINN_VREF_2 = 0x10u, /**< Use Vinn := 1/2 * Vref */
ADC_VINN_AVSS = 0x00u, /**< Use Vinn := Vss */
} qn908x_adc_vinn_t;
/**
* @brief ADC SD Gain configuration
*
* This multiplies the sampled value (difference between +/- signals) by the
* given value.
*
* Note: these values logic xor 0x40 match the values for ADC_CFG_ADC_GAIN
* field. This is selected so that omitting this flag in the config field
* defaults to x1.0 gain but it can still be converted to the ADC_GAIN field
* with a simple logic xor.
*/
typedef enum {
ADC_GAIN_X05 = 0x40u, /**< Use gain := 0.5 */
ADC_GAIN_X1 = 0x00u, /**< Use gain := 1 */
ADC_GAIN_X15 = 0xC0u, /**< Use gain := 1.5 */
ADC_GAIN_X20 = 0x80u, /**< Use gain := 2 */
} qn908x_adc_gain_t;
/**
* @brief CPU specific ADC configuration
*
* ADC Channel, Vinn, Vref and gain configuration.
*
* This value should be set to the logic or between the following values:
* * bit 3: the optional flag @ref ADC_PGA_ENABLE,
* * bits 4-5: a @ref qn908x_adc_vinn_t value defining Vinn if needed,
* * bits 6-7: a @ref qn908x_adc_gain_t optional gain value,
* * bit 8: the optional flag @ref ADC_VREF_GAIN_X15,
* * bits 9-13: the selected @ref qn908x_adc_channel_t, and
* * bits 14-15: the @ref qn908x_adc_vref_t value defining Vref.
*
* The same channels with different settings can be configured as different ADC
* lines in the board, just using different adc_conf_t entries.
*/
typedef uint16_t adc_conf_t;
/** @} */
/**
* @brief CPU specific timer Counter/Timers (CTIMER) configuration
* @{

338
cpu/qn908x/periph/adc.c Normal file
View File

@ -0,0 +1,338 @@
/*
* Copyright (C) 2020 iosabi
*
* 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_qn908x
* @ingroup drivers_periph_adc
*
* @{
*
* @file
* @brief Low-level ADC driver implementation
*
* @author iosabi <iosabi@protonmail.com>
*
* This driver supports most of the ADC capabilities of the ADC block in this
* CPU, but there are some functions left out of this driver:
*
* * The software decimation is fixed to 32 samples. This could be configured
* per line instead with many more filtering options.
*
* * There's no amplification support (PGA) other than 1x. The main issue with
* supporting this configuration even at build time only is that it also
* requires to adjust the signal's common mode voltage level so that it
* matches roughly Vcc/2, leaving enough dynamic range in the ADC. This is
* maybe beyond the scope of this interface, but it could be configured from
* the board if needed. See "PGA output VCM" section in the user manual for
* details.
*
* @}
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include "bitarithm.h"
#include "cpu.h"
#include "mutex.h"
#include "xtimer.h"
#include "gpio_mux.h"
#include "periph/adc.h"
#include "vendor/drivers/fsl_clock.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#ifndef QN908X_ADC_CLOCK
#define QN908X_ADC_CLOCK ADC_CLOCK_500K
#endif
/* Value of the ADC_CTRL_CLKSEL field which is actually CLKSEL in the lower two
* bits (0 for 4 MHz source and 1 for 32 KHz source) and the divisor exponent
* in the bits 2 to 4 (0 for 128 divisor and 7 for 1 divisor). */
static const uint8_t adc_ctrl_clksel[] = {
28, /* 4 MHz*/
24, /* 2 MHz*/
20, /* 1 MHz*/
16, /* 500 KHz*/
12, /* 250 KHz*/
8, /* 125 KHz*/
4, /* 62.5 KHz*/
0, /* 31.25 KHz*/
1, /* 32 KHz or 32.768 KHz from the low speed source. */
};
/* ADC oversample clock cycle in microseconds (used for delay). */
static const uint8_t adc_clock_cycle_us[] = {
1, /* 4 MHz*/
1, /* 2 MHz*/
1, /* 1 MHz*/
2, /* 500 KHz*/
4, /* 250 KHz*/
8, /* 125 KHz*/
16, /* 62.5 KHz*/
32, /* 31.25 KHz*/
32, /* 32 KHz or 32.768 KHz from the low speed source. */
};
/* Mutex for the public interface. This guards against two threads calling the
* ADC at the same time. */
static mutex_t adc_lock = MUTEX_INIT;
/* Flag telling whether the ADC block was initialized. We only initialize the
* ADC block once, although some pin initialization is done per ADC line. */
static bool adc_init_done = false;
/* Mutex to lock the @ref adc_sample function waiting for the interrupt to
* retrieve the value. */
static mutex_t adc_sample_isr_lock = MUTEX_INIT_LOCKED;
/* Pointer to the variable that should hold the pending ADC result. This is
* used by the ISR to store the value. */
static volatile int32_t* adc_data_ptr = NULL;
/* Factory calibration actual voltage of the internal 1.2V bandgap source. */
static uint32_t adc_bandgap_calib_mv;
/**
* @brief Get the 1.2V bandgap reference voltage in mV.
*/
static uint32_t _adc_bandgap_calib_mv(void) {
uint32_t calib = *(uint32_t*)FSL_FEATURE_FLASH_ADDR_OF_BANDGAP_VOL;
DEBUG("[adc] bandgap calib = %" PRIu32 " mV\n", calib);
/* Check that the bandgap calibration value stored in the Flash Information
* Page (Section 7.3.1.1 in the User Manual) makes sense and use a
* reasonable default otherwise. */
if (calib < 1214 || calib > 1228) {
calib = 1222;
}
return calib;
}
/* Extract the channel number from the adc_conf_t. */
#define ADC_CONF_T_CHANNEL_NUM(conf) (((conf) >> 9u) & 0x1fu)
/* Mask to and against the adc_conf_t to get the channel enum value. */
#define ADC_CONF_T_CHANNEL_MASK (0x1fu << 9u)
int adc_init(adc_t line)
{
if (line >= ADC_NUMOF) {
return -1;
}
mutex_lock(&adc_lock);
if (!adc_init_done) {
DEBUG("[adc] ADC block init\n");
adc_bandgap_calib_mv = _adc_bandgap_calib_mv();
/* Power ON the ADC by clearing the disable (DIS) bits. */
SYSCON->PMU_CTRL1 &= ~(SYSCON_PMU_CTRL1_ADC_BUF_DIS_MASK | /* PGA */
SYSCON_PMU_CTRL1_ADC_BG_DIS_MASK |
SYSCON_PMU_CTRL1_ADC_DIS_MASK |
SYSCON_PMU_CTRL1_ADC_VCM_DIS_MASK | /* VINN */
SYSCON_PMU_CTRL1_ADC_VREF_DIS_MASK);
/* Need to wait 100 us before the ADC can be used for sampling. We could
* in theory avoid this wait since it is only needed before adc_sample()
* is called but it is short enough that it is safer to include it. */
xtimer_usleep(100u);
/* Enable the ADC clock so we can use the ADC. */
CLOCK_EnableClock(kCLOCK_Adc);
/* Reset the ADC. */
SYSCON->RST_SW_SET = SYSCON_RST_SW_SET_SET_ADC_RST_MASK;
SYSCON->RST_SW_CLR = SYSCON_RST_SW_CLR_CLR_ADC_RST_MASK;
/* Enabled the ADC module: single conversion mode, scan disabled, window
* mode disabled, 32-bit signed data format, software trigger.
*/
ADC->CTRL = ADC_CTRL_CLKSEL(adc_ctrl_clksel[QN908X_ADC_CLOCK]) |
ADC_CTRL_CONV_MODE(1) | ADC_CTRL_SCAN_EN(0) |
ADC_CTRL_CH_IDX_EN(0) |
ADC_CTRL_DATA_FORMAT(1) | /* DATA is sign extended. */
ADC_CTRL_SIG_INV_EN(0) |
#ifdef CONFIG_BOARD_HAS_ADC_PA06_CAP
ADC_CTRL_VREFO_EN(1) |
#endif /* CONFIG_BOARD_HAS_ADC_PA06_CAP */
ADC_CTRL_TRIGGER(35 /* software trigger */);
#ifdef CONFIG_BOARD_HAS_ADC_PA06_CAP
/* Use PA06 as the ADC_EX_CAP signal. */
gpio_init_mux(GPIO_PIN(PORT_A, 6), 1);
#endif /* CONFIG_BOARD_HAS_ADC_PA06_CAP */
/* Always use CFG[0] to sample all channels. */
ADC->CH_CFG = 0u;
/* Enable the PGA chopper. */
ADC->BG_BF |= ADC_BG_BF_PGA_CHOP_EN_MASK;
/* Enable interrupts. */
ADC->INTEN = ADC_INTEN_DAT_RDY_INTEN_MASK | ADC_INTEN_ADC_INTEN_MASK;
NVIC_EnableIRQ(ADC_IRQn);
adc_init_done = true;
}
const adc_conf_t conf = adc_config[line];
DEBUG("[adc] ADC line %u init: ch=%u\n", line, ADC_CONF_T_CHANNEL_NUM(conf));
uint32_t func1_pins = 0;
/* ADC pins are all function 1. */
switch (conf & ADC_CONF_T_CHANNEL_MASK) {
case ADC_CHANNEL_ADC0_ADC1:
func1_pins = (1u << 0) | (1u << 1);
break;
case ADC_CHANNEL_ADC2_ADC3:
func1_pins = (1u << 4) | (1u << 5);
break;
case ADC_CHANNEL_ADC4_ADC5:
func1_pins = (1u << 8) | (1u << 9);
break;
case ADC_CHANNEL_ADC6_ADC7:
func1_pins = (1u << 10) | (1u << 11);
break;
case ADC_CHANNEL_ADC0_VINN:
func1_pins = 1u << 0;
break;
case ADC_CHANNEL_ADC1_VINN:
func1_pins = 1u << 1;
break;
case ADC_CHANNEL_ADC2_VINN:
func1_pins = 1u << 4;
break;
case ADC_CHANNEL_ADC3_VINN:
func1_pins = 1u << 5;
break;
case ADC_CHANNEL_ADC4_VINN:
func1_pins = 1u << 8;
break;
case ADC_CHANNEL_ADC5_VINN:
func1_pins = 1u << 9;
break;
case ADC_CHANNEL_ADC6_VINN:
func1_pins = 1u << 10;
break;
case ADC_CHANNEL_ADC7_VINN:
func1_pins = 1u << 11;
break;
case ADC_CHANNEL_TEMP:
/* Enable the internal temperature source. */
ADC->BG_BF |= ADC_BG_BF_TEMP_EN_MASK;
break;
case ADC_CHANNEL_VCC4_VINN:
/* Enable the Vcc/4 source. */
SYSCON->ANA_EN |= SYSCON_ANA_EN_BAT_MON_EN_MASK;
break;
default:
break;
}
const uint32_t vref_sel = conf & ADC_CTRL_VREF_SEL_MASK;
if (ADC_CTRL_VREF_SEL(1) == vref_sel || ADC_CTRL_VREF_SEL(2) == vref_sel) {
/* ADC_VREFI (A7 pin) used as Vref input. */
gpio_init_mux(GPIO_PIN(PORT_A, 7), 1);
}
while (func1_pins) {
uint8_t pin;
func1_pins = bitarithm_test_and_clear(func1_pins, &pin);
gpio_init_mux(GPIO_PIN(PORT_A, pin), 1);
}
mutex_unlock(&adc_lock);
return 0;
}
int32_t adc_sample(adc_t line, adc_res_t res) {
if (line >= ADC_NUMOF) {
return -1;
}
mutex_lock(&adc_lock);
const adc_conf_t conf = adc_config[line];
/* Enable the ADC and set the Vref selection. */
ADC->CTRL = (ADC->CTRL & ~ADC_CTRL_VREF_SEL_MASK) |
ADC_CTRL_ENABLE_MASK | (conf & ADC_CTRL_VREF_SEL_MASK) ;
/* The ADC needs one ADC clock cycle before it can be triggered after we
* enabled it. */
/* Configure the channel parameters. We always use CFG[0]. */
ADC->CH_SEL = 1u << ADC_CONF_T_CHANNEL_NUM(conf);
ADC->CFG[0] =
ADC_CFG_PGA_GAIN(0) | /* PGA gain = x1 */
/* PGA_VINN, ADC_GAIN and VREF_GAIN directly from the config, flipping
* 0x40 to default ADC_GAIN to x1.0, flipping 0x30 to default Vinn to
* Vss and flipping 0x08 to default to PGA disabled (bypass enabled). */
((conf ^ 0x78) & (ADC_CFG_PGA_BP_MASK | ADC_CFG_PGA_VINN_MASK |
ADC_CFG_ADC_GAIN_MASK)) |
ADC_CFG_PGA_VCM_EN(0) | /* Vcm control disabled. */
/* TODO: Allow the board to configure the decimation. */
ADC_CFG_DOWN_SAMPLE_RATE(1) | /* down sample 32 */
ADC_CFG_DS_DATA_STABLE(7) |
ADC_CFG_SCAN_INTV(4); /* Switching ADC source every 32 clock cycles. */
/* Need to wait for one ADC cycle before it can be started. */
xtimer_usleep(adc_clock_cycle_us[QN908X_ADC_CLOCK]);
/* Configure the destination of the ADC value read from the interrupt. */
volatile int32_t adc_data = 0;
adc_data_ptr = &adc_data;
/* Triggers the sample event. When done the interrupt will fire and release
* the mutex. */
ADC->CTRL |= ADC_CTRL_SW_START_MASK;
/* Wait for the interrupt to return a value in adc_data. */
mutex_lock(&adc_sample_isr_lock);
mutex_unlock(&adc_lock);
int32_t ret = -1;
/* adc_data has 23-bit signed number, with the sign extended to an int32_t.
*/
if (res <= ADC_RES_MAX) {
ret = adc_data >> (22 - res);
} else if (res == ADC_RES_UV &&
(conf & ADC_CTRL_VREF_SEL_MASK) == ADC_VREF_1V2) {
/* Returning in uV is only supported when using Vref as the internal
* 1.2v. */
uint32_t vref = adc_bandgap_calib_mv;
if (conf & ADC_VREF_GAIN_X15) {
vref = vref + vref / 2;
}
/* adc_data is a signed 23 bit number and the Vref is at most 1.8v, so
* an unsigned 11 bit number. To make this fit in signed 32-bit
* arithmetic we need to drop some bits from the adc_data. Then we
* convert the result to uV dividing by (1 << 20) which is now the full
* scale of (adc_data >> 2) and multiplying by 1000. The last two
* operations can be done as a single division still in 32-bit integer
* space. */
ret = ((adc_data >> 2) * (int32_t)vref) / ((1 << 20) / 1000);
}
DEBUG("[adc] sample line %u: ch=%u res=%u conf=0x%.4" PRIx16
" adc=0x%.8" PRIx32 " ret=%" PRIi32 "\n",
line, ADC_CONF_T_CHANNEL_NUM(conf), (unsigned)res, conf, adc_data,
ret);
return ret;
}
void isr_adc(void)
{
if (ADC->INT & ADC_INT_DAT_RDY_INT_MASK) {
uint32_t data = ADC->DATA;
/* The DAT_RDY_INT bit clears automatically when reading the data. */
if (adc_data_ptr) {
*adc_data_ptr = data;
}
adc_data_ptr = NULL;
mutex_unlock(&adc_sample_isr_lock);
}
cortexm_isr_end();
}