/* Turn - Read a Phase Quadrature Rotary Shaft Encoder, debounce, interrupt driven Turn 0.0 Arduino library Copyright (c) 2011 Devon Sean McCullough Licensed under the GPL (GNU Public License) 2011 Jul 22 Fri 00:25 EDT Devon Turn sketch into library HARDWARE Mama's LCD Card <===> MotherBoard Shield <===> Arduino interrupts A, B on rotary motion digital inputs A, B = rotary angle optional digital input P = button push BUGS Can only create a single instance POSSIBLE VARIATIONS battery-saver no-poll sleep Optional user interrupt handler would moot count wraparound bugs Optional #define TURN_MAX_INSTANCES up to, say, three devices Ignore first interrupt during initial uncertainty Accurately track phase in counter low two bits Single channel interrupt instead of both Poll on timer interrupt Poll when called see also designs at http://www.arduino.cc/playground/Main/RotaryEncoders http://www.arduino.cc/en/Reference/AttachInterrupt ... external interrupts: numbers 0 (on digital pin 2) and 1 (on digital pin 3). http://www.helicron.net/avr/quadrature Quadrature decoding with a tiny AVR _____ _____ _____ A _/ \_____/ \_____/ \____ _____ _____ _____ B ____/ \_____/ \_____/ \_ time O P Q R S T U V W X Y Z count - <<< down up >>> + ______________ This diagram shows ______/ ______ but the reality is ______/\/\/\/\/ To debounce, sample only when the other channel changes and disable interrupts on the bouncing channel, e.g., at time O, A changes, so sample B, disable interrupts on bouncing A and enable interrupts on stable B. Count up or down depending on oldA and oldB samples: changed A count oldA oldB time oldA >< oldB + 0 0 O == + 1 1 Q == - 1 0 O != - 0 1 Q != changed B count oldA oldB time oldA >< oldB + 1 0 P != + 0 1 R != - 1 1 P == - 0 0 R == This debouncer skips a step after changing direction, i.e., count is off by one when going backwards. Race condition in unlikely case where it would bounce faster than an interrupt handler can disable itself. The initial state of oldA and oldB is arbitrary so the first step may go the wrong direction. Might initialize from the digital inputs but may mis-count anyway if bouncing. Not worth fixing here but a simple, reliable fix would be first-time-only interrupt handlers which ignore the first transition. */ #include "WProgram.h" #include "Turn.h" // dreadful kludge single static instance (see BUGS) void changedA(); void changedB(); static uint8_t _intA, _intB, _pinA, _pinB; static volatile uint8_t _oldA = 0; static volatile uint8_t _oldB = 0; static volatile uint8_t _backward = 0; static volatile int _count = 0; static int _oldCorrectedCount = 0; #ifdef DEBUG static uint8_t initialized = 0; #endif static void Turn_pinModeInputWithPullup(uint8_t pin) { pinMode(pin, INPUT); digitalWrite(pin, HIGH); } void changedA() { detachInterrupt(_intA); attachInterrupt(_intB, changedB, CHANGE); _oldB = digitalRead(_pinB); if (_oldA != _oldB) _count++, _backward = 0; else _count--, _backward = 1; } void changedB() { detachInterrupt(_intB); attachInterrupt(_intA, changedA, CHANGE); _oldA = digitalRead(_pinA); if (_oldA == _oldB) _count++, _backward = 0; else _count--, _backward = 1; } Turn::Turn(uint8_t intA, uint8_t intB, uint8_t pinA, uint8_t pinB) { #ifdef DEBUG if (initialized) lose("Turn: re-init"); initialized = 1; #endif _intA = intA; _intB = intB; _pinA = pinA; _pinB = pinB; Turn_pinModeInputWithPullup(_pinA); Turn_pinModeInputWithPullup(_pinB); attachInterrupt(_intA, changedA, CHANGE); attachInterrupt(_intB, changedB, CHANGE); } void Turn::begin(uint8_t pinP) { Turn_pinModeInputWithPullup(pinP); } void Turn::reset(int count = 0) { _count = count; } int Turn::count() { _oldCorrectedCount = _count + _backward; return _oldCorrectedCount; } void Turn::sleep() { // lame implementation while (_oldCorrectedCount == _count + _backward) delay(100); } // end Turn.cpp