attachInterrupt timing

Hello,

I'm generating a wave that's roughly 2.94MHz (340ns periodicity) feeding to digital pin 2. I attached an interrupt with its rising edge to do something. In this case, to make things as simple as possible, I just made digital pin 8 high then low right away.

When viewed through a logic analyzer, I see that it works but the timing when the pin 8 changes is inconsistent to when the rising edge happens on pin 2.

Here is the simple code that I have used:

void setup() {
  pinMode(8, OUTPUT);
  digitalWriteDirect(8, LOW);

  pinMode(2, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(2), thisInterrupt, RISING);
}

void loop()
{
}

void thisInterrupt()
{
  digitalWriteDirect(8, HIGH);
  digitalWriteDirect(8, LOW);
}

inline void digitalWriteDirect(int pin, boolean val) {
  if (val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
  else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;
}

Here are pictures from the logic analyzer where ch3 is pin 2 and ch4 is pin 8:

1

2

As you can see, the change to pin 8 is inconsistent as stated above.

Is there any way to have the timing for the attachInterrupt be consistent?

Hi @etk2022

It's because the interrupt period is too short for the CPU to handle.

Hi MartinL, is there any way to make it so pin 8 happens at a consistent timing based on the rising edge of pin 2?

Hi @etk2022

So you require pin 8 to output a delayed pulse, triggered by the rising edge of the input signal on pin 2?

Use a much faster microcontroller?!

You didn't say what type of Arduino you are using, so we will assume Uno, which has a 16MHz clock speed. Your interrupts are occurring every 16/2.94=5 or 6 clock cycles. There's no way your interrupt code is going to execute in less than 5 clock cycles because each individual machine instruction takes a minimum of one clock cycle. A line of C code can translate into many machine instructions, tens or even hundreds.

Maybe your interrupt code takes less than 50 cycles, in which case you need an MCU with a clock speed of at least 160MHz.

Or you make a hardware circuit to do it...

We are in Arduino Due topic so I assume it is not Uno.

1 Like

Oops, I completely missed that, having picked it up from the "New" topics. Sorry @etk2022

So the clock speed is 5x faster and interrupts will happen every ~28 clock cycles. That still might not be fast enough, I guess. There will be additional overhead cycles needed when an interrupt is called, in addition to the interrupt code itself.

Maybe this could save a few cycles. Untested!

const int myPin = 8;
const unsigned long setMyPinHigh = g_APinDescription[myPin].pPort -> PIO_SODR;
const unsigned long setMyPinLow = g_APinDescription[myPin].pPort -> PIO_CODR;
const unsigned long myPinMask = g_APinDescription[myPin].ulPin;

void setup() {
  pinMode(myPin, OUTPUT);
  digitalWrite(myPin, LOW);

  pinMode(2, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(2), thisInterrupt, RISING);
}

void loop()
{
}

void thisInterrupt()
{
  setMyPinHigh = myPinMask;
  setMyPinLow = myPinMask;
}

Hi @etk2022

As the CPU is unable to service interrupts at the 3MHz, another option is to use 3MHz signal as an input to the Due's TC timer trigger pin and output a delayed pulse on the timer's output.

The following code sets up timer TC6 (confusingly also know as: TC2 Channel 0) with the trigger input on D30 and the output on D5. The input set to trigger on the rising edge of the signal. The result is an output pulse delayed with respect to the rising edge of the input trigger:

// Set up the Arduino Due's TC6 (TC2 Channel 0) timer to trigger on the rising edge of input D30 and 
// output a delayed pulse output on D5

void setup() 
{ 
  PMC->PMC_PCER1 |= PMC_PCER1_PID33;                      // Enable peripheral TC6 (TC2 Channel 0) 
  PIOC->PIO_ABSR |= PIO_ABSR_P26 | PIO_ABSR_P25;          // Switch the multiplexer to peripheral B for TIOA6 (D5) and TIOB6 (D4)
  PIOC->PIO_PDR |= PIO_PDR_P26 | PIO_PDR_P25;             // Disable the GPIO on the corresponding pins
  PIOD->PIO_ABSR |= PIO_ABSR_P9;                          // Switch the multiplexer to peripheral B for TCLK8 (D30)
  PIOD->PIO_PDR |= PIO_PDR_P9;                            // Disable the GPIO on the corresponding pin

  TC2->TC_CHANNEL[0].TC_CMR = TC_CMR_ACPC_CLEAR |         // Clear TIOA6 on counter match with RC0                           
                              TC_CMR_ACPA_SET |           // Set TIOA6 on counter match with RA0
                              TC_CMR_WAVE |               // Enable wave mode
                              TC_CMR_WAVSEL_UP_RC |       // Count up with automatic trigger on RC compare
                              TC_CMR_ENETRG |             // Enable an external trigger event
                              //TC_CMR_EEVT_TIOB |          // Set the external trigger input to TIOB6 (D4)
                              TC_CMR_EEVT_XC2 |           // Set the external trigger input to TCLK8 (D30)
                              TC_CMR_ETRGEDG_RISING |     // Set to trigger event on rising edge
                              TC_CMR_CPCSTOP |            // Stop the timer at the end of the cycle (oneshot operation)                          
                              TC_CMR_TCCLKS_TIMER_CLOCK1; // Set the timer clock to TCLK1 (MCK/2 = 84MHz/2 = 42MHz)

  TC2->TC_CHANNEL[0].TC_RA = 1;                            // Set the trigger to output rising edge delay 1/42MHz * 1 = 24ns + propagation delay 
  TC2->TC_CHANNEL[0].TC_RC = 2;                            // Set the output pulse width 1/42 * (2 - 1) = 24ns

  TC2->TC_CHANNEL[0].TC_CCR = TC_CCR_CLKEN;               // Enable the timer TC6 (TC2 Channel 0)
}

void loop() {}

Set TC_RA to adjust the delay and TC_RC the pulse width.

Hi @PaulRB, as others mentioned, I'm indeed doing this on the Due. I have a question in regards to interrupts. Let's just use your example of 5 clock cycles for simplicity. If the command I want in the interrupt takes 20 clock cycles, will that entire command not complete since it's being constantly interrupted every 5 cycles or does it wait for whatever is happening inside the interrupt function?

Hi @MartinL, thanks for the example. I have tried it and it works well but the end goal is to have more code than just a pin going high and low so I was really hoping attachInterrupt would be the answer.

An interrupt on a cortex-m3 CPU takes at least 12 cycles (for 0-wait-state RAM) to enter an ISR, and another 12 cycles to exit the ISR. An 84MHz CPU is not going to handle a 3MHz interrupt rate...

will that entire command not complete since it's being constantly interrupted every 5 cycles or does it wait for whatever is happening inside the interrupt function?

Usually the interrupt isn't re-enabled until the ISR is exited. The CM3 has a tail-chaining ISR feature such that if a new interrupt occurs before the ISR exit, it will skip most of the register stacking it would normally do, which can reduce the latency in responding to the second/etc interrupt. But if you have continuous interrupts coming in faster than the overhead+ISR code time, you might never get to execute and non-interrupt code.

If I only have one interrupt in the program, does setting a higher priority provide higher consistency in the interrupt? For my project, the consistency in timing between the two events (falling edge to trigger interrupt and actions taken in the interrupt) is much more important than the latency.

If setting a higher priority can help, what commands do I need to include in the program?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.