Created
June 30, 2022 22:48
-
-
Save mnesarco/9f138cef1308904ba3050324feb0d9e6 to your computer and use it in GitHub Desktop.
Arduino Rotary Encoder with Automatic Debounce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Rotary encoder handler for arduino. v2.0 | |
* | |
* Original author of version 1.1: Ben Buxton <[email protected]> | |
* https://github.com/buxtronix/arduino/tree/master/libraries/Rotary | |
* | |
* Author of version 2.0 based on 1.1: Frank Martinez <https://twitter.com/mnesarco> | |
* | |
* Copyright 2011 Ben Buxton. Licensed under the GNU GPL Version 3. | |
* Contact: [email protected] | |
* | |
* Copyright 2022 Frank Martinez. | |
* Contact: <https://twitter.com/mnesarco> | |
* | |
* +------------------------------------------------------+ | |
* | Changes introduced in version 2.0: | | |
* | | | |
* | 1. Make it a Header only library. | | |
* | 2. Removed macros, replaced by c++ templates. | | |
* | 3. Added begin() method because pinMode should not | | |
* | be called before setup(). | | |
* +------------------------------------------------------+ | |
* | Usage: | | |
* | | | |
* | Rotary<HalfStepEncoder, INPUT_PULLUP> half(2,3); | | |
* | Rotary<FullStepEncoder, INPUT_PULLUP> full(4,5); | | |
* | | | |
* | void setup() | | |
* | { | | |
* | half.begin(); | | |
* | full.begin(); | | |
* | } | | |
* | | | |
* | loop() | | |
* | { | | |
* | unsigned char v1 = half.process(); | | |
* | unsigned char v2 = full.process(); | | |
* | } | | |
* | | | |
* | Rest is the same as of 1.1 | | |
* +------------------------------------------------------+ | |
* | |
* A typical mechanical rotary encoder emits a two bit gray code | |
* on 3 output pins. Every step in the output (often accompanied | |
* by a physical 'click') generates a specific sequence of output | |
* codes on the pins. | |
* | |
* There are 3 pins used for the rotary encoding - one common and | |
* two 'bit' pins. | |
* | |
* The following is the typical sequence of code on the output when | |
* moving from one step to the next: | |
* | |
* Position Bit1 Bit2 | |
* ---------------------- | |
* Step1 0 0 | |
* 1/4 1 0 | |
* 1/2 1 1 | |
* 3/4 0 1 | |
* Step2 0 0 | |
* | |
* From this table, we can see that when moving from one 'click' to | |
* the next, there are 4 changes in the output code. | |
* | |
* - From an initial 0 - 0, Bit1 goes high, Bit0 stays low. | |
* - Then both bits are high, halfway through the step. | |
* - Then Bit1 goes low, but Bit2 stays high. | |
* - Finally at the end of the step, both bits return to 0. | |
* | |
* Detecting the direction is easy - the table simply goes in the other | |
* direction (read up instead of down). | |
* | |
* To decode this, we use a simple state machine. Every time the output | |
* code changes, it follows state, until finally a full steps worth of | |
* code is received (in the correct order). At the final 0-0, it returns | |
* a value indicating a step in one direction or the other. | |
* | |
* It's also possible to use 'half-step' mode. This just emits an event | |
* at both the 0-0 and 1-1 positions. This might be useful for some | |
* encoders where you want to detect all positions. | |
* | |
* If an invalid state happens (for example we go from '0-1' straight | |
* to '1-0'), the state machine resets to the start until 0-0 and the | |
* next valid codes occur. | |
* | |
* The biggest advantage of using a state machine over other algorithms | |
* is that this has inherent debounce built in. Other algorithms emit spurious | |
* output with switch bounce, but this one will simply flip between | |
* sub-states until the bounce settles, then continue along the state | |
* machine. | |
* A side effect of debounce is that fast rotations can cause steps to | |
* be skipped. By not requiring debounce, fast rotations can be accurately | |
* measured. | |
* Another advantage is the ability to properly handle bad state, such | |
* as due to EMI, etc. | |
* It is also a lot simpler than others - a static state table and less | |
* than 10 lines of logic. | |
*/ | |
#ifndef RotaryEncoder_h | |
#define RotaryEncoder_h | |
#include <Arduino.h> | |
struct HalfStepEncoder {}; | |
struct FullStepEncoder {}; | |
// Values returned by 'process' | |
// No complete step yet. | |
static constexpr unsigned char DIR_NONE = 0x0; | |
// Clockwise step. | |
static constexpr unsigned char DIR_CW = 0x10; | |
// Anti-clockwise step. | |
static constexpr unsigned char DIR_CCW = 0x20; | |
/* | |
* The below state table has, for each state (row), the new state | |
* to set based on the next encoder output. From left to right in, | |
* the table, the encoder outputs are 00, 01, 10, 11, and the value | |
* in that position is the new state to set. | |
*/ | |
static constexpr unsigned char R_START = 0x0; | |
// Full Step States | |
static constexpr unsigned char RF_CW_FINAL = 0x1; | |
static constexpr unsigned char RF_CW_BEGIN = 0x2; | |
static constexpr unsigned char RF_CW_NEXT = 0x3; | |
static constexpr unsigned char RF_CCW_BEGIN = 0x4; | |
static constexpr unsigned char RF_CCW_FINAL = 0x5; | |
static constexpr unsigned char RF_CCW_NEXT = 0x6; | |
template<typename EncoderType = FullStepEncoder> | |
constexpr unsigned char ttable[7][4] = { | |
// R_START | |
{R_START, RF_CW_BEGIN, RF_CCW_BEGIN, R_START}, | |
// RF_CW_FINAL | |
{RF_CW_NEXT, R_START, RF_CW_FINAL, R_START | DIR_CW}, | |
// RF_CW_BEGIN | |
{RF_CW_NEXT, RF_CW_BEGIN, R_START, R_START}, | |
// RF_CW_NEXT | |
{RF_CW_NEXT, RF_CW_BEGIN, RF_CW_FINAL, R_START}, | |
// RF_CCW_BEGIN | |
{RF_CCW_NEXT, R_START, RF_CCW_BEGIN, R_START}, | |
// RF_CCW_FINAL | |
{RF_CCW_NEXT, RF_CCW_FINAL, R_START, R_START | DIR_CCW}, | |
// RF_CCW_NEXT | |
{RF_CCW_NEXT, RF_CCW_FINAL, RF_CCW_BEGIN, R_START}, | |
}; | |
// Half Step States | |
static constexpr unsigned char RH_CCW_BEGIN = 0x1; | |
static constexpr unsigned char RH_CW_BEGIN = 0x2; | |
static constexpr unsigned char RH_START_M = 0x3; | |
static constexpr unsigned char RH_CW_BEGIN_M = 0x4; | |
static constexpr unsigned char RH_CCW_BEGIN_M = 0x5; | |
template<> | |
constexpr unsigned char ttable<HalfStepEncoder>[6][4] = { | |
// R_START (00) | |
{RH_START_M, RH_CW_BEGIN, RH_CCW_BEGIN, R_START}, | |
// RH_CCW_BEGIN | |
{RH_START_M | DIR_CCW, R_START, RH_CCW_BEGIN, R_START}, | |
// RH_CW_BEGIN | |
{RH_START_M | DIR_CW, RH_CW_BEGIN, R_START, R_START}, | |
// RH_START_M (11) | |
{RH_START_M, RH_CCW_BEGIN_M, RH_CW_BEGIN_M, R_START}, | |
// RH_CW_BEGIN_M | |
{RH_START_M, RH_START_M, RH_CW_BEGIN_M, R_START | DIR_CW}, | |
// RH_CCW_BEGIN_M | |
{RH_START_M, RH_CCW_BEGIN_M, RH_START_M, R_START | DIR_CCW}, | |
}; | |
template<typename EncoderType = FullStepEncoder, uint8_t PinMode = INPUT_PULLUP> | |
class Rotary | |
{ | |
public: | |
Rotary(char _pin1, char _pin2) { | |
pin1 = _pin1; | |
pin2 = _pin2; | |
state = R_START; | |
} | |
void begin() | |
{ | |
pinMode(pin1, PinMode); | |
pinMode(pin2, PinMode); | |
} | |
unsigned char process() { | |
// Grab state of input pins. | |
unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1); | |
// Determine new state from the pins and state table. | |
state = ttable<EncoderType>[state & 0xf][pinstate]; | |
// Return emit bits, ie the generated event. | |
return state & 0x30; | |
} | |
private: | |
unsigned char state; | |
unsigned char pin1; | |
unsigned char pin2; | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment