Reading samples from external ADC using SPIM with EasyDMA arrayList

Hi,

I am trying to read samples from an external ADC at 100kHz sampling rate using the SPIM peripheral. I want to take a window of 2000 samples then can pause for processing. It is important that the sampling rate is always 100kHz and that I don't miss any samples.

The ADC samples are 2 bytes and are read after toggling the ADC's conversion start pin (CNVST) low. I am switching the CNVST pin low every 10us using PPI, Timer and GPIOTE. This works reliably and I am able to get samples periodically by reading the data in the SPI buffer in the SPI interrupt handler like below. After I reach 2000 samples (ADC_QUE_SIZE) I stop and process the data.

void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                       void *                    p_context)
{
    if(count==ADC_QUE_SIZE)
    {
      spi_sampling_event_disable();
      spi_xfer_done = true;
    }
    else
    {
      adc[count++] = ((uint16_t)(m_rx_buf[0]<<8) | m_rx_buf[1]);
    }
}

The method above requires CPU intervention in the interrupt handler, the problem is when I have an active Bluetooth connection this interrupt handler can get delayed and I miss samples by the time the SPI reads can resume.

I am trying to implement a sampling method using an array list as discussed in this this post. My idea is to have 2000* 2-byte buffers that will be sequentially filled without requiring the CPU but I am not getting any data. It is my understanding that that the max SPI buffer is 155 bytes, but can I have more than this number of buffers in the arrayList for EasyDMA?

Here is my code trying to do this, adapted from this answer

#define ADC_QUE_SIZE    2000

typedef struct ArrayList
{  
    uint8_t rx_buffer[2];
}ArrayList_type;

static ArrayList_type p_rx_buffer[ADC_QUE_SIZE];

void spi_config(void)
{
    NRF_SPIM0->RXD.MAXCNT = 2;
    NRF_SPIM0->RXD.PTR = (int32_t) &p_rx_buffer;
    NRF_SPIM0->RXD.LIST = SPIM_RXD_LIST_LIST_ArrayList;

    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
    spi_config.mode     = NRF_DRV_SPI_MODE_1;
    spi_config.frequency = NRF_DRV_SPI_FREQ_8M;
    
    spi_config.ss_pin   = NRF_DRV_SPI_PIN_NOT_USED;
    spi_config.miso_pin = SPI_MISO_PIN;
    spi_config.mosi_pin = NRF_DRV_SPI_PIN_NOT_USED;
    spi_config.sck_pin  = SPI_SCK_PIN;
    APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, NULL, NULL)); 
      
    memset(p_rx_buffer, 0, sizeof(p_rx_buffer));

    nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(m_tx_buf, m_length, (uint8_t*)p_rx_buffer, 2);
    
    uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER    |
                     NRF_DRV_SPI_FLAG_REPEATED_XFER|
                     NRF_DRV_SPI_FLAG_RX_POSTINC   |
                     NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER;
    
    ret_code_t ret = nrf_drv_spi_xfer(&spi, &xfer, flags); 
}

void timer_count_handler(nrf_timer_event_t event_type, void * p_context)
{
    switch (event_type)
    {
        case NRF_TIMER_EVENT_COMPARE0:
            spi_sampling_event_disable();
            spi_xfer_done = true;
            break;

        default:
            //Do nothing.
            break;
    }
}

