Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Can't Figure Out How To Program Multiple SAADC Channels

I'm sorry, maybe it's me, but your SAADC documentation leaves much to be desired, as I was easily able to implement getting readings from a single analog input (non-blocking), but not multiple inputs.  Your documentation doesn't seem to explain what I need to do and why so I can implement what I need.  I want to have 4 analog inputs convert when I call nrfx_saadc_sample() in the main loop of my program (non-blocking), and I shouldn't need a timer and PPI (because I didn't need either for a single channel, right?  And, the documentation for that routine says that calling it samples all enabled channels).  What I did get from the documentation and from the examples, it seems that you have to call the following:

  • nrf_drv_saadc_init() to define the address of the callback handler.
  • nrfx_saadc_channel_init() for each channel, with one channel assigned to each analog input.
  • nrfx_saadc_buffer_convert() for each channel, to give it the number of samples to read and provide the address of where to store those samples in the order you defined the channels.
  • call nrfx_saadc_sample() repetitively at regular intervals
  • the callback will be called for each channel.

The result is that I never get through the third step.  I'm attaching the file that I'm trying to do this through.  When I tried to run this, I got a NRFX_ERROR_BUSY so I decided to wait, but the farther I got, the longer I waited until it went on forever.  Please tell me what I'm doing wrong, why, and what I need to do it right (i.e. please tell me what I'm missing in my understanding and what I need to know to get this to work).

Thank you so much ahead of time, as you guys have always come through for me, and maybe someone else will be able to learn from this.

// SAADC - Successive Approximation Analog to Digital Converter Driver

#include "saadc.h"
#include "nrf_drv_saadc.h"
#include "nrf_wdt.h"
#include "nrf_drv_wdt.h"

// Voltage Reference
// 8 bit  = approx 0.01289     =  12.89  millivolts
// 10 bit = approx 0.003226    =  3.226  millivolts
// 12 bit = approx 0.00080566  =  0.80566 Millivolts = 805.66 Micro volts  4096
// 14 bit = approx 0.00020141  =  0.20141 Millivolts = 201.41 Micro Volts

#define PIU_CURRENT_NOISE_OFFSET 14 // Include this because we're reading negative voltages

// Samples are needed to be stored in a buffer, we define the length here
#define SAADC_SAMPLES_IN_BUFFER 4

extern nrfx_wdt_channel_id m_channel_id;

typedef enum
{
    SAADC_CH_VDIFF = 0,
    SAADC_CH_5V_MON = 1,
    SAADC_CH_12V_MON = 2,
    SAADC_CH_24V_MON = 3,
    SAADC_CHANNELS = 4
} eSAADCChannel;

// Save the sample returned, last one is average
static nrf_saadc_value_t m_buffer[SAADC_CHANNELS][SAADC_SAMPLES_IN_BUFFER]; 

// Value to return to host.c
static nrf_saadc_value_t adc_value[SAADC_CHANNELS];

// Handle the events once the samples are received in the buffer
void saadc_callback_handler(nrf_drv_saadc_evt_t const * p_event)
{
    nrf_saadc_value_t adc_average = 0;
    eSAADCChannel channel;
    
    // check if the sampling is done and we are ready to take these samples for processing
    if (p_event -> type == NRFX_SAADC_EVT_DONE)
    {
        ret_code_t err_code; // a variable to hold errors code

        // Take the samples in the buffer in the form of 2's complement and convert them to 16-bit interger values
        err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code); // check for errors

        // Determine which channel we're dealing with, so we know where to put the return value
        if (p_event->data.done.p_buffer == m_buffer[SAADC_CH_VDIFF])
            channel = SAADC_CH_VDIFF;
        else if (p_event->data.done.p_buffer == m_buffer[SAADC_CH_5V_MON])
            channel = SAADC_CH_5V_MON;
        else if (p_event->data.done.p_buffer == m_buffer[SAADC_CH_12V_MON])
            channel = SAADC_CH_12V_MON;
        else if (p_event->data.done.p_buffer == m_buffer[SAADC_CH_24V_MON])
            channel = SAADC_CH_24V_MON;
        else
            return; // We didn't find which channel we're dealing with

        // Store the value for returning
        for (int i = 0; i < SAADC_SAMPLES_IN_BUFFER; ++i)
            adc_average += p_event->data.done.p_buffer[i] + 9;
        adc_value[channel] = adc_average >> 2; // We know SAADC_SAMPLES_IN_BUFFER is 4
    }
}

