bobpellegrino
Well-known member
I'm working on a project using a Teensy 4.1 and MJS513's Quad Encoder library to read a standard rotary encoder for user input (setting speed on a model train throttle). My goal is to use an interrupt-driven approach to detect every single step of the encoder, whether it's an increment or a decrement.
I've successfully used the library in a polling mode (encoder.read() in the main loop), and that works perfectly, confirming the hardware counting is functional.
However, I'm struggling to find the intended interrupt-driven method for catching both directions reliably using the library as-is. I explored using the Position Compare feature:
This works fine for detecting increments, as the counter eventually reaches currentPos + ENCODER_DIVIDER. However, if the encoder is turned backwards (decrementing), the counter moves away from the set compare value, and the compare interrupt never fires, so decrements are missed.
My question is: What is the intended interrupt-driven mechanism within the QuadEncoder library to reliably detect both increment and decrement steps?
I've successfully used the library in a polling mode (encoder.read() in the main loop), and that works perfectly, confirming the hardware counting is functional.
However, I'm struggling to find the intended interrupt-driven method for catching both directions reliably using the library as-is. I explored using the Position Compare feature:
- Enable positionCompareMode.
- In the main loop, after processing a change indicated by QuadEncoder::compareValueFlag, I read the current position (currentPos = encoder.read()).
- I then set a new compare value, aiming to catch the next step: encoder.setCompareValue(currentPos + ENCODER_DIVIDER); (where ENCODER_DIVIDER is my steps-per-detent).
- Finally, I re-enable the compare interrupt using encoder.enableCompareInterrupt() (since the library ISR appears to disable ENC_CTRL_CMPIE_MASK upon match).
This works fine for detecting increments, as the counter eventually reaches currentPos + ENCODER_DIVIDER. However, if the encoder is turned backwards (decrementing), the counter moves away from the set compare value, and the compare interrupt never fires, so decrements are missed.
My question is: What is the intended interrupt-driven mechanism within the QuadEncoder library to reliably detect both increment and decrement steps?
- Am I misunderstanding how the Position Compare feature (positionCompareMode, setCompareValue) is meant to be used for this scenario?
- Is there another interrupt source handled by the library's ISR (perhaps related to Rollover/Rollunder flags ROIRQ/RUIRQ, or the hardware Direction Change flag DIRQ, even if not explicitly documented for this purpose) that should be configured or enabled to achieve this?
- Or is polling the hardware counter (encoder.read()) the recommended approach when an interrupt on every arbitrary step change is desired?
C++:
/*
* Model Train Controller
*/
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "QuadEncoder.h"
// Display Configuration
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Encoder Configuration
const int ENCODER_CHANNEL = 1; // Using encoder channel 1 (of 4 available)
const int ENCODER_PIN_A = 8; // Phase A pin (TEENSY_ENCA)
const int ENCODER_PIN_B = 7; // Phase B pin (TEENSY_ENCB)
const int ENCODER_PULLUPS = 0; // No pullups required (0) or required (1)
const int ENCODER_INDEX_PIN = 255; // No index pin used (255 = disabled)
const int ENCODER_DIVIDER = 4; // Encoder counts per detent (adjust based on encoder)
const int MAX_SPEED_VALUE = 999; // Maximum speed value (0-999) for 0-99.9 display
// Variables to track encoder position
int newPosition;
int old_position = 0;
// Create encoder object
QuadEncoder encoder(ENCODER_CHANNEL, ENCODER_PIN_A, ENCODER_PIN_B, ENCODER_PULLUPS, ENCODER_INDEX_PIN);
// Function prototypes
void setupEncoder();
void updateDisplay();
void checkPosition();
void setup() {
Serial.begin(115200);
Serial.println("Train Controller Initializing...");
setupEncoder();
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("SSD1306 display initialization failed");
} else {
display.clearDisplay();
display.display();
}
Serial.println("Initialization complete");
}
void loop() {
// Enable roll over/under interrupts to detect direction changes
encoder.EncConfig.positionROIE = ENABLE;
encoder.EncConfig.positionRUIE = ENABLE;
// Check if position compare flag is set by the hardware
if (QuadEncoder::compareValueFlag) {
QuadEncoder::compareValueFlag = 0;
// Check encoder position and update display if needed
checkPosition();
}
}
void setupEncoder() {
encoder.setInitConfig();
// Configure filtering for mechanical encoders
encoder.EncConfig.filterCount = 3; // Range 0-7, higher values = more filtering
encoder.EncConfig.filterSamplePeriod = 20; // Range 0-255, higher values = slower sample rate
// Enable monitoring of rollover/rollunder for direction detection
encoder.EncConfig.revolutionCountCondition = ENABLE;
// Initialize encoder hardware
encoder.init();
// Set initial position
encoder.write(0);
// Enable position compare mode
encoder.EncConfig.positionCompareMode = ENABLE;
// Set initial compare value - will trigger on any change
encoder.setCompareValue(ENCODER_DIVIDER);
// Enable compare interrupt
encoder.enableCompareInterrupt();
Serial.println("Encoder initialized");
}
void checkPosition() {
newPosition = encoder.read() / ENCODER_DIVIDER;
// Apply constraints to keep within valid range
if (newPosition < 0) {
newPosition = 0;
encoder.write(0);
}
if (newPosition > MAX_SPEED_VALUE) {
newPosition = MAX_SPEED_VALUE;
encoder.write(MAX_SPEED_VALUE * ENCODER_DIVIDER);
}
// Update display if position changed
if (newPosition != old_position) {
Serial.print("Position: ");
Serial.println(newPosition);
old_position = newPosition;
updateDisplay();
}
// Set a new compare value that will trigger on either increment or decrement
int currentPos = encoder.read();
// This value is an absolute position to compare against, not a relative offset
encoder.setCompareValue(currentPos + ENCODER_DIVIDER);
// Re-enable the compare interrupt
encoder.enableCompareInterrupt();
}
void updateDisplay() {
display.clearDisplay();
display.setTextWrap(false);
display.setTextSize(5);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.print((float)newPosition/10, 1);
display.display();
}