void spi_sampling_event_init(void)
{
    ret_code_t err_code;
    nrf_drv_gpiote_out_config_t config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);  
    err_code = nrf_drv_gpiote_out_init(GPIO_OUTPUT_PIN_NUMBER, &config);
    
    // Fs = 100kHz: cc_value = 159. With interrupt disabled (false)
    nrf_drv_timer_extended_compare(&timer_adc, NRF_TIMER_CC_CHANNEL0, 159, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);

    // Compare event after 2000 samples with interrupt enabled (true)
    nrf_drv_timer_extended_compare(&timer_count, NRF_TIMER_CC_CHANNEL0, 2000, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);

    uint32_t gpiote_task_addr = nrf_drv_gpiote_out_task_addr_get(GPIO_OUTPUT_PIN_NUMBER);
        
    uint32_t spi_start_task_addr   = nrf_drv_spi_start_task_get(&spi);
    uint32_t spi_end_evt_addr = nrf_drv_spi_end_event_get(&spi);

    uint32_t timer_compare_event_addr = nrf_drv_timer_compare_event_address_get(&timer_adc, NRF_TIMER_CC_CHANNEL0);

    uint32_t timer_count_task = nrf_drv_timer_task_address_get(&timer_count, NRF_TIMER_TASK_COUNT);
    uint32_t timer_cc_event = nrf_drv_timer_event_address_get(&timer_count,  NRF_TIMER_EVENT_COMPARE0);

    err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_1);
    err_code = nrf_drv_ppi_channel_assign(ppi_channel_1, timer_compare_event_addr, gpiote_task_addr);
    err_code = nrf_drv_ppi_channel_fork_assign(ppi_channel_1, spi_start_task_addr);	
    																			 																		 
    err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_2);
    err_code = nrf_drv_ppi_channel_assign(ppi_channel_2, spi_end_evt_addr, gpiote_task_addr);
    err_code = nrf_drv_ppi_channel_fork_assign(ppi_channel_2, timer_count_task);
}

void spi_sampling_event_enable(void)
{
    ret_code_t err_code;
    nrf_drv_gpiote_out_task_enable(GPIO_OUTPUT_PIN_NUMBER);
    err_code = nrf_drv_ppi_channel_enable(ppi_channel_1);
    err_code = nrf_drv_ppi_channel_enable(ppi_channel_2);
    nrf_drv_timer_enable(&timer_adc);
    nrf_drv_timer_enable(&timer_count);

    // Configure short between spi end event and spi start task
    nrf_spim_shorts_enable(spi.u.spim.p_reg, NRF_SPIM_SHORT_END_START_MASK);
    nrf_spim_task_trigger(spi.u.spim.p_reg, NRF_SPIM_TASK_START);
}

void spi_sampling_event_disable(void)
{
    ret_code_t err_code;
    nrf_drv_gpiote_out_task_disable(GPIO_OUTPUT_PIN_NUMBER);
    err_code = nrf_drv_ppi_channel_disable(ppi_channel_1);
    err_code = nrf_drv_ppi_channel_disable(ppi_channel_2);
    nrf_drv_timer_disable(&timer_adc);
    nrf_drv_timer_disable(&timer_count);

    // Configure short between spi end event and spi start task
    nrf_spim_shorts_disable(spi.u.spim.p_reg, NRF_SPIM_SHORT_END_START_MASK);
}

The data in rx_buffer is never written to and stays 0, from the memset to 0 in the spi_config function.

It would be great if someone could point me toward some similar examples as I find the documentation on EasyDMA array lists quite cryptic.

Many thanks,

Daragh

Specs:

nRF52832QFAA on custom PCB interfacing with MAX11198 ADC (16bit)

Segger Embedded Studio v6.30 with nRF5_SDK_17.1.0

