Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

Sampling two ADC channels at 16 kHz

Hi,

I'm trying to implement an application that acquires data from two channels of the SAADC in "parallel". I'm currently using the nRF52 DK and nRF5 SDK v17.1.0. After reading several post on DevZone and some examples on Github, I developed the code on the bottom of this post. In the reference posts that I read, it was stated several times that I have to uninit() one ADC channel for being able to switch to the other ADC channel. So in my code, after receiving the NRF_DRV_SAADC_EVT_DONE, I convert the buffer before trying to uninit() ADC channel 0 and call init() on channel 1 and vice versa. However, I always receive NRF_ERROR_BUDY, when trying to uninit().

I would be grateful for some hints, if I'm on the right track or if there is a major misunderstanding of how the ADC works.

Here is my code:

#include "saadc_itf.h"

#include <stdbool.h>
#include <stdint.h>

#include "sdk_errors.h"
#include "nrf_drv_saadc.h"
#include "nrf_drv_ppi.h"
#include "nrf_drv_timer.h"
#include "nrf_queue.h"
#include "nrf_delay.h"
#include "nrf_log.h"
#include "nrf.h"
#include "app_util_platform.h"
#include "app_error.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define SAMPLES_IN_BUFFER     64
#define SAMPLE_RATE           16000

static const nrf_drv_saadc_config_t saadc_config = {
    .resolution = NRF_SAADC_RESOLUTION_12BIT,
    .oversample = NRF_SAADC_OVERSAMPLE_DISABLED,
    .interrupt_priority = 6,
    .low_power_mode = false
};

static const nrf_drv_timer_t  m_timer = NRF_DRV_TIMER_INSTANCE(3);
static nrf_ppi_channel_t      m_ppi_channel;

uint16_t CC_VALUE = 1000;     // sampling frequency = 16 MHz / CC_VALUE
//static nrf_saadc_value_t    m_buffer_pool[2][SAMPLES_IN_BUFFER];
static nrf_saadc_value_t      m_buffer_ch0[SAMPLES_IN_BUFFER];
static nrf_saadc_value_t      m_buffer_ch1[SAMPLES_IN_BUFFER];
static uint32_t               m_adc_evt_counter;
static uint8_t test_array[128] = {0};

static uint8_t                      m_adc_channel_enabled; 
static nrf_saadc_channel_config_t   channel_0_config;
static nrf_saadc_channel_config_t   channel_1_config;

NRF_QUEUE_DEF(uint8_t, m_queue0, 5120, NRF_QUEUE_MODE_NO_OVERFLOW);
NRF_QUEUE_DEF(uint8_t, m_queue1, 5120, NRF_QUEUE_MODE_NO_OVERFLOW);

static nrf_ppi_channel_t     m_ppi_channel;
static uint32_t              m_sample_rate_us = (1000000 / SAMPLE_RATE);

uint32_t saadc_get_data(uint8_t * buffer, uint8_t buffer_len, uint8_t channel)
{
    uint32_t err_code;

    if (channel == 0)
    {
        NRF_LOG_INFO("Current queue utilization: %d", ((int) nrf_queue_utilization_get(&m_queue)));

        //? use a different buffer size to match the BLE characteristic length
        err_code = nrf_queue_read(&m_queue0, buffer, (size_t) buffer_len);
        if (err_code == NRF_SUCCESS) 
        {
            return NRF_SUCCESS;
        }
        else if (err_code == NRF_ERROR_NOT_FOUND)
        {
            NRF_LOG_DEBUG("Not enough data in queue.");
            NRF_LOG_INFO("Current queue utilization: %d", ((int) nrf_queue_utilization_get(&m_queue)));
        }
        else if (err_code == NRF_ERROR_BUSY) 
        {
            NRF_LOG_DEBUG("Queue 0 busy");
        }
        else
        {
            NRF_LOG_DEBUG("reading from queue 0 returned error.");
            APP_ERROR_CHECK(err_code);
        }

        return err_code; 

    }
    else if (channel == 1)
    {
        NRF_LOG_INFO("Current queue utilization: %d", ((int) nrf_queue_utilization_get(&m_queue)));

        //? use a different buffer size to match the BLE characteristic length
        err_code = nrf_queue_read(&m_queue1, buffer, (size_t) buffer_len);
        if (err_code == NRF_SUCCESS) 
        {
            return NRF_SUCCESS;
        }
        else if (err_code == NRF_ERROR_NOT_FOUND)
        {
            NRF_LOG_DEBUG("Not enough data in queue.");
            NRF_LOG_INFO("Current queue utilization: %d", ((int) nrf_queue_utilization_get(&m_queue)));
        }
        else if (err_code == NRF_ERROR_BUSY) 
        {
            NRF_LOG_DEBUG("Queue 1 busy");
        }
        else
        {
            NRF_LOG_DEBUG("reading from queue 1 returned error.");
            APP_ERROR_CHECK(err_code);
        }

        return err_code; 
    }
}

