DMA in nrf52840 QFAA

Hi,

We are using nrf52840 QFAA with our amplifier that gives ADC values at 100hz. We need to process the data via low pass, band pass filters, median filter, moving average filter, S Goley and FFT filter.

As there is a long list of processing, we need DMA for the ADC so that processor is available for all this work.

Do you have an example on how to implement DMA  on ADC?

How to increase the clock speed to maximum?

Will using nrf5340 be a better option?

Regards 

Vivek

  • Hello,

    If you want this to happen as autonomous as possible, then you need to look at using the low level nrfx driver (not zephyr drivers):
    https://github.com/zephyrproject-rtos/hal_nordic/tree/master/nrfx 
    https://github.com/zephyrproject-rtos/hal_nordic/tree/master/nrfx/samples/src/nrfx_saadc
    https://github.com/zephyrproject-rtos/hal_nordic/tree/master/nrfx/samples/src/nrfx_saadc/boards 

    In terms how to set it up in specific for sampling you can refer to this example (written for the old nRF5 SDK though):
    https://github.com/NordicPlayground/nRF52-ADC-examples/tree/master/nrfx_saadc_continuous_sampling 

    Not possible to change clock frequency, the nRF52840 is a good choice.

    Kenneth

  • Hi Kenneth,

    We currently have the below setup using saadc api but are unsure about the DMA enabling. So will you please check and let me know if the below configurations and the code is enough to be able to get the DMA enabled for ADC sampling.

    prj.conf with configurations for ADC

    CONFIG_NRFX_SAADC=y
    CONFIG_NRFX_PPI=y
    CONFIG_NRFX_TIMER2=y
    CONFIG_NRFX_TIMER3=y
    CONFIG_NRFX_DPPI=y
    CONFIG_NRFX_EGU0=y
    # CONFIG_ADC=y
    CONFIG_ADC_ASYNC=y
    CONFIG_DMA=y
    
    CONFIG_ASSERT=y
    CONFIG_RESET_ON_FATAL_ERROR=n
    
    CONFIG_NRFX_TIMER1=y

    adc_read.c file where adc_init() is triggered from main()

    #include "adc_read.h"
    #include <zephyr/kernel.h>
    #include <nrfx_saadc.h>
    #include <nrfx_timer.h>
    #include <helpers/nrfx_gppi.h>
    #if defined(DPPI_PRESENT)
    #include <nrfx_dppi.h>
    #else
    #include <nrfx_ppi.h>
    #endif
    
    #define SAADC_SAMPLE_INTERVAL_MS 50  // 20 samples per second
    #define TIMER_INST_IDX 2
    #define SAADC_BUFFER_SIZE 2  // Size of each buffer (2 channels)
    
    #define LOG_MODULE_NAME ADC_READ
    LOG_MODULE_REGISTER(LOG_MODULE_NAME);
    
    // Two buffers for double buffering
    static int16_t saadc_sample_buffer[2][SAADC_BUFFER_SIZE];
    static uint32_t saadc_current_buffer = 0;  // Track which buffer is current
    
    uint16_t rawData[512];
    int dataCount = 0;
    static int16_t sleep_sense_raw;
    static int16_t batt_raw;
    
    // Timer instance
    static const nrfx_timer_t timer_inst = NRFX_TIMER_INSTANCE(TIMER_INST_IDX);
    static uint8_t ppi_sample_channel;
    static uint8_t ppi_start_channel;  // For END to START connection
    
    static K_SEM_DEFINE(adc_init_ok, 0, 1);  // Semaphore for ADC Initialization
    
    static void saadc_event_handler(nrfx_saadc_evt_t const * p_event)
    {
        nrfx_err_t err;
    	static bool alternate = false;
        
        switch (p_event->type) {
            case NRFX_SAADC_EVT_READY:
                // LOG_INF("SAADC event NRFX_SAADC_EVT_READY called");
                // Start the timer to begin sampling
                break;
                
            case NRFX_SAADC_EVT_BUF_REQ:
                // LOG_INF("SAADC event NRFX_SAADC_EVT_BUF_REQ called");
                // Set up the next available buffer. Alternate between buffer 0 and 1
                err = nrfx_saadc_buffer_set(saadc_sample_buffer[(saadc_current_buffer++) % 2], SAADC_BUFFER_SIZE);
                if (err != NRFX_SUCCESS) {
                    LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
                    return;
                }
                break;
                
            case NRFX_SAADC_EVT_DONE:
                // LOG_INF("SAADC event NRFX_SAADC_EVT_DONE called");
                // Process the filled buffer
    			sleep_sense_raw = ((int16_t *)p_event->data.done.p_buffer)[0];
    			batt_raw = ((int16_t *)p_event->data.done.p_buffer)[1];
                
                if (alternate == false)
    			{
    				rawData[dataCount++] = sleep_sense_raw;
    				// process_filter(sleep_sense_raw);
    				alternate = true;
    			} else {
    				alternate = false;
    			}
    			// LOG_WRN("dataCount:%d",dataCount);
                break;
                
            case NRFX_SAADC_EVT_FINISHED:
                LOG_ERR("SAADC event finished");
                break;
                
            default:
                LOG_INF("Other SAADC event: %d", p_event->type);
                break;
        }
    }
    
    static void adc_configure(void)
    {
        nrfx_err_t err_code;
    
        // Connect IRQs
        IRQ_CONNECT(DT_IRQN(DT_NODELABEL(adc)),
                    DT_IRQ(DT_NODELABEL(adc), priority),
                    nrfx_isr, nrfx_saadc_irq_handler, 0);
        
        // Initialize SAADC with default configuration
        err_code = nrfx_saadc_init(DT_IRQ(DT_NODELABEL(adc), priority));
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_saadc_init error: %08x", err_code);
            return;
        }
        
        // Configure channel 0 - SLEEP_SENS
        nrfx_saadc_channel_t channel_0 = NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN2, 0);
        channel_0.channel_config.gain = NRF_SAADC_GAIN1_4;
        channel_0.channel_config.reference = NRF_SAADC_REFERENCE_VDD4;
        
        // Configure channel 1 - BAT_MON_A
        nrfx_saadc_channel_t channel_1 = NRFX_SAADC_DEFAULT_CHANNEL_SE(NRF_SAADC_INPUT_AIN3, 1);
        channel_1.channel_config.gain = NRF_SAADC_GAIN1_6;
        channel_1.channel_config.reference = NRF_SAADC_REFERENCE_INTERNAL;
        
        // Configure both channels
        nrfx_saadc_channel_t channels[] = {channel_0, channel_1};
        err_code = nrfx_saadc_channels_config(channels, 2);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_saadc_channels_config error: %08x", err_code);
            return;
        }
        
        // Configure SAADC in advanced mode
        nrfx_saadc_adv_config_t adv_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;
        err_code = nrfx_saadc_advanced_mode_set(BIT(0) | BIT(1), 
                                               NRF_SAADC_RESOLUTION_12BIT,
                                               &adv_config,
                                               saadc_event_handler);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_saadc_advanced_mode_set error: %08x", err_code);
            return;
        }
        
        // Set both buffers for double buffering
        err_code = nrfx_saadc_buffer_set(saadc_sample_buffer[0], SAADC_BUFFER_SIZE);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_saadc_buffer_set error: %08x", err_code);
            return;
        }
        
        err_code = nrfx_saadc_buffer_set(saadc_sample_buffer[1], SAADC_BUFFER_SIZE);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_saadc_buffer_set error: %08x", err_code);
            return;
        }
    
        // Trigger the SAADC
        err_code = nrfx_saadc_mode_trigger();
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_saadc_mode_trigger error: %08x", err_code);
            return;
        }
    }
    
    static void timer_init(void)
    {
        nrfx_err_t err_code;
        
        // Configure timer with 1MHz frequency (1us resolution)
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(1000000);
        timer_config.bit_width = NRF_TIMER_BIT_WIDTH_32;
        
        err_code = nrfx_timer_init(&timer_inst, &timer_config, NULL);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_timer_init error: %08x", err_code);
            return;
        }
        
        // Clear the timer
        nrfx_timer_clear(&timer_inst);
        
        // Calculate ticks for desired interval
        uint32_t ticks = nrfx_timer_ms_to_ticks(&timer_inst, SAADC_SAMPLE_INTERVAL_MS);
        
        // Set up timer to clear on compare match to create recurring timer
        nrfx_timer_extended_compare(&timer_inst, 
                                   NRF_TIMER_CC_CHANNEL0, 
                                   ticks, 
                                   NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, 
                                   false); // false = no interrupts
    }
    
    static void setup_ppi(void)
    {
        nrfx_err_t err_code;
        
        // Allocate PPI channel for timer to SAADC sample task
        err_code = nrfx_gppi_channel_alloc(&ppi_sample_channel);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_gppi_channel_alloc error: %08x", err_code);
            return;
        }
        
        // Allocate PPI channel for SAADC END to START task
        err_code = nrfx_gppi_channel_alloc(&ppi_start_channel);
        if (err_code != NRFX_SUCCESS) {
            LOG_ERR("nrfx_gppi_channel_alloc error: %08x", err_code);
            return;
        }
        
        // Connect timer compare event to SAADC sample task
        nrfx_gppi_channel_endpoints_setup(
            ppi_sample_channel,
            nrfx_timer_event_address_get(&timer_inst, NRF_TIMER_EVENT_COMPARE0),
            nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE));
        
        // Connect SAADC END event to START task for automatic buffer switching
        nrfx_gppi_channel_endpoints_setup(
            ppi_start_channel,
            nrf_saadc_event_address_get(NRF_SAADC, NRF_SAADC_EVENT_END),
            nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_START));
        
        // Enable both PPI channels
        nrfx_gppi_channels_enable(BIT(ppi_sample_channel) | BIT(ppi_start_channel));
    }
    
    void adc_init(void)
    {
        // Initialize peripherals
    	timer_init();
        adc_configure();
        setup_ppi();
    
        k_sem_give(&adc_init_ok);  // Activate the ADC sampling thread
        LOG_INF("ADC Initialized Successfully");
    }
    
    void adc_sampling_thread(void)
    {
        uint32_t count = 0;
        LOG_WRN("Waiting for ADC initialization...");
        
        /* Wait for ADC to be ready */
        k_sem_take(&adc_init_ok, K_FOREVER);
    
    	nrfx_timer_enable(&timer_inst);
        
        // Initialize filter (assuming this is defined elsewhere)
        init_filter();
        
        printk("ADC sampling started\n");
        
        // Enter main loop
        while (1) {
            // Process collected samples
            for(int i = 0; i < dataCount; i++) {
                process_filter(rawData[i]);
    			printk("Sample : %u\n", count++);
            }
            dataCount = 0;
    
            
            // Sleep briefly to allow other tasks to run
            k_sleep(K_MSEC(10));
        }
        
        return;
    }
    
    
    #define ADC_SAMPLING_THREAD_STACKSIZE 1024*8
    #define ADC_SAMPLING_THREAD_PRIORITY 7
    
    K_THREAD_DEFINE(adc_sampling_thread_id, ADC_SAMPLING_THREAD_STACKSIZE, adc_sampling_thread, NULL, NULL,
        ADC_SAMPLING_THREAD_PRIORITY, 6, 0, 0);

    As we need DMA enabled as we want the sampling part to be done separately i.e. without CPU involvment, so is our current code and configurations doing that OR we need to modify anything ?

    Regards 

    Vivek

  • Hi Vivek,

    This looks okey. FYI: The saadc also have a built in timer that can be used, then you avoid the ppi setup that you are doing from an external timer, but no problem to do it either way.

    Kenneth

Related