Parents
  • Hi Daragh

    Do you mind sharing the entire example? 

    I tried to build it on my end, but it seems various defines and variables are missing. 

    In general it should be possible to set up the arraylist to sample continuously, while allowing you to read out the first half of the buffer once the buffers are more than half full. 

    Best regards
    Torbjørn

  • Hi Torbjørn,

    That code was part of a bigger project so I have copied the relevant parts into a clean project derived from the SPI example in the SDK. Please find attached main.c and sdk_config.h, it should hopefully work if you replace the main and sdk_config in the SPI example. I have modified to only take 5 samples in a row from the ADC rather than 2000 to start with. 

    I have set a flag on line 50, array_list_mode, if set to false it samples the adc using a single 2 byte rx buffer and an interrupt handler at the end of every spi transaction to read the values in the buffer and save to another variable. This method works and I am able to get correct samples from the ADC.

    If you set the flag to true the code is meant to sample the adc without cpu intervention by using the easydma list array method with automatic buffer incrementing (NRF_DRV_SPI_FLAG_RX_POSTINC). A PPI channel is used to increment a counter at every SPI end event and when the counter reaches NUM_SAMPLES (5) it's interrupt handler is meant to stop the SPI sampling and process the data in the main loop.

    In this mode I noticed that the code gets stuck inside line 151 of main ( nrf_drv_spi_xfer(&spi0, &xfer, flags) ). I followed through the code and see it gets stuck inside nrfx_spim.c line 518 (function is nrf_spim_event_check, see photo for context). 

    Am I doing something wrong with the SPI initialization when not using an interrupt handler? 

    For anyone looking for examples related to this, I found a very helpful github repo that shows how to interface with an IMU using TWI and easy dma.

    Thanks

    #include "nrf_drv_spi.h"
    #include "nrf_drv_timer.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_gpiote.h"
    #include "app_util_platform.h"
    #include "nrf_gpio.h"
    #include "nrf_delay.h"
    #include "boards.h"
    #include "app_error.h"
    #include <string.h>
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define CNVST_GPIOTE_PIN 31 // conversion start pin on ADC
    #define NUM_SAMPLES      5  // number of samples to read in a window
    
    #define SPI_INSTANCE0  0 /**< SPI instance index. */
    static const nrf_drv_spi_t spi0 = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE0);  /**< SPI instance. */
    
    static volatile bool spi_xfer_done;  /**< Flag used to indicate that SPI instance completed the transfer. */
    
    static nrf_drv_timer_t timer_adc     = NRF_DRV_TIMER_INSTANCE(1); // timer for adc sampling rate
    static nrf_drv_timer_t timer_counter = NRF_DRV_TIMER_INSTANCE(2); // timer for counting samples read
    
    // Variable holding PPI channel number
    nrf_ppi_channel_t ppi_channel_spi_start;
    nrf_ppi_channel_t ppi_channel_spi_end;
    nrf_ppi_channel_t ppi_channel_spi_count;
    
    #define TEST_STRING "Hi"
    static uint8_t       m_tx_buf[] = TEST_STRING;           /**< TX buffer. */
    static uint8_t       m_rx_buf[2];    /**< RX buffer. */
    static const uint8_t m_length = sizeof(m_rx_buf);        /**< Transfer length. */
    
    // Define a type with a two dimensional array for easyDMA to use for SPI rx
    typedef struct ArrayList
    {
        uint8_t buffer[2];
    }array_list_t;
    
    array_list_t rx_buf_array[NUM_SAMPLES];
    
    uint16_t count = 0; // count how many samples have been read
    uint16_t samples[NUM_SAMPLES];
    float    samples_volts[NUM_SAMPLES];
    
    const float LSB = 5.0/65536.0; // LSB for ADC 16bit sample to voltage conversion
    
    bool array_list_mode = true;
    
    void spi_sampling_enable(void)
    {
        ret_code_t err_code;
    
        // Enable sampling SPI and toggling CNVST pin
        nrf_drv_gpiote_out_task_enable(CNVST_GPIOTE_PIN);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_spi_start);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_spi_end);
        err_code = nrf_drv_ppi_channel_enable(ppi_channel_spi_count);
        nrf_drv_timer_enable(&timer_adc);
        if (array_list_mode)
        {
            nrf_drv_timer_enable(&timer_counter);
        }
    }
    
    void spi_sampling_disable(void)
    {
        ret_code_t err_code;
        
        // Disable sampling SPI and toggling CNVST pin
        nrf_drv_gpiote_out_task_disable(CNVST_GPIOTE_PIN);
        err_code = nrf_drv_ppi_channel_disable(ppi_channel_spi_start);
        err_code = nrf_drv_ppi_channel_disable(ppi_channel_spi_end);
        err_code = nrf_drv_ppi_channel_disable(ppi_channel_spi_count);
        nrf_drv_timer_disable(&timer_adc);
        if (array_list_mode)
        {
            nrf_drv_timer_disable(&timer_counter);
        }
    }
    
    /**
     * @brief SPI user event handler.
     * @param event
     */
    void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                           void *                    p_context)
    {
        if(count==NUM_SAMPLES)
        {
            spi_sampling_disable();
            spi_xfer_done = true;
        }
        else
        {
            samples[count] = ((uint16_t)(m_rx_buf[0]<<8) | m_rx_buf[1]);
            count++;
        }
    }
    
    // SPI transfer start handler for timer_adc. Not used
    void timer_adc_dummy_handler(nrf_timer_event_t event_type, void * p_context)
    { // Do nothing
    }
    
    void timer_count_handler(nrf_timer_event_t event_type, void * p_context)
    {
        spi_sampling_disable();
        spi_xfer_done = true;
    }
    
    void spi_sampling_init(void)
    {
        printf("a\n");
    
        ret_code_t err_code;
    
        nrf_drv_gpiote_out_config_t gpiote_config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(true);  
        err_code = nrf_drv_gpiote_out_init(CNVST_GPIOTE_PIN, &gpiote_config);
        
        // Initialise rx buffer as all 0
        memset(m_rx_buf, 0, m_length);
        memset(rx_buf_array, 0, NUM_SAMPLES*2);
    
        nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
        spi_config.mode     = NRF_DRV_SPI_MODE_1;
        spi_config.frequency = NRF_DRV_SPI_FREQ_8M;
        spi_config.ss_pin   = NRF_DRV_SPI_PIN_NOT_USED;
        spi_config.miso_pin = 30; //SPI_MISO_PIN;
        spi_config.mosi_pin = NRF_DRV_SPI_PIN_NOT_USED;
        spi_config.sck_pin  = 28; //SPI_SCK_PIN;
    
        // Set up next transfer sequence
        if (array_list_mode)
        {
            APP_ERROR_CHECK(nrf_drv_spi_init(&spi0, &spi_config, NULL, NULL));
            nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(m_tx_buf, m_length, (uint8_t*)rx_buf_array, m_length);
            
            printf("b\n");
            // Set flags for next transfer sequence
             uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER     |
                              NRF_DRV_SPI_FLAG_REPEATED_XFER |
                              NRF_DRV_SPI_FLAG_RX_POSTINC    |
                              NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER;
            printf("c\n");
    
            // Pass configurations and flags to SPIM driver.
            // The SPIM is now on and prepared, but not actually started.
            err_code = nrf_drv_spi_xfer(&spi0, &xfer, flags);
            
            APP_ERROR_CHECK(err_code);
        }
        else
        {
            APP_ERROR_CHECK(nrf_drv_spi_init(&spi0, &spi_config, spi_event_handler, NULL));
            nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(m_tx_buf, m_length, m_rx_buf, m_length);
            
            // Set flags for next transfer sequence
            uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER |
                         NRF_DRV_SPI_FLAG_REPEATED_XFER;
        
            // Pass configurations and flags to SPIM driver.
            // The SPIM is now on and prepared, but not actually started.
            err_code = nrf_drv_spi_xfer(&spi0, &xfer, flags);
            APP_ERROR_CHECK(err_code);
        }
    
        nrf_drv_timer_config_t timer_adc_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_adc_cfg.frequency = NRF_TIMER_FREQ_16MHz;
        // Initializing the timer triggering the SPI transfers. 
        err_code = nrf_drv_timer_init(&timer_adc, &timer_adc_cfg, timer_adc_dummy_handler);
        APP_ERROR_CHECK(err_code);
        
        // Sample every 10 us (100 kHz)
        // uint32_t adc_sample_period = nrf_drv_timer_us_to_ticks(&timer_instance_twi_start, 10); 
        uint32_t adc_sample_period = 159;
    
        // Configuring the timer triggering the TWI transfers
        nrf_drv_timer_extended_compare(&timer_adc, NRF_TIMER_CC_CHANNEL0, adc_sample_period, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);
        
        uint32_t gpiote_task_addr = nrf_drv_gpiote_out_task_addr_get(CNVST_GPIOTE_PIN);    
        uint32_t timer_start_compare_event_addr = nrf_drv_timer_compare_event_address_get(&timer_adc, NRF_TIMER_CC_CHANNEL0);
        uint32_t spi_start_task_addr   = nrf_drv_spi_start_task_get(&spi0);
        uint32_t spi_end_evt_addr = nrf_drv_spi_end_event_get(&spi0);
        
        // Allocate PPI channel to toggle on CNVST pin on timer_adc events (100kHz)
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_spi_start);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel_spi_start, timer_start_compare_event_addr, gpiote_task_addr);
        APP_ERROR_CHECK(err_code);
    
        // Use same PPI channel to start SPI task on same timer_adc event
        err_code = nrf_drv_ppi_channel_fork_assign(ppi_channel_spi_start, spi_start_task_addr);
        APP_ERROR_CHECK(err_code);
        				
        // Allocate another PPI channel to toggle off CNVST pin at SPI end event                     															 																		 
        err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_spi_end);
        APP_ERROR_CHECK(err_code);
        err_code = nrf_drv_ppi_channel_assign(ppi_channel_spi_end, spi_end_evt_addr, gpiote_task_addr);
        APP_ERROR_CHECK(err_code);
    
        if (array_list_mode)
        {
            // set up another timer in counter mode for counting SPI transfers
            nrf_drv_timer_config_t counter_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
            counter_config.mode = NRF_TIMER_MODE_COUNTER;
            // Initializing the SPI counter. 
            err_code = nrf_drv_timer_init(&timer_counter, &counter_config, timer_count_handler);
            APP_ERROR_CHECK(err_code);
            
            nrf_drv_timer_extended_compare(&timer_counter, NRF_TIMER_CC_CHANNEL0, NUM_SAMPLES, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, true);
            
            uint32_t task_address_counter_inc = nrf_drv_timer_task_address_get(&timer_counter, NRF_TIMER_TASK_COUNT);
    
            // Add another PPI channel for counting SPI transfers - increment counter on timer_adc events
            err_code = nrf_drv_ppi_channel_alloc(&ppi_channel_spi_count);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_drv_ppi_channel_assign(ppi_channel_spi_count, spi_end_evt_addr, task_address_counter_inc);
            APP_ERROR_CHECK(err_code);
        }
    }
    
    /**
     * @brief ADC conversion to voltage.
     * @param ADC value
     */
    float convertToVoltage(uint16_t ADC)
    {
      uint16_t df;
      float xdoffv;
      df = (1<<15);
      
      if(ADC & df)
      {
            uint16_t temp1, temp2;
            temp1 = ~(ADC) + (uint16_t)1;       
            xdoffv = -1 * (temp1 * LSB);        
      }
      else
      {
            xdoffv = (float)ADC * LSB;
      }
      return xdoffv;
    }
    
    
    int main(void)
    {
        ret_code_t err_code;
        uint8_t timer_count_value;
    
        APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        err_code = nrf_drv_ppi_init();
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_gpiote_init();
        APP_ERROR_CHECK(err_code);
    
        printf("start\n");
        //nrf_delay_ms(100);
    
        spi_sampling_init();
    
        spi_sampling_enable();
    
        printf("set up done\n");
    
        while (1)
        {
            /*
            // check if timer counter is ever incrementing
            NRF_TIMER2->TASKS_CAPTURE[0] = 1; 
            timer_count_value = NRF_TIMER2->CC[0];
            NRF_TIMER2->TASKS_CAPTURE[0] = 0;
            printf("count = %d\n", timer_count_value);
            nrf_delay_ms(100);
            */
    
            if(spi_xfer_done == true)
            {
                for(int i = 0; i < NUM_SAMPLES; i++)
                {
                    //printf("%x %x\n", rx_buf_array[i].buffer[0], rx_buf_array[i].buffer[1]);
                    //samples[i] = ((uint16_t)(rx_buf_array[i].buffer[0]<<8) | rx_buf_array[i].buffer[1]);
                    samples_volts[i] = convertToVoltage(samples[i]);
                    printf("%f\n", samples_volts[i]);
                }
                nrf_delay_ms(5);
                
                // reset sampling
    
                if (array_list_mode)
                {
                    // Set up next transfer sequence
                    nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(m_tx_buf, m_length, (uint8_t*)rx_buf_array, m_length);
       
                    // Set flags for next transfer sequence
                    uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER           |
                                     NRF_DRV_SPI_FLAG_RX_POSTINC          |
                                     NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER |
                                     NRF_DRV_SPI_FLAG_REPEATED_XFER;
        
                    // Pass configurations and flags to SPIM driver.
                    // The SPIM is now on and prepared, but not actually started.
                    err_code = nrf_drv_spi_xfer(&spi0, &xfer, flags);
                    APP_ERROR_CHECK(err_code);
                }
    
                count = 0;
                spi_xfer_done = false;
                spi_sampling_enable();
                printf("\n\n");
            }
        }
    }
    

    83671.sdk_config.h

  • Hi Daragh

    I had a discussion with one of the driver developers, and we weren't able to conclusively decide if there is a way to update the array list buffers in a double buffered manner, so that you can have the SPIM peripheral jump from one list of buffers to the next automatically while you can spend time reading out the first list of buffers. 

    I have an idea to a system where a timer is set up in counter mode to count the number of samples, so that you can process the first half of the buffer list once the half point is reached. Then at some point when the sampling gets close to the end of the list (without actually reaching it) you need to reset the SPIM peripheral to the start of the buffer. 

    The problem of this method is that the reset operation might be delayed by an interrupt, which means you need to check how many samples were actually written to the buffer before processing the samples (using the counter described earlier). 

    I will try to put together a small proof of concept example later this week to see if this approach is valid. 

    Best regards
    Torbjørn

  • Hi Torbjørn,

    Thanks a lot for your excellent information.

    I’m working on nRF52832 with EasyDMA, with the chip ADS8666. I saw your answers in this link

    In my application, my tasks are:

    In the process of 20 seconds, I will do the period timer of 10 ms:

       . Reading data from ADS in EasyDMA (with 200 bytes total)

       . Sending 200 bytes to a BLE master

    Can you advise how to do my tasks?

    I’m interested in your method of “process the first half of the buffer list once the half point is reached”. How can I do this, and do you have an example of this work?

    Best,

    Jack

  • Hi Jack

    You mean to say you need to read 200 bytes over SPI every 10ms, and then send it over BLE?

    I don't think you need such a sophisticated system in this case, this is a thousand times slower than the original question after all. 

    For more questions on this please open a new ticket, the devzone isn't really designed to handle mutliple different questions in the same thread. 

    Back to the code example, I have something running now but I need some time to make it more presentable. With some luck I will have some code to share by tomorrow. 

    Best regards
    Torbjørn

  • Hi Daragh

    I made a prototype example and shared it here:
    https://github.com/too1/ncs-spi-arraylist-example

    It uses arraylist to trigger SPI transactions automatically based on a timer, and uses a second timer module to count the number of transactions. 

    I didn't have as much time as I'd like to work on the example, but I will share what I have for now if you want to have a look at it:
    https://github.com/too1/ncs-spi-arraylist-example

    I will be out in vacation next week, but will be back over Easter if you have comments or questions. 

    Best regards
    Torbjørn

  • Hi Torbjørn,

    Thanks for the example code, I have just gotten back to this now. I was able to implement a very similar method based on your example and it is nearly working. Unfortunately I'm still missing some samples between the end of the buffer and the start of the next one. The application requires very precise coherent sampling so missing data for any time will cause issues.

    I didn't try to add some margin as I think the issue is in the extra time it takes to start sampling again rather than extra samples being taken at the end, I might be wrong and will try this if you have no other suggestions. I am manually setting the EasyDMA RXD pointer back to the start of the buffer at the end of the 2nd half in the timer counter interrupt handler like this:

    void timer_count_handler(nrf_timer_event_t event_type, void * p_context)
    {
        //ret_code_t err_code;
        //spi_sampling_disable();
        spi_xfer_done = true;
    
        switch (event_type)
        {
            case NRF_TIMER_EVENT_COMPARE0:
                // halfway through buffer (1 window sampled)
                current_sample_num = 0;
                buffer_half = 1;
                //printf("NRF_TIMER_EVENT_COMPARE0\n");
                break;
    
            case NRF_TIMER_EVENT_COMPARE1:
                // at end of buffer (2 windows sampled)
                m_spi.u.spim.p_reg->RXD.PTR = (uint32_t)rx_buf_array;
                m_spi.u.spim.p_reg->TXD.PTR = (uint32_t)m_tx_buf;
                nrf_drv_timer_clear(&timer_counter);
                current_sample_num = NUM_SAMPLES;
                buffer_half = 2;
                //printf("NRF_TIMER_EVENT_COMPARE1\n");
                break;
            //TODO check if this interrupt is getting delayed and missing samples
            default:
                // Do nothing
                break;
        }
    }

    I'm curious to know if there is a way to do this using PPI or similar so that interrupt won't be required for resetting the buffer address.

    For an example here is a plot of the ADC values when sampling a single tone sine wave. I save the last 100 samples from the buffer and then the first 100 samples from the next buffer. You can see a slight discontinuity where the first buffer ends and the next buffer starts (in reality there is a single buffer that is being written over).

    Thanks,

    Daragh

