Teensy 4.0/4.1 Interrupts Issue

mwomack

Well-known member
I am writing some code to count the U/V/W encoder signals from a BLDC eScooter motor. The encoder signals from my motor seem to be rather noisy, which I can see on my digital analyzer. However, my code works as expected on the Teensy 3.5 chip, seeing the encoder fire signals in the expected order with no transient signals. However, when I changed to the Teensy 4.1 chip, I found that I get transient signals that are duplicating a previously fired signal or a signal out of order. My motor encoders are at 5v, so I use a level shifter to bring them down to 3.3 to connect to the Teensy 4.1 pins. Is there something about the Teensy 4.1 that would cause it to be more susceptible to noise?

Here are the major excerpts of my code:

Code:
//// pin assignments
// Teensy 3.5/4.0/4.1 - These assignments work across all versions
const uint8_t PWM_SPEED_PIN(2);
const uint8_t BRAKE_PIN(3);
const uint8_t MOTOR_DIR_PIN(4);
const uint8_t LED_BUILTIN_PIN(13);
const uint8_t U_ENCODER_SIGNAL_PIN(14); // yellow - Ha - U
const uint8_t V_ENCODER_SIGNAL_PIN(15); // blue   - Hb - V
const uint8_t W_ENCODER_SIGNAL_PIN(16); // green  - Hc - W
const uint8_t BUTTON_PIN(23);

volatile int32_t tickCount;
volatile int32_t uCount;
volatile int lastUVal;
volatile int32_t vCount;
volatile int lastVVal;
volatile int32_t wCount;
volatile int lastWVal;
volatile char lastEncoder;

...

  pinMode(U_ENCODER_SIGNAL_PIN, INPUT);
  lastUVal = digitalRead(U_ENCODER_SIGNAL_PIN);
  attachInterrupt(U_ENCODER_SIGNAL_PIN, countUTick, CHANGE);
  
  pinMode(V_ENCODER_SIGNAL_PIN, INPUT);
  lastVVal = digitalRead(V_ENCODER_SIGNAL_PIN);
  attachInterrupt(V_ENCODER_SIGNAL_PIN, countVTick, CHANGE);
  
  pinMode(W_ENCODER_SIGNAL_PIN, INPUT);
  lastWVal = digitalRead(W_ENCODER_SIGNAL_PIN);
  attachInterrupt(W_ENCODER_SIGNAL_PIN, countWTick, CHANGE);

/**
  Interrupt handler for the U encoder signal.
**/
void countUTick() {
  // Read the current signal value
  int val = digitalRead(U_ENCODER_SIGNAL_PIN);

  // Check for an encoder fault  
  bool encoderFault = (lastEncoder == 'U') || // Last encoder was this one
                      (lastEncoder == 'W' && motorContext.motorDirection) || // Last encoder was W, but going forward
                      (lastEncoder == 'V' && !motorContext.motorDirection);  // Last encoder was V, but going reverse

  //if (encoderFault) {
    DebugMsgs.notification().print("U ").print(lastUVal).print(' ').print(val).print(' ')
      .print(lastEncoder).println(encoderFault ? " *" : "");
  //}

  // If there is a fault, don't record a tick.
  if (encoderFault) { return; }
  
  int increment = (lastEncoder == 'W') ? 1 : -1;
  uCount += increment;
  tickCount += increment;
  lastEncoder = 'U';
  lastUVal = val;
}

The interrupt handlers for the V and W encoders look exactly the same, only the variable names are changed to protect the innocent. If you want to see the full source, you can find it here.

I took the step of running the encoder signals through a schmitt trigger, and that helped by cleaning up the signal somewhat. But on the Teensy 4.1 I still get some number of transients.

Any advice or insight appreciated.

-Mark
 
Last edited by a moderator:
Sorry, I should have said that my current hardware is using the Teensy 4.0, though I was seeing the same thing with the Teensy 4.1 before.
 
Code:
//// pin assignments
// Teensy 3.5/4.0/4.1 - These assignments work across all versions
const uint8_t PWM_SPEED_PIN(2);
const uint8_t BRAKE_PIN(3);
const uint8_t MOTOR_DIR_PIN(4);
const uint8_t LED_BUILTIN_PIN(13);
const uint8_t U_ENCODER_SIGNAL_PIN(14); // yellow - Ha - U
const uint8_t V_ENCODER_SIGNAL_PIN(15); // blue - Hb - V
const uint8_t W_ENCODER_SIGNAL_PIN(16); // green - Hc - W
const uint8_t BUTTON_PIN(23);

volatile int32_t tickCount;
volatile int32_t uCount;
volatile int lastUVal;
volatile int32_t vCount;
volatile int lastVVal;
volatile int32_t wCount;
volatile int lastWVal;
volatile char lastEncoder;

...

