Teensy 4.1 glitch on digital interrupt

DMorris

Member
Hi all,

I've been working on setting up a simple motor control system, using a BLDC with an AMT102 encoder and a Teensy 4.1 (600 MHz clock speed). The encoder is set to 100 PPR, with the motor's RPM below the max RPM for this encoder resolution (there's a 5:1 reduction gearhead on the motor, hence the low PPR setting). I'm using ~5.6k resistors to condition the 5V output signal for 3.3V tolerant inputs.

I've noticed that, when just using the A/B channels, that I'm getting a fair bit of overshoot when using bang-bang and PID control, probably due to motor inertia, so I've added in the index pulse so that I can periodically correct the motor position to avoid accumulating position error. I'm using the twisted pair shielded cable that CUI makes to reduce noise from the motor.

To test the performance, I've got a small program that uses a rising edge pin change interrupt on the encoder's index pin to toggle a GPIO pin (and increment a counter), so that I can check if pulses are being missed/if there is too much noise. On the scope everything looks usable, but I noticed that occasionally the number of counts recorded is slightly too large. On the scope I can see that, sometimes, the digital output quickly toggles twice for a single rising edge. There doesn't seem to be an obvious cause, and it doesn't seem to be reliably repeatable.

I don't think motor noise is causing issues, as I can't see noise on the scope that could be responsible. I'm considering adding short debouncing to the index pulses, but want to see if there may be some other culprit first.

Does anyone have any thoughts?

Code snippet and scope plots below; encoder index pulses are in yellow, digital IO pin being toggled is in green. Please excuse the scope noise levels, the scope I'm using is fairly old and has had a hard life!

C:
volatile int32_t idx_counts = 0;

void PC_INDEX_ISR(void) {
  ++idx_counts;
  digitalToggleFast(7);
}

void setup() {
  // IO + motor control setup...

  // Setup PC interrupt for index pulses
  attachInterrupt(digitalPinToInterrupt(channelX), PC_INDEX_ISR, RISING);

  // Drive motor at a fixed speed for a known distance, then stop...
}

Index pulses + GPIO toggle. Note the apparently missed pulse in the centre. False GPIO toggle shown below.
Index_pulses.png
Pulse_GPIO_toggle_glitch.png

Pulse_GPIO_glitch_demarked.png
 
Last edited:
Judging from the second pic, it looks like the index pulse is taking too long to rise (and possibly fall), hovering around the no-mans-land between "high" and "low" long enough to cause false readings. What happens if the index pin is configured with pinMode INPUT_PULLDOWN or INPUT_PULLUP (which activates the hysteresis filter)?
 
Interesting read - not sure about some details.

Assuming a Teensy 4.x?

not a practiced o'scope reader - scaling? -- are those toggles about 22us apart on the 'pulses' image? And scale 1us on second glitch image?

Posted recently where pin CHANGE ISR could be self triggered from loop() "TOGGLE" with two I/O wired together in about 200 clock cycles or 3 times per 1us. It was more or less depending on the code in the ISR.

This says that transition could be REAL [ going down and back up in 1 us ] as the short ISR shown could respond to received input that fast - a Half microsecond.
 
Judging from the second pic, it looks like the index pulse is taking too long to rise (and possibly fall), hovering around the no-mans-land between "high" and "low" long enough to cause false readings. What happens if the index pin is configured with pinMode INPUT_PULLDOWN or INPUT_PULLUP (which activates the hysteresis filter)?
I did wonder about that; my understanding was that Paul changed the attachInterrupt routine to automatically turn on hysteresis, so it shouldn't need to be manually configured, but I can give that a try.

Looking at the IMXRT1060 datasheet, the transition voltage should be above the logic low input maximum, but could be in the deadband with a 3.3V GPIO supply...

Also added a patch to always turn on hysteresis.


Hopefully these 2 will give a smoother attachInterrupt experience in the upcoming 1.59 release.
 
Interesting read - not sure about some details.

Assuming a Teensy 4.x?

not a practiced o'scope reader - scaling? -- are those toggles about 22us apart on the 'pulses' image? And scale 1us on second glitch image?

Posted recently where pin CHANGE ISR could be self triggered from loop() "TOGGLE" with two I/O wired together in about 200 clock cycles or 3 times per 1us. It was more or less depending on the code in the ISR.

This says that transition could be REAL [ going down and back up in 1 us ] as the short ISR shown could respond to received input that fast - a Half microsecond.
Sorry, I forgot to put that in the post itself - I'm using a Teensy 4.1, 600 MHz clock setting, with a ~5.6k resistor between the index pulse line and the GPIO pin to current limit (I read a post somewhere from Paul about doing this with the AMT encoder, as it signals at 5V, can't find it to link sorry!).

