I use one channel of the SAADC on nRF52832 to measure the power supply voltage. Code for configuration & operation of SAADC attached:
#include "adc.h"
#include "main_keyboard.h"
#include "nrf_drv_saadc.h"
#include "nrf_log.h"
#include "app_scheduler.h"
/**
* Adapted from: https://github.com/NordicPlayground/nRF52-ADC-examples/tree/master/saadc_low_power
*/
#define SAADC_SAMPLES_IN_BUFFER 1 // Number of SAADC samples in RAM before returning a SAADC event. For low power SAADC set this constant to 1. Otherwise the EasyDMA will be enabled for an extended
// time which consumes high current.
#define SAADC_OVERSAMPLE NRF_SAADC_OVERSAMPLE_4X // Oversampling setting for the SAADC. Setting oversample to 4x This will make the SAADC output a single averaged value when the SAMPLE task is triggered 4 times.
// Enable BURST mode to make the SAADC sample 4 times when triggering SAMPLE task once.
#define SAADC_BURST_MODE 1 // Set to 1 to enable BURST mode, otherwise set to 0.
static nrf_saadc_value_t buffer[SAADC_SAMPLES_IN_BUFFER];
static void saadc_isr_callback(nrf_drv_saadc_evt_t const *p_event);
static void saadc_evt_callback(void *p_event_data, uint16_t event_data_size);
void ADC_Init()
{
nrf_drv_saadc_config_t saadc_config;
nrf_saadc_channel_config_t channel_config;
// Configure SAADC
saadc_config.low_power_mode = true; // Enable low power mode.
saadc_config.resolution = NRF_SAADC_RESOLUTION_12BIT; // Set SAADC resolution to 12-bit. This will make the SAADC output values from 0 (when input voltage is 0V) to 2^12=4096 (when input voltage is 3.6V for channel gain setting of 1/6).
saadc_config.oversample = SAADC_OVERSAMPLE; // Set oversample to 4x. This will make the SAADC output a single averaged value when the SAMPLE task is triggered 4 times.
saadc_config.interrupt_priority = APP_IRQ_PRIORITY_LOW; // Set SAADC interrupt to low priority.
// Initialize SAADC
APP_ERROR_CHECK(nrf_drv_saadc_init(&saadc_config, saadc_isr_callback)); // Initialize the SAADC with configuration and callback function. The application must then implement the saadc_callback function, which will be called when SAADC
// interrupt is triggered
// Configure SAADC channel
channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL; // Set internal reference of fixed 0.6 volts
channel_config.gain = NRF_SAADC_GAIN1_6; // Set input gain to 1/6. The maximum SAADC input voltage is then 0.6V/(1/6)=3.6V. The single ended input range is then 0V-3.6V
channel_config.acq_time = NRF_SAADC_ACQTIME_10US; // Set acquisition time. Set low acquisition time to enable maximum sampling frequency of 200kHz. Set high acquisition time to allow maximum source resistance up to 800 kohm, see
// the SAADC electrical specification in the PS.
channel_config.mode = NRF_SAADC_MODE_SINGLE_ENDED; // Set SAADC as single ended. This means it will only have the positive pin as input, and the negative pin is shorted to ground (0V) internally.
if (SAADC_BURST_MODE)
{
channel_config.burst = NRF_SAADC_BURST_ENABLED; // Configure burst mode for channel 0. Burst is useful together with oversampling. When triggering the SAMPLE task in burst mode, the SAADC will sample "Oversample" number of
// times as fast as it can and then output a single averaged value to the RAM buffer. If burst mode is not enabled, the SAMPLE task needs to be triggered "Oversample" number of
// times to output a single averaged value to the RAM buffer.
}
channel_config.pin_p = NRF_SAADC_INPUT_AIN0; // Select the input pin for the channel. AIN0 pin maps to physical pin P0.02.
channel_config.pin_n = NRF_SAADC_INPUT_DISABLED; // Since the SAADC is single ended, the negative pin is disabled. The negative pin is shorted to ground internally.
channel_config.resistor_p = NRF_SAADC_RESISTOR_DISABLED; // Disable pullup resistor on the input pin
channel_config.resistor_n = NRF_SAADC_RESISTOR_DISABLED; // Disable pulldown resistor on the input pin
// Initialize SAADC channel
APP_ERROR_CHECK(nrf_drv_saadc_channel_init(0, &channel_config)); // Initialize SAADC channel 0 with the channel configuration
// Note: We don't call nrf_drv_saadc_buffer_convert() here, we'll first do the calibration and call this at the end of it.
}
/**
* Starts the calibration, measurement will follow afterwards.
*/
void ADC_Start()
{
nrf_drv_saadc_abort(); // Abort all ongoing conversions. Calibration cannot be run if SAADC is busy
NRF_LOG_INFO("SAADC: Calibration starting");
while (nrf_drv_saadc_calibrate_offset() != NRF_SUCCESS) // Trigger calibration task
{
}
}
static void saadc_isr_callback(nrf_drv_saadc_evt_t const *p_event)
{
if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
{
NRF_LOG_INFO("SAADC: Measurement complete");
uint32_t vin = p_event->data.done.p_buffer[0]; // vin = Digits * Reference / Gain / 2^resolution
vin = vin * 3600; // Vref=0.6V; gain = 1/6 => full scale = 3.6V = 3600mV
uint16_t voltage_mV = vin >> 12;
app_sched_event_put(&voltage_mV, sizeof(voltage_mV), saadc_evt_callback);
}
else if (p_event->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE)
{
NRF_LOG_INFO("SAADC: Calibration complete");
APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(buffer, SAADC_SAMPLES_IN_BUFFER)); // Set buffer so the SAADC can write to it.
app_sched_event_put(NULL, 0, saadc_evt_callback);
}
}
static void saadc_evt_callback(void *p_event_data, uint16_t event_data_size)
{
if (event_data_size == 0) // End of calibration
{
NRF_LOG_INFO("SAADC: Measurement starting");
nrf_drv_saadc_sample(); // Trigger the SAADC SAMPLE task
}
else // End of measurement
{
KBD_Evt(EVT_VOLTAGE_MEASUREMENT_DONE, p_event_data, event_data_size);
}
}
Short description:
- Configure the SAADC once by calling ADC_Init().
- Each time you need a measurement, call ADC_Start(). This will trigger the calibration. Once it completes, a measurement will be triggered.
This works well when I build & flash in debug mode, i.e. I read the true power supply voltage value from voltage_mV. When I switch to release mode, I start getting voltage_mV values that are ~3 times larger. How is this possible?