pinMode(U_ENCODER_SIGNAL_PIN, INPUT);
lastUVal = digitalRead(U_ENCODER_SIGNAL_PIN);
attachInterrupt(U_ENCODER_SIGNAL_PIN, countUTick, CHANGE);

pinMode(V_ENCODER_SIGNAL_PIN, INPUT);
lastVVal = digitalRead(V_ENCODER_SIGNAL_PIN);
attachInterrupt(V_ENCODER_SIGNAL_PIN, countVTick, CHANGE);

pinMode(W_ENCODER_SIGNAL_PIN, INPUT);
lastWVal = digitalRead(W_ENCODER_SIGNAL_PIN);
attachInterrupt(W_ENCODER_SIGNAL_PIN, countWTick, CHANGE);

/**
Interrupt handler for the U encoder signal.
**/
void countUTick() {
	// Read the current signal value
	int val = digitalRead(U_ENCODER_SIGNAL_PIN);

	// Check for an encoder fault
	bool encoderFault = (lastEncoder == 'U') || // Last encoder was this one
		(lastEncoder == 'W' && motorContext.motorDirection) || // Last encoder was W, but going forward
		(lastEncoder == 'V' && !motorContext.motorDirection); // Last encoder was V, but going reverse

	//if (encoderFault) {
	DebugMsgs.notification().print("U ").print(lastUVal).print(' ').print(val).print(' ')
		.print(lastEncoder).println(encoderFault ? " *" : "");
	//}

	// If there is a fault, don't record a tick.
	if (encoderFault) { return; }

	int increment = (lastEncoder == 'W') ? 1 : -1;
	uCount += increment;
	tickCount += increment;
	lastEncoder = 'U';
	lastUVal = val;
}
When you paste code can you put it between CODE tags using the # button above (when editing post).
It makes your code so much more easy to read, understand and therefore be able to help you.
 
Thank you for the replies. I will look into those options. But I wanted to also update some exploration I did with the interrupts on the 3.5 and 4.0.

I created a simple circuit that has a potentiometer with output connected to both an analog pin (so I could read the level) and an interrupt pin (so it could trigger). I created the same circuit for the 3.5 and 4.0 chips. And I wrote a simple program to trigger an interrupt on a RISING edge and report the level.

Code:
#include <Arduino.h>
#include <inttypes.h>

//// pin assignments
const uint8_t INTERRUPT_PIN(2);
const uint8_t SIGNAL_LEVEL_PIN(0);

void handleInterrupt() {
    int signalLevelAtInterrupt = analogRead(SIGNAL_LEVEL_PIN);
    Serial.print("Interrupt: "); // I know this can't be here in real code
    Serial.println(signalLevelAtInterrupt);
}

void setup() {
  Serial.begin(9600);
  // give serial some time to catch up
  delay(1000);

  // Pin setup
  pinMode(SIGNAL_LEVEL_PIN, INPUT);
  pinMode(INTERRUPT_PIN, INPUT);

  attachInterrupt(INTERRUPT_PIN, handleInterrupt, RISING);

  Serial.println("Starting...");
}

void loop() {
  // Loop for an interrupt...
}

I will say upfront that I am doing this all by hand, turning the potentiometer very slowly to find the trigger point. I wish I had a digital potentiometer that I could use to drive this programmatically. As it is, my potentiometer value can be somewhat noisy, the value can switch between a value +/-2, even when I am not touching it. At least that I saw when I had a lot more output to serial, which made the program very unwieldly to debug with.

3.5: It triggered consistently around 1.83v with everything at 5v. There was only a single interrupt, and I can change the value quite a bit (making it less), before it will triggers with another RISING interrupt when i increase the value.

4.0: It triggered consistently around 1.58v with everything at 3.3v. But interestingly, there were two interrupts, one right after the other. The values reported are different by about 8 (510 vs 518). And there is a much narrower window that I can change the value before it triggers again. But it appears that my potentiometer is noisy enough to cause dual triggers? I also got into a state where I was not moving the potentiometer, but the noise it was creating was just triggering over and over. But I was only able to do that once, so I can't speak to it as well.

But it does appear that 3.5 and 4.0 have different behavior here. The 4.0 seems to trigger multiple times for some reason. Maybe it is more sensitive when values drop after a RISING trigger that it triggers again when there is just a small increase?

Without a digital potentiometer, I can't do much more to simulate noise in a controlled manner. And the signals I am getting from the encoders does not hover around the trigger point. It goes to 0 and 3.3v, not 1.58v. But there seems to be a difference in behavior here.

-Mark
 
And there is a much narrower window that I can change the value before it triggers again

You can try to change
Code:
  pinMode(INTERRUPT_PIN, INPUT);

to
Code:
  pinMode(INTERRUPT_PIN, INPUT[COLOR="#FF0000"]_PULLDOWN[/COLOR]);

This is supposed to enable some hysteresis on the pin which should also remove multiple interrupts caused by to noisy input.
 
Back
Top