Accurate tens of MHz digital read

JulesMhz

Member
Hello, Bonjour,

I'm interfacting a LTC2207 16-Bit 105Msps ADC with teensy 4.0.

I'm generating the (80MHz) square ENC signal from pin 12 using FlexIO, which seems to work well.

Now I'm trying to read 8bits D0..D7 of ADC on the CLK+ rising front.

I did a first try using interrupt on CLK+ signal and reading according GPIO port using GPIOX_PSR. It works more or less at frequency far from my expectation.

Could I expect better performances using Camera Sensor Interface (CSI), and doing something like linking CSI PCLK to CLK+ and D0..D7 to data bus of CSI ? Or is it a completly dumb idea ?
If it is doable, could you suggest documentations or references concerning a related use because CSI is a subject I really don't know.

Is there an other way to achieve better performance ?

For information, CLK+ is on pin 2, CLK- on 3, D0..D7 are on pins 11 to 4 respectivelly.

Thank you for your help.

Best regards.
Julien.
 
If you have configured an interrupt on the rising edge of CLK, 80 MHz is too fast. That would mean interrupt enter/read/exit in 7.5 CPU cycles, which is not possible. The highest you could achieve this way is 10-20 MHz. Someone else will have to answer whether you could configure some other feature (FlexIO?) to do everything in hardware instead of software.
 
Years ago I did something to handle high datarates on another system using SDRAM (original 100MHz style, not DDR). Basically ran a datapath using SDRAMs, 16 bit D-Q latches (TI "widebus" series, 74AC16374), and generated the control signals for the SDRAM on the microcontroller - but that was the Propeller microcontroller which is cycle-exact without any interrupts/DMA to mess with the timing (think 8 cores with microcode).

You could use DMA to clock out SDRAM instructions and thus clock the ADC direct into SDRAM at high speed, then read back more slowly later on.

My system was a display driver (I think I managed 40MB/s or similar on 2-sided PCB). SDRAMs I used had a minimum clock speed of 1MHz, max of 100 or 133MHz depending on which SIMM/DIMM I desoldered them from(!)
 
Thank you for your repply.

It sounds like for ma need FlexIO is the most suitable.

Here is what I'd like to achieve:

