Theremingenieur
Senior Member+
This time, it's simply about these small and cheap rotary encoder knobs which one uses often as input controls, replacing analog potentiometers or whatever. Basically, these are quadrature encoders like others, but when using these as user control inputs, for example for a midi device, you don't need a huge numeric range to count millions of turns at 0.1° angle resolution.
The problem with these mechanical switches is that these bounce. That means that instead of simply closing a contact once, it will bounce back and forth tenths of times during a few milliseconds before remaining stable which might lead to wrong results. That's why one needs to do either hardware debouncing with external components (resistor-capacitor filters, schmitt triggers), or software debouncing, filtering the wrong impulses out and/or waisting time with delay().
Most encoder drivers or libraries I've seen attach interrupts to the encoder pins which means that even if the code will filter the wrong pulses out, a lot of unnecessary interrupts could (and would) still be fired by the bouncing contacts, eating up precious cpu cycles.
That's why I decided to write my own simple and compact rotKnob class (templated, thanks to @tni who taught us that). The approach to filter bouncing is as follows: Knowing that the rotary knob will pull down and let up pins A and B alternating, there is no reason to handle for example multiple consecutive interrupts (due to contact bouncing) on pinA after the first one. That's why the first pinA interrupt will disable further pinA interrupts with a quick port config register write until an interrupt at pinB will occur, which will disable pinB interrupts and re-enable pinA interrupts and vice versa. That means that independent of contact bouncing, for turning the rotary knob by one detent, only exactly 4 interrupts A-B-A-B or B-A-B-A will be fired and handled, not more. Since the class is templated by pin pairs, one might use as many of these rotary knobs as one wants as long as there are enough free digital pins, since the memory and CPU impact is greatly reduced. Just declare these as rotKnob<pinA1,pinB1> enc1; ... rotKnob<pinAx, pinBx> encx; before setup() in your sketch as in the following example and have fun:
The problem with these mechanical switches is that these bounce. That means that instead of simply closing a contact once, it will bounce back and forth tenths of times during a few milliseconds before remaining stable which might lead to wrong results. That's why one needs to do either hardware debouncing with external components (resistor-capacitor filters, schmitt triggers), or software debouncing, filtering the wrong impulses out and/or waisting time with delay().
Most encoder drivers or libraries I've seen attach interrupts to the encoder pins which means that even if the code will filter the wrong pulses out, a lot of unnecessary interrupts could (and would) still be fired by the bouncing contacts, eating up precious cpu cycles.
That's why I decided to write my own simple and compact rotKnob class (templated, thanks to @tni who taught us that). The approach to filter bouncing is as follows: Knowing that the rotary knob will pull down and let up pins A and B alternating, there is no reason to handle for example multiple consecutive interrupts (due to contact bouncing) on pinA after the first one. That's why the first pinA interrupt will disable further pinA interrupts with a quick port config register write until an interrupt at pinB will occur, which will disable pinB interrupts and re-enable pinA interrupts and vice versa. That means that independent of contact bouncing, for turning the rotary knob by one detent, only exactly 4 interrupts A-B-A-B or B-A-B-A will be fired and handled, not more. Since the class is templated by pin pairs, one might use as many of these rotary knobs as one wants as long as there are enough free digital pins, since the memory and CPU impact is greatly reduced. Just declare these as rotKnob<pinA1,pinB1> enc1; ... rotKnob<pinAx, pinBx> encx; before setup() in your sketch as in the following example and have fun:
Code:
/* Compact and quick encoder class for these horribly bouncing
* cheap rotary knobs with 4 transitions per click.
* Default range is from -128 to +127 starting at 0.
*
* Might be initialized with begin(startVal); to start at
* a predefined number.
* Might be initialized with begin(startVal, lowBound, hiBound);
* to reduce the range, see examples below.
* Works with all TEENSY 3.x processors.
*
* Free to use for everybody - by (ThF) 2017 */
template<uint8_t pinA, uint8_t pinB>
class rotKnob {
public:
void begin(int8_t startVal, int8_t lowBound, int8_t hiBound) {
d._sValue = startVal;
d._lBound = lowBound;
d._hBound = hiBound;
d._aConf = digital_pin_to_info_PGM[(pinA)].config;
d._bConf = digital_pin_to_info_PGM[(pinB)].config;
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
delay(1);
d._aVal = digitalReadFast(pinA);
d._bVal = digitalReadFast(pinB);
attachInterrupt(pinA, intPinA, CHANGE);
attachInterrupt(pinB, intPinB, CHANGE);
}
void begin(int8_t startVal) {
begin(startVal, -128, 127);
}
void begin() {
begin(0, -128, 127);
}
void end() {
detachInterrupt(pinA);
detachInterrupt(pinB);
}
int8_t read() {
d._avail = false;
return d._sValue;
}
bool available() {
return d._avail;
}
private:
struct objData {
volatile uint32_t* _aConf, *_bConf;
volatile int8_t _lBound, _hBound, _sValue;
volatile bool _aVal, _bVal, _avail;
};
static objData d;
static void intPinA() {
*d._aConf &= ~0x000F0000; // disable pin A interrupts
// decoding logic
if (!d._aVal) {
if ((d._sValue < d._hBound) && !d._bVal) {
d._sValue++;
d._avail = true;
}
if ((d._sValue > d._lBound) && d._bVal) {
d._sValue--;
d._avail = true;
}
}
d._bVal = digitalReadFast(pinB); // read pinB which is stable after pinA transition
*d._bConf |= 0x000B0000; // (re-) enable pinB interrupts
}
static void intPinB() {
*d._bConf &= ~0x000F0000; // disable pinB interrupts
d._aVal = digitalReadFast(pinA); // read pinA which is stable after pinB transition
*d._aConf |= 0x000B0000; // (re-) enable pinA interrupts
}
};
template<uint8_t pinA, uint8_t pinB>
typename rotKnob<pinA, pinB>::objData rotKnob<pinA, pinB>::d;
/* Describe your hardware connections, pins and knobs */
rotKnob<18, 19> enc1;
rotKnob<7, 8> enc2;
void setup() {
Serial.begin(57600);
while (!Serial)
;
delay(5);
pinMode(LED_BUILTIN, OUTPUT);
/* Configure your encoders */
enc1.begin(); // will start at 0 and go from -128 to 127
enc2.begin(20, 0, 40); // will start at 20 and go from 0 to 40
}
void loop() {
if (enc1.available()) {
Serial.print("Encoder 1: ");
Serial.println(enc1.read());
}
if (enc2.available()) {
Serial.print("Encoder 2: ");
Serial.println(enc2.read());
}
}
Last edited: