I normally use, for "full step" encoders, an FSM based approach.
It's not - if you look at it squinting a bit - very different from the one ehughes suggested...
It works very well, both in polling and interrupt mode - even without any HW debouncing.
I generally use it polling, wrapped by code for velocity implementation.
// Read a full step encoder with negative logic
// Based on a simple state machine
// Licence: WTFPL - http://www.wtfpl.net/
typedef enum
{
ENCIDLE = 0,
CW__INI,
CW__MID,
CW__END,
CCW_INI,
CCW_MID,
CCW_END,
ENC_ERR,
CW__OUT = 0x10u, /* The actual state is IDLE, action encoded in high nibble */
CCW_OUT = 0x20u, /* The actual state is IDLE, action encoded in high nibble */
} State;
#define ENC_STATE_MASK 0x0Fu
/** As the new state for the encoder FSM depends only on the phase inputs and
* the previous state, a simple state table is enough.
* Clockwise step input sequence: 11, 10, 00, 01, 11, 11 is rest position
* CounterCW step input sequence: 11, 01, 00, 10, 11
* Unexpected transitions goto an error state, ENC_ERR, then to idle, ENC_IDLE.
* The error state persists until a 11 is read (return to rest position).
* If the unexpected transition is to the rest position, ENC_ERR is skipped.
**/
unsigned char const nextState[4][8] = {
/* Previous state
ENCIDLE CW__INI CW__MID CW__END CCW_INI CCW_MID CCW_END ENC_ERR */
{ENCIDLE, ENCIDLE, ENCIDLE, CW__OUT, ENCIDLE, ENCIDLE, CCW_OUT, ENCIDLE}, // Input 11
{CW__INI, CW__INI, CW__INI, ENC_ERR, ENCIDLE, CCW_END, CCW_END, ENC_ERR}, // Input 10
{CCW_INI, ENC_ERR, CW__END, CW__END, CCW_INI, CCW_INI, ENC_ERR, ENC_ERR}, // Input 01
{ENC_ERR, CW__MID, CW__MID, CW__MID, CCW_MID, CCW_MID, CCW_MID, ENC_ERR}, // Input 00
};
typedef struct
{
GPIO_Type *encGPIO;
const uint32_t ph1Pin;
const uint32_t ph2Pin;
const int16_t kInc;
const int16_t kDec;
State FSMstate;
} Encoder;
#define ENCODERS 1
static Encoder enc[ENCODERS] = {
{.encGPIO = ENC_PH1_GPIO,
.ph1Pin = ENC_PH1_PIN,
.ph2Pin = ENC_PH2_PIN,
.kInc = +1,
.kDec = -1,
.FSMstate = ENCIDLE},
};
// Read full step encoder
int PollEncoder(Encoder *enc)
{
int key = 0;
bool ph1 = GPIO_PinRead(enc->encGPIO, enc->ph1Pin);
bool ph2 = GPIO_PinRead(enc->encGPIO, enc->ph2Pin);
uint32_t FSMinput = (ph1 | (ph2 << 1)) ^ 0x03u; /* Remove the XOR if rest position is 00 */
/* Advance the state machine */
State newState = nextState[FSMinput][enc->FSMstate];
/* Check if we have a full step */
if (newState == CW__OUT)
key = enc->kInc;
else if (newState == CCW_OUT)
key = enc->kDec;
/* Clean up the state from the ouput indication */
enc->FSMstate = newState & ENC_STATE_MASK;
return key;
}