SparkFun Forums 

Where electronics enthusiasts find answers.

General project discussion / help
Did you make a robotic coffee pot which implements HTCPCP and decafs unauthorized users? Show it off here!
By alim
#189902
Hello everyone,
I have used rotary encoder for distance measuring. In my circuit, I have one device LCD display. I have realized that without LCD display, encoder works fine. When I add LCD display, it starts missing steps and gives me wrong output.
Here is my basic codes:
/* Rotary encoder read example */
#define ENC_A A0 // connected to analog pin
#define ENC_B A1 // connected to analog pin
#define ENC_PORT PINC
double distance=0;

void setup()
{
/* Setup encoder pins as inputs */
pinMode(ENC_A, INPUT);
digitalWrite(ENC_A, HIGH);
pinMode(ENC_B, INPUT);
digitalWrite(ENC_B, HIGH);
Serial.begin (115200);
Serial.println("Start");
}

void loop()
{
static uint16_t counter = 0; //this variable will be changed by encoder input
int8_t tmpdata;
/**/
tmpdata = read_encoder();
if( tmpdata ) {
Serial.print("Counter value: ");
Serial.println(counter, DEC);
counter += tmpdata;
}
}

Does anyone has any suggestion

/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
static uint8_t old_AB = 0;
/**/
old_AB <<= 2; //remember previous state
old_AB |= ( ENC_PORT & 0x03 ); //add current state
return ( enc_states[( old_AB & 0x0f )]);
}
By jremington
#189913
The program does not read the encoder while printing, and printing to LCD displays is slower than serial output (at high Baud rates).

You will need to use interrupts to read the encoder, if using an LCD display.
By alim
#189924
Thanks, you are correct. I have used one pin as interrupt and another non-interrupt. I took this program from Arduino.cc playground. Program has improved a lot. Just wondering - do I have to change baud rate from 9600 to 115200 ? Any suggestions?
May be if I use both pin as interrupt pin, that might get better.
By stevech
#189926
I just went through getting the SFE rotary encoder to work properly. If I can summarize...


Must get interrupt from both A and B phase and on both rising and falling edge - in order to fully debounce the switches. Sometimes they glitch while gliding along the conductive strip.

Debouncing slow rotation, say, 10 seconds per revolution - is harder than faster rotation speed. More switch bounces.
Code has to detect and ignore pulse widths of less than about 2 mSec. A simplistic debounce routing didn't get what I needed: No missed steps, no spurious steps.

I wanted to be able to set an up/down counter to 0. Then rotate clockwise (CW) 360 degrees, then CCW 360 degrees, and have the counter within 1 or 2 counts of 0. Repeatedly. This was hard to get to work reliably, esp. as speed of rotation varies.

I did this on a Raspberry Pi. GPIO, 2 bits, configured to interrupt on "BOTH" edges and both A and B signals. The debounce built into RPi's GPIO was not at all effective in dealing with all of the above bouncing conditions. The code also needs to have 16 values in a table to lookup last interrupt values from A, B versus current ones, after debouncing. This lookup then has what value to add to the counter: 0 (invalid condition for a gray code, so it must have been a glitch in middle of conductive area; +1 or -1 depending on phase of A and B.

Many people mistakenly say the MCU must be super fast. Not so. The RPi with Python and the strategy I discussed here can get interrupts for pulse widths of 500 microseconds (1/2 millisecond) and process (with the GPIO debounce in the library disabled).

