nRF52832: how to trigger SAADC sampling externally via PPI event?

Hello,

thank you for your help to solve my previous issue with resetting the RTC event ( nRF52832: problem with refreshing/clearing RTC for looping). I'm glad to move to the next problem with my task, which is triggering the SAADC sampling every time an RTC event occurs.

Description:

I would like to use RTC event COMPARE0 via PPI to trigger SAADC's sampling task. The SAADC sampling/conversion isn't continuous in a sense, since it shouldn't use internal clock, but is dependent on an external signal. The RTC would trigger an event on it's own refresh pace, and so the SAADC conversion isn't critically timing-bound (the sampling rate is roughly 3 Hz).

The RTC inititalization has been tested by connecting the COMPARE0 event to a GPIOTE toggle task. The result occurs as a PWM-type behaviour, which suggests that the RTC succesfully regenerates the COMPARE0 event.

mm_rtc.c (only the essential for this problem)

uint32_t mm_rtc_address_event_comp0(void)
{
    return nrfx_rtc_event_address_get(&mm_rtc, NRF_RTC_EVENT_COMPARE_0);
}

uint32_t mm_rtc_address_task_clear(void)
{
    return nrfx_rtc_task_address_get(&mm_rtc, NRF_RTC_TASK_CLEAR);
}

mm_ppi.c

static nrf_ppi_channel_t mm_ppi_ch0;

void ppi_init(void)
{
    nrfx_ppi_channel_alloc(&mm_ppi_ch0);
    nrfx_ppi_channel_assign(mm_ppi_ch0, mm_rtc_address_event_comp0(), mm_saadc_address_task_sample());
    nrfx_ppi_channel_fork_assign(mm_ppi_ch0, mm_rtc_address_task_clear());
    nrfx_ppi_channel_enable(mm_ppi_ch0);
}

To solve my problem I'm using a single SAADC channel and a result buffer array, which holds 2 samples. If I want to use external source to trigger the sampling, I should use the advanced mode. From the nrfx_saadc.h:


"*The advanced mode allows performing double-buffered conversions of arbitrary length.
 * The conversions can be done in a blocking or non-blocking manner. When performing conversions
 * in the non-blocking manner and @ref nrfx_saadc_adv_config_t.internal_timer_cc is set to 0,
 * sampling needs to be done by triggering @ref NRF_SAADC_TASK_SAMPLE externally
 * (for example by using the TIMER and/or the PPI/DPPI)."

mm_saadc.c

#define MM_SAADC_BUF_CNT 2

static nrf_saadc_value_t buffer[MM_SAADC_BUF_CNT];
static nrfx_saadc_channel_t channels[1] = {
    NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN0, 0),
    //NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN1, 1),
};

static nrfx_saadc_adv_config_t p_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;


uint32_t mm_saadc_address_task_sample()
{
    return nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
}

uint32_t mm_saadc_address_event_done()
{
    return nrf_saadc_event_address_get(NRF_SAADC, NRF_SAADC_EVENT_END);
}

static uint8_t next_free_buf_index(void)
{
    static uint8_t buffer_index = -1;
    buffer_index = (buffer_index + 1) % MM_SAADC_BUF_CNT;
    return buffer_index;
}

static void event_handler(nrfx_saadc_evt_t const * p_event)
{
    nrfx_err_t err;
    switch (p_event->type)
    {
        case NRFX_SAADC_EVT_DONE:
            LOG_INF("-------------> NRFX_SAADC_EVT_DONE");
            for (int i = 0; i < p_event->data.done.size; i++) {
                LOG_INF("CH%d: %d", i, p_event->data.done.p_buffer[i]);
            }
            break;
        case NRFX_SAADC_EVT_BUF_REQ:
            LOG_INF("NRFX_SAADC_EVT_BUF_REQ");
            err = nrfx_saadc_buffer_set(&buffer[next_free_buf_index()], 1);
            LOG_INF("nrfx_saadc_buffer_set err: %x", err);
            break;
        default:
            LOG_INF("default: event type %x", p_event->type);
            break;
    }
}

int mm_saadc_init(void)
{
    nrfx_err_t err;

    IRQ_CONNECT(DT_IRQN(DT_NODELABEL(adc)), DT_IRQ(DT_NODELABEL(adc), priority),
                nrfx_isr, nrfx_saadc_irq_handler, 0);

    err = nrfx_saadc_init(NRFX_SAADC_DEFAULT_CONFIG_IRQ_PRIORITY);
    err = nrfx_saadc_channels_config(channels, 1);

    err = nrfx_saadc_advanced_mode_set(BIT(0), NRF_SAADC_RESOLUTION_12BIT,
                                       &p_config, event_handler);

    err = nrfx_saadc_buffer_set(&buffer[next_free_buf_index()], 1);

    err = nrfx_saadc_mode_trigger();

    return NRFX_SUCCESS;
}

