Triggering single-byte SPI transaction on 1.25MHz clock barely fits in time

thebeak

New member
I'm trying to trigger a single-byte SPI transaction (reading parallel lines via a shift register, the 74HC165), with the rising edge only of a 1.25MHz square wave signal (DS1077 programmable clock), and at present, the first digital write (pull the shift register load low) transaction only starts 110nsec after the rising edge. Then there is an intentional 50nsec delay for the shift register, then two digital writes - the shift register load high, then the chip select for the SPI transaction, which starts 250nsec after the rising edge. The problem is I wish to run with a higher clock rate (4MHz), and the interrupts stack up/fail if I run any faster than 1.25MHz.

I've tried to reduce timing to complete the SPI transaction as much as possible and have tried 1) polling (variable and often slower), 2) attachInterrupt and 3) attachInterruptVector (#define in the attached example sketch). However, I am very unsure whether I'm using attachInterruptVector correctly or not.

1000001753.jpg

With attachInterruptVector, I can get slightly faster times, but its a wash and worse, it misses some of the clocks.
1000001752.jpg

I've used a teensy 4.0 and 4.1 (wasn't expecting any changes, but considered perhaps I'd damaged one of them) with no changes in observed behavior.

I had thought the Teensy 4 would be able to complete its transaction somewhat faster, its possible I've just got inflated expectations (since the teensy is so darn capable). I've seen others toggling gpios faster than this, is there a reason why there's a long delay between the ISR getting triggered (or polling) and being able to do a digitalWriteFast or SPI transaction?
 

Attachments

  • example_problem_isr.ino
    5.8 KB · Views: 15
The sketch below shows how to bypass the code that figures out which pin the interrupt came from, which is one source of delay in your interrupt response. It uses pin 18 to generate PWM and pin 34 to interrupt on rising edges of the PWM. It uses 5 MHz by default, but works up to 10 MHz, IIRC. You'll definitely need to use this technique to go faster.

I learned this technique from user @slash2 on the thread titled Fast Data Logger.

Note that when you generate these higher PWM frequencies on Teensy, each PWM period is a small number of the 150-MHz clocks for the peripheral. This means you can generate frequencies that divide evenly into 150, such as 5, 7.5, 10, but if you choose other frequencies they won't accurate.

Let us know what happens and whether you get to a higher frequency.

Code:
// Benchmark interrupts on digital input pin

// connect pins 18 and 34
const int PWMPin = 18;       // square wave output
const int InterruptPin = 34; // square wave input

// pre-calculate address of status register for clearing digital interrupt
volatile uint32_t& portStatusReg = (digitalPinToPortReg(InterruptPin))[6];
uint32_t mask = digitalPinToBitMask(InterruptPin);

volatile uint32_t isrCount;
volatile uint32_t cycleCount;
elapsedMillis oneSec;

void InterruptHandler()
{
  portStatusReg = mask;        // default handler bypassed, so we must reset the status flag
  cycleCount = ARM_DWT_CYCCNT; // read cycle counter to simulate input
  isrCount++;                  // keep track of ISR executions
  asm volatile("dsb");         // avoid double calls due to possible bus sync issues
}

void setup()
{
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {}

  analogWriteFrequency( PWMPin, 5'000'000 ); // set PWM frequency
  analogWrite( PWMPin, 128 );                // 50% duty cycle -> square wave

  // configure pin as digital input
  pinMode( InterruptPin, INPUT );
 
  // attach an interrupt on RISING edges (let Teensyduino do the setup work)
  attachInterrupt( InterruptPin, nullptr, RISING );
 
  // override Teensyduino handler by vectoring IRQ_GPIO6789 directly to our ISR
  attachInterruptVector( IRQ_GPIO6789, InterruptHandler );

  // enable IRQ and set priority to 0 (highest, might be a good idea to reduce a bit)
  NVIC_ENABLE_IRQ( IRQ_GPIO6789 );
  NVIC_SET_PRIORITY( IRQ_GPIO6789, 0 );
}

void loop()
{
  while (1) {
    if (oneSec >= 1000) {
      uint32_t temp = isrCount;
      isrCount = 0;
      oneSec -= 1000;
      Serial.println( temp );
    }
  }
}
 
Thank you @joepasquariello , this gets me to 2.3MHz (with the SPI transfer incorporated), which is much closer to where I wanted to be - I may not be able to get to 4MHz but I'll keep trying and thank you for pointing out @slash2 's post. I moved my first digitalWriteFast to before clearing portStatusReg which gave me a few more tens of nsec. At 600MHz, I had thought the tightly coupled gpios might be able to get to within a few clock cycles (1.67nsec times a small integer).
 
I understand now, this is about the minimum given the need to save all registers. I read the "Teensy 4.1 Interrupt Problem" post from @MaKettle and 27 clocks is quite reasonable.
 
If you feel up to reading the IMXRT1062 datasheet, it is possible to program the SPI hardware so that transactions will be triggered by a pin - not slave mode, but regular master mode with queued transactions triggered by HREQ.
 
Quick note on pin IRQs, is sometimes I have played with using pin 34 on T4.1
34RX82:29,3:292.29B1_13CSI_VSYNC

Which is on GPIO 2.29, so I might switch pin 31 back to Normal mode (so GPIO 2, not 7)
And use the IRQ: IRQ_GPIO2_16_31
Which pins are fast is setup in startup.c
Code:
#if defined(__IMXRT1062__)
    // Use fast GPIO6, GPIO7, GPIO8, GPIO9
    IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
    IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
#endif
So your code simply would need to update IOMUXC_GPR_GPR27 and set bit 29 to a 0...
 
Back
Top