Reply
  • Hi Torbjørn,

    Thanks for the example code, I have just gotten back to this now. I was able to implement a very similar method based on your example and it is nearly working. Unfortunately I'm still missing some samples between the end of the buffer and the start of the next one. The application requires very precise coherent sampling so missing data for any time will cause issues.

    I didn't try to add some margin as I think the issue is in the extra time it takes to start sampling again rather than extra samples being taken at the end, I might be wrong and will try this if you have no other suggestions. I am manually setting the EasyDMA RXD pointer back to the start of the buffer at the end of the 2nd half in the timer counter interrupt handler like this:

    void timer_count_handler(nrf_timer_event_t event_type, void * p_context)
    {
        //ret_code_t err_code;
        //spi_sampling_disable();
        spi_xfer_done = true;
    
        switch (event_type)
        {
            case NRF_TIMER_EVENT_COMPARE0:
                // halfway through buffer (1 window sampled)
                current_sample_num = 0;
                buffer_half = 1;
                //printf("NRF_TIMER_EVENT_COMPARE0\n");
                break;
    
            case NRF_TIMER_EVENT_COMPARE1:
                // at end of buffer (2 windows sampled)
                m_spi.u.spim.p_reg->RXD.PTR = (uint32_t)rx_buf_array;
                m_spi.u.spim.p_reg->TXD.PTR = (uint32_t)m_tx_buf;
                nrf_drv_timer_clear(&timer_counter);
                current_sample_num = NUM_SAMPLES;
                buffer_half = 2;
                //printf("NRF_TIMER_EVENT_COMPARE1\n");
                break;
            //TODO check if this interrupt is getting delayed and missing samples
            default:
                // Do nothing
                break;
        }
    }

    I'm curious to know if there is a way to do this using PPI or similar so that interrupt won't be required for resetting the buffer address.

    For an example here is a plot of the ADC values when sampling a single tone sine wave. I save the last 100 samples from the buffer and then the first 100 samples from the next buffer. You can see a slight discontinuity where the first buffer ends and the next buffer starts (in reality there is a single buffer that is being written over).

    Thanks,

    Daragh

