Simple one shot ADC example

Hi,

I'm trying to find simple ADC code for one shot modus as described in the documentation:
https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fsaadc.html

The question has been asked before (without any concrete answer):
https://devzone.nordicsemi.com/f/nordic-q-a/46716/simple-adc-code/184681#184681

Another here (but I don't understand/cannot follow the thread):
https://devzone.nordicsemi.com/f/nordic-q-a/7188/simple-thermistor-adc-reading

The SDK provides an example with SAADC, which I got to work fine.
The disadvantage is that this example is pretty complex and uses timers (which conflicts with other parts of my code which also need timers).
Also I'm not able to integrate this SAADC example into my project, I have the same problem as here:
https://devzone.nordicsemi.com/f/nordic-q-a/33850/nrf_section_iter-problem-with-sdk-15-and-armgcc/130057#130057

I do not understand "linker script file"?

But I also don't understand why the examples are so complex? Why is it extremely difficult to combine examples?

Simple Arduino example how to perform an analog read:

int analogPin = A3; // potentiometer wiper (middle terminal) connected to analog pin 3
                    // outside leads to ground and +5V
int val = 0;  // variable to store the value read

void setup() {
  Serial.begin(9600);           //  setup serial
}

void loop() {
  val = analogRead(analogPin);  // read the input pin
  Serial.println(val);          // debug value
}

I found this thread "Simple ADC read":
https://devzone.nordicsemi.com/f/nordic-q-a/19934/simple-analog-read-on-nrf52/77586#77586

And they point to an example on github:
github.com/.../main.c

The example doesn't give any output, so I updated the code a bit:

#include <stdio.h>
#include <nrf.h>
#include "boards.h"
#include "nrf_delay.h"
#include "nrf_drv_saadc.h"

#define VERSION_ADC      ((0 << 5) | (31 & 0x1F))    // P0.31
#define VERSION_EN       ((0 << 5) | (26 & 0x1F))    // P0.26

void version_enable_init() {
    nrf_gpio_cfg_output(VERSION_EN);  
    nrf_gpio_pin_write(VERSION_EN,1);
}

int main(void)
{
  volatile int16_t result = 0;
  volatile float precise_result = 0;
  version_enable_init();
  nrf_delay_ms(100);
  printf("starting\n");

  // Start HFCLK from crystal oscillator, this will give the SAADC higher accuracy
  NRF_CLOCK->TASKS_HFCLKSTART = 1;
  while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
  NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;

  // Configure SAADC singled-ended channel, Internal reference (0.6V) and 1/6 gain.
  NRF_SAADC->CH[0].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_6    << SAADC_CH_CONFIG_GAIN_Pos) |
                            (SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos) |
                            (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                            (SAADC_CH_CONFIG_RESN_Bypass     << SAADC_CH_CONFIG_RESN_Pos) |
                            (SAADC_CH_CONFIG_RESP_Bypass     << SAADC_CH_CONFIG_RESP_Pos) |
                            (SAADC_CH_CONFIG_TACQ_3us        << SAADC_CH_CONFIG_TACQ_Pos);

  // Configure the SAADC channel with VDD as positive input, no negative input(single ended).
  //NRF_SAADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_VDD << SAADC_CH_PSELP_PSELP_Pos;
  //NRF_SAADC->CH[0].PSELP = VERSION_ADC;
  NRF_SAADC->CH[0].PSELP = NRF_SAADC_INPUT_AIN7;
  NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;

  // Configure the SAADC resolution.
  NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_14bit << SAADC_RESOLUTION_VAL_Pos;

  // Configure result to be put in RAM at the location of "result" variable.
  NRF_SAADC->RESULT.MAXCNT = 1;
  NRF_SAADC->RESULT.PTR = (uint32_t)&result;

  // No automatic sampling, will trigger with TASKS_SAMPLE.
  NRF_SAADC->SAMPLERATE = SAADC_SAMPLERATE_MODE_Task << SAADC_SAMPLERATE_MODE_Pos;

  // Enable SAADC (would capture analog pins if they were used in CH[0].PSELP)
  NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;

  // Calibrate the SAADC (only needs to be done once in a while)
  NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
  while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0);
  NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
  while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy <<SAADC_STATUS_STATUS_Pos));

  // Start the SAADC and wait for the started event.
  NRF_SAADC->TASKS_START = 1;
  while (NRF_SAADC->EVENTS_STARTED == 0);
  NRF_SAADC->EVENTS_STARTED = 0;

  // Do a SAADC sample, will put the result in the configured RAM buffer.
  NRF_SAADC->TASKS_SAMPLE = 1;
  while (NRF_SAADC->EVENTS_END == 0);
  NRF_SAADC->EVENTS_END = 0;

  // Convert the result to voltage
  // Result = [V(p) - V(n)] * GAIN/REFERENCE * 2^(RESOLUTION)
  // Result = (VDD - 0) * ((1/6) / 0.6) * 2^14
  // VDD = Result / 4551.1
  precise_result = (float)result / 4551.1f;
  printf("result: %0.2f\n",precise_result);
  precise_result; // to get rid of set but not used warning

  // Stop the SAADC, since it's not used anymore.
  NRF_SAADC->TASKS_STOP = 1;
  while (NRF_SAADC->EVENTS_STOPPED == 0);
  NRF_SAADC->EVENTS_STOPPED = 0;


  while (1)
  {
    __WFE();
  }
}