Here's an annotated trace; time between glitch transitions is about ~0.5 us, and index pulses are about 30 ms apart, with a pulse width of ~50 us. Pulse voltage levels are about 0-3.3V as expected.

Interesting, I can see how that kind of change ISR could be self-triggered if the ISR is fast enough. I don't think that's happening here, as I'm just looking for externally driven rising edge triggers. I would have thought that this would require the pulse to cross the rising edge level, dip back below the hysteresis band, then increase over the rising edge level again, which isn't apparent on the scope trace.

I suppose its possible that the index pulse edges are fast enough to trigger the ISR twice in quick succession, I'm planning on testing some short debouncing timing this afternoon which might confirm that!

Pulse_GPIO_glitch_demarked.png
 
Out of interest does the frequency of the problem change when you are measuring the interrupt input pin on the scope?
Realistically your scope isn't nearly fast enough to reliably and accurately capture the sort of glitches you look to be experiencing. Glitches are one of the times where a high bandwidth and high sample rate scope makes a big difference.

As has been noted it looks like the interrupt is triggering multiple times on the same rising edge. A glitch filter/hysteresis would help with this. You could also put a firmware rate limit in:

Code:
void PC_INDEX_ISR(void) {
  static uint32_t lastTrigger = 0;
  uint32_t now=micros();
  if ((now-lastTrigger) > 1) {
      ++idx_counts;
      digitalToggleFast(7);
    lastTrigger = now;
  }
}

This does limit the maximum rate to a 2 us period, if that's not fast enough you could always use CPU clock counts instead of microseconds.
 
CPU clock counts instead of microseconds
The thread linked in p#3 shows use of ARM_DWT_CYCCNT - and it reads at 3 cpu cycles, 10 times faster than micros(), and counts of 600 per us.

That sketch can trigger the ISR 3-4 times per us and track it accurately. You could even add an else for logging the glitches:
Code:
volatile uint32_t glitch_cnt = 0;
void PC_INDEX_ISR(void) {
  static uint32_t lastTrigger = 0;
  uint32_t now=ARM_DWT_CYCCNT;
  if ((now-lastTrigger) > 600) { // once per us at 600 MHz
    ++idx_counts;
    digitalToggleFast(7);
    lastTrigger = now;
  }
  else {
    ++glitch_cnt;
    //     digitalToggleFast( X ); // trigger a monitor pin 'X' on glitch
  }
}
 
A slow edge like that, taking several µs to rise or fall can easily trigger multiple transitions on a fast processor. A proper Schmitt-trigger like a 74HC14 is needed to clean it up to a nice fast transition. In a noisy environment this is a standard approach to help with noise-margins too.

