mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #15847 from iosabi/qn908x_adc
cpu/qn908x: ADC support
This commit is contained in:
commit
bd837fe98b
@ -17,9 +17,10 @@ config BOARD_QN9080DK
|
||||
# Put defined MCU peripherals here (in alphabetical order)
|
||||
select BOARD_HAS_XTAL32K
|
||||
select BOARD_HAS_XTAL_32M
|
||||
select HAS_PERIPH_ADC
|
||||
select HAS_PERIPH_I2C
|
||||
select HAS_PERIPH_TIMER
|
||||
select HAS_PERIPH_UART
|
||||
select HAS_PERIPH_I2C
|
||||
select HAS_PERIPH_UART_MODECFG
|
||||
|
||||
source "$(RIOTBOARD)/common/qn908x/Kconfig"
|
||||
|
@ -2,6 +2,7 @@ CPU = qn908x
|
||||
CPU_MODEL = qn9080xhn
|
||||
|
||||
# Put defined MCU peripherals here (in alphabetical order)
|
||||
FEATURES_PROVIDED += periph_adc
|
||||
FEATURES_PROVIDED += periph_gpio periph_gpio_irq
|
||||
FEATURES_PROVIDED += periph_i2c
|
||||
FEATURES_PROVIDED += periph_timer
|
||||
|
@ -28,6 +28,29 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name ADC configuration
|
||||
*
|
||||
* Names "An" are as described in the "Analog In" header on the PCB. All "An"
|
||||
* inputs are configured referenced to 1.8V.
|
||||
* @{
|
||||
*/
|
||||
static const adc_conf_t adc_config[] = {
|
||||
ADC_CHANNEL_ADC7_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A0 */
|
||||
ADC_CHANNEL_ADC6_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A1 */
|
||||
ADC_CHANNEL_ADC4_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A2 */
|
||||
ADC_CHANNEL_ADC5_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A3 */
|
||||
ADC_CHANNEL_ADC1_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A4 */
|
||||
ADC_CHANNEL_ADC0_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A5 */
|
||||
ADC_CHANNEL_ADC6_ADC7 | ADC_VREF_GAIN_X15, /* A1-A0 */
|
||||
ADC_CHANNEL_ADC4_ADC5 | ADC_VREF_GAIN_X15, /* A2-A3 */
|
||||
ADC_CHANNEL_ADC0_ADC1 | ADC_VREF_GAIN_X15, /* A5-A4 */
|
||||
ADC_CHANNEL_TEMP, /* temperature */
|
||||
ADC_CHANNEL_VCC4_VINN | ADC_VINN_AVSS, /* Vcc/4 */
|
||||
};
|
||||
#define ADC_NUMOF ARRAY_SIZE(adc_config)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name I2C configuration
|
||||
* @{
|
||||
|
@ -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
|
||||
|
@ -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
338
cpu/qn908x/periph/adc.c
Normal 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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user