But the result is always 0 (or 3 when using VDD). Did I set the wrong ADC channel? I'm sure I need NRF_SAADC_INPUT_AIN7?

I'm sure it is possible to do a ADC read without timers, but how?

  • Hello,

    I agree that this example is a bit complex for showing some simple functionality.

    But it is actually quite simple to change this from using PPI to trigger it manually. Please check out the attached main.c file, which is a modification from the saadc examples main.c file:

    /**
     * Copyright (c) 2014 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     * @defgroup nrf_adc_example main.c
     * @{
     * @ingroup nrf_adc_example
     * @brief ADC Example Application main file.
     *
     * This file contains the source code for a sample application using ADC.
     *
     * @image html example_board_setup_a.jpg "Use board setup A for this example."
     */
    
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include "nrf.h"
    #include "nrf_drv_saadc.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_timer.h"
    #include "boards.h"
    #include "app_error.h"
    #include "nrf_delay.h"
    #include "app_util_platform.h"
    #include "nrf_pwr_mgmt.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define SAMPLES_IN_BUFFER 5
    volatile uint8_t state = 1;
    
    static const nrf_drv_timer_t m_timer = NRF_DRV_TIMER_INSTANCE(0);
    static nrf_saadc_value_t     m_buffer_pool[2][SAMPLES_IN_BUFFER];
    static nrf_ppi_channel_t     m_ppi_channel;
    static uint32_t              m_adc_evt_counter;
    
    
    void timer_handler(nrf_timer_event_t event_type, void * p_context)
    {
        NRF_LOG_INFO("Timer interrupt");
        
        NRF_SAADC->TASKS_SAMPLE = 1;
        /*
        // Alternatively, you can use the driver to trigger a sample:
        nrf_drv_saadc_sample();
        */
    }
    
    
    void saadc_sampling_event_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
        APP_ERROR_CHECK(err_code);
    
        /* setup m_timer for compare event every 400ms */
        uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, 400);
    
        nrf_drv_timer_extended_compare(&m_timer,
                                       NRF_TIMER_CC_CHANNEL0,
                                       ticks,
                                       NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK,
                                       true);
        nrf_drv_timer_enable(&m_timer);
    
        uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer,
                                                                                    NRF_TIMER_CC_CHANNEL0);
        uint32_t saadc_sample_task_addr   = nrf_drv_saadc_sample_task_get();
    
        /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
    //    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    //    APP_ERROR_CHECK(err_code);
    
    //    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel,
    //                                          timer_compare_event_addr,
    //                                          saadc_sample_task_addr);
        APP_ERROR_CHECK(err_code);
    }
    
    
    void saadc_sampling_event_enable(void)
    {
        ret_code_t err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    
        APP_ERROR_CHECK(err_code);
    }
    
    
    void saadc_callback(nrf_drv_saadc_evt_t const * p_event)
    {
        if (p_event->type == NRF_DRV_SAADC_EVT_DONE)
        {
            ret_code_t err_code;
    
            err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAMPLES_IN_BUFFER);
            APP_ERROR_CHECK(err_code);
    
            int i;
            NRF_LOG_INFO("ADC event number: %d", (int)m_adc_evt_counter);
    
            for (i = 0; i < SAMPLES_IN_BUFFER; i++)
            {
                NRF_LOG_INFO("%d", p_event->data.done.p_buffer[i]);
            }
            m_adc_evt_counter++;
        }
    }
    
    
    void saadc_init(void)
    {
        ret_code_t err_code;
        nrf_saadc_channel_config_t channel_config =
            NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
    
        err_code = nrf_drv_saadc_init(NULL, saadc_callback);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_channel_init(0, &channel_config);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0], SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1], SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    
    }
    
    
    /**
     * @brief Function for main application entry.
     */
    int main(void)
    {
        uint32_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        ret_code_t ret_code = nrf_pwr_mgmt_init();
        APP_ERROR_CHECK(ret_code);
    
        saadc_init();
        saadc_sampling_event_init();
        //saadc_sampling_event_enable();
        NRF_LOG_INFO("SAADC HAL simple example started.");
    
        while (1)
        {
            nrf_pwr_mgmt_run();
            NRF_LOG_FLUSH();
        }
    }
    
    
    /** @} */
    

    Please note that I changed one parameter in nrf_drv_timer_extended_compare() as well, in order to trigger the timer interrupt handler in the application.

    The advantage of using PPI is that you don't need to rely on/wait for the CPU in order to trigger the sampling, but it is not necessary to use it.

    Best regards,

    Edvin

  • The code looks broadly correct assuming that pin P0.26 provides the excitation voltage for something connected on pin P0.31. (edited, I misread the original)

    You are missing an errata workaround, however, detailed here:

      // Calibrate the SAADC (only needs to be done once in a while)
      NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
      while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0)
         ;
      NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
      while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos))
         ;
      // [Errata 86] SAADC: Triggering START task after offset calibration may write a sample to RAM
      //  This anomaly applies to IC Rev. Rev 2, build codes QFAA-Ex0, CIAA-Ex0, QFAB-Ex0.
      //  Calibration should follow the pattern STOP -> STOPPED -> CALIBRATEOFFSET -> CALIBRATEDONE -> STOP -> STOPPED -> START
      // Stop the SAADC and wait for the stopped event as per Errata 86
      NRF_SAADC->TASKS_STOP = 1;
      while (NRF_SAADC->EVENTS_STOPPED == 0)
         ;
      NRF_SAADC->EVENTS_STOPPED = 0;

    Additionally I would suggest looking at the binary result in case there is some issue with IDE floating point settings in (say) printf as below:

      printf("result: 0x%04X, precise_result: %0.2f\n", result, precise_result);
      printf("result: 0x%04X, precise_result: %4.2f\n", result, precise_result);

    %0.2f does not do what you expect; try 5.2% maybe :-)

  • Hi Edwin,

    Thanks for your reply.

    This code does not use one shot mode but continues mode.

    Also this code uses timers. I don't have timers available because they are being used by other peripherals.

    I know the advantage of not waiting for the CPU. But because of the limited resources I rather have a simple example that waits to the CPU than a complex example that uses a lot of fancy includes & functionality that make no sense to me.

    Is it even possible to do ADC without timer? All I need is:

    int value = analogRead(NRF_SAADC_INPUT_AIN7);

    I don't mind to wait 50ms.

    /Philip

  • 0
    17946 pts.
    in reply to ephimee

    This is what I tried to show with the attached example.

    Use 

    nrf_drv_saadc_sample();

    or

    NRF_SAADC->TASKS_SAMPLE = 1;

    to trigger one sample from anywhere in your project. The point was that I detached the PPI from the timer, and called it in the interrupt. You can of course call it from anywhere.

  • If I follow your code.

    In the function main() there's a call to saadc_sampling_event_init().

    In the function saadc_sampling_event_init() there's a call to nrf_drv_timer_init().

    I tried to remove all timer related stuff, but then it no longer works. I'm looking specifically for an example without timers. Which means without any reference to *timer* function calls and without any *timer* includes.

Related