Any way to route GPIO register data direct to USB using DMA?

felipesandoval

New member
Hello, I am attempting to read parallel data on GPIO1 using DMA as fast as possible, and wanted to know if there is a way to set the output buffer to somehow be the USB FIFO buffer. My goal is to get continuous data from the GPIO register as fast as possible into the USB buffer. My current implementation is using two buffers on RAM2 as the DMA buffer, switching between them when they get full, and sending them as follows:

C++:
// Buffer Definitions
#include BUFFER_SIZE 8192
#include SEND_SIZE BUFFER_SIZE*4 // 4 bytes for 32-bit register

uint32_t bufferA[BUFFER_SIZE] DMAMEM __attribute__ ((aligned(32)));
uint32_t bufferB[BUFFER_SIZE] DMAMEM __attribute__ ((aligned(32)));
uint32_t * currBuff;

// DMA ISR when buffer is full
bool isA = true;
void dma_isr()
{
  dma.clearInterrupt();
  asm("DSB");
 
  // Switch which buffer DMA uses
  uint32_t * prevBuff = currBuff;
  currBuff = isA ? bufferB : bufferA;
  isA = !isA;
  dma.destinationBuffer(currBuff, SEND_SIZE);
  dma.enable();
 
  Serial.write((uint8_t*)prevBuff, SEND_SIZE); // Send full buffer to USB
}

The problem is that as I continuously read from the USB buffer, when the buffers switch and are sent over there are glitches in the data, i.e., I believe the time it takes to switch between buffers is too long at the frequencies I am receiving data on the GPIO register (~6MHz).

I'm not sure how else to implement this, as I need continuous data output from GPIO1 to USB.
 
You haven't showed the complete code which makes it difficult to give specific advice...

Try using one big buffer with a DMA interrupt at halfway as well as completion, that way it's always enabled.
(Also don't Serial.write() from an ISR! Post a flag and handle it in the main loop...)
 
Here is the full code with the Serial.write not in the ISR:

Code:
#include <Arduino.h>
#include <imxrt.h>
#include <DMAChannel.h>

#define BUFFER_SIZE 8192
#define SEND_SIZE BUFFER_SIZE*4

void xbar_connect(unsigned int input, unsigned int output);
void dma_isr();
void setup_dma();

uint32_t bufferA[BUFFER_SIZE] DMAMEM __attribute__ ((aligned(32)));
uint32_t bufferB[BUFFER_SIZE] DMAMEM __attribute__ ((aligned(32)));

uint32_t * prevBuff;
uint32_t * currBuff;

DMAChannel dma;

volatile bool capturing = false;

void setup()
{
  // while(!Serial);

  GPIO1_GDIR &= ~(0xFFFF300Cu); // All exposed pins of GPIO1 CHECK THAT THIS WORKS
  // Need to switch the IO pins back to GPIO1 from GPIO6
    IOMUXC_GPR_GPR26 &= ~(0xFFFF300Cu); // All exposed pins of GPIO1

  setup_dma();
  capturing = true;

  dma.enable();
}

volatile bool col = false;

void loop()
{
  if(!capturing)
  {
    Serial.write((uint8_t*)prevBuff, SEND_SIZE);
    arm_dcache_delete(prevBuff, BUFFER_SIZE);
  }
}

bool isA = true;
void dma_isr()
{
  dma.clearInterrupt();
  asm("DSB"); // do i need this???

  capturing = false;

  prevBuff = currBuff;
  currBuff = isA ? bufferB : bufferA;
  isA = !isA;
  dma.destinationBuffer(currBuff, SEND_SIZE);
  dma.enable();
}

void setup_dma()
{
  dma.begin();

  dma.source(GPIO1_PSR);

  dma.destinationBuffer(bufferA, SEND_SIZE);
  currBuff = bufferA;
  isA = true;

  dma.interruptAtCompletion();
  dma.attachInterrupt(dma_isr);

  // Enable XBAR clock
  CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);
  CCM_CBCDR &= ~(CCM_CBCDR_IPG_PODF(3));
  CCM_CBCDR |= CCM_CBCDR_IPG_PODF(1);

  // This setup is for DDR clock on pin 0 of Teensy
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_03 = 1;

  xbar_connect(XBARA1_IN_IOMUX_XBAR_INOUT17, XBARA1_OUT_DMA_CH_MUX_REQ30);

  IOMUXC_GPR_GPR6 &= ~(IOMUXC_GPR_GPR6_IOMUXC_XBAR_DIR_SEL_17);  // Make sure it is input mode

  IOMUXC_XBAR1_IN17_SELECT_INPUT = 1; // Make sure this signal goes to this pin...

  XBARA1_CTRL0 = XBARA_CTRL_STS0 | XBARA_CTRL_EDGE0(1) | XBARA_CTRL_DEN0; // XBARA_CTRL_EDGE0(1) is for RISING only, XBARA_CTRL_EDGE0(3) is for RISING & FALLING

  // One request per XBAR1 OUT0 event
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_0);
  dma.disableOnCompletion();
}

If I were to use the interrupt halfway, how would I send that half to Serial.write()?

To clarify, the reason I know it's not spitting out continuous data is because to the top 16-bits of GPIO1, I am inputting a ramp of int16 (so all int16 values), but when I plot all the output values of the USB I get a plot as such:
1770668630662.png

Which you can see clear discontinuities. Let me know if this helps.
 
You never set capturing back to true so presumably loop() is just continuously sending the same values endlessly.
 
To capture continuously, you have a couple options.

You can use 2 DMA channels, where each causes the other to begin when it completes. You would create 2 similar interrupt functions which collect the captured data just the recently completed DMA channel and then configure it to be ready to start when the other completes. The critically important part is the hand-off from one DMA channel to the other is done by the DMA hardware.

You can also do it with just 1 DMA channel by leveraging some of the special DMA hardware features. The interrupt can be configured to trigger at half full and also completely full. The DMA controller has hardware to automatically adjust the destination address register when it completes. You can use that feature to set the address back to the beginning of your buffer. If you haven't set it to disable upon completing, the DMA channel will just keep running over and over. You interrupt function would look at the destination register to determine whether the interrupt was caused by half full or completely full. You would then grab the half of your buffer which is just completed. Usually in this usage you don't reconfigure the DMA at all, because it runs 100% continuously on the same DMA channel and automatically sets its destination address back to the beginning of the buffer. Most of the audio library DMA works this way, if you'd like an example to read.
 
Back
Top