# Rotary encoder

Behaviour during clockwise rotation (CW)

``````00
10, INT0
00, 10, 00, ... 10 (1 ms)
11, INT1
10, 11, 10, ... 11 (1 ms)
01, INT0
11, 01, 11, ... 01 (1 ms)
00, INT1
01, 00, 01, ... 00 (1 ms)
``````

Behaviour during counter clockwise rotation (CCW)

``````00
01, INT1
00, 01, 00, ... 01 (1 ms)
11, INT0
01, 11, 01, ... 11 (1 ms)
10, INT1
11, 10, 11, ... 10 (1 ms)
00, INT0
10, 00, 10, ... 00 (1 ms)
``````

States

``````prev (P) = previous stable state [00,01,10,11]
next (N) = next stable state [00,01,10,11]
``````

On interrupt (INT0 or INT1)

Get next state 2 ms after last interrupt.

Get rotation from P and N.

``````P  N
=========
00 10 CW
10 11 CW
11 01 CW
01 00 CW
00 01 CCW
01 11 CCW
11 10 CCW
10 00 CCW
00 11 N/A
00 00 N/A
01 10 N/A
01 01 N/A
10 01 N/A
10 10 N/A
11 00 N/A
11 11 N/A
``````

Using only previous and interrupt.

Delay 1 ms in interrupt routine so that interupts is not triggered by bounces. Interrupts are automatically disabled in the interrupt routine.

Next state is also clearly defined from interupt pin. You don't need to read pins after delay.

``````P  IRQ         P'
=================
00 INT0 -> CW  10
00 INT1 -> CCW 01
01 INT0 -> CCW 11
01 INT1 -> CW  00
10 INT0 -> CCW 00
10 INT1 -> CW  11
11 INT0 -> CW  01
11 INT1 -> CCW 10
``````

## Example code for NodeMCU 0.9 and ArduinoIDE

``````const int BLUE_LED = D10;
const int A_PIN = D1;
const int B_PIN = D2;
const int PUSH_PIN = D4;

void ICACHE_RAM_ATTR pushISR(); // ICACHE_RAM_ATTR force routine to IRAM
void ICACHE_RAM_ATTR ARotISR();
void ICACHE_RAM_ATTR BRotISR();

volatile byte pushInt = 0;      // volatile is needed in ISR
volatile byte ARotInt = 0;
volatile byte BRotInt = 0;

int pushCount = 0;
byte rotState = 0; // CW: 0, 2, 3, 1, ... CCW: 0, 1, 3, 2, ...
int rotation = 0;

byte state_a_change[] = {2, 3, 0, 1}; // new state after a change
byte state_b_change[] = {1, 0, 3, 2}; // b change

// #  Pr Ne
//    AB AB
//
// 0  00 00 -
// 1  00 01 CCW
// 2  00 10 CW
// 3  00 11 -
//
// 4  01 00 CW
// 5  01 01 -
// 6  01 10 -
// 7  01 11 CCW
//
// 8  10 00 CCW
// 9  10 01 -
// 10 10 10 -
// 11 10 11 CW
//
// 12 11 00 -
// 13 11 01 CW
// 14 11 10 CCW
// 15 11 11 -

const int CW = 1;     // clockwise rotation
const int CCW = -1;   // couter clockwise
const int N = 0;      // no rotation

int rot_inc[] = {N,CCW,CW,N, CW,N,N,CCW, CCW,N,N,CW, N,CW,CCW,N};

void setup() {
pinMode(BLUE_LED, OUTPUT);
pinMode(A_PIN, INPUT_PULLUP);
pinMode(B_PIN, INPUT_PULLUP);
pinMode(PUSH_PIN, INPUT_PULLUP);
Serial.begin(115200);
attachInterrupt(digitalPinToInterrupt(PUSH_PIN), pushISR, RISING);
attachInterrupt(digitalPinToInterrupt(A_PIN), ARotISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(B_PIN), BRotISR, CHANGE);
Serial.print("Setup done ...");
}

void pushISR() {
pushInt++;
}

void ARotISR() {
ARotInt++;
}

void BRotISR() {
BRotInt++;
}

digitalWrite(BLUE_LED, LOW);
delay(50);
digitalWrite(BLUE_LED, HIGH);
}

// the loop function runs over and over again forever
void loop() {
if (pushInt > 0) {
pushInt--;
pushCount++;
Serial.print("push count: ");
Serial.println(pushCount);
}
if (ARotInt > 0) {
noInterrupts();
ARotInt--;
byte newState = state_a_change[rotState];
byte i = (rotState << 2) | newState;
rotation += rot_inc[i];
rotState = newState;
Serial.print("A change, rotation: ");
Serial.println(rotation);
interrupts();
}
if (BRotInt > 0) {
noInterrupts();
BRotInt--;
byte newState = state_b_change[rotState];
byte i = (rotState << 2) | newState;
rotation += rot_inc[i];
rotState = newState;
Serial.print("B change, rotation: ");
Serial.println(rotation);
interrupts();
}
}
``````

The version above does not work! The `CHANGED`interrupt detection generate too many interrupts and missed interrupts corrupts the counting.

An improved variant is shown below which only detect `RISING` changes on rotation and require two steps (pre-CW before CW and pre-CCW before CCW) to commit a count.

``````const int BLUE_LED = D10;
const int A_PIN = D1;
const int B_PIN = D2;
const int PUSH_PIN = D4;

void ICACHE_RAM_ATTR pushISR();   // ICACHE_RAM_ATTR force routine to IRAM
void ICACHE_RAM_ATTR ARotISR();
void ICACHE_RAM_ATTR BRotISR();

// Rotation states
const byte NONE = 0;
const byte PCW = 1;
const byte PCCW = 2;  // pre counter clockwise turn
const byte CW = 3;
const byte CCW = 4;

volatile byte rotState = NONE;

const byte A = 2;   // only A HIGH
const byte B = 1;
const byte AB = 3;  // A and B HIGH

volatile int pushCount = 0;
volatile int rotation = 0;

volatile byte pushChanged = 0;
volatile byte rotChanged = 0;

void setup() {
pinMode(BLUE_LED, OUTPUT);
pinMode(A_PIN, INPUT_PULLUP);
pinMode(B_PIN, INPUT_PULLUP);
pinMode(PUSH_PIN, INPUT_PULLUP);
Serial.begin(115200);
attachInterrupt(digitalPinToInterrupt(PUSH_PIN), pushISR, RISING);
attachInterrupt(digitalPinToInterrupt(A_PIN), ARotISR, RISING);
attachInterrupt(digitalPinToInterrupt(B_PIN), BRotISR, RISING);
Serial.print("Setup done ...");
}

return AB;
} else {
return A;
}
} else {
return B;
} else {
return NONE;
}
}
}

void pushISR() {
noInterrupts();
pushCount++;
pushChanged++;
interrupts();
}

void ARotISR() {
noInterrupts();
if (rot == A) {
rotState = PCCW;
} else if (rotState == PCW && rot == AB) {
rotation++;
rotChanged++;
rotState = NONE;
}
interrupts();
}

void BRotISR() {
noInterrupts();
if (rot == B) {
rotState = PCW;
} else if (rotState == PCCW && rot == AB) {
rotation--;
rotChanged++;
rotState = NONE;
}
interrupts();
}

digitalWrite(BLUE_LED, LOW);
delay(10);
digitalWrite(BLUE_LED, HIGH);
}

void loop() {
if (pushChanged > 0) {
pushChanged = 0;
Serial.print("push count: ");
Serial.println(pushCount);