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

nRF52840 SPI EasyDMA Double Buffering

Hello,

I am trying to determine whether or not the SPI EasyDMA functionality of the nrf52840 supports double buffering in the sense that the DMA controller will switch buffers without intervention from the processor. In other words, can you start one DMA transfer and then immediately queue the next DMA transfer so that the following scenario happens:

Example:

255 Byte DMA buffer x 2

1. Start DMA transfer from buffer1 and immediately queue DMA transfer from buffer2.

2. Receive DMA transfer complete interrupt from buffer1. The DMA controller has already automatically switched to buffer2. Load buffer1 with new data and queue this transfer.

3. Receive DMA transfer complete interrupt from buffer2. The DMA controller has already automatically switched to buffer1. Load buffer2 with new data and queue this transfer.

4. etc...

I ask because I am using the SoftDevice while receiving ADC data over SPI sampled at 250 Hz and I want to make sure that if the DMA interrupt is delayed by any BLE activity that I do not miss a sample from my ADC's. If my understanding of this is correct, is there an example I can reference?

SoftDevice: S140

SDK: 15.0.0

Thanks,

Derek

Parents Reply Children
  • Thanks for the response!

    Let me preface this with what I am trying to now that I have gotten my hands on my DAC hardware. The DAC requires 2 bytes to be written while toggling chip select in-between transfers. I am trying to store an entire waveform in RAM and kick off a DMA SPI transfer to loop over this data indefinitely or until stopped by the application without intervention from the processor because the DAC writes cannot be interrupted. At the same time, the SoftDevice will be "listening" for a stop command to halt the DAC updates. My concern is that the SoftDevice interrupts will corrupt the output waveform.

    After looking at this thread: https://devzone.nordicsemi.com/f/nordic-q-a/18638/easydma-array-list, it looks like this can be done using Array Lists, hardware timers, and PPI. However, does this method also toggle the chip select line between transfers of each buffer in the Array List?

    My idea was to store each 2-byte value into a very large ArrayList as shown below and loop over this using SPI DMA, hardware timers, and PPI with no processor usage. Is this possible? If not, how can I achieve this? Are there any examples for SPI?

    Thanks!

    typedef struct ArrayList
    {
    uint8_t buffer[2]; // 2 DAC bytes
    } ArrayList_type;
    
    ArrayList_type MyArrayList[SIZEOF_WAVEFORM];

  • The SPIM0-2 peripherals does not have HW Chip Select, but the new SPIM3/QSPI peripheral does.

    If you need the QSPI peripheral for other devices I suggest you used SPIM0-2 and control the CS pin via PPI and GPIOTE. SPI slaves will usually have timing requirements for when the CS pin is pulled low and high, therefore I suggest that you control both the CS and the SPIM's TASKS_START with a TIMER and PPI. 

    You'll need to connect the TIMER's Compare0 event to the a GPIOTE TASKS_OUT, where the GPIOTE task is set up to pull the CS low. Then you'll need to connect TIMER's Compare1 event to the SPIM's TASKS_START, and the SPIM's EVENTS_END event to the GPIOTE TASKS_OUT[1], where the GPIOTE task is set up to pull the CS line high between each transfer. You will also need to fork the SPIM's EVENTS_END event to the TIMER's TASKS_CLEAR to restart the cycle. 

  • From what I have read on the Devzone and the datasheet, what you describe makes sense. Is there a working SPI driver example that implements this or similar functionality that I can reference? Specifically in regards to setting up the timers, tasks, forking, and PPI? I greatly appreciate your help.

    Thanks!

  • See Timer ExamplePPI ExampleGPIOTE Example, SPI Master ExampleGPIOTE Driver description, SPI master Driver description, GPIOTE Driver and HAL API, PPI Driver and HAL API, and TIMER Driver and HAL API.

    I'd start by playing with the TIMER and GPIOTE via HAL. Set up some GPIOTE tasks like pin toggles that are triggered by a TIMER's compare tasks. Use a digital analyzer to see how the GPIOs behave. This will teach you the basics of the PPI system (EVENT --> TASK) and how to set up the TIMER and GPIOTE. 

    The PPI system uses the register address of an EVENT and couples it to the register address of a TASK. All drivers or HALs should have a function for getting the address of an EVENT or TASK. Those addresses are also given in the Registers chapter of a peripheral's technical specification. 

    You can use the SPIM driver to initialize the SPIM peripheral and the HAL API to enable the linked list feature with a call tonrf_spim_tx_list_enable

  • I have spent a bit of time playing around with these examples and have gotten DMA transmit working with SPIM3 and HW chip select. Thanks for pointing that out, I didn't realize SPIM3 had this feature. However, I have run into a small problem and hopefully I am missing something here.

    My DAC SPI writes need to occur at specific intervals without CPU involvement indefinitely. So what I have done is set up a periodic timer to trigger each SPI transfer using PPI. I have set up a second timer to count the number of transfers using the SPI end event. This works great in that my SPI transmit ArrayList is iterated through as expected solely using PPI and timers. The problem arises when you want to reset the SPI transfer pointer back to the top of the ArrayList. How can this be done without CPU involvement?

    Unless I am missing something, this was my idea as a possible solution. Since the SPIM buffer pointers are double buffered, I was going to set up a third timer to count the number of SPI transfers -1 and interrupt the CPU. So while the last transfer is ongoing, the 3rd timer callback will set the SPI pointer back to the top, ie Channel.PTR = &MyArrayList;. Once the next SPI transfer begins, it will start back at the top. My only concern with this is that the system could stall due to other interrupts and not reset the pointer soon enough and overflow the buffer.

    Does this approach makes sense and seem feasible? Is there an easier way that I am missing to reset the pointer back to the top of the buffer? Just to reiterate, it is imperative that there is no noticeable delay in DAC writes when looping over this buffer.

    Edit: Not sure why I wasn't able to find this article before, but it appears that the ArrayList pointer cannot be reset without CPU involvement after-all: https://devzone.nordicsemi.com/f/nordic-q-a/23349/ringbuffer-spi-twi-tx-with-fixed-sample-rate . Hopefully my idea of using a third timer will work unless you have any other tricks or ideas?

    Thanks!

Related