'Ping Pong' DMA for SPI

Status
Not open for further replies.

Xenoamor

Well-known member
Hi guys, this a is a bit of an odd one so I'll start by explaining what I'm doing.

I'm using some TLC5955 chips to drive LEDs. These have 16bit colour depth but my data is only 8bits.
So simple enough I transmit one byte of my data then one byte of 0x00 padding, like so:
Code:
0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 A 0 B 0 C 0 D 0 E 0 F 0 10 0 11 0 12 0 13 0 14 0 15 0 16 0 17 0 18 0 19 0 1A 0 1B 0 1C 0 1D 0 1E 0 1F 0 20 0 21 0 22 0 23 0 24 0 25 0 26 0 27 0 28 0 29 0 2A 0 2B 0 2C 0 2D 0 2E 0 2F 0

Now that's great and all but I'm now trying to use DMA to reduce my CPU load and up the speed. I can quite easily pad my data with 0x00 every other byte, which works as you would expect. However, this takes up a lot more memory then I would like.

So I decided to attempt to use a second DMA channel triggered after every write to transmit the 0x00s. Here's what I have so far:
Code:
uint8_t dmaData[48];
uint8_t emptyBuffer = 0x00;

dmachannel1.destination((volatile uint8_t&)SPI0_PUSHR);
dmachannel1.sourceBuffer(dmaData, 48);
dmachannel1.disableOnCompletion();
dmachannel1.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
	
dmachannel2.destination((volatile uint8_t&)SPI0_PUSHR);
dmachannel2.source(emptyBuffer);
dmachannel2.triggerAtTransfersOf(dmachannel1);
dmachannel2.triggerAtCompletionOf(dmachannel1);
dmachannel2.enable();

dmachannel1.enable(); // Begin transfer

Now the issue I think I'm having is that 'dmachannel2' is trying to fill the SPI0_PUSHR buffer with 0x00 before it is ready, which is usually marked by the DMA request on DMAMUX_SOURCE_SPI0_TX.

Can anyone give me any pointers as to a possible solution?

I was potentially thinking to use one DMA channel and use it's modulo register to send the same data twice everytime. This would also be acceptable
I believe the SPI buffer is 16 bits as well so I could DMA data to a temporary buffer like so:
Code:
uint16_t tempBuffer = 0x0000
DMA transfer of 8bits to tempBuffer
tempBuffer now = 0xFF00
DMA tempBuffer to SPI 16 bit buffer
Neither of these solutions seem elegant though
 
Last edited:
Right I've managed to find a nice solution to this.

Set up SPI for 16bit transfers. Now whenever a transfer occures SPI0_PUSHR is reset to 0x0000.

All we have to do is DMA data into the top byte of that register. Here's the code that does all this:
Code:
#include <SPI.h>
#include "DMAChannel.h"

DMAChannel dmachannel0;

#define BUFFERSIZE 8

uint8_t dmaData[BUFFERSIZE];

void setup() {

  // Create some test data
  for(uint8_t i = 0; i < BUFFERSIZE; ++i) {
    dmaData[i] = i;
  }

  // Setup SPI //
  SPI.setDataMode(SPI_MODE0);
  SPI.setBitOrder(MSBFIRST);
  SPI.begin();
  
  // Set SPI FMSZ up for 16 bit SPI transfer
  SPI0_CTAR0 |= SPI_CTAR_FMSZ(15); 
  
  // Setup SPI for DMA transfer
  SPI0_SR = 0xFF0F0000;
  SPI0_RSER = 0;
  SPI0_RSER = SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;

  // Setup DMA //
  dmachannel0.sourceBuffer(dmaData, BUFFERSIZE);
  dmachannel0.destination((volatile uint8_t&)SPI0_PUSHR);    // Setup DMA to expect an 8 bit destination
  dmachannel0.TCD->DADDR = (volatile void *)(0x4002C034+1);  // Offset the destination so our data is in the top byte
  dmachannel0.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX); // Transfer after every SPI transmit
  dmachannel0.disableOnCompletion();                         // Stop transmitting once full buffer has been sent

}

void loop() {

  delay(1000);
  dmachannel0.enable(); // Begin transfer

}

I really don't like this line however:
Code:
dmachannel0.TCD->DADDR = (volatile void *)(0x4002C034+1);  // Offset the destination so our data is in the top byte
If anyone can figure out how to use SPI0_PUSHR in that equation so it's not so hard coded that'd be great
 
Status
Not open for further replies.
Back
Top