Important changes to forums and questions
All forums and questions are now archived. To start a new conversation or read the latest updates go to forums.mbed.com.
11 years, 11 months ago.
struggling with a rotary quadrature encoder and mbed... check my code ?
I must have re=written this code a dozen times now, always convincing myself that I'm making a stupid mistake, but the code never gets any more than barely close to usable...
Stripped to its bare bones I have a single quadrature encoder (2 bit grey code), connected to two pins on the mbed(11u24 version), I have a timer that checks the state of those two pins every 5 milliseconds, and based on the last and currently sensed state of those two pins, it _should_ give me a direction of rotation of the knob accumulated in a register, reading the accumulator register should reset the accumulator. As best as I see it, there are only four states that are valid and a progression from one state to the adjacent state is the only valid transition, and transitions in one direction or the other indicate a rotation in one direction or another. Debounce should not be a big problem if I only check every 5ms.
BUT... I see very random increments and decrements of the direction accumulator, often in the tens of hundreds for one detent on the encoder. I can only think its my code, but I've tried multipel versions of the algorithm ,some with more success than others (edge triggered interrupt driven state machines on the two pins, or as now timer driven polling of the inputs), neither gives me the nice smooth increments of decrements that I need for a simple user interface based on twisting a knob to set values.
here is my bare bones code... any comments welcome.. please.... Oh and it could be a really dumb mistake I'm just not seeing...
include "mbed.h" DigitalIn qa(p13); //phase a of the quadrature encoder DigitalIn qb(p14); //phase b of the quadrature encoder Serial pc(USBTX,USBRX); Ticker qt; //create a timer to sample the keys int dir=0; //hold the signed value corresponding to the number of clicks left or right since last sample int qslast=0; //keep a record of the last actioned sample state of the Qb+Qa outputs for comparison on each interrupt void qdec (void){ int qas=qa.read(); // read the state of phase a qa= __|-----|_____|-----|_____ int qbs=qb.read(); // read the state of phase b qb= -----|_____|-----|_____|- int qs=qbs<<1+qas; // create a two bit value out of qb and qa, qs= 10 11 01 00 10 11 01 00 switch (qslast) { // depending on the last actionable state = 2 3 1 0 2 3 1 0 case 0: // if last state was 0, <- left right-> (direction of know rotation) if (qs==1) { // and the currently sampled state is 1, then the knob was rotated left, dir--; // so subtract one from the diretion accumulator qslast=1; // and set the new last state to 1 and leave the if statement break; } else if (qs==2) { // otherwise if the currently sampled state is 2, then the knob was rotated right, dir++; // so increase the rotation accumulator qslast=2; // and set the new last state to break; } case 1: if (qs==3) { dir--; qslast=3; break; } else if (qs==0) { dir++; qslast=0; break; } case 2: if (qs==0) { dir--; qslast=0; break; } else if (qs==3) { dir++; qslast=3; break; } case 3: if (qs==2) { dir--; qslast=2; break; } else if (qs==1) { dir++; qslast=1; break; } default: break; } } int getrot() { // function to read the rotation accumulator, and once read, reset it to zero int res=dir; // this allows the knob to be rotated "while im not looking at it' and still return the dir=0; // actual number of clicks that have been rotated since last time I was checking it. return (res); } int main() { int counter=0; qa.mode(PullUp); // qa is set with a built in pull-up resistor qb.mode(PullUp); // qb is set with a built in pull-up resistor qt.attach_us(&qdec,5000); // make the quadrature decoder function check the knob once every 1000us = 1ms while (1) // loop forever { printf("dir = %d,%d\r\n",dir,qslast); // print the value of the rotation accumulator register wait_ms(200); // every 200 ms. } }
3 Answers
11 years, 11 months ago.
Rob is correct, the breaks are wrong. I also recommend that you add external pullup Rs of 1k instead of the much higher internal pullups. In addition you should add small capacitors of 10n between the inputpins and gnd. This provides much better defined levels and debouncing. Declare the dir and qslast as volatile. They are updated inside an interrupt driven function. The compiler may not understand this and optimise out some of the code.
11 years, 11 months ago.
There are already several Quadrature Encoding Interfaces written for the mbed. Have a look at: https://mbed.org/users/aberk/code/QEI/file/5c2ad81551aa/QEI.cpp
11 years, 11 months ago.
The problem with your code is the positioning of your break statements - for any given value of qlast the code will fall through the case statements until it finds a matching value of qs and then it will execute those statements and then break out of the switch statement, so then general arrangemen of your code should be
switch( qLast ) { case 0: if( qs == 1 ) { } else if( qs == 3 ) { } break;
case 1: add code here break; }
A much better implementation would use the mBed edge detection logic
InterruptIn inPhaseA( p5 ); InterruptIn inPhaseB( p6 );
set interrupt on each encoder edge inPhaseA.rise( &encoderEdge ); inPhaseB.rise( &encoderEdge ); inPhaseA.fall( &encoderEdge ); inPhaseB.fall( &encoderEdge );
static void encoderEdge() {
- define PREV_MASK 0x1 Mask for the previous state in determining direction of rotation.
- define CURR_MASK 0x2 Mask for the current state in determining direction of rotation.
- define INVALID 0x3 XORing two states where both bits have changed.
static int previousState = 0;
int currentState = ( inPhaseA.read() << 1 ) | inPhaseB.read();
Entered a new valid state. if( ((currentState ^ previousState) != INVALID) && (currentState != previousState) ) { 2 bit state. Right hand bit of prev XOR left hand bit of current gives 0 if clockwise rotation and 1 if counter clockwise rotation. pulses += (previousState & PREV_MASK) ^ ((currentState & CURR_MASK) >> 1) ? -1 : 1; }
previousState = currentState; }
thanks Rob...
But.. moving the breaks as you recommended doesn't fix the problem. The code is still responding like the transitions are super bouncy and direction is even muddled sometimes (count up or down by tens or even hundreds of counts (usually in the right direction at least, but never by just one 'tick'). In fact sometimes, the counts seem to lock up and continue to assert that I'm spinning the knob when i'm not. Weirdest thing.
My original versions where all edge interrupt driven, and exhibited essentially the same issue.
I have tried at least a couple of encoders from each of three different sources, $0.50 nameless ones from China via E-bay, the few dollar ones from SparkFun and top of the line optical ones at $50 from Grayhill... no real difference in the results, its got to be my code that is the problem.
posted by 29 Dec 2012Peter
I have tested the following code with my encoder (connected to pins 5 and 6
- include "mbed.h"
DigitalIn phaseA( p5 ); DigitalIn phaseB( p6 );
int encoderClickCount = 0; int previousEncoderState = 0;
void quadratureDecoder( void ) { int currentEncoderState = (phaseB.read() << 1) + phaseA.read();
if( currentEncoderState == previousEncoderState ) { return; }
switch( previousEncoderState )
{
case 0:
if( currentEncoderState == 1 )
{
encoderClickCount;
}
else if( currentEncoderState == 2 )
{
encoderClickCount++;
}
break;
case 1:
if( currentEncoderState == 3 )
{
encoderClickCount;
}
else if( currentEncoderState == 0 )
{
encoderClickCount++;
}
break;
case 2:
if( currentEncoderState == 0 )
{
encoderClickCount;
}
else if( currentEncoderState == 3 )
{
encoderClickCount++;
}
break;
case 3:
if( currentEncoderState == 2 )
{
encoderClickCount;
}
else if( currentEncoderState == 1 )
{
encoderClickCount++;
}
break;
default: break; } previousEncoderState = currentEncoderState; }
read the rotation accumulator, and once read, reset it to zero int getClicks( void ) { int res = encoderClickCount;
encoderClickCount = 0;
return res; }
int main() { Serial pc( USBTX, USBRX ); Ticker sampleTicker; int currentClicks;
phaseA.mode( PullUp ); phaseB.mode( PullUp );
sampleTicker.attach_us( &quadratureDecoder, 1000 );
pc.printf( "Encoder test started\r\n" ); while( 1 ) { currentClicks = getClicks(); if( currentClicks != 0 ) { pc.printf( "click count = %d,%d\r\n", currentClicks, previousEncoderState ); } wait_ms( 200 ); } }
If this code does not work, then you need to check your encoder
Rob
posted by 30 Dec 2012I wanted to give your code a try but it looks weirdly formatted, looks like its editted out of a bigger program with some odd formatting and wrong syntax and misssing actions as a result of the pasting probably.
posted by 31 Dec 2012Thats what happens when you dont put code tags around it, it is his complete program probably though. But is it now fixed with volatile declaration?
posted by 31 Dec 2012Peter
ok, I have published the program here
http://mbed.org/users/zoot661/code/encoderTest/
Rob
posted by 31 Dec 2012