Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Manually triggering SAADC - which to use?

I want to trigger SAADC samples manually, when a certain BLE message arrives.  I'd ideally like to have a trigger scan 4 individual channels.

I see two function calls, though.  Here are their descriptions:

  • nrfx_saadc_mode_trigger() - Function for triggering the conversion in the configured mode.
  • nrfx_saadc_sample() - Function for starting the SAADC sampling.

Then, to make things even more confusing, there's this:

  • nrf_drv_saadc_buffer_convert() - The function nrf_drv_saadc_buffer_convert() can be used to start the conversion in non-blocking mode

I'm using S140, and now I'm not sure which one of those is correct.  This API is just confusing the hell out of me.My best guess here is as follows, maybe somebody will be kind enough to tell me if this is right.  The idea here comes from the nordic playground on github.  I initialize things like this (I edited the code to make it more readable, which is why you don't see any error checks or anything)

/* This is 4 because we're sampling 4 channels */
#define SAADC_SAMPLES_IN_BUFFER 4

/* I don't think this is used because I'm manually triggering */
#define SAADC_SAMPLE_RATE               250                                         /**< SAADC sample rate in ms. */               

/*
 * The [2] here is because we're setting up two buffers, each being
 * numChannels long - i.e. it's double buffered
 */
static nrf_saadc_value_t     m_buffer_pool[2][SAADC_SAMPLES_IN_BUFFER];


...
    /* Initialize the SAADC driver */
    err_code = nrf_drv_saadc_init(&saadc_config, saadc_callback);

    /*
     * Initialize each individual channel using the channel_x_config
     * structs have already been set up
     */
    err_code = nrf_drv_saadc_channel_init(0, &channel_0_config);
    err_code = nrf_drv_saadc_channel_init(1, &channel_1_config);
    err_code = nrf_drv_saadc_channel_init(2, &channel_2_config);
    err_code = nrf_drv_saadc_channel_init(3, &channel_3_config);

    /*
     * What is this doing exactly?  I envision that it's taking some
     * memory buffers that belong to me - let's call it "user space" -
     * and handing them over to the saadc driver, maybe by putting
     * them into some kind of queue
     */
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[0],SAADC_SAMPLES_IN_BUFFER);
    err_code = nrf_drv_saadc_buffer_convert(m_buffer_pool[1],SAADC_SAMPLES_IN_BUFFER);

where SAMPLES_IN_BUFFER is 4 beacuse I'm scanning 4 channels, and [2] is because it's somehow double buffering.  With manual triggering, I'm not really sure that's necessary, though, is it?

I think, then, when I want it to fire, I call nrfx_saadc_mode_trigger() which has the effect of triggering it ONCE.  It will then call the saadc_callback that I specified.  That callback doesn't have to do anything except read the data out of the buffer.  Or does it?  Does the callback have to call nrf_drv_saadc_buffer_convert() again?  The init code called nrf_drv_saadc_buffer_convert() and I'm not sure why.  I'm not sure if you have to do that before starting it, or only before you start processing the data.  If it "starts the conversion" as the description says then it wouldn't make sense to call it twice.

  • I am kind of thinking that the callback calls nrf_drv_saadc_buffer_convert() to hand a userspace chunk of memory back to the SAADC driver so that it can use it.  I'm thinking it's more of a queueing kind of operation.  So in the example, it calls it twice in init() I think to hand both buffers to the SAADC driver.  Then in the callback, it uses it to tell the SAADC driver that userspace has taken the data it wants out, and so it is handing the buffer BACK to the SAADC driver for the next time.  What's confusing is that in the example code it calls buffer_convert before it actually uses the data.  I think, logically, it would be the other way around: you get a callback, and it's p_event ->data.done.p_buffer is a pointer to the buffer.  I would think you would 1) consume that data 2) when you are DONE call buffer_convert() to hand the buffer back to the SAADC driver.  But it does it the opposite way.

    static 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;
        uint16_t adc_value;
        uint8_t value[SAADC_SAMPLES_IN_BUFFER * 2];
        uint16_t bytes_to_send;
    
        // set buffers
        // The function nrf_drv_saadc_buffer_convert can be used to start
        // conversion in non-blocking mode. The function returns immediately
        // after the buffer is configured. If the driver is busy, it returns
        // with an error. nrf_drv_saadc_buffer_convert sets the SAADC up for
        // conversion, but does not trigger sampling. To trigger sampling,
        // call the function nrf_drv_saadc_sample or, through PPI, use the
        // task SAMPLE from SAADC. nrf_drv_saadc_sample_task_get can be used
        // to get the task address. Single sampling triggers conversion on
        // all initialized channels. When the requested buffer is filled
        // with samples, an event of type NRF_DRV_SAADC_EVT_DONE is generated.
        err_code = nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, SAADC_SAMPLES_IN_BUFFER);
        APP_ERROR_CHECK(err_code);
    
        // print samples on hardware UART and parse data for BLE transmission
        printf("ADC event number: %d\r\n", (int)m_adc_evt_counter);
        for (int i = 0; i < SAADC_SAMPLES_IN_BUFFER; i++) {
          printf("%d\r\n", p_event->data.done.p_buffer[i]);
    
          adc_value = p_event->data.done.p_buffer[i];
          value[i * 2] = adc_value;
          value[(i * 2) + 1] = adc_value >> 8;
        }
    
        // Send data over BLE via NUS service. Create string from samples and send string with correct length.
        uint8_t nus_string[50];
        bytes_to_send = sprintf(nus_string,
            "CH0: %d\r\nCH1: %d\r\nCH2: %d\r\nCH3: %d",
            p_event->data.done.p_buffer[0],
            p_event->data.done.p_buffer[1],
            p_event->data.done.p_buffer[2],
            p_event->data.done.p_buffer[3]);
    
        //        err_code = ble_nus_data_send(&m_nus, nus_string, &bytes_to_send, m_conn_handle);
        if ((err_code != NRF_ERROR_INVALID_STATE) && (err_code != NRF_ERROR_NOT_FOUND)) {
          APP_ERROR_CHECK(err_code);
        }
    
        m_adc_evt_counter++;
      }
    }
    

  • I see two function calls, though.  Here are their descriptions:

    • nrfx_saadc_mode_trigger() - Function for triggering the conversion in the configured mode.
    • nrfx_saadc_sample() - Function for starting the SAADC sampling.

     use nrfx_saadc_sample.  

    Then, to make things even more confusing, there's this:

    • nrf_drv_saadc_buffer_convert() - The function nrf_drv_saadc_buffer_convert() can be used to start the conversion in non-blocking mode

     It prepares the next buffer by writing to the RESULT.PTR and RESULT.MAXCNT registers and then triggering a START task. It does not start sampling however. 


     

    where SAMPLES_IN_BUFFER is 4 beacuse I'm scanning 4 channels, and [2] is because it's somehow double buffering.  With manual triggering, I'm not really sure that's necessary, though, is it?

     If you trigger another SAMPLE task before you have process your buffer you will override it, double-buffering prevents that. Whether you need it or not is up to you to decide.

  • When you call the buffer_convert function in the saadc callback you're telling the driver to switch to the next buffer. The pointer given to buffer_convert is then added to the queue. 

Related