Teensy 4.1 GPIO_6 Register Read Erratic Timing

Status
Not open for further replies.

Paul G

New member
Hi, First post from a novice; hope that you can help with this as I have become bogged down.

Am using a Teensy 4.1 with 8MB PSRAM. It is connected to an external 10bit parallel ADC (Analogue Devices AD9201). The ADC is dual channel I and Q device intended for communication use and signal analysis. The ADC has an external clock pin to instruct the next IQ sample pair and a select pin to put either I or Q on parallel port. The 10bit parallel data is mapped to GPIO_1 (6) bits 16-25 as these are accessible on pins on Teensy4.1.

The input to the ADC will be a radar receiver. On receiving a trigger pulse (the interrupt) the Teensy acquires 1ms of I and Q data from the ADC at a rate of 2MSamples/s and runs a convolution process to compress the received chirp modulated pulses from 100us to 1us. The output will connect to parallel DAC and then a display monitor. The code for the output side is not yet written.

The convolution process is all ok; even using just the standard math library, I am pleased that Teensy processes 1ms of data in approx 0.25s. I will later expand the sketch to acquire, say, 32 x 1ms data sets and process these within an 8second period.

I have a sketch that, on interrupt, successfully clocks the ADC at 2MSample/s and toggles the Select line to read both I and Q. Thanks to research reading on this forum I am also reading the GPIO_6 register direct and then >>16 to get the correct value.

My ISR basically creates a pair of 2MHz square waves, one for ADC clock and one for ADC select. There is some NOPs built around the clock transitions to match the settling time and data-ready requirements of the ADC and to adjust the sampling rate to 2MHz which is critical for the signal processing. This was all good.

Having integrated the ISR with the signal processing routine I am finding that my stable sampling is now erratic. By running the ISR without the register read, I have determined that it is the register read that is responsible. I have attached scope grab to show 'holes' in the sampling pattern. Clearly there is some conflict or distraction going on. I am hoping that there is some setting that will force the register read to be consistent.
Thanks,
Paul

Sampling.jpg




Code:
#include <math.h> 
#include <SD.h>
#include <SPI.h>


#define IMXRT_GPIO6_DIRECT  (*(volatile uint32_t *)0x42000000)//32bit read direct from register
#define NOP __asm__ __volatile__ ("nop\n\t")

const int interruptPin = 28; //Trigger for sampling
const int clk = 31; //Read ADC I and Q channels at 2MSamples/s
const int sel = 30; //Select I or Q channel
volatile int sample = 0;
const float iAdcOffset = -0.048; //Set zero signal input of I chan
const float qAdcOffset = -0.036; //Set zero signal input of Q chan
unsigned long adcI; //10bit sample from ADC I chan 
unsigned long adcQ; //10bit sample from ADC Q chan
const int nSamples = 2101; // For 1.05ms of data
volatile EXTMEM double I[2101]; //Scaled samples from I chan
volatile EXTMEM double Q[2101]; //Scaled samples from Q chan
double rxA[2101]; // Received signal Amplitude calc from I and Q
double rxPh[2101]; // Received signal Phase calc from I and Q

// Reference signal Amplitude and Phase for convolution
const double txA[301] //data set removed for clarity
const double txPh[301] //data set removed for clarity 

double convPh[1801];//Convolution of received signal and reference signal phase
double convA[1801]; //Convolution of received signal and reference signal amplitude
double out[1801]; //Signal output (will go to external parallel DAC)

void setup() {
  pinMode(clk, OUTPUT); //ADC clock
  pinMode(sel, OUTPUT); //ADC Select I or Q
  pinMode(19, INPUT); //ADC d0
  pinMode(18, INPUT); //ADC d1
  pinMode(14, INPUT); //ADC d2
  pinMode(15, INPUT); //ADC d3
  pinMode(40, INPUT); //ADC d4
  pinMode(41, INPUT); //ADC d5
  pinMode(17, INPUT); //ADC d6
  pinMode(16, INPUT); //ADC d7
  pinMode(22, INPUT); //ADC d8
  pinMode(23, INPUT); //ADC d9
    
  
  digitalWrite(clk, LOW);
  attachInterrupt(interruptPin, readAdc, RISING);
}

