ADC Battery Voltage Sample
Introduction
The ADC (Analog to Digital Converter) battery voltage sample sets up the ADC on the Icarus, reads the battery value from pin A0
, converts it into battery voltage and prints it. This sample is compatible with the following boards:
In order to use this sample with the Icarus SoM DK, some additional steps must be taken. See the Icarus SoM DK datasheet for more information.
[NCS v2.2.x] Download sample from Icarus - ADC Battery Voltage Sample (NCS v2.2.x)
[NCS v2.1.x] Download sample from Icarus - ADC Battery Voltage Sample (NCS v2.1.x)
(legacy) Download sample from Icarus - ADC Battery Voltage Sample (legacy)
Project configuration
The initialization of the application is done in the prj.conf
file. By enabling certain options in this file, Zephyr will include the required libraries for the application. For this sample the following options are set:
CONFIG_ADC=y
The option CONFIG_ADC
enables the use of the ADC library. This is needed since this sample is reading from an analog pin.
Code explanation
First a reference to the ADC needs to be obtained. The converter is defined in the device tree of the board. The device tree is expressed as a set of files which are included in the nRF Connect SDK. In these files, the hardware of the Icarus boards is described in terms of hardware addresses and pin numbers.
In main.c
, this reference is obtained in the init_adc
function:
static const struct device *adc_dev;
adc_dev = DEVICE_DT_GET(ADC_NODE);
with the ADC_NODE
defined as:
#define ADC_NODE DT_NODELABEL(adc)
In the device tree, the ADC is declared as a node: adc
. The converter is needed in order to read analog signals and convert them into a digital signal. This digital signal can then be used to calculate the current battery voltage. The Icarus boards have a reserved pin for battery voltage measurement: Analog input pin 0.
Next the ADC channel needs to be set up. This is done using the adc_channel_setup
function provided by the ADC library. This function requires the reference to the ADC which was setup already, as well as a channel configuration structure. The channel is set up as follows:
adc_channel_setup(adc_dev, &m_1st_channel_cfg);
with m_1st_channel_cfg
being a structure of type struct adc_channel_cfg
:
static const struct adc_channel_cfg m_1st_channel_cfg = {
.gain = ADC_GAIN,
.reference = ADC_REFERENCE,
.acquisition_time = ADC_ACQUISITION_TIME,
.channel_id = ADC_1ST_CHANNEL_ID,
.input_positive = ADC_1ST_CHANNEL_INPUT,
};
In this sample, the ADC_REFERENCE
is set to ADC_REF_INTERNAL
which is 0.6V, ADC_GAIN
is set up as 1/6 so the resulting input voltage range is: 0.6/(1/6) = 3.6V
and for the Icarus boards the value is read from ADC_1ST_CHANNEL_INPUT
which is actually SAADC_CH_PSELP_PSELP_AnalogInput0
(Analog input pin 0)
With all the initialization completed, reading a value from the ADC is done with the call:
adc_read(adc_dev, &sequence)
with sequence
being a structure of type struct adc_sequence
:
const struct adc_sequence sequence = {
.channels = BIT(ADC_1ST_CHANNEL_ID),
.buffer = m_sample_buffer,
.buffer_size = sizeof(m_sample_buffer),
.resolution = ADC_RESOLUTION,
};
This structure describes the buffer where the values are stored and sets up the ADC read resolution ADC_RESOLUTION
which is set to 10 bit in this sample (values from 0 to 1023).
After this call the buffer contains a sample value that can be used to compute the battery voltage value. The calculation, for the Icarus boards goes as follows:
sample_value * (INPUT_VOLT_RANGE / VALUE_RANGE_10_BIT) * ((BATVOLT_R1 + BATVOLT_R2) / BATVOLT_R2));
with:
INPUT_VOLTAGE_RANGE
= 3.6V (see previous paragraphs)
VALUE_RANGE_10_BIT
= 1.023 (max value for a 10 bit output divided by 1000 as the voltage is expressed in V instead of mV)
BATVOLT_R1
= 4.7 MOhm (see also Battery Voltage Measurement in Peripherals)
BATVOLT_R2
= 10.0 MOhm (see also Battery Voltage Measurement in Peripherals)