uint32_t get_queue_utilization(uint8_t channel)
{
    if (channel == 0)
    {
        uint32_t util = (int) nrf_queue_utilization_get(&m_queue0);
        return util;
    }
    else if (channel == 1)
    {
        uint32_t util = (int) nrf_queue_utilization_get(&m_queue1);
        return util;
    }
}

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);

        if (m_adc_channel_enabled == 0) 
        {
            err_code = nrf_queue_write(&m_queue0, p_event->data.done.p_buffer, (SAMPLES_IN_BUFFER * 2));

            if (err_code == NRF_ERROR_NO_MEM)
            {
                // queue full
                NRF_LOG_DEBUG("QUEUE FULL!");
                nrf_queue_reset(&m_queue0);
            }
            else if (err_code != NRF_SUCCESS)
            {
                APP_ERROR_CHECK(err_code);
            }
        }
        else if (m_adc_channel_enabled == 1)
        {
            err_code = nrf_queue_write(&m_queue1, p_event->data.done.p_buffer, (SAMPLES_IN_BUFFER * 2));
            if (err_code == NRF_ERROR_NO_MEM)
            {
                // queue full
                NRF_LOG_DEBUG("QUEUE FULL!");
                nrf_queue_reset(&m_queue1);
            }
            else if (err_code != NRF_SUCCESS)
            {
                APP_ERROR_CHECK(err_code);
            }
        }
        else 
        {
            NRF_LOG_ERROR("Invalid ADC channel enabled: %d", m_adc_channel_enabled);
        }

        //uninit current channel
        err_code = nrf_drv_saadc_channel_uninit(m_adc_channel_enabled);
        APP_ERROR_CHECK(err_code);

        // switch channel
        m_adc_channel_enabled = 1 - m_adc_channel_enabled;

        // init other channel
        if (m_adc_channel_enabled == 0)
        {
            err_code = nrf_drv_saadc_channel_init(m_adc_channel_enabled, &channel_0_config);
            APP_ERROR_CHECK(err_code);
        }
        else if (m_adc_channel_enabled == 1)
        {
            err_code = nrf_drv_saadc_channel_init(m_adc_channel_enabled, &channel_1_config);
            APP_ERROR_CHECK(err_code);
        }
        else
        {
            NRF_LOG_ERROR("Invalid ADC channel selected: %d", m_adc_channel_enabled);
        }

        m_adc_evt_counter++;

    }

}
 
void timer_handler(nrf_timer_event_t event_type, void* p_context) {
    //NRF_LOG_DEBUG("TIMER HANDLER CALLED.");
}
 
