Slow register access on Teensy 4.1

MarkW307

Member
Hi folks. I am in the midst of developing code to operate the AD7380 ADC at high speed with low jitter and minimal overhead. Thus far I have code to initialize the ADC, set up a PWM, set up the SPI1 and the XBAR. The result is that the PWM triggers the ADC with very low jitter and the PWM then triggers the SPI to read the data and finally the TCF interrupt from the SPI module fires an interrupt in which I store the data into a circular buffer. Long term I plan to use two buffers so the data from a previous scan can be processed while taking new data.

I expected the interrupt routine to take much less time than it does so I have implemented a GPIO pin to allow me to measure elapsed time at various points in the ISR for the SPI1 (LPSPI3). I am seeing about 12 ns if I just toggle the pin and measure the time for the ASM instruction "dsb". However, If I look at the load of the TDR it takes about 35 nS. I don't understand how it can be that slow with the processor running at 600 MHz.

Here is the SPI1 initialization:
// start the SPI library:
SPI1.begin();
SPI1.setMISO(39);
SPI1.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE2));
LPSPI3_TCR = LPSPI_TCR_FRAMESZ(31); //32Bit Mode

Ultimately I need to run at 30 MHz for the SCK, but with my current wiring that leads to poor signal quality. I was initially thinking that the register access might be slow due to the fact that I am talking to the LPSPI module, but I see the same slow behavior when saving the data to RAM or dealing with the buffer index.

