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

Related