uint32_t saadc_init(void) {

    ret_code_t err_code;
 
    err_code = nrf_drv_saadc_init(&saadc_config, saadc_callback);
    APP_ERROR_CHECK(err_code);
 
    channel_0_config.mode = NRF_SAADC_MODE_SINGLE_ENDED;
    channel_0_config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
    channel_0_config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
    channel_0_config.reference  = NRF_SAADC_REFERENCE_INTERNAL;
    channel_0_config.acq_time   = NRF_SAADC_ACQTIME_40US;
    channel_0_config.burst      = NRF_SAADC_BURST_DISABLED;
    channel_0_config.pin_p      = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN1);
    channel_0_config.pin_n      = NRF_SAADC_INPUT_DISABLED;
    channel_0_config.gain = NRF_SAADC_GAIN1_6;

    channel_1_config.mode = NRF_SAADC_MODE_SINGLE_ENDED;
    channel_1_config.resistor_p = NRF_SAADC_RESISTOR_DISABLED;
    channel_1_config.resistor_n = NRF_SAADC_RESISTOR_DISABLED;
    channel_1_config.reference  = NRF_SAADC_REFERENCE_INTERNAL;
    channel_1_config.acq_time   = NRF_SAADC_ACQTIME_40US;
    channel_1_config.burst      = NRF_SAADC_BURST_DISABLED;
    channel_1_config.pin_p      = (nrf_saadc_input_t)(NRF_SAADC_INPUT_AIN2);
    channel_1_config.pin_n      = NRF_SAADC_INPUT_DISABLED;
    channel_1_config.gain = NRF_SAADC_GAIN4;

    m_adc_channel_enabled = 0;
    err_code = nrf_drv_saadc_channel_init(m_adc_channel_enabled, &channel_0_config);
    APP_ERROR_CHECK(err_code);
 
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_ch0, SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_drv_saadc_buffer_convert(m_buffer_ch1, SAMPLES_IN_BUFFER);
    APP_ERROR_CHECK(err_code);

    return err_code;

}
 
void saadc_timer_init(void) {

    uint32_t time_us = m_sample_rate_us;
    uint32_t time_ticks;
 
    nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;

    ret_code_t err_code = nrf_drv_timer_init(&m_timer, &timer_cfg, timer_handler);
    APP_ERROR_CHECK(err_code);
 
    time_ticks = nrf_drv_timer_us_to_ticks(&m_timer, time_us);

    nrf_drv_timer_extended_compare(
         &m_timer, NRF_TIMER_CC_CHANNEL0, time_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

}
 
void saadc_ppi_init(void) {

    ret_code_t err_code = nrf_drv_ppi_init();
    APP_ERROR_CHECK(err_code);
 
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    APP_ERROR_CHECK(err_code);
 
    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);

    uint32_t saadc_task_addr = nrf_drv_saadc_sample_task_get();
 
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, saadc_task_addr);
    APP_ERROR_CHECK(err_code);
 
    err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    APP_ERROR_CHECK(err_code);

}
 
void saadc_sampling_start(void) {

    nrf_drv_timer_enable(&m_timer); // Enable the timer to start sampling
    NRF_LOG_INFO("SAMPLING STARTED");

}
 
void saadc_sampling_stop(void) {

    nrf_drv_timer_disable(&m_timer); // Disable the timer to stop sampling
    NRF_LOG_INFO("SAMPLING STOPPED");

}

int main(void)
{
    bool erase_bonds;
    uint32_t err_code;
    uint8_t current_channel = 0;

    // Initialize.
    log_init();
    timers_init();
    buttons_leds_init(&erase_bonds);
    power_management_init();

    state_machine_init(&state_indication);

    // Initialize SAADC
    err_code = saadc_init();
    saadc_timer_init();
    saadc_ppi_init();
    if (err_code == NRF_SUCCESS)
    {
        NRF_LOG_INFO("SAADC successfully initialized.");
    }
    else
    {
        NRF_LOG_INFO("Error initializing SAADC.");
    }
    
    saadc_sampling_start();
    
    for (;;)
    {
        err_code = saadc_get_data(m_array, len_m_array, current_channel);
        current_channel = 1 - current_channel;
        if (err_code == NRF_SUCCESS)
        {
            // do something with the data
        }
        idle_state_handle();
        NRF_LOG_FLUSH();
    }
}

Related