I will appreciate any insight you all might have.
Code:
```cpp
#include "ad738x.h"

//struct ad738x_dev *dev;
const int testPin = 5;
const int PWM_testPin = 3;
const int XBARtestPin = 33;

void xbar_connect(unsigned int input, unsigned int output)
{
  if (input >= 88) return;
  if (output >= 132) return;

  volatile uint16_t *xbar = &XBARA1_SEL0 + (output / 2);
  uint16_t val = *xbar;
  if (!(output & 1)) {
    val = (val & 0xFF00) | input;
  } else {
    val = (val & 0x00FF) | (input << 8);
  }
  *xbar = val;
}

//EXTMEM uint32_t ADC_buf[262144];
uint32_t ADC_buf[65535];  // 262,140 bytes of RAM; about
uint32_t ADC_buf_indx = 0;

int led_state = 0;

void LpSpi3_ISR()
{

  ADC_buf[ADC_buf_indx] =  LPSPI3_RDR;

  if (++ADC_buf_indx >= 65535) ADC_buf_indx = 0;

 CORE_PIN3_PORTSET = CORE_PIN3_BITMASK;
  LPSPI3_TDR = 0x00000000;   //Perform dummy write to read the next ADC results
CORE_PIN3_PORTCLEAR = CORE_PIN3_BITMASK;

  LPSPI3_SR = LPSPI_SR_TCF;  //Clear the interrupt flag
 
  /* NOTE
  Due to the clock frequency difference between CPU and
  peripheral, in some corner case, peripheral interrupt flag may
  not be really cleared before CPU exits ISR. In such cases, user
  should add DSB instruction right after instruction to clear
  interrupt flag. */
 
  asm volatile ("dsb");

}  // End LpSPI3_ISR()

 void xbar_init()
{
    CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON);   //turn clock on for xbara1
    xbar_connect(XBARA1_IN_FLEXPWM2_PWM1_OUT_TRIG1, XBARA1_OUT_LPSPI3_TRG_INPUT); //FlexPWM2 to LPSPI3
}



static void toggle_led()
{
  if (led_state)
  {
  digitalWriteFast(LED_BUILTIN, LOW); // LED off
  led_state = 0;
  }
  else
  {
  digitalWriteFast(LED_BUILTIN, HIGH); // LED on
  led_state = 1;
  }
} 


void setup() {
    //Setup clock for ADC
  FLEXPWM2_MCTRL        |= FLEXPWM_MCTRL_CLDOK( 1 );

  FLEXPWM2_SM0CTRL     = FLEXPWM_SMCTRL_FULL | FLEXPWM_SMCTRL_PRSC(0);
  FLEXPWM2_SM0CTRL2     = FLEXPWM_SMCTRL2_INDEP | FLEXPWM_SMCTRL2_CLK_SEL(0);            // wait enable? debug enable?
  FLEXPWM2_SM0INIT     = 0;
  FLEXPWM2_SM0VAL0     = 0;                                    // midrange value to position the signal
  FLEXPWM2_SM0VAL1     = 600;                                // max value for 250 kHz
  FLEXPWM2_SM0VAL2     = 0;                                    // start A
  FLEXPWM2_SM0VAL3     = 40;                                // end A - for positive pulse width
  FLEXPWM2_OUTEN        |= FLEXPWM_OUTEN_PWMA_EN( 1 );
  //FLEXPWM2_SM0TCTRL     = FLEXPWM_SMTCTRL_PWAOT0;              // route the output waveform directly to the trigger
  IOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_06 = 1;  // route the output to Teensy pin 4
  IOMUXC_SW_PAD_CTL_PAD_GPIO_EMC_06 |= IOMUXC_PAD_SPEED(2) | IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SRE;
  FLEXPWM2_MCTRL        |= FLEXPWM_MCTRL_LDOK( 1 );
  FLEXPWM2_MCTRL        |= FLEXPWM_MCTRL_RUN( 1 );
  // Trigger setup. Page 3282
  //FLEXPWM2_SM0TCTRL = FLEXPWM_SMTCTRL_PWAOT0 | FLEXPWM_SMTCTRL_PWBOT1;
  FLEXPWM2_SM0TCTRL = FLEXPWM_SMTCTRL_OUT_TRIG_EN(8);  //xx_1xxxb - PWM_OUT_TRIG1 will set when the counter value matches the VAL3 value.

  xbar_init();

  ad738x_init(&dev);

 //Enable SPI1 interrupt to read ADC
  attachInterruptVector(IRQ_LPSPI3, LpSpi3_ISR);
  LPSPI3_CFGR0 = LPSPI_CFGR0_HRSEL | LPSPI_CFGR0_HREN; //Setup SPI3 to use trigger from PWM via XBAR, active low | LPSPI_CFGR0_HRPOL
  NVIC_ENABLE_IRQ(IRQ_LPSPI3);
  LPSPI3_IER = LPSPI_IER_TCIE;   // Enable Transfer Complete interrupt

    // initialize the digital pin as an output.
  pinMode(testPin, OUTPUT);
  pinMode(PWM_testPin, OUTPUT);

  pinMode(LED_BUILTIN, OUTPUT);

}

// Motor direction
#define UP 0
#define DOWN 1

  uint16_t UpDown = DOWN;
  int16_t ADC_ChanA_In = 0;
  int16_t ADC_ChanB_In = 0;



void loop() {
  // put your main code here, to run repeatedly:

  static uint32_t last_millis = 0;
  uint32_t duration;
  duration = millis() - last_millis;


    if (duration > 1000)
    {
      int16_t adc_data_chA = 0;
      int16_t adc_data_chB = 0;
      float adc_ch0, adc_ch1;

      union values{
      uint8_t buf[4];
      uint32_t int32_val;
      } val;


      if (ADC_buf_indx) val.int32_val = ADC_buf[ADC_buf_indx - 1];
        else val.int32_val = ADC_buf[ADC_buf_indx];
      
        //  Conversion data is 32 bits long in 1-wire mode, containing both channels.
        //  Separate the two channels.
        adc_data_chB =  (int16_t)((val.buf[1] << 8) | val.buf[0]);
        adc_data_chA =  (int16_t)((val.buf[3] << 8) | val.buf[2]);

      adc_ch0 = adc_data_chA * dev.ref_voltage_mv;
      adc_ch1 = adc_data_chB * dev.ref_voltage_mv;
      adc_ch0 /= 65535;
      adc_ch1 /= 65535;

      last_millis = millis();
      toggle_led();
      
      //Serial.printf("reference %d\n", dev.ref_voltage_mv);
      //Serial.printf("%4.2f   %4.2f\n", adc_ch0, adc_ch1);
      //Serial.printf("%x  %x\n", adc_data_chA, adc_data_chB);
    }

}
```
 

Attachments

  • ad738x.cpp
    11.4 KB · Views: 29
  • ad738x.h
    7.5 KB · Views: 29
  • no_os_util.h
    9.2 KB · Views: 34
I have moved the test pin code up to surround the increment and test of the buffer index.

ADC_buf[ADC_buf_indx] = LPSPI3_RDR;

CORE_PIN3_PORTSET = CORE_PIN3_BITMASK;
if (++ADC_buf_indx >= 65535) ADC_buf_indx = 0;
CORE_PIN3_PORTCLEAR = CORE_PIN3_BITMASK;

LPSPI3_TDR = 0x00000000; //Perform dummy write to read the next ADC results

I am using a 300 MHz scope (RTB2004) which may be responsible for some increase in the rise and fall times. However, 60 nS seems long for the increment/if-then statement.

Green is the GPIO pin for time interval measurements
Blue is the SCK
Orange is SDI

1722454175177.png
 
Back
Top