void readAdc() {
  for (sample = 0; sample < nSamples; sample++)
  {
    digitalWrite(clk, HIGH);
    digitalWrite(sel, HIGH);
    for (int wait = 0; wait < 6; wait ++) NOP;
    adcI = IMXRT_GPIO6_DIRECT >>16;
    I[sample]  = (adcI / 511.5) - 1 + iAdcOffset;
    for (int wait = 0; wait < 6; wait ++) NOP;
    digitalWrite(clk, LOW);
    digitalWrite(sel, LOW);
    for (int wait = 0; wait < 6; wait ++) NOP;
    adcQ = IMXRT_GPIO6_DIRECT >>16;
    Q[sample] = (adcQ / 511.5) - 1 + qAdcOffset;
    for (int wait = 0; wait < 6; wait ++) NOP;   
  }
  
}

void loop()
{
  for(sample=0; sample < nSamples; sample++)
{
  rxA[sample] = sqrt(I[sample]*I[sample] + Q[sample]*Q[sample]);
  rxPh[sample] = atan2(Q[sample], I[sample]);
}
for(int k=0; k < 1801; k++)      
{
  for(int j=0; j < 301; j++)
  {
    convPh[j] = rxPh[j+k] - txPh[j];    
    convA[j] = rxA[j+k] * txA[j];    
    out[k] += convA[j] * cos(convPh[j]);  
  }
}

}
 
Last edited by a moderator:
I have attached scope grab to show 'holes' in the sampling pattern. Clearly there is some conflict or distraction going on.

Maybe another interrupt is happening? I don't see any use of Serial.print() in your program, so the only one that should be running is Systick every 1ms.

Anyway, if it is another interrupt, usually the solution is to increase the priority setting for the interrupt you're using, so others can't interrupt it.
 
Just been looking closely at this on the scope and observed that the 'holes' are punched around ~20us into my 1ms block and then again at around ~520us! So I was thinking there is a clue here about something at 1ms perhaps! Sounds like there is a solution. By the way, I get the same problem with multiple digitalReadFast and assembling my 10bit word from them; thought it was worth a try.

I don't yet know how to set interrupt priority but I guess a search here will help.
Many thanks for this.
 
Noting this code uses PSRAM:
Code:
volatile EXTMEM double I[2101]; //Scaled samples from I chan
volatile EXTMEM double Q[2101]; //Scaled samples from Q chan

Does the QSPI hardware perhaps 'blip' the CPU processing order to complete that I/O during the _isr >> void readAdc() when it does the writes to I[] and Q[]

That is only 33K of memory - what happens if those EXTMEM's move to DMAMEM's?

@Paul G : there is a '#' code marker on the edit toolbar that puts [C0DE] // code here [/C0DE] to preserve indenting for readability { note it is actually CODE (cap letter O) - a 0/Zero was inserted instead to stop it getting parsed }
 
Does the QSPI hardware perhaps 'blip' the CPU processing order to complete that I/O during the _isr >> void readAdc() when it does the writes to I[] and Q[]

That is only 33K of memory - what happens if those EXTMEM's move to DMAMEM's?

I'll keep this is mind but it looks like the interrupt priority may be the issue. Have set IRQ_GPIO6789 at highest priority (as not sure what SysTick is called in the list to check its priority) and verified with NVIC_GET_PRIORITY(IRQ_GPIO6789). So far so good.

The EXTMEM is for when this is scaled up by 32x although even then I see that I could retain DMAMEM for the capture and use EXTMEM for the intermediate arrays in the processing routine.

Thanks and apologies for missing the # option when posting my code.
 
I would go even further and move those I and Q arrays into DTCM to avoid any possibility of cache interference with the collection. As defragster points out, it is only 33K of memory, but that is 1KB past what I think the cache can handle at one time. Moving those arrays to DTCM may require moving some other variables into DMAMEM, but if the processing is done in the foreground, it is less likely to present timing issues.

Setting a higher interrupt priority won't protect you from the Systick interrupt, which runs at the highest priority. Only masking all interrupts for the 1mSec of collection will do that, but it will also have an effect on the micros() and millis() functions.
 
Setting a higher interrupt priority won't protect you from the Systick interrupt, which runs at the highest priority. Only masking all interrupts for the 1mSec of collection will do that, but it will also have an effect on the micros() and millis() functions.

Ok; understood. Will look at masking all other interrupts and explore recommended memory plan. Thanks
 
Status
Not open for further replies.
Back
Top