ADC Battery Voltage Sample

Last updated on: January 10, 2023

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:

  • Icarus IoT board
  • Icarus SoM DK
NOTE: 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)