I want to read a quadrature rotary encoders at full resolution with only one interrupt on Arduino Nano (ATmega328). So I found out that we can use XOR to reach a full resolution:
Where pin 3
is interruptable but 4
and 5
are not.
Our requirements are:
- Full resolution: for example, the solution offered in this answer works at a half-resolution only, not detecting all the edges.
- Real-time: being as fast as possible up to the level that this hardware allows and safe in terms of not missing any of the signal edges.
- using only one interrupt. We need the other one for other purposes.
So far, I have developed two different codes, one aiming for safety and the other for performance:
Preamble:
#include "digitalWriteFast.h"
#define pinT 3 // trigger pin comming from XOR
#define pinA 4 // channel A
#define pinB 5 // channel B
volatile long counter = 0;
long counter_ = counter;
where the digitalWriteFast
library can be downloaded from here.
Performant:
volatile short int increment = 0;
volatile bool tmpA;
volatile bool tmpB;
volatile bool lastA;
ISR(trigger) {
tmpA = digitalReadFast(pinA);
tmpB = digitalReadFast(pinB);
counter += (1 - 2 * tmpA) * (1 - 2 * tmpB) * (1 - 2 * (lastA != tmpA));
lastA = tmpA;
}
void setup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinT), trigger, CHANGE);
lastA = digitalReadFast(pinA);
Serial.begin(9600);
}
void loop() {
if (counter != counter_) {
Serial.println(counter);
counter_ = counter;
}
}
Safe:
volatile unsigned long error = 0;
unsigned long error_ = 0;
volatile bool inputA;
volatile bool inputB;
volatile short state; // 100 110 101 111
volatile short state_;
void detectState() {
inputA = digitalReadFast(pinA);
inputB = digitalReadFast(pinB);
if (inputA) {
if (inputB) {
state = 111;
} else {
state = 110;
}
} else {
if (inputB) {
state = 101;
} else {
state = 100;
}
}
}
ISR(trigger) {
detectState();
if (state_ != state) {
switch (state_) {
case 100:
if (state == 110) {
counter++;
} else if (state == 101) {
counter--;
} else {
error++;
}
break;
case 110:
if (state == 100) {
counter--;
} else if (state == 111) {
counter++;
} else {
error++;
}
break;
case 101:
if (state == 100) {
counter++;
} else if (state == 111) {
counter--;
} else {
error++;
}
break;
case 111:
if (state == 110) {
counter--;
} else if (state == 101) {
counter++;
} else {
error++;
}
break;
default:
error++;
break;
}
state_ = state;
} else {
error++;
}
}
void setup() {
pinMode(pinT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinT), trigger, CHANGE);
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
detectState();
state_ = state;
Serial.begin(9600);
}
void loop() {
if (counter != counter_) {
Serial.print("counter: ");
Serial.println(counter);
counter_ = counter;
}
if (error != error_) {
Serial.print(error);
Serial.println(" errors");
error_ = error;
}
}
Now my questions are:
- Is there any way to improve the speed/safety of the performant/safe code? Any way to improve this code is appreciated.
- Is it possible to have maximum speed and safety together or some trade-off in between?
- What is the maximum frequency we can get out of this configuration?
Thanks for your help in advance.
AND
.