// Initialize the Successive Approximation Analog to Digital Converter Driver
void SaadcInit(void)
{
    ret_code_t err_code; // variable to store the error code
    nrf_saadc_channel_config_t channel0_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
    nrf_saadc_channel_config_t channel1_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3);
    nrf_saadc_channel_config_t channel2_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN7);
    nrf_saadc_channel_config_t channel3_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6);

    // Initialize adc module (NULL is for configurations) configured via CMSIS Configuration wizard
    err_code = nrf_drv_saadc_init(NULL, saadc_callback_handler);
    APP_ERROR_CHECK(err_code);

    // allocate the channels along with the configurations
    err_code = nrfx_saadc_channel_init(SAADC_CH_VDIFF, &channel0_config);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channel_init(SAADC_CH_5V_MON, &channel1_config);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channel_init(SAADC_CH_12V_MON, &channel2_config);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_saadc_channel_init(SAADC_CH_24V_MON, &channel3_config);
    APP_ERROR_CHECK(err_code);

    // allocate buffer where the values will be stored once sampled
    do
    {
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_VDIFF], SAADC_SAMPLES_IN_BUFFER);
        nrfx_wdt_channel_feed(m_channel_id);
    } while (err_code == NRFX_ERROR_BUSY);
    APP_ERROR_CHECK(err_code);
    do
    {
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_5V_MON], SAADC_SAMPLES_IN_BUFFER);
        nrfx_wdt_channel_feed(m_channel_id);
    } while (err_code == NRFX_ERROR_BUSY);
    APP_ERROR_CHECK(err_code);
    do
    {
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_12V_MON], SAADC_SAMPLES_IN_BUFFER);
        nrfx_wdt_channel_feed(m_channel_id);
    } while (err_code == NRFX_ERROR_BUSY);
    APP_ERROR_CHECK(err_code);
    do
    {
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_24V_MON], SAADC_SAMPLES_IN_BUFFER);
        nrfx_wdt_channel_feed(m_channel_id);
    } while (err_code == NRFX_ERROR_BUSY);
    APP_ERROR_CHECK(err_code);
}

void UpdateSaadc(void) // Call this regularly and repeditively through the main loop
{
    nrfx_saadc_sample(); // FYI: The error busy is ignored because it will do nothing
}

uint16_t GetHostPIUCurrent(void)
{
    return (adc_value[SAADC_CH_VDIFF] >= 0) ? adc_value[SAADC_CH_VDIFF] : 0; // + PIU_CURRENT_NOISE_OFFSET???
}

