Capturing digital VGA signal with T4.0

KingK

New member
Hi all,
I'm trying to read a digital video signal and stream it over USB. The input data has similar specifications to VGA (640x480, 25MHz pixel update rate, Vsync and Hsync, no clock signal, etc.) except that the actual color data is digital (parallel, 4 bits per channel) rather than analog. I'm currently capturing 2 bits per color channel so that each pixel fits into 1 byte and the bitrate requirements don't exceed the ~250Mbps that I can send over USB.

My current issue is that I can't seem to get DMA to work correctly. I'm trying to use a DMA channel with a very short-period PIT timer to sample the I/O pins at 25MHz, but it doesn't appear to perform any transfers. All of the other PIT timer configuration appears to be working properly, though.
This is my entire program so far, there's not particularly much going on:
Code:
#include <DMAChannel.h>

#define PIN_VS 0
#define PIN_HS 1
#define PIN_B0 19
#define PIN_B1 18
#define PIN_G0 14
#define PIN_G1 15
#define PIN_R0 17
#define PIN_R1 16

#define RGB_PINREG GPIO6_PSR
// gpio 6:
// pin 0 @ 3
// pin 1 @ 2
// pin 2 @ 4
// pin 14 @ 18
// pin 15 @ 19
// pin 16 @ 23
// pin 17 @ 22
// pin 18 @ 17
// pin 19 @ 16

volatile uint8_t frame[640 * 480]; // 1 full frame of data
volatile uint32_t line = 0;
volatile bool synced = false; 
volatile bool conn = false;
volatile bool newdata = false;

DMAChannel dma;

void isr_vsync()
{
  line = 490;
  if (conn) synced = true;

  asm volatile("dsb");
}

void isr_hsync()
{
  // enable the timer right away
  IMXRT_PIT_CHANNELS[1].TCTRL |= PIT_TCTRL_TEN;

  if (line >= 480 || !synced)
  {
    // nope
    IMXRT_PIT_CHANNELS[1].TCTRL &= ~(PIT_TCTRL_TEN);
  }

  if (synced)
  {
    ++line;
    if (line == 525) line = 0;
  }

  asm volatile("dsb");
}

void isr_dma_complete()
{
  // DMA transfer complete, notify the USB loop
  newdata = true;
  digitalWriteFast(13, 1);
  
  asm volatile("dsb");
}

void isr_pit()
{
  // the only PIT set up with interrupts is PIT 1 (the delay)
  // it's time, start the DMA
  dma.enable();
  
  // turn the timer off
  IMXRT_PIT_CHANNELS[1].TCTRL &= ~(PIT_TCTRL_TEN);

  asm volatile("dsb");
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(0);

  // attachInterrupt(PIN_HS, isr_hsync, EDGE_FALLING);

  /*
   * PIT initialization
   * PIT_MCR - module control register
   * * enable PIT_MCR_MDIS (set to 1) to disable timers for configuration
   * 
   * 4 channels (IMXRT_PIT_CHANNELS[i])
   * * CVAL = current timer value (counts down)
   * * LDVAL = value to count from
   * * TCTRL = timer control 
   *   * PIT_TCTRL_CHN (bit 2) - when 1, waits for the n-1'th timer to expire before counting down. Doesn't work on timer 0
   *   * PIT_TCTRL_TIE (bit 1) - when 1, generates interrupts. If an interrupt is pending, enabling this will immediately signal an interrupt unless the TFLGn.TIF flag is cleared
   *   * PIT_TCTRL_TEN (bit 0) - when 1, enables this timer
   * * TFLG = timer flags
   *   * PIT_TFLG_TIF (bit 0) - interrupt flag, set every timer period. Write 1 to clear.
   *   
   * Also we need to enable the PIT clock and attach it to something
   */
  // setup peripheral clock - disable CSCMR1[PERCLK_CLK_SEL] to allow full speed
  CCM_CSCMR1 &= ~(CCM_CSCMR1_PERCLK_CLK_SEL);
  // set CSCMR1[PERCLK_PODF] = 1 (div by 2)
  CCM_CSCMR1 |= 0x1;
  CCM_CSCMR1 &= ~(0x3E);
  // enable the PIT clock
  CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON);

  // disable PIT so we can work on it
  PIT_MCR = PIT_MCR_MDIS;
  
  // base PIT clock should be 75MHz at this point (600MHz base / 4 / 2 = 75MHz)
  // we'll use the timer to divide it by 3 to get our 25MHz
  // use channel 0
  IMXRT_PIT_CHANNELS[0].LDVAL = 2;
  IMXRT_PIT_CHANNELS[0].TCTRL = PIT_TCTRL_TEN; // only enable the timer, no chaining or interrupts
  // use channel 1 to count down from Hsync to DMA start
  IMXRT_PIT_CHANNELS[1].LDVAL = 142; // 144 pixels minus a few (should be tuned)
  IMXRT_PIT_CHANNELS[1].TCTRL = PIT_TCTRL_TIE | PIT_TCTRL_CHN;
  IMXRT_PIT_CHANNELS[1].TFLG = 1; // write 1 to clear interrupt flag
  

  // re-enable the PIT
  PIT_MCR = 0;

  // set up the DMA transfer
  dma.source(*((uint8_t*)&GPIO6_PSR + 2)); // byte 2 of the pin register data
  dma.destinationCircular(frame, 640 * 480);
  dma.transferSize(1);
  dma.transferCount(640);
  dma.disableOnCompletion();
  dma.interruptAtCompletion();
  dma.attachInterrupt(isr_dma_complete);
  
  // set up periodic trigger (manually)
  DMAMUX_CHCFG0 = 0;
  DMAMUX_CHCFG0 = DMAMUX_CHCFG_A_ON | DMAMUX_CHCFG_TRIG | DMAMUX_CHCFG_ENBL;

  pinMode(PIN_VS, INPUT);
  pinMode(PIN_B0, INPUT);
  pinMode(PIN_B1, INPUT);
  pinMode(PIN_G0, INPUT);
  pinMode(PIN_G1, INPUT);
  pinMode(PIN_R0, INPUT);
  pinMode(PIN_R1, INPUT);
  pinMode(13, OUTPUT);

  attachInterrupt(PIN_VS, isr_vsync, FALLING);
  attachInterrupt(PIN_HS, isr_hsync, FALLING);

  attachInterruptVector(IRQ_PIT, isr_pit);
  NVIC_ENABLE_IRQ(IRQ_PIT);
}

uint32_t wr_line = 0;

void loop() {
  // put your main code here, to run repeatedly:
  if (!Serial)
  {
    // force all the condition variables off
    synced = false;
    conn = false;
    newdata = false;
  }
  else
  {
    conn = true;
    if (newdata)
    {
      newdata = false;
      // write 1 line
      Serial.write((char *)(&frame[640 * wr_line]), 640);
      if (++wr_line == 480)
      {
        wr_line = 0;
      }
    }
  }
}

This is my first attempt at using DMA on the T4, so I probably missed something simple. I probably need to do some smaller-scale testing to gain a better understanding of how the system works.
If anyone could point me in the right direction on this, it would be greatly appreciated!
 
Back
Top