This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Offset in SAADC samples with Easy DMA and BLE

I have a nrf52 application that samples four saadc channels at 1kHZ. That is: Map four pins to ADC input and let Easy DMA take care of the sampling so that the data is processed ten times a second (100 * 4 samples). This works pretty well, except...

When I enable the BLE connection, the data is shifted in the buffer. Without BLE enabled, the data layout in the memory is as following {{1,2,3,4}, {1,2,3,4}, ...}. But, when BLE is activated, the memory layout is: {{4,1,2,3}, {4,1,2,3}, ...} I really don't know what causes the difference. I have no way to check if the data is shifted, or did the samples just swap places. I wonder if the softdevice blocks some of the samples that would cause the problem.

The saadc implementation is double buffered, like in "saadc_sample_from_two_pins - scan mode" here

The BLE implementation is based on ble_app_hrs_freertos in SDK 12.1.0. That is also the SDK version I'm using.

Any help would be appreciated.

Parents
  • Hi,

    There have actually been some progress with this issue. The reason why this swap/shift in the buffer occurs, is related to how samples are triggered, and how events are handled.

    The EasyDMA chapter in the SAADC documentation show the order of tasks and events for proper operation.

    The problem arise when the SAADC is configured in continuous mode using PPI to trigger the SAMPLE task at a regular interval, while the END event and START task is handled by CPU interrupt. When the SAMPLE task is triggered, each channel is sampled and written to RAM with DMA as fast as possible. When the buffer have been filled, the DMA transfer will be delayed until the START task have been triggered. You are triggering the START task in the interrupt handler after receiving the END event. If you receive the END event when IRQ is disabled or an interrupt with higher priority is executing, the triggering of the START task can get delayed until after the SAMPLE task have been triggered using PPI. Triggering of the SAMPLE task will generate a DMA transfer request, but this request will not be acknowledged until the START task have been triggered. The scan cycle of the SAADC will however expect the DMA transfer to finish, and will sample next channel. When the START task is triggered, the pending DMA transfer will be executed, but the transferred sample will correcpond to the latest sampled channel. Samples from previous channels will have been lost.

    There are two possible solutions to this problem:

    1. Use PPI to trigger START task on an END event. This will avoid the delayed triggering og the START task due to a queued interrupt generated by the END event, but in the case of high sample frequency and long delays, it can cause your buffer to be overwritten before you are able to process the buffer. In case of using this solution, it is neccessary to use double buffering and large enough buffers to avoid data loss.
    2. Trigger sampling from CPU interrupt. If the SAMPLE task is triggered from an interrupt with lower priority than the SAADC IRQ handler, the START task will always be triggered between an END event and a new SAMPLE task. This solution will make the sampling vary a bit in time, as higher priority tasks can cause the triggering of SAMPLE task to be delayed.

    This is a typical case of hard real-time requirements, that cannnot be guaranteed with a task/event based system. Since many users are experiencing this issue, we will try to update the documentation to make this requirement more visible.

    Best regards,

    Jørgen

  • Are you able to elaborate on the code required to implement the first solution? I have tried the code below as a first attempt, but it hasn't resolved the issue and this is probably because I haven't interpreted the description of the first solution correctly.

    err_code = saadc_init();
    APP_ERROR_CHECK(err_code);
    
    nrf_drv_timer_config_t timer_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
    err_code = nrf_drv_timer_init(&m_timer, &timer_config, timer_handler);
    RETURN_IF_ERROR(err_code);
    
    /* setup m_timer for compare event */
    uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, meas_interval_ms);
    nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    nrf_drv_timer_enable(&m_timer);
    
    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
    uint32_t saadc_sample_event_addr = nrf_drv_saadc_sample_task_get();
    
    err_code = nrf_drv_ppi_init();
    RETURN_IF_ERROR(err_code);
    
    /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, saadc_sample_event_addr);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel0);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_enable(m_ppi_channel0);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel0, nrf_saadc_event_address_get(NRF_SAADC_EVENT_END), nrf_saadc_task_address_get(NRF_SAADC_TASK_START));
    RETURN_IF_ERROR(err_code);
    
Reply
  • Are you able to elaborate on the code required to implement the first solution? I have tried the code below as a first attempt, but it hasn't resolved the issue and this is probably because I haven't interpreted the description of the first solution correctly.

    err_code = saadc_init();
    APP_ERROR_CHECK(err_code);
    
    nrf_drv_timer_config_t timer_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
    err_code = nrf_drv_timer_init(&m_timer, &timer_config, timer_handler);
    RETURN_IF_ERROR(err_code);
    
    /* setup m_timer for compare event */
    uint32_t ticks = nrf_drv_timer_ms_to_ticks(&m_timer, meas_interval_ms);
    nrf_drv_timer_extended_compare(&m_timer, NRF_TIMER_CC_CHANNEL0, ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
    nrf_drv_timer_enable(&m_timer);
    
    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&m_timer, NRF_TIMER_CC_CHANNEL0);
    uint32_t saadc_sample_event_addr = nrf_drv_saadc_sample_task_get();
    
    err_code = nrf_drv_ppi_init();
    RETURN_IF_ERROR(err_code);
    
    /* setup ppi channel so that timer compare event is triggering sample task in SAADC */
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_enable(m_ppi_channel);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel, timer_compare_event_addr, saadc_sample_event_addr);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channel0);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_enable(m_ppi_channel0);
    RETURN_IF_ERROR(err_code);
    
    err_code = nrf_drv_ppi_channel_assign(m_ppi_channel0, nrf_saadc_event_address_get(NRF_SAADC_EVENT_END), nrf_saadc_task_address_get(NRF_SAADC_TASK_START));
    RETURN_IF_ERROR(err_code);
    
Children
No Data
Related