Any gadget goes better with a knob and what better knob than one with detents all the way around? This one even has a push-on momentary switch on the shaft, so you can switch gain / change modes / control power while turning:
Using the Arduino’s internal pullups means you just plug the wires into the header sockets and it works:
The code uses the same interrupt-driven state machine I described there to avoid glitches and twitches. The test code dumps the accumulated knob rotation count and switch state out the serial port:
Knob encoder
Ed Nisley - KE4ZNU - November 2012
Knob count: 0 Switch: 0
Knob count: 0 Switch: 1
Knob count: 1 Switch: 1
Knob count: 1 Switch: 0
Knob count: 2 Switch: 0
Knob count: 3 Switch: 0
Knob count: 4 Switch: 0
Knob count: 4 Switch: 1
Knob count: 3 Switch: 1
Knob count: 2 Switch: 1
Knob count: 1 Switch: 1
Knob count: 0 Switch: 1
Knob count: -1 Switch: 1
Knob count: -2 Switch: 1
Knob count: -3 Switch: 1
Knob count: -4 Switch: 1
Knob count: -5 Switch: 1
Knob count: -5 Switch: 0
Knob count: -4 Switch: 0
Yes, you (well, I) can spin the knob fast enough to accumulate a few counts between the 10 ms updates and it will return to zero at the same physical location, so it’s not losing any counts in the process.
Knob encoder
Ed Nisley - KE4ZNU - November 2012
Knob count: 0 Switch: 0
Knob count: -4 Switch: 0
Knob count: -6 Switch: 0
Knob count: -7 Switch: 0
Knob count: -6 Switch: 0
Knob count: -4 Switch: 0
Knob count: -3 Switch: 0
Knob count: -6 Switch: 0
Knob count: -8 Switch: 0
Knob count: -9 Switch: 0
Knob count: -10 Switch: 0
Knob count: -12 Switch: 0
Knob count: -18 Switch: 0
Knob count: -19 Switch: 0
Knob count: -20 Switch: 0
Knob count: -19 Switch: 0
Knob count: -18 Switch: 0
Knob count: -15 Switch: 0
Knob count: -9 Switch: 0
Knob count: -7 Switch: 0
Knob count: -6 Switch: 0
Knob count: -4 Switch: 0
Knob count: -3 Switch: 0
Knob count: -2 Switch: 0
Knob count: -1 Switch: 0
Knob count: 0 Switch: 0
Not very exciting as it stands, but it’s got plenty of upside potential.
The Arduino source code:
// Quadrature knob with switch
// Ed Nisley - KE4ANU - November 2012
// Based on:
// https://softsolder.com/2009/03/03/reading-a-quadrature-encoded-knob-in-double-quick-time/
//----------
// Pin assignments
const byte PIN_KNOB_A = 2; // knob A switch - must be on ext interrupt 2
const byte PIN_KNOB_B = 4; // .. B switch
const byte PIN_KNOB_SW = A5; // .. push-close momentary switch
const byte PIN_SYNC = 13; // scope sync
//----------
// Constants
const int UPDATEMS = 10; // update LEDs only this many ms apart
#define TCCRxB 0x02 // Timer prescaler
//----------
// Globals
enum KNOB_STATES {KNOB_CLICK_0,KNOB_CLICK_1};
volatile char KnobCounter = 0;
volatile char KnobState;
char PrevKnobCounter = 0;
char KnobSwitch, PrevSwitch = 0;
unsigned long MillisNow;
unsigned long MillisThen;
//-- Helper routine for printf()
int s_putc(char c, FILE *t) {
Serial.write(c);
}
//-- Knob interrupt handler
void KnobHandler(void)
{
byte Inputs;
Inputs = digitalRead(PIN_KNOB_B) << 1 | digitalRead(PIN_KNOB_A); // align raw inputs
// Inputs ^= 0x02; // fix direction
switch (KnobState << 2 | Inputs) {
case 0x00 : // 0 00 - glitch
break;
case 0x01 : // 0 01 - UP to 1
KnobCounter++;
KnobState = KNOB_CLICK_1;
break;
case 0x03 : // 0 11 - DOWN to 1
KnobCounter--;
KnobState = KNOB_CLICK_1;
break;
case 0x02 : // 0 10 - glitch
break;
case 0x04 : // 1 00 - DOWN to 0
KnobCounter--;
KnobState = KNOB_CLICK_0;
break;
case 0x05 : // 1 01 - glitch
break;
case 0x07 : // 1 11 - glitch
break;
case 0x06 : // 1 10 - UP to 0
KnobCounter++;
KnobState = KNOB_CLICK_0;
break;
default : // something is broken!
KnobCounter = 0;
KnobState = KNOB_CLICK_0;
}
}
//-- Read switch, flip sense
char ReadSwitch(int PinNumber) {
return !digitalRead(PinNumber);
}
//------------------
// Set things up
void setup() {
pinMode(PIN_SYNC,OUTPUT);
digitalWrite(PIN_SYNC,LOW); // show we arrived
// TCCR1B = TCCRxB; // set frequency for PWM 9 & 10
// TCCR2B = TCCRxB; // set frequency for PWM 3 & 11
pinMode(PIN_KNOB_B,INPUT_PULLUP);
pinMode(PIN_KNOB_A,INPUT_PULLUP);
pinMode(PIN_KNOB_SW,INPUT_PULLUP);
KnobState = digitalRead(PIN_KNOB_A);
PrevSwitch = digitalRead(PIN_KNOB_SW);
attachInterrupt((PIN_KNOB_A - 2),KnobHandler,CHANGE);
Serial.begin(9600);
fdevopen(&s_putc,0); // set up serial output for printf()
printf("Knob encoder\r\nEd Nisley - KE4ZNU - November 2012\r\n");
MillisThen = millis();
}
//------------------
// Run the test loop
void loop() {
MillisNow = millis();
if ((MillisNow - MillisThen) &gt; UPDATEMS) {
digitalWrite(PIN_SYNC,HIGH);
KnobSwitch = ReadSwitch(PIN_KNOB_SW);
if ((PrevKnobCounter != KnobCounter) || (PrevSwitch != KnobSwitch)) {
printf("Knob count: %d Switch: %d\n",KnobCounter,KnobSwitch);
PrevKnobCounter = KnobCounter;
PrevSwitch = KnobSwitch;
}
digitalWrite(PIN_SYNC,LOW);
MillisThen = MillisNow;
}
}
_The OpenSCAD source code:_
So what happens when this gets to the 3D printer ??
Why, it just prints the equivalent FPGA pattern, of course…
Thanks for the catch!
How did you know I was looking for this solution last night? Quick googling showed that Quadrature decoding isn’t supported in hardware on AVRMegas, so I pulled out an old XMega prototype board and started trying to implement app note AVR1600. This morning I saw you had a complete solution, so I spent 5 minutes and gave it a go… it appears to be good enough that I can do what I want with an Arduino. For what it’s worth, I’ve got a motor with a shaft encoder that reports 1024 counts per turn, and adjusting the voltage on the motor till the index pulse shows about 30 Hz on my scope, and letting it run for a while, your code doesn’t loose position. Somewhere between 30 Hz and 35 Hz it starts to miss counts. 30 rev/sec 1024 interrupts/rev is about 30,000 interrupts a second. I’m pretty sure you won’t be able to spin your encoder by hand too fast :-)
– Steven Ciciora
If the truth be known, I realized you needed this info a week ago! [grin]
Which works out to a bit over 30 microseconds/count. At 16 MHz, that’s about 500 machine ops/count and, using my “It takes 10 machine ops per line of C” rule-of-thumb, the poor thing must handle everything in a mere 50 lines of code.
Yeah, I can see why it’d run out of steam above that speed…
Glad it works for you!