How to create periodic timer event and use iADC for EFR32 series 2 to do battery voltage monitor in GSDK 4.x and EmberZnet 7.x
1. Add the following code in app.c
#define _IADC_
#ifdef _IADC_
#include "em_device.h"
#include "em_chip.h"
#include "em_cmu.h"
#include "em_iadc.h"
#include "em_gpio.h"
#endif //#ifdef _IADC_
#ifdef _IADC_
#define CLK_SRC_ADC_FREQ 20000000 // CLK_SRC_ADC
#define CLK_ADC_FREQ 10000000 // CLK_ADC - 10MHz max in normal mode
/*
* Specify the IADC input using the IADC_PosInput_t typedef. This
* must be paired with a corresponding macro definition that allocates
* the corresponding ABUS to the IADC. These are...
*
* GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AEVEN0_ADC0
* GPIO->ABUSALLOC |= GPIO_ABUSALLOC_AODD0_ADC0
* GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BEVEN0_ADC0
* GPIO->BBUSALLOC |= GPIO_BBUSALLOC_BODD0_ADC0
* GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDEVEN0_ADC0
* GPIO->CDBUSALLOC |= GPIO_CDBUSALLOC_CDODD0_ADC0
*
* ...for port A, port B, and port C/D pins, even and odd, respectively.
*/
#define IADC_INPUT_0_PORT_PIN iadcPosInputAvdd;
#define IADC_INPUT_1_PORT_PIN iadcNegInputGnd;
#define IADC_INPUT_0_BUS BBUSALLOC
#define IADC_INPUT_0_BUSALLOC GPIO_BBUSALLOC_BEVEN0_ADC0
#define IADC_INPUT_1_BUS BBUSALLOC
#define IADC_INPUT_1_BUSALLOC GPIO_BBUSALLOC_BODD0_ADC0
/*******************************************************************************
*************************** GLOBAL VARIABLES *******************************
******************************************************************************/
static volatile int32_t sample;
static volatile double singleResult; // Volts
static sl_sleeptimer_timer_handle_t bat_volt_read_periodic_timer;
static void bat_volt_read_periodic_timer_callback(sl_sleeptimer_timer_handle_t *handle, void *data)
{
(void)handle;
(void)data;
// Start IADC conversion
IADC_command(IADC0, iadcCmdStartSingle);
// Wait for conversion to be complete
while((IADC0->STATUS & (_IADC_STATUS_CONVERTING_MASK
| _IADC_STATUS_SINGLEFIFODV_MASK)) != IADC_STATUS_SINGLEFIFODV); //while combined status bits 8 & 6 don't equal 1 and 0 respectively
// Get ADC result
sample = IADC_pullSingleFifoResult(IADC0).data;
// Calculate input voltage:
// For differential inputs, the resultant range is from -Vref to +Vref, i.e.,
// for Vref = VBGR = 2.42V, and with analog gain = 0.5, 12 bits represents
// 4.84V full scale IADC range.
singleResult = (sample * 4.84) / 0xFFF;
}
/**************************************************************************//**
* @brief Initialize IADC function
*****************************************************************************/
void initIADC (void)
{
// Declare init structs
IADC_Init_t init = IADC_INIT_DEFAULT;
IADC_AllConfigs_t initAllConfigs = IADC_ALLCONFIGS_DEFAULT;
IADC_InitSingle_t initSingle = IADC_INITSINGLE_DEFAULT;
IADC_SingleInput_t initSingleInput = IADC_SINGLEINPUT_DEFAULT;
// Enable IADC0 and GPIO clock branches
/* Note: For EFR32xG21 radio devices, library function calls to
* CMU_ClockEnable() have no effect as oscillators are automatically turned
* on/off based on demand from the peripherals; CMU_ClockEnable() is a dummy
* function for EFR32xG21 for library consistency/compatibility.
*/
CMU_ClockEnable(cmuClock_IADC0, true);
CMU_ClockEnable(cmuClock_GPIO, true);
// Reset IADC to reset configuration in case it has been modified by
// other code
IADC_reset(IADC0);
// Select clock for IADC
CMU_ClockSelectSet(cmuClock_IADCCLK, cmuSelect_FSRCO); // FSRCO - 20MHz
// Modify init structs and initialize
init.warmup = iadcWarmupKeepWarm;
// Set the HFSCLK prescale value here
init.srcClkPrescale = IADC_calcSrcClkPrescale(IADC0, CLK_SRC_ADC_FREQ, 0);
// Configuration 0 is used by both scan and single conversions by default
// Use internal bandgap (supply voltage in mV) as reference
initAllConfigs.configs[0].reference = iadcCfgReferenceInt1V2;
initAllConfigs.configs[0].vRef = 1210;
initAllConfigs.configs[0].analogGain = iadcCfgAnalogGain1x;
// Divides CLK_SRC_ADC to set the CLK_ADC frequency
initAllConfigs.configs[0].adcClkPrescale = IADC_calcAdcClkPrescale(IADC0,
CLK_ADC_FREQ,
0,
iadcCfgModeNormal,
init.srcClkPrescale);
// Assign pins to positive and negative inputs in differential mode
initSingleInput.posInput = IADC_INPUT_0_PORT_PIN;
initSingleInput.negInput = IADC_INPUT_1_PORT_PIN;
// Initialize the IADC
IADC_init(IADC0, &init, &initAllConfigs);
// Initialize the Single conversion inputs
IADC_initSingle(IADC0, &initSingle, &initSingleInput);
// Allocate the analog bus for ADC0 inputs
GPIO->IADC_INPUT_0_BUS |= IADC_INPUT_0_BUSALLOC;
GPIO->IADC_INPUT_1_BUS |= IADC_INPUT_1_BUSALLOC;
}
#endif//#ifdef _IADC_
2. Add the following codes in emberAfMainInitCallback
#ifdef _IADC_
initIADC();
sl_sleeptimer_start_periodic_timer(&bat_volt_read_periodic_timer, 1000, bat_volt_read_periodic_timer_callback, NULL, 0, 0);
#endif //#ifdef _IADC_
3. Battery voltage input to AVDD would be read to singleResult in bat_volt_read_periodic_timer_callback every second.
Hi, nice code!!
ReplyDeleteI will need to read the adc only 1 time a day. only call when needed?
How can I turn off the ADC after using it so that it is not consuming background current because it is enabled?
Thanks!