I think the slow rise is mainly cable capacitance (the AMT102 is spec'd for 30ns rise and fall times).

Some encoders use differential outputs (think 3 RS485 pairs, for A, B and Z), so that the noise-margin is good and edges don't slow down due to capacitance.

I'm using the twisted pair shielded cable that CUI makes to reduce noise from the motor.
On the motor leads, the encoder, or one on each?

I'd forgotten CUI Devices had changed their name to Same Sky, makes me laugh for some reason.
 
Thanks for all your thoughts folks! I've added a simple deglitch filter using a 6us one-shot timer to ensure the pulse is still high after a PC interrupt, which seems to have fixed the issue.

I had assumed the internal Schmitt triggers would be sufficient to catch these, but obviously that isn't the case with fast, somewhat noisy transitions!

For those curious, here's an outline of the deglitched method I'm using. I'm using Luni's TeensyTimerTool to generate the one-shot timer, which works well at this speed (6us +/- 1us).

C:
OneShotTimer idx_debounce_timer(GPT1);
volatile bool idx_rising_edge = false;

// Callback for incrementing index count. Run by deglitch one-shot timer.
void idx_inc(void) {
  cli();
  if (idx_rising_edge) {
    ++idx_counts;
    idx_rising_edge = false;
  }
  sei();
}

// Setup ISR to trigger on pin change (either edge)
void PC_INDEX_ISR_DEBOUNCE(void) {
  if (digitalReadFast(channelX) && (!idx_rising_edge)) {
    // New rising edge; start timer, set rising edge flag
    idx_rising_edge = true;
    idx_debounce_timer.trigger(INDEX_DEBOUNCE_PERIOD_US);
  }
  else {
    // Falling edge; clear flag
    idx_rising_edge = false;
  }
}
 
Stumbled upon this post looking for a solution. Yet here we are.

I have a zero-cross optocoupler circuit looking at 60hz. I toggle a pin ON/OFF on entrance/exit with a defined "RISING" pin interrupt. It fires 2-4, sometimes 6-7 times! Used INPUT_PULLUPS to see if there's added hysteresis, nope. Also added 100nF capacitance, nope.

Only thing I can really do is detach quickly in the interrupt and re-arm sometime later with a timer.

It seems to be caching multiple interrupt events on the stack and later processing the ISR as it is so freaking fast.

Because I can place a long delay in the ISR, and it will re-trigger the ISR long outside of the transition event. Its very strange.

Green: Opto-output going to teensy 4.1 pin.
Purple: ISR pin, High=in ISR, Low= not in ISR

SDS00008.png



SDS00009.png
 
That looks like the same behaviour I was experiencing with my encoder. If multiple fast transitions occur, they will be queued in the NVIC, so the interrupt will trigger multiple times. Adding a delay to the interrupt won't fix the issue, as the queued interrupts will still fire (just with a delay between them).

What worked for me was rate-limiting triggering the desired interrupt processing by adding a layer between the hardware interrupt and the actual interrupt processing I wanted to do - see the deglitch code snippet above. Essentially, when a hardware interrupt is triggered by a rising edge I restart a short timer, and only increment my counter once the timer has expired. That way the hardware interrupt can be triggered multiple times in quick succession, but my counter only gets incremented once.
 
I've been diving in the reference manual, particularly section 11.7.191. Currently using Teensy pins 23 and 22 for interrupt inputs,

Just blindly writing the hysteresis bit, but this does not improve. I'll have to investigate further.
IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_08 |= (1 << 16); // Set HYS bit, pin 22
IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_09 |= (1 << 16); // Set HYS bit, pin 23

I will have to debug what the actual hardware register is using before and after modification as this comes with teensyduino,
volatile uint32_t *configRegister = portConfigRegister( digitalPinToInterrupt(22) );

Looking at the teensyduino core libraries, I see in interrupts.c
C:
*pad |= IOMUXC_PAD_HYS;        // use hystersis avoid false trigger by slow signals"

Will need to do experimentation tomorrow.
 
Last edited:
Correction:
C:
volatile uint32_t *configRegister = portControlRegister( digitalPinToInterrupt( 22 ) );

Got the correct hardware address and hysteresis bit is set by attachInterrupt(). When I clear it.. holy moly!

Seems the silicon Schmitt trigger is still too sensitive. Haven't dabbled into interrupt caching and don't have the time as I'm constrained at the moment. Will likely pursue high speed sampling of the pin.
 

Attachments

  • SDS00010.png
    SDS00010.png
    31.9 KB · Views: 4
and... resolved! Added a 0603 10nF decoupling cap directly on pins 22 and 23 to ground.

Interrupt now fires exactly 1 time throughout the slow rise with Hysteresis ON, even letting it run on the scope for several minutes without a glitched trigger. While you cannot see the glitch on the interrupt pin, something is definitely there and the decoupling cap is there to squash it.

With Hysteresis OFF, interrupt fires 1-2 times during transition. Quite the improvement (at the cost of added time-constant).
 
The cap will be reducing the level of noise - you don't need to slow down the edge (in fact the standard solution is normally a 74HC14 which has proper amounts of hysteresis, much less noise sensitive then).

The noteworthy fact is that a CMOS logic input held at about 1/2 the rail voltage dissipates power (enough to be worth avoiding for battery operation).
 
Back
Top