The problem:

The SAADC eventually reaches the case NRFX_SAADC_EVT_DONE once, prints out the buffer values, and stops there; so called one-shot. Since the RTC event re-triggering has been proven, I believe the problem lies within the SAADC module. I'm not 100% sure what I'm missing, but I think it might have something to do with the result buffer. While searching for any help on this forum, I came across with a function that does not exist in my version:

nrf_drv_saadc_buffer_convert()

Does this have something to do with my problem?

Another thought I had was that maybe I should reset/clear some of the SAADC registers?

Essentially, how do I get the SAADC to provide a converted result every time an RTC event is triggered? I don't want to use SAADC's internal timers, refresh rates or what-have-yous.

Thank you.

  • Hi 

    My colleague Jørgen made an example that sounds very similar to what you are doing. 

    Could you start by taking a look at that and see if you can spot the issue with your own example?
    https://github.com/jorhol/NCS-SAADC-samples/tree/main/nrfx_saadc_advanced_nrfx_rtc_ppi/

    If you still have questions or concerns after checking out his example just let me know, and I will do my best to help out. 

    Best regards
    Torbjørn

  • Hi

    The link you provided helped, I managed to get the SAADC to perform a conversion on every COMPARE0-event. I shall provide my solution here just to avoid a situation in the future where the link is not usable.

    I followed the sample, and was pleased that my approach was quite similar in comparison. The only steps I didn't do with my SAADC code was to use the task START. I was under the impression that when you use task SAMPLE it would trigger START automatically. These are the changes I made to my code to get it working:

    mm_saadc.c

    #define MM_SAADC_CH_CNT 1
    
    static nrf_saadc_value_t buffer[MM_SAADC_CH_CNT];
    static nrfx_saadc_channel_t channels[MM_SAADC_CH_CNT] = {
        NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN0, 0),
        //NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN1, 1),
    };
    
    uint32_t mm_saadc_address_event_started()
    {
        return nrf_saadc_event_address_get(NRF_SAADC, NRF_SAADC_EVENT_STARTED);
    }
    
    uint32_t mm_saadc_address_task_start()
    {
        return nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_START);
    }
    
    static void event_handler(nrfx_saadc_evt_t const * p_event)
    {
        nrfx_err_t err;
        switch (p_event->type)
        {
            case NRFX_SAADC_EVT_DONE:
                for (int i = 0; i < p_event->data.done.size; i++) {
                    LOG_INF("CH%d: %d", i, p_event->data.done.p_buffer[i]);
                }
                err = nrfx_saadc_buffer_set(&buffer[0], MM_SAADC_CH_CNT);
                break;
    /*
            case NRFX_SAADC_EVT_BUF_REQ:
                LOG_INF("NRFX_SAADC_EVT_BUF_REQ");
                err = nrfx_saadc_buffer_set(&buffer[next_free_buf_index()], MM_SAADC_CH_CNT);
                LOG_INF("NRFX_SAADC_EVT_BUF_REQ err: %x", err);
                break;
    */
            case NRFX_SAADC_EVT_READY:
                LOG_INF("Stopping SAADC");
                nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_STOP);
                break;
            default:
                LOG_INF("default: event type %x", p_event->type);
                break;
        }
    }

    mm_ppi.c

    void mm_ppi_set()
    {
        nrfx_err_t err = NRFX_SUCCESS;
    
        err = nrfx_ppi_channel_alloc(&mm_ppi_ch0);
        err = nrfx_ppi_channel_assign(mm_ppi_ch0, mm_rtc_address_event_comp0(), mm_saadc_address_task_start());
        err = nrfx_ppi_channel_fork_assign(mm_ppi_ch0, mm_rtc_address_task_clear());
        err = nrfx_ppi_channel_enable(mm_ppi_ch0);
    
        err = nrfx_ppi_channel_alloc(&mm_ppi_ch2);
        err = nrfx_ppi_channel_assign(mm_ppi_ch2, mm_saadc_address_event_started(), mm_saadc_address_task_sample());
        err = nrfx_ppi_channel_enable(mm_ppi_ch2);
    }

    Further development:

    I might need to add more SAADC channels to the project, but as long as the configurations are done correctly, this solution should work with minimal modifications.

    Thank you.

  • Hi 

    Good to hear that the example was helpful, and thanks for sharing the specifics you needed to change in order to make it work. 

    Triggering SAMPLE will not invoke START unfortunately, so START has to be called first. 

    Best regards
    Torbjørn

Related