I did this in Python on the RPi if anyone is interested.
By lyndon
#189940
@stevech:
If you only allow valid transitions of the encoder edges through the various quadrature states, do you find that the SF encoder still needs debouncing? I've used that technique with noisy mechanical encoders and found that it gives me error free operation. Although I will admit that I haven't used mechanical encoders very often so maybe I just got lucky.
By alim
#190022
I am using now interrupt for both pins. It seems missing steps has reduced. I was looking at a youtube video (https://youtu.be/HQuLZHsGZdI) that claims for one complete rotation, it counts 96, however I found it is 24. What am I missing? Here is my code:

/* interrupt routine for Rotary Encoders
tested with Noble RE0124PVB 17.7FINB-24 http://www.nobleusa.com/pdf/xre.pdf - available at pollin.de
and a few others, seems pretty universal

The average rotary encoder has three pins, seen from front: A C B
Clockwise rotation A(on)->B(on)->A(off)->B(off)
CounterCW rotation B(on)->A(on)->B(off)->A(off)

and may be a push switch with another two pins, pulled low at pin 8 in this case
raf@synapps.de 20120107

*/

// usually the rotary encoders three pins have the ground pin in the middle
enum PinAssignments {
encoderPinA = 2, // rigth
encoderPinB = 3, // left
//clearButton = 8 // another two pins
};

volatile unsigned int encoderPos = 0; // a counter for the dial
unsigned int lastReportedPos = 1; // change management
static boolean rotating=false; // debounce management

// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;


void setup() {

pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
//pinMode(clearButton, INPUT);
// turn on pullup resistors
digitalWrite(encoderPinA, HIGH);
digitalWrite(encoderPinB, HIGH);
//digitalWrite(clearButton, HIGH);

// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);

Serial.begin(9600); // output
}

// main loop, work is done by interrupt service routines, this one only prints stuff
void loop() {
rotating = true; // reset the debouncer

if (lastReportedPos != encoderPos) {
Serial.print("Index:");
Serial.println(encoderPos, DEC);
lastReportedPos = encoderPos;
}
//if (digitalRead(clearButton) == LOW ) {
// encoderPos = 0;
//}
}

// Interrupt on A changing state
void doEncoderA(){
// debounce
if ( rotating ) delay (1); // wait a little until the bouncing is done

// Test transition, did things really change?
if( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;

// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos += 1;

rotating = false; // no more debouncing until loop() hits again
}
}

// Interrupt on B changing state, same as A above
void doEncoderB(){
if ( rotating ) delay (1);
if( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
// adjust counter - 1 if B leads A
if( B_set && !A_set )
encoderPos -= 1;

rotating = false;
}
}
By jremington
#190023
Please edit your post to add code tags.

These variables must be declared volatile:
Code: Select all
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;
By stevech
#190028
lyndon wrote:@stevech:
If you only allow valid transitions of the encoder edges through the various quadrature states, do you find that the SF encoder still needs debouncing? I've used that technique with noisy mechanical encoders and found that it gives me error free operation. Although I will admit that I haven't used mechanical encoders very often so maybe I just got lucky.
Yes. Debounce is essential to get correct counts, despite the code that ignores invalid next-states. Example: A and B are in state 10 and there is motion and there is a brief 00 then 10. This can be dirt or whatever in the middle of a contact path.

I discard all interrupts where the time since the prior interrupt is x mSec or more. The value of x depends on the use case RPM. For my 6RPM case, 2 to 8 mSec is best.
The debounce code I've seen, e.g., in RPi's GPIO lib, just delays 2mSec after an edge and checks again, I think. That algorithm isn't defined. One can't just delay 2mSec in an ISR of course, so I used threads in Python to do this.

I get 96 as well. 96/2 (rising, falling edges) is 48 which is twice 24, for 2 bits. I guess.
By alim
#190029
So, if I understand correctly, I have to delay every time there is interrupt called. For 6 rotation per minute, this delay could be 2-8 msec. That is lot.
By alim
#190076
I made lot changes in my code, I was able to get 48 counts from following code, but it should give me 96 counts (according to https://youtu.be/HQuLZHsGZdI) in one rotation. Can any one tell me what changes I need to do in order to get 96 counts?

My recent code:

// This program uses both interrupt and can generate 48 pulses by one rotation

int pulses=0, A_SIG=0, B_SIG=0;

#define encoderPinB 2
#define encoderPinA 3


void setup()
{
pinMode(encoderPinA, INPUT);
pinMode(encoderPinB, INPUT);
attachInterrupt(0,A_RISE, RISING);
attachInterrupt(1,B_RISE, RISING);

// turn on pullup resistors
digitalWrite(encoderPinA, HIGH);
digitalWrite(encoderPinB, HIGH);
Serial.begin(115200);
}
void loop() {
}

void A_RISE() {
detachInterrupt(0);
delay(1);
//A_SIG=1;
B_SIG=digitalRead(encoderPinB);
if(B_SIG==0){
pulses++; // moving forward
}
if(B_SIG==1){
pulses--; // moving reverse
}
Serial.println(pulses);
attachInterrupt(0,A_FALL, FALLING);
}

void A_FALL() {
detachInterrupt(0);
delay(1);
//A_SIG=0;
B_SIG=digitalRead(encoderPinB);
if(B_SIG==1){
pulses++; // moving forward
}
if(B_SIG==0){
pulses--; // moving reverse
}
Serial.println(pulses);
attachInterrupt(0,A_RISE, RISING);
}

void B_RISE() {
detachInterrupt(1);
delay(1);
//B_SIG=1;
A_SIG=digitalRead(encoderPinA);
if(A_SIG==1){
pulses++; // moving forward
}
if(A_SIG==0){
pulses--; // moving reverse
}
Serial.println(pulses);
attachInterrupt(0,B_FALL, FALLING);
}

void B_FALL() {
detachInterrupt(1);
delay(1);
//B_SIG=0;
A_SIG=digitalRead(encoderPinA);
if(A_SIG==0){
pulses++; // moving forward
}
if(A_SIG==1){
pulses--; // moving reverse
}
Serial.println(pulses);
attachInterrupt(0,B_RISE, RISING);
}