Parents
  • Hello,

    If you want to have two SAADC channels, you should just need to initialize both (2 or more) channels before you start the ADC. You still only need to convert the buffer once for every call to saadc_callback_handler(). I have at least tested this earlier using the SDK\examples\peripheral\saadc example. Try it out, and I will test again tomorrow if it doesn't work. 

    Hint: Use the same number of elements in the adc buffer as number of channels you have, so that each channel will report to one instance in the buffer.

    Best regards,

    Edvin

  • Thank you for getting back to me.  I decided to do without the two extra calls (step 3 in my last response), because all of the multiple analog input examples I have found on line have only two channels (and it’s hanging anyway).  Now it at least got through the initialization (and I didn’t have to feed the watchdog timer).  Next, I decided to look at the values of m_buffer[], which displayed as follows:

    m_buffer = [0xfff7, 0x0934, 0x07df, 0x00a2, 0xfff5, 0x0936, 0x07e8, 0x00a6, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000]

    Which looks exactly like the data from the four channels repeated twice, so in a sense, it is working, and I re-wrote this way which works.  Thank you once again, I’m all set.

    // SAADC - Successive Approximation Analog to Digital Converter Driver
    
    #include "saadc.h"
    #include "pinout.h"
    #include "nrf_drv_saadc.h"
    #include "nrf_wdt.h"
    #include "nrf_drv_wdt.h"
    
    // Voltage Reference (Default set in sdk_config.h should be 12 bits)
    // 8 bit  = approx 0.01289     =  12.89  millivolts
    // 10 bit = approx 0.003226    =  3.226  millivolts
    // 12 bit = approx 0.00080566  =  0.80566 Millivolts = 805.66 Micro volts  4096
    // 14 bit = approx 0.00020141  =  0.20141 Millivolts = 201.41 Micro Volts
    
    // Samples are needed to be stored in a buffer, we define the length here
    typedef enum
    {
        SAADC_CHANNEL_0 = 0,
        SAADC_CHANNEL_1 = 1,
        SAADC_CHANNELS = 2
    } eSAADCChannel;
    
    typedef enum
    {
        SAADC_CH_VDIFF = 0,
        SAADC_CH_5V_MON = 1,
        SAADC_CH_12V_MON = 2,
        SAADC_CH_24V_MON = 3,
        SAADC_INPUTS = 4
    } eSAADCInput;
    
    // Save the sample returned, last one is average
    static nrf_saadc_value_t m_buffer[SAADC_CHANNELS][SAADC_INPUTS]; 
    
    // Value to return to host.c
    static nrf_saadc_value_t adc_value[SAADC_INPUTS] = {0, 0, 0, 0};
    static nrf_saadc_value_t adc_sample[SAADC_INPUTS][4] = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}};
    static nrf_saadc_value_t adc_total[SAADC_INPUTS] = {0, 0, 0, 0};
    static byte index = 0;
    
    
    // Handle the events once the samples are received in the buffer
    void saadc_callback_handler(nrf_drv_saadc_evt_t const * p_event)
    {
        eSAADCChannel channel;
        
        // check if the sampling is done and we are ready to take these samples for processing
        if (p_event -> type == NRFX_SAADC_EVT_DONE)
        {
            ret_code_t err_code; // a variable to hold errors code
    
            // Take the samples in the buffer in the form of 2's complement and convert them to 16-bit interger values
            err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_INPUTS);
            APP_ERROR_CHECK(err_code); // check for errors
    
            // Determine which channel we're dealing with, so we know where to put the return value
            if (p_event->data.done.p_buffer == m_buffer[SAADC_CHANNEL_0])
                channel = SAADC_CHANNEL_0;
            else if (p_event->data.done.p_buffer == m_buffer[SAADC_CHANNEL_1])
                channel = SAADC_CHANNEL_1;
            else
                return; // We didn't find which channel we're dealing with
    
            // Update the total and samples
            for (int i = 0; i < SAADC_INPUTS; ++i)
            {
                adc_total[i] -= adc_sample[i][index];
                adc_sample[i][index] = p_event->data.done.p_buffer[i];
                adc_total[i] += adc_sample[i][index];
            }
            index = (index + 1) & 0x03;
    
            // Return the average
            if (channel == SAADC_CHANNEL_1)
            {
                for (int i = 0; i < SAADC_INPUTS; ++i)
                    adc_value[i] = adc_total[i] / 4;
            }
        }
    }
    
    // Initialize the Successive Approximation Analog to Digital Converter Driver
    void SaadcInit(void)
    {
        ret_code_t err_code; // variable to store the error code
        nrf_saadc_channel_config_t channel0_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
        nrf_saadc_channel_config_t channel1_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3);
        nrf_saadc_channel_config_t channel2_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN7);
        nrf_saadc_channel_config_t channel3_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6);
    
        // Initialize adc module (NULL is for configurations) configured via CMSIS Configuration wizard
        err_code = nrf_drv_saadc_init(NULL, saadc_callback_handler);
        APP_ERROR_CHECK(err_code);
    
        // allocate the channels along with the configurations
        err_code = nrfx_saadc_channel_init(SAADC_CH_VDIFF, &channel0_config);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channel_init(SAADC_CH_5V_MON, &channel1_config);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channel_init(SAADC_CH_12V_MON, &channel2_config);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channel_init(SAADC_CH_24V_MON, &channel3_config);
        APP_ERROR_CHECK(err_code);
    
        // allocate buffer where the values will be stored once sampled
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_VDIFF], SAADC_INPUTS);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_5V_MON], SAADC_INPUTS);
        APP_ERROR_CHECK(err_code);
    }
    
    void UpdateSaadc(void) // Call this regularly and repeditively through the main loop
    {
        nrfx_saadc_sample(); // FYI: The error busy is ignored because it will do nothing
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 Amp (* 1.0931 avg calibration)
    
    uint16 GetHostPIUCurrent(void)
    {
        return (adc_value[SAADC_CH_VDIFF] >= 0) ? adc_value[SAADC_CH_VDIFF] : 0;
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 * 2.4394 Volts (* 1.0895 calibration)
    uint16 GetHost5VMonitor(void)
    {
        return (adc_value[SAADC_CH_5V_MON] >= 0) ? adc_value[SAADC_CH_5V_MON] : 0;
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 * 6.925 Volts (* 1.0917 calibration)
    uint16 GetHost12VMonitor(void)
    {
        return (adc_value[SAADC_CH_12V_MON] >= 0) ? adc_value[SAADC_CH_12V_MON] : 0;
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 * 13.272 Volts (* 1.0981 calibration)
    uint16 GetHost24VMonitor(void)
    {
        return (adc_value[SAADC_CH_24V_MON] >= 0) ? adc_value[SAADC_CH_24V_MON] : 0;
    }
    

Reply
  • Thank you for getting back to me.  I decided to do without the two extra calls (step 3 in my last response), because all of the multiple analog input examples I have found on line have only two channels (and it’s hanging anyway).  Now it at least got through the initialization (and I didn’t have to feed the watchdog timer).  Next, I decided to look at the values of m_buffer[], which displayed as follows:

    m_buffer = [0xfff7, 0x0934, 0x07df, 0x00a2, 0xfff5, 0x0936, 0x07e8, 0x00a6, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000]

    Which looks exactly like the data from the four channels repeated twice, so in a sense, it is working, and I re-wrote this way which works.  Thank you once again, I’m all set.

    // SAADC - Successive Approximation Analog to Digital Converter Driver
    
    #include "saadc.h"
    #include "pinout.h"
    #include "nrf_drv_saadc.h"
    #include "nrf_wdt.h"
    #include "nrf_drv_wdt.h"
    
    // Voltage Reference (Default set in sdk_config.h should be 12 bits)
    // 8 bit  = approx 0.01289     =  12.89  millivolts
    // 10 bit = approx 0.003226    =  3.226  millivolts
    // 12 bit = approx 0.00080566  =  0.80566 Millivolts = 805.66 Micro volts  4096
    // 14 bit = approx 0.00020141  =  0.20141 Millivolts = 201.41 Micro Volts
    
    // Samples are needed to be stored in a buffer, we define the length here
    typedef enum
    {
        SAADC_CHANNEL_0 = 0,
        SAADC_CHANNEL_1 = 1,
        SAADC_CHANNELS = 2
    } eSAADCChannel;
    
    typedef enum
    {
        SAADC_CH_VDIFF = 0,
        SAADC_CH_5V_MON = 1,
        SAADC_CH_12V_MON = 2,
        SAADC_CH_24V_MON = 3,
        SAADC_INPUTS = 4
    } eSAADCInput;
    
    // Save the sample returned, last one is average
    static nrf_saadc_value_t m_buffer[SAADC_CHANNELS][SAADC_INPUTS]; 
    
    // Value to return to host.c
    static nrf_saadc_value_t adc_value[SAADC_INPUTS] = {0, 0, 0, 0};
    static nrf_saadc_value_t adc_sample[SAADC_INPUTS][4] = {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}};
    static nrf_saadc_value_t adc_total[SAADC_INPUTS] = {0, 0, 0, 0};
    static byte index = 0;
    
    
    // Handle the events once the samples are received in the buffer
    void saadc_callback_handler(nrf_drv_saadc_evt_t const * p_event)
    {
        eSAADCChannel channel;
        
        // check if the sampling is done and we are ready to take these samples for processing
        if (p_event -> type == NRFX_SAADC_EVT_DONE)
        {
            ret_code_t err_code; // a variable to hold errors code
    
            // Take the samples in the buffer in the form of 2's complement and convert them to 16-bit interger values
            err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_INPUTS);
            APP_ERROR_CHECK(err_code); // check for errors
    
            // Determine which channel we're dealing with, so we know where to put the return value
            if (p_event->data.done.p_buffer == m_buffer[SAADC_CHANNEL_0])
                channel = SAADC_CHANNEL_0;
            else if (p_event->data.done.p_buffer == m_buffer[SAADC_CHANNEL_1])
                channel = SAADC_CHANNEL_1;
            else
                return; // We didn't find which channel we're dealing with
    
            // Update the total and samples
            for (int i = 0; i < SAADC_INPUTS; ++i)
            {
                adc_total[i] -= adc_sample[i][index];
                adc_sample[i][index] = p_event->data.done.p_buffer[i];
                adc_total[i] += adc_sample[i][index];
            }
            index = (index + 1) & 0x03;
    
            // Return the average
            if (channel == SAADC_CHANNEL_1)
            {
                for (int i = 0; i < SAADC_INPUTS; ++i)
                    adc_value[i] = adc_total[i] / 4;
            }
        }
    }
    
    // Initialize the Successive Approximation Analog to Digital Converter Driver
    void SaadcInit(void)
    {
        ret_code_t err_code; // variable to store the error code
        nrf_saadc_channel_config_t channel0_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN2);
        nrf_saadc_channel_config_t channel1_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN3);
        nrf_saadc_channel_config_t channel2_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN7);
        nrf_saadc_channel_config_t channel3_config = NRFX_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN6);
    
        // Initialize adc module (NULL is for configurations) configured via CMSIS Configuration wizard
        err_code = nrf_drv_saadc_init(NULL, saadc_callback_handler);
        APP_ERROR_CHECK(err_code);
    
        // allocate the channels along with the configurations
        err_code = nrfx_saadc_channel_init(SAADC_CH_VDIFF, &channel0_config);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channel_init(SAADC_CH_5V_MON, &channel1_config);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channel_init(SAADC_CH_12V_MON, &channel2_config);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channel_init(SAADC_CH_24V_MON, &channel3_config);
        APP_ERROR_CHECK(err_code);
    
        // allocate buffer where the values will be stored once sampled
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_VDIFF], SAADC_INPUTS);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_buffer_convert(m_buffer[SAADC_CH_5V_MON], SAADC_INPUTS);
        APP_ERROR_CHECK(err_code);
    }
    
    void UpdateSaadc(void) // Call this regularly and repeditively through the main loop
    {
        nrfx_saadc_sample(); // FYI: The error busy is ignored because it will do nothing
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 Amp (* 1.0931 avg calibration)
    
    uint16 GetHostPIUCurrent(void)
    {
        return (adc_value[SAADC_CH_VDIFF] >= 0) ? adc_value[SAADC_CH_VDIFF] : 0;
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 * 2.4394 Volts (* 1.0895 calibration)
    uint16 GetHost5VMonitor(void)
    {
        return (adc_value[SAADC_CH_5V_MON] >= 0) ? adc_value[SAADC_CH_5V_MON] : 0;
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 * 6.925 Volts (* 1.0917 calibration)
    uint16 GetHost12VMonitor(void)
    {
        return (adc_value[SAADC_CH_12V_MON] >= 0) ? adc_value[SAADC_CH_12V_MON] : 0;
    }
    
    // Raw A/D reading where each 12-bit count is 0.00080566 * 13.272 Volts (* 1.0981 calibration)
    uint16 GetHost24VMonitor(void)
    {
        return (adc_value[SAADC_CH_24V_MON] >= 0) ? adc_value[SAADC_CH_24V_MON] : 0;
    }
    

Children
No Data
Related