Here is a snippet that I wrote a while ago. It is specifically written for the common (cheap) rotary encoders that give four counts per detent. The count handles two encoders and you can set independent velocity based increments for each one. There are three velocity thresholds and associated increment values, coarse, medium and fine.
I do not use pin change interrupts, but do everything in a 1kHz timer interrupt, it makes it a lot easier in my opinion.
PS. This is not a library, I just hacked bits out of my program and added a few comments, so it quite possible that I may have omitted a few pertinent bit, in which case I apologise.
PPS. This code is for AVR & gcc, but most of it pretty generic, do it should easily translate to another architecture
// encoder 1 & 2 pins
#define SW1A PB0
#define SW1B PB1
#define SW2A PC2
#define SW2B PC3
// Timer ticks per second
#define TICKTIME 1000
// Encoder limits and increments
// 4 transitions for 1 count
#define ENDCODERSCALE 4
// Max encoder count
#define ENCODERMAX (3000)
// These are the value increments seen by the main program for the
// three velocity ranges
#define ENC1_FINE 1
#define ENC1_MEDIUM 10
#define ENC1_COARSE 50
#define ENC2_FINE 10
#define ENC2_MEDIUM 20
#define ENC2_COARSE 50
// velocity thresholds for encoders
// Speed is actually number of ticks between clicks
#define SPEED_LOW (TICKTIME/2)
#define SPEED_MED (TICKTIME/20)
#define SPEED_HI (TICKTIME/50)
volatile uint32_t ticks;
// Encoder state transition table
// The table is indexed by a four bit number representing
// the previous and current state of the A&B pins of the encoder
// The value is what we should add to the current encoder count, either +1
// for forward motion, -1 for reverse or 0 for an invalid state transition
uint8_t enc_states[16] PROGMEM = {
0, //0000
1, //0001
-1,//0010
0, //0011
-1,//0100
0, //0101
0, //0110
1, //0111
1, //1000
0, //1001
0, //1010
-1,//1011
0, //1100
-1,//1101
1, //1110
0 //1111
};
// The state structure for each encoder. This allows for
// independent velocity dependent increments. The only bit the
// main routine needs to look at is 'value' which will vary between
// zero and ENCODERMAX
typedef struct {
uint8_t state;
volatile uint16_t value;
uint16_t inc;
uint16_t inc_fine;
uint16_t inc_med;
uint16_t inc_coarse;
int8_t count;
uint32_t speed;
uint32_t last_tick;
} encoder_t;
// Array for the two encoder state structures
encoder_t encoders[2];
// bit 0 and 1 indicates to the main loop that the encoders have changed
// needs to be reset by the main loop after processing
volatile uint8_t encoder_flags;
// Checks if velocity thresholds crossed and adjusts the increment value
static void check_enc_speed(uint8_t enc_no)
{
encoders[enc_no].speed = ticks - encoders[enc_no].last_tick;
encoders[enc_no].last_tick = ticks;
if (encoders[enc_no].speed < SPEED_MED) {
if (encoders[enc_no].speed < SPEED_HI) {
encoders[enc_no].inc = encoders[enc_no].inc_coarse;
} else {
encoders[enc_no].inc = encoders[enc_no].inc_med;
}
} else {
encoders[enc_no].inc = encoders[enc_no].inc_fine;
}
}
// Update the encoder value
static void update_encoder(uint8_t enc_no,uint8_t buttons)
{
encoders[enc_no].count += (int8_t)pgm_read_byte(&(enc_states[buttons]));
if (encoders[enc_no].count > 3) {
check_enc_speed(enc_no);
if (encoders[enc_no].value < (ENCODERMAX-encoders[enc_no].inc)) {
encoders[enc_no].value+=encoders[enc_no].inc;
} else {
encoders[enc_no].value = ENCODERMAX;
}
encoder_flags |= 0x01 << enc_no;
encoders[enc_no].count = 0;
} else if (encoders[enc_no].count < -3) {
check_enc_speed(enc_no);
if (encoders[enc_no].value >= encoders[enc_no].inc) {
encoders[enc_no].value-=encoders[enc_no].inc;
} else {
encoders[enc_no].value = 0;
}
encoder_flags |= 0x01 << enc_no;
encoders[enc_no].count = 0;
}
encoders[enc_no].state = (buttons & 0b0011) << 2;
}
// 1 kHz timer interrupt. Reads encoders and adjusts velocity and values.
ISR (TIMER0_COMPA_vect)
{
uint8_t state;
ticks++;
// check encoder 0
state = encoders[0].state | (PINB & (_BV(SW1A)|_BV(SW1B)));
update_encoder(0,state);
// check encoder 1
// convoluted logic, but encoder 2 is on portc 2&3
state = encoders[1].state | ((PINC & (_BV(SW2A)|_BV(SW2B))) >> 2);
update_encoder(1,state);
}