Children
  • Hi Daragh

    As I remember it from working on the example some months ago the issue is that the arraylist feature won't work with double buffering in the same way that other peripherals do. As an example, if you were to run several long SPI transactions not using array list you would have the entirety of the first transaction to prepare the buffers for the second transaction. 

    The way arraylist works by simply updating the TXD.PTR and RXD.PTR automatically you lose this capability.  

    D_Crow said:
    I didn't try to add some margin as I think the issue is in the extra time it takes to start sampling again

    Why would it take extra time to start sampling again? Isn't the sampling being run over PPI automatically?

    I think we should start by verifying whether or not buffer overrun is the issue. 

    Testing this is quite simple. Just add some margin to your buffer, initialize the entire buffer to some fixed value that is unlikely to show up in your data (0xFFFF or 0 is probably sufficient), and run the code until the issue occurs. Stop the execution in the debugger and see how much of the buffer has been overwritten with sensor data, whether it is just til the end (not including margin), or whether any of the margin elements have been overwritten as well. 

    Best regards
    Torbjørn

  • I tried this and you're completely right, that interrupt handler is delayed by up to 10 samples (usually only 1 or 2 samples) at the end of the buffer (10 samples is 0.1ms). So that explains my missing data. I'll be able to add a check for this but will have to figure out how to handle it efficiently as it will quickly add up over time.

    Thanks again for the help, I'll mark your reply with the prototype code as the answer to this. It would be great to see double buffering with array lists in the future.

  • Hi Daragh

    I am glad I could help out Slight smile

    I am not sure what the best way is to process the samples. Either you have to process the samples in unevenly sized chunks (the half point chunk would be the right size, while the second half would be longer because of the overflow), or you would have to schedule the next interrupt earlier to compensate for the added samples at the end. Essentially save the 'margin' samples for the next processing cycle, and set up the next interrupt to occur earlier. 

    Best regards
    Torbjørn

Related