I have looked around a bunch and read what I can find on rotary encoders but I can’t seem to lock down a basic implementation. I’m sure this is due to my lack of hardware understanding.
goal: use encoder to increment/decrement a counter variable while rest of program loops.
what is the right setup?:
pinMode(analog or digital?, PULLUP or PULLDOWN or ?);
attachInterrupt( InterruptablePin ,increment_function , CHANGE or ?);
Serial1.print(counter_var_incremented_in_function);
whats the correct wiring layout with whichever is the better (or simplest) implementation?
I tried adapting a couple of those but my understanding is clearly lacking. Most of them also say they are not optimal in one way or other. Here’s 2 examples that just stream numbers to the display as I have “adapted” them to the core.
#define encoder0PinA A2
#define encoder0PinB A3
volatile unsigned int encoder0Pos = 0;
static boolean rotating=false;
void setup() {
pinMode(encoder0PinA, INPUT);
digitalWrite(encoder0PinA, HIGH);
pinMode(encoder0PinB, INPUT);
digitalWrite(encoder0PinB, HIGH);
attachInterrupt(A2, rotEncoder, CHANGE);
Serial1.begin (9600);
}
void rotEncoder(){
rotating=true;
// If a signal change (noise or otherwise) is detected
// in the rotary encoder, the flag is set to true
}
void loop() {
while(rotating) {
delay(2);
// When signal changes we wait 2 milliseconds for it to
// stabilise before reading (increase this value if there
// still bounce issues)
if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
encoder0Pos++;
}
else {
encoder0Pos--;
}
rotating=false; // Reset the flag back to false
Serial1.println(encoder0Pos);
}
}
And a Classy one:
The code:
// This #include statement was automatically added by the Spark IDE.
#include "Encoder.h"
Encoder encoder(A2, A3);
void setup() {
attachInterrupt(A2, doEncoder, CHANGE);
Serial1.begin (9600);
Serial1.println("start");
}
void loop(){
delay(1000);
// do some stuff here - the joy of interrupts is that they take care of themselves
}
void doEncoder(){
encoder.update();
Serial1.println( encoder.getPosition() );
}
The Class:
class Encoder {
/*
wraps encoder setup and update functions in a class
!!! NOTE : user must call the encoders update method from an
interrupt function himself! i.e. user must attach an interrupt to the
encoder pin A and call the encoder update method from within the
interrupt
uses Arduino pullups on A & B channel outputs
turning on the pullups saves having to hook up resistors
to the A & B channel outputs
// ------------------------------------------------------------------------------------------------
// Example usage :
// ------------------------------------------------------------------------------------------------
#include "Encoder.h"
Encoder encoder(2, 4);
void setup() {
attachInterrupt(0, doEncoder, CHANGE);
Serial.begin (115200);
Serial.println("start");
}
void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}
void doEncoder(){
encoder.update();
Serial.println( encoder.getPosition() );
}
// ------------------------------------------------------------------------------------------------
// Example usage end
// ------------------------------------------------------------------------------------------------
*/
public:
// constructor : sets pins as inputs and turns on pullup resistors
Encoder( int8_t PinA, int8_t PinB) : pin_a ( PinA), pin_b( PinB ) {
// set pin a and b to be input
pinMode(pin_a, INPUT);
pinMode(pin_b, INPUT);
// and turn on pullup resistors
digitalWrite(pin_a, HIGH);
digitalWrite(pin_b, HIGH);
};
// call this from your interrupt function
void update () {
if (digitalRead(pin_a)) digitalRead(pin_b) ? position++ : position--;
else digitalRead(pin_b) ? position-- : position++;
};
// returns current position
long int getPosition () { return position; };
// set the position value
void setPosition ( const long int p) { position = p; };
private:
long int position;
int8_t pin_a;
int8_t pin_b;
};
A project that I have worked on, Brewpi, uses a rotary encoder - the implementation uses grey codes that avoids the debounce issue (one turn reported as multiple interrupts.)
I have a tutorial I am working on using a rotary encoder with interrupts. It has been a busy Summer with other things but I will try to get it pulled together soon.
Some advice–I have found all the software debouncing can be eliminated by adding some 0.1uF caps between the core inputs and ground.
I have D1 connected to pin “A” of the encoder, D0 to pin “B” and the center common pin going to ground.
As suggested by @bko I have #104 capacitors in between the encoder A & B pins and the D0 & D1 pins respectively going to ground ( I think this is right, please correct if not).
int encoderPinA = D0;
int encoderPinB = D1;
volatile int counter;
void setup() {
pinMode(encoderPinA, INPUT_PULLUP);
pinMode(encoderPinB, INPUT_PULLUP);
attachInterrupt(encoderPinA, adjust_counter, FALLING);
Serial1.begin (9600);
}
void loop(){
//Do stuff here
clear_screen();
Serial1.print(counter);
delay(1000); //just here to slow down the output, and show it will work even during a delay
}
void adjust_counter(){
if (digitalRead(encoderPinB))
counter-- ;
else
counter++ ;
Serial1.print("called"); // sanity check how many times interrupt was called
}
void clear_screen (){
Serial1.write(254); // move cursor to beginning of first line
Serial1.write(128);
Serial1.write(" "); // clear display
Serial1.write(" ");
Serial1.write(254); // move cursor to beginning of first line
Serial1.write(128);
}
bko, Did you ever post your tutorial on using the rotary encoder with particle.io? I have success with the Arduino using this library: http://www.pjrc.com/teensy/td_libs_Encoder.html but I have not been able to port it to Particle for the photon. Thanks
Kyle
I never posted a tutorial on this but I do have some code. I found that the software debouncing was not working for me with many examples that I tried, so putting the input pins to pull-up mode, I added two 0.1uF capacitors, one from A6 to GND and one from A7 to GND to debounce the inputs. That worked perfectly. Hope this helps!
void doEncoderA();
void doEncoderB();
int encoderA = A7;
int encoderB = A6;
volatile bool A_set = false;
volatile bool B_set = false;
volatile int encoderPos = 0;
int prevPos = 0;
int value = 0;
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println(encoderPos);
pinMode(encoderA, INPUT_PULLUP);
pinMode(encoderB, INPUT_PULLUP);
attachInterrupt(encoderA, doEncoderA, CHANGE);
attachInterrupt(encoderB, doEncoderB, CHANGE);
}
void loop() {
if (prevPos != encoderPos) {
prevPos = encoderPos;
Serial.println(encoderPos);
}
delay(50);
}
//----------------------------------------------------------------
void doEncoderA(){
if( digitalRead(encoderA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos += 1;
}
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
if( digitalRead(encoderB) != B_set ) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if( B_set && !A_set )
encoderPos -= 1;
}
}
Ah man you are the best. I was just looking for more examples and found this. Thanks @bko
Quick question. I tried your code with D0 and D1 and it only worked clockwise. Why is that? It worked find on A0 and A1. They both doing a digitalRead()
I bet it is because D0 and D1 share an interrupt. Perhaps @BDub can help us out with a definite answer as to why.
If it is a shared interrupt problem, I bet we could re-write the code to work since right now it assumes that if you land in doEncoderA it is because pin encoderA changed, but that assumption could be tested explicitly.
I will but I am actually ok analog pins, it just so happened I had them on digital ones will I was trying some other encoder examples. Thanks to @bko for his example that I am using, it works perfectly.
My C++ skills are more like a C-- and I tried to move Brian’s example to a library but still can’t get it to compile. If there is any interest I would be happy to post it to github if anyone wants to look at it.
@bko@kbowerma I’m looking at BKO’s Quadurature Encoder code he posted awhile back and I’m wondering if it’s also perfectly fine for use with the Photon or if the interrupt input pins need changed due to not running this on the older Spark Core.
Also I’m using a magnetic off axis rotary encoder chip that provides digital A & B output pulses which I think will not need to be debounced since it is not a mechanical switch so I’m pretty sure I will not need to add the capicators avoid debouncing.
Yeah it works like a charm on the photon. BKO is the man! I have been meaning to turn it into a library but never got around to it. For some reason I switch to A0 and A1, I think is was because I used the oled shield in SPI mode.
Here is some sample code you are welcome to. https://github.com/kbowerma/holiday_road. It has a lot of moving parts so it might not be the prettiest thing in town.
Thanks to everyone above. I created a Gist that strips @kbowerma’s code down to just the encoder parts. Thanks to him and @bko for the code and help. Caps and code work great.