Using FlexIO to generate the square encode clock of my ADC, (80MHz, and 40MHz for testing), setting trigger of FlexIO on CLK+ rising edge, when a trigger occurs, generating either an interrupt or a DMA request (here I don't know which one fit to my need) to read the GPIO port.

I've been able to configure FlexIO to generate my square encode clock using something like:
Code:
uint8_t clk_pred = 2;
uint8_t clk_podf = 0;
uint8_t clk_div = 0;

// Enable CLK
CCM_CCGR3 |= CCM_CCGR3_FLEXIO2(CCM_CCGR_ON);

// Fast FlexIO CLK from 480MHz PLL
CCM_CS1CDR &= ~( CCM_CS1CDR_FLEXIO2_CLK_PRED( 7 ) );
CCM_CS1CDR |= CCM_CS1CDR_FLEXIO2_CLK_PRED( clk_pred );         // divider 1
CCM_CS1CDR &= ~( CCM_CS1CDR_FLEXIO2_CLK_PODF( 7 ) );
CCM_CS1CDR |= CCM_CS1CDR_FLEXIO2_CLK_PODF( clk_podf );         // divider 2

FLEXIO2_CTRL |= 1;

IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_01 = 4;

// High speed pad
IOMUXC_SW_PAD_CTL_PAD_GPIO_B0_01 =
IOMUXC_PAD_SRE |
IOMUXC_PAD_DSE(7) |
IOMUXC_PAD_SPEED(2);

// Disable FlexIO during config
FLEXIO2_CTRL &= ~FLEXIO_CTRL_FLEXEN;

// Timer compare
// toggle every cycle
FLEXIO2_TIMCMP0 = 2;

FLEXIO2_TIMCFG0 =
FLEXIO_TIMCFG_TIMOUT(0) |
FLEXIO_TIMCFG_TIMDEC(0) |
FLEXIO_TIMCFG_TIMRST(0) |
FLEXIO_TIMCFG_TIMDIS(0) |
FLEXIO_TIMCFG_TIMENA(0) |
FLEXIO_TIMCFG_TSTOP(0);

FLEXIO2_TIMCTL0 =
FLEXIO_TIMCTL_PINCFG(3) |   // enable pin
FLEXIO_TIMCTL_PINSEL(1) |   // D01 -> pin 12
FLEXIO_TIMCTL_TIMOD(2);     // dual 8-bit baud / toggle mode

I'm not perfectly sure of my code. How could I set a trigger to read the GPIO port now ?

Thank you.
 
You probably want to also use FlexIO to read the data signals, because it's (theoretically) faster than GPIO.

But if you want to explore the GPIO route, probably look to the OctoWS2811 library for inspiration. It uses XBAR1 to trigger DMA. The signal that causes the trigger to happen must be on the list in Table 4-5 starting on page 61 in the reference manual. OctoWS2811 uses one of the Quad Timer channels. Looks like FlexIO might also work. So can the actual XBAR IN pins on the chip, so in theory anything that can create a signal on a physical pin could be connected by a wire to any of the XBAR IN pins... even if that's a bit "messy" compared to keeping the trigger on chip.

XBAR's main purpose is just routing trigger signals between on-chip peripherals. But it also has 4 special DMA trigger units. They're documented in part 61.3.5 on page 3308, and in their control registers starting on page 3346. Those DMA trigger units are your secret weapon to cause timers that don't have anything to do with DMA, or are limited to DMA that updates their own timer registers, to be used to trigger DMA that accesses anything else. The DMA can be made to do GPIO because you would put the GPIO register address into the DMA channel's source or destination register.

DMA to GPIO has another subtle "gotcha" which you'll see in the OctoWS2811 library code, and I'll explain here to hopefully save you from a lot of frustration. There are actually 2 sets of GPIO peripherals inside the chip. NXP's earlier RT1052 chip had only 1 set, which is the normal way you would expect GPIO to work in pretty much any microcontroller. But they are connected to the normal peripheral bus which is downstream from the high-overhead AXI bus and also a bus bridge. So access to GPIO1 - GPIO4 peripherals is relatively slow. When they made RT1062, they put another duplicate set of GPIO peripherals GPIO6 - GPIO9 into the chip and connected them to the Cortex-M7 special low latency bus. They dedicated 4 of the IOMUXC_GPR registers to choose for every single pin whether it connects to slow GPIO1 - GPIO4 or fast GPIO6 - GPIO9. The main problem is DMA isn't supposed on the low latency bus. If you do everything else right but put any of the GPIO6 - GPIO9 register addresses into the DMA TCD registers, it simply can't access GPIO6 - GPIO9. So you have to switch the pins you wish to use back to GPIO1 - GPIO4 by writing to those IOMUXC_GPR registers.

Whether the slow GPIO peripherals or even the DMA controller can really handle so many operations per second, I don't know. I'm not optimistic. But if you want to try, hopefully this gives you a better start than just having to endlessly read the 3000-some pages in the reference manual.
 
Thank you for your repply.

If I can use FlexIO to do all the job (clock, triger and read input) it sounds good to me. But I didn't see any example on how to achieve this. Do you have references, documentation, codes to suggest ?

Thank you.
 
Hi,

My brain is working hard on this topic, and I did little progress thanx to lectures you shared and ither posts of this forum.

Here is my goal:
continuous output 80 or 100MHz square clock from teensy pin 12 (IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_01)
on rising edge detect on teensy pin 2 (IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_04) read 1 bit teensy pin 11 (IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_02)

Here is what I do:
Code:
#include <DMAChannel.h>

#define USB Serial

#define D9 0
#define D8 1
#define CLKP 2
#define CLKN 3
#define D7 4
#define D6 5
#define D5 6
#define D4 7
#define D3 8
#define D2 9
#define D1 10
#define D0 11
#define ENC 12
#define D10 23
#define D11 22
#define OF 21
#define LED_ON 14
#define STATUS 15

#define BUF_SIZE 512

DMAChannel dma;
volatile uint32_t sample;

volatile uint32_t buffer[BUF_SIZE];
volatile uint16_t writeIndex = 0;
volatile uint16_t readIndex = 0;

volatile uint32_t count = 0;
volatile uint32_t last = 0;
unsigned long previousMillis = 0;
const unsigned long interval = 100;


void dma_isr() {
  dma.clearInterrupt();  // tell system we processed it.
  count += 1;
  writeIndex++;
  if (writeIndex >= BUF_SIZE)
    writeIndex = 0;
  //asm("DSB");           // this is a memory barrier
}

void setup() {
 
  //// ENC PWM
 
    uint8_t clk_pred = 2;
    uint8_t clk_podf = 0;
    uint8_t clk_div = 0;

    // Fast FlexIO CLK from 480MHz PLL
    CCM_CS1CDR &= ~( CCM_CS1CDR_FLEXIO2_CLK_PRED( 7 ) );
    CCM_CS1CDR |= CCM_CS1CDR_FLEXIO2_CLK_PRED( clk_pred );         // divider 1
    CCM_CS1CDR &= ~( CCM_CS1CDR_FLEXIO2_CLK_PODF( 7 ) );
    CCM_CS1CDR |= CCM_CS1CDR_FLEXIO2_CLK_PODF( clk_podf );         // divider 2

    // Disable CLK
    //CCM_CCGR3 &= ~CCM_CCGR3_FLEXIO2(CCM_CCGR_ON);
    CCM_CCGR3 |= CCM_CCGR3_FLEXIO2(CCM_CCGR_ON);
    FLEXIO2_CTRL &= ~FLEXIO_CTRL_FLEXEN;
    
    IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_01 = 4;
    IOMUXC_SW_MUX_CTL_PAD_GPIO_B0_02 = 4;
    IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_04 = 4;


    FLEXIO2_SHIFTCTL0   = FLEXIO_SHIFTCTL_TIMSEL( 0 )       |           // timer 0
                          FLEXIO_SHIFTCTL_TIMPOL            |           // on pos edge
                          FLEXIO_SHIFTCTL_PINCFG( 0 )       |           // pin output disabled
                          FLEXIO_SHIFTCTL_PINSEL( 2 )       |           // pin 11
                          //FLEXIO_SHIFTCTL_PINPOL          |           // active high
                          FLEXIO_SHIFTCTL_SMOD( 1 );                    // receive mode
    
    FLEXIO2_SHIFTCFG0   = FLEXIO_SHIFTCFG_PWIDTH( 0 );   //    |           // 1bits wide
                          //FLEXIO_SHIFTCFG_INSRC             |           // from shifter n+1
                          FLEXIO_SHIFTCFG_SSTOP( 0 )        |           // stop bit disabled
                          FLEXIO_SHIFTCFG_SSTART( 0 );  // start bit disabled

  
    FLEXIO2_TIMCFG0 =
          FLEXIO_TIMCFG_TIMOUT(0) |
          FLEXIO_TIMCFG_TIMDEC(0) |
          FLEXIO_TIMCFG_TIMRST(0) |
          FLEXIO_TIMCFG_TIMDIS(0) |
          FLEXIO_TIMCFG_TIMENA(0) |
          FLEXIO_TIMCFG_TSTOP(0);

    FLEXIO2_TIMCTL0 =
          FLEXIO_TIMCTL_TRGSEL(3) |       // trigger = pin2
          FLEXIO_TIMCTL_TRGSRC |
          FLEXIO_TIMCTL_TRGPOL |         // rising edge
          FLEXIO_TIMCTL_PINCFG(3) |   // enable pin
          FLEXIO_TIMCTL_PINSEL(1) |   // ENC -> pin 12
          FLEXIO_TIMCTL_TIMOD(2);     // dual 8-bit baud / toggle mode //1?


  FLEXIO2_TIMCMP0  = 0;
  FLEXIO2_CTRL |= FLEXIO_CTRL_FLEXEN;
  //// End ENC PWM

  dma.begin(true);
  dma.source((volatile uint32_t&)FLEXIO2_SHIFTBUF0);
  dma.destinationBuffer((uint32_t*)buffer, BUF_SIZE * sizeof(uint32_t));
  dma.triggerAtHardwareEvent(DMAMUX_SOURCE_FLEXIO2_REQUEST0);
  dma.TCD->CSR       &= ~(DMA_TCD_CSR_DREQ);              // do not disable the channel after it completes - so it just keeps going
  dma.TCD->CSR      |= DMA_TCD_CSR_INTMAJOR;// | DMA_TCD_CSR_INTHALF;
  dma.attachInterrupt(dma_isr);
  dma.interruptAtCompletion();

  FLEXIO2_SHIFTSDEN |= (1 << 0);
 
  dma.enable();

  // wait for USB to be ready
  while (!USB && millis() < 10000) {
    delay(100);
  }
    
 }

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    USB.println(count);
    count=0;
  }
/*while (readIndex != writeIndex) {
    uint32_t val = buffer[readIndex] & 1;  // lit le bit reçu
    readIndex++;
    if (readIndex >= BUF_SIZE) readIndex = 0;
    USB.println(val);
  }*/
}

I'm able to see about 2090 on the monitor, which means that the DMA interrupt works well, but 2090 interrupt per 100ms is not the speed I expect. More over, when I print the buffer it is always 0.

Do you have any suggestion ?

Thank you.
 
Back
Top