This example builds on Blinky with SWO and demonstrates how to add an analog input and read back measurements taken through it using the Nordic SuccessiveApproximation Analog to Digital Converter (SAADC). Download the example BlinkyADC.zip which contains a readme describing each file in the package.
Hooking up the Circuit
The circuit for this example is the same as for Blinky with SWO except it adds a potentiometer.

Code
Source code for configuring and reading an analog value.
#include <nrf.h>
#include "nrf_delay.h"
#include "nrf_drv_saadc.h"
#include "sdk_errors.h"
#include "main.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
//
// Description of how the SAADC operates can be found at:
//
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fsaadc.html&cp=2_1_0_36_2&anchor=saadc_digital_output
//
static bool sampleInProgress = false;
static bool calibrationInProgress = false;
static nrf_saadc_value_t saadcValue;
static uint32_t sampleNumber = 0;
static uint32_t latestSampleValue = 0;
//
// Gets the last sample value as the raw digitised value
//
uint32_t GetLastADCSampleValueRaw(uint32_t* pSampleNumber)
{
if(pSampleNumber)
{
*pSampleNumber = sampleNumber;
}
return latestSampleValue;
}
//
// Gets the last sample value converted to a voltage
//
// Uses the formula:
//
// vADC = (ADCValue * vRef) / (resolution * gain)
//
// When the channel was initialised (ADCInit()):
//
// vRef was set to VDD / 4. For 3.3V this is 0.825
// gain was set to 1/6
// resolution was 12 bit (4096)
//
float GetLastADCSampleValueVoltage(uint32_t* pSampleNumber)
{
if(pSampleNumber)
{
*pSampleNumber = sampleNumber;
}
const float vRef = 0.825;
const float resolution = 4096;
const float gain = 0.16667;
float vADC = ((float)latestSampleValue * vRef) / (resolution * gain);
return vADC;
}
//
// Callback invoked when an ADC operation completes.
//
void ADCConversionCompleteCallback(nrf_drv_saadc_evt_t const * pEvent)
{
if (pEvent->type == NRF_DRV_SAADC_EVT_DONE)
{
//
// Sample complete. Have seen the sample value go -ve?? Clamp to 0
//
++sampleNumber;
if(saadcValue < 0)
{
saadcValue = 0;
}
latestSampleValue = (uint32_t)saadcValue;
sampleInProgress = false;
}
else if (pEvent->type == NRF_DRV_SAADC_EVT_CALIBRATEDONE)
{
//
// Calibration complete
//
calibrationInProgress = false;
}
}
//
// Start a calibration. According to the documentation calibrations
// should be performed at least once and whenever there is a significant
// change in temperature.
//
ret_code_t StartADCCalibration()
{
sampleInProgress = false;
calibrationInProgress = true;
return nrf_drv_saadc_calibrate_offset();
}
//
// Requests a single (batch) of samples are taken to produce a single
// reading from from the ADC
//
ret_code_t StartADCSample()
{
//
// Do not attempt to start a measurement if either a calibration or
// another sample is in progress.
//
ret_code_t errCode = NRF_ERROR_BUSY;
if(!sampleInProgress && !calibrationInProgress)
{
//
// Submit a buffer to be filled with the digitised input.
// Remember, the ADC has been configured to oversample to
// mimimise the effects of noise so the digitised input value
// will be an average of all of the readings made for this sample.
// nrf_drv_saadc_buffer_convert also instructs the SDK to perform
// this asynchronously and not to block when nrf_drv_saadc_sample
// is called to trigger the sample.
//
errCode = nrf_drv_saadc_buffer_convert(&saadcValue, 1);
if(errCode == NRF_SUCCESS)
{
//
// Trigger the sample. Once the value has been read the
// callback will fire to signal that the result is now
// available.
//
errCode = nrf_drv_saadc_sample();
if(errCode == NRF_SUCCESS)
{
sampleInProgress = true;
}
else
{
SWOPrintString("Error: could not start sampling with code %u",errCode);
}
}
else
{
SWOPrintString("Error: could not submit buffer to saadc with code %u",errCode);
}
}
else
{
SWOPrintString("Sample or calibration in progress");
}
return errCode;
}
//
// Prepares the microcontroller to read analog inputs
//
ret_code_t InitADC()
{
//
// Initilaise the ADC peripheral pf the SoC
// https://devzone.nordicsemi.com/f/nordic-q-a/14583/nrf52832-saadc-sampling
//
// Set up the configuration of the SAADC
// Enable low power mode, set resolution to 12 bits,
// set oversampling to 32x to reduce noise. Will need to also
// set burst mode so that all the samples are taken with a
// single triggered
//
SWOPrintString("Initialising saadc...");
nrf_drv_saadc_config_t ADCConfig;
ADCConfig.low_power_mode = true;
ADCConfig.resolution = NRF_SAADC_RESOLUTION_12BIT;
ADCConfig.oversample = NRF_SAADC_OVERSAMPLE_32X;
ADCConfig.interrupt_priority = SAADC_CONFIG_IRQ_PRIORITY;
//
// Apply the configuration and register a
// callback function to handle SAADC events
//
ret_code_t errCode = nrf_drv_saadc_init(&ADCConfig, ADCConversionCompleteCallback);
if(errCode == NRF_SUCCESS)
{
//
// Configure and initialise each of the ADC channels.
// In this example only one channel is used (channel 0) which is
// assigned to pin AIN0. Seeing as the SAADC has been configured to
// oversample, configure the channel to operate in burst mode such
// that only a single trigger is required to take all the samples
// necessary and produce an average.
//
//
// Set the reference voltage to be VDD / 4
// Gain depends on input VDD. For 3.3V a gain of 1/6 will perform
// well over the whole range.
//
int adcChannelNumber = 0;
int adcPinNumber = NRF_SAADC_INPUT_AIN0;
nrf_saadc_channel_config_t adcChannelCfg = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(adcPinNumber);
adcChannelCfg.reference = NRF_SAADC_REFERENCE_VDD4;
adcChannelCfg.gain = NRF_SAADC_GAIN1_6;
adcChannelCfg.burst = NRF_SAADC_BURST_ENABLED;
errCode = nrf_drv_saadc_channel_init(adcChannelNumber, &adcChannelCfg);
if(errCode != NRF_SUCCESS)
{
SWOPrintString("Error: could not initialise saadc channel %d with code %u",adcChannelNumber,errCode);
}
}
else
{
SWOPrintString("Error: could not initialise saadc peripheral with code %u",errCode);
}
return errCode;
}
At the start of main() add the lines below. This will configure and initialise the ADC and perform a calibration of the SAADC:
ADCInit();
StartADCCalibration();
Now whenever you want to trigger an analog reading you can do so with:
StartADCSample();
In the earlier code snapshot where the ADC was configured, sampling was set to be asynchronous (non-blocking) therefore StartADCSample will return immediately. When the sample is ready, the callback ADCConversionCompleteCallback will fire and the value of the input is available. The value can be obtained as either a digital represention or as a voltage on the pin.
uint32_t digitalValue= GetLastADCSampleValueRaw(NULL);
float voltageValue = GetLastADCSampleValueVoltage(NULL);
main.cpp
Added calls to ADCInit, StartADCCalibration, StartADCSample, GetLastADCSampleValueRaw and GetLastADCSampleValueVoltage. Main now looks like this:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "nrf_delay.h"
#include "boards.h"
#include "main.h"
//
// Main entry point
//
int main()
{
//
// Initialise the board.
//
nrf_gpio_cfg_output(LED_A);
//
// Enable SWO output
//
SWOInit();
//
// Enable ADC and immediately start a calibration
//
InitADC();
StartADCCalibration();
//
// Loop forever toggling the led ON/OFF and performing a sample
// of the analog input
//
uint32_t lastSampleNumber = 0;
bool setOn = true;
while (true)
{
if(setOn)
{
nrf_gpio_pin_set(LED_A);
}
else
{
nrf_gpio_pin_clear(LED_A);
}
setOn = setOn ? false : true;
//
// Before going to sleep, start a sample
//
StartADCSample();
nrf_delay_ms(500);
//
// See if since StartADCSample a new sample value
// has become available
//
uint32_t currentSampleNumber;
uint32_t currentSampleValue = GetLastADCSampleValueRaw(¤tSampleNumber);
if(currentSampleNumber > lastSampleNumber)
{
//
// Sample number has changed therefore there
// must be a new sample available
//
float vSample = GetLastADCSampleValueVoltage(NULL);
SWOPrintString("Sample[%4u]: %4u. %fV",currentSampleNumber,currentSampleValue,vSample);
lastSampleNumber = currentSampleNumber;
}
}
}
Building and Flashing the Program
Any toolchain/programmer can be used for these steps. All the examples here are built using make and gcc. The binary is flashed using the Raspberry Pi networked programmer. See here for how to set up make, gcc and the Raspberry Pi networked programmer.
Building
From the terminal type:
C:\Projects\BlinkyADC> "c:\Program Files (x86)\GnuWin32\bin\make.exe" -f makefile CONFIG=Debug all
Flashing the Binary to the Microcontroller
Hooking up the Programmer to the Microcontroller
See here for how to connect the programmer to the microcontroller.
Flashing
From a terminal type:
C:\Projects\BlinkyADC> "C:\Program Files\NoSMD\SWorD\bin\SWorDProgrammer.exe" -program:bin\debug\blinkyadc.elf -probe:rpi4programmer -pagesize:4096
Attempting to connect to rpi4programmer on 192.168.0.26::33332...
Connected to rpi4programmer on 192.168.0.26::33332...
Progress 100%
Completed with success
C:\Projects\BlinkyADC>
As for Blinky and BlinkySWO you should now see the LED blinking and there should be printf like output from the SWO pin.
Adjusting the potentiometer should result in different values being displayed via SWO.
Initialising saadc...
Sample or calibration in progress
Sample[ 1]: 141. 0.170395V
Sample[ 2]: 154. 0.186105V
Sample[ 3]: 140. 0.169186V
Sample[ 4]: 155. 0.187313V
Sample[ 5]: 141. 0.170395V
Sample[ 6]: 153. 0.184896V
Sample[ 7]: 141. 0.170395V
Sample[ 8]: 154. 0.186105V
Sample[ 9]: 139. 0.167978V
Sample[ 10]: 145. 0.175228V
Sample[ 11]: 140. 0.169186V
Sample[ 12]: 156. 0.188522V
Sample[ 13]: 321. 0.387919V
Sample[ 14]: 364. 0.439884V
Sample[ 15]: 357. 0.431424V
Sample[ 16]: 363. 0.438675V
Sample[ 17]: 357. 0.431424V
Sample[ 18]: 520. 0.628405V
Sample[ 19]: 774. 0.935357V
Sample[ 20]: 903. 1.091250V
Sample[ 21]: 1147. 1.386117V
Sample[ 22]: 1357. 1.639897V
Sample[ 23]: 1538. 1.858630V
Sample[ 24]: 1553. 1.876757V
Sample[ 25]: 2121. 2.563169V
Sample[ 26]: 2376. 2.871329V
Sample[ 27]: 2543. 3.073144V
Sample[ 28]: 2723. 3.290669V
Sample[ 29]: 2719. 3.285835V
Sample[ 30]: 2722. 3.289461V
Sample[ 31]: 2718. 3.284626V
Sample[ 32]: 2723. 3.290669V
Sample[ 33]: 2720. 3.287044V
Sample[ 34]: 2724. 3.291878V
Sample[ 35]: 2645. 3.196408V
Sample[ 36]: 2199. 2.657430V
Sample[ 37]: 2080. 2.513622V
Sample[ 38]: 1096. 1.324485V
Sample[ 39]: 951. 1.149257V
Sample[ 40]: 60. 0.072508V
0 Comments