I'm trying to detect the direction of rotation for an incremental rotary encoder. I'm supposed to get +1 for clockwise and -1 for counter-clockwise. However, I receive random values. Any help appreciated.
Following is the code.
const int encoderPinA = 7;
const int encoderPinB = 12;
int val;
int encoderPos = 0;
boolean encoderALast = LOW; // remember the previous pin state
void setup() {
pinMode(encoderPinA,INPUT);
pinMode(encoderPinB,INPUT);
Serial.begin(9600);
}
void loop() {
analogWrite(3,250); //powering motor
val = encoder_direction();
// receive the direction for clockwise 1 and for counter clock wise -1
Serial.println(val); // print it in serial monitor
}
int encoder_direction() {
boolean encoderA = digitalRead(encoderPinA);
if ((encoderALast == HIGH) && (encoderA == LOW)) {
if (digitalRead(encoderPinB)) {
//encoderPos --;
encoderPos = -1;
} else {
//encoderPos ++;
encoderPos = 1;
}
return encoderPos;
// return the value either 1 or -1
}
encoderALast = encoderA;
}
// using digitalread() code for four encoders
const int ENCODERS = 4; // the number of encoders
const int encoderPinA[ENCODERS] = { 7, 8, A1 , 2 };
// encoderA pins on 2 and 4
const int encoderPinB[ENCODERS] = { 12, 5, 4 , A2 };
// encoderB pins on 3 and 5
int encoderPos[ ENCODERS] = { 0, 0, 0, 0 };
// initialize the positions to 0
boolean encoderALast[ENCODERS] = { LOW, LOW, LOW, LOW };
// holds last state of encoderA pin
void setup() {
Serial.begin (9600);
}
int updatePosition(int encoderIndex) {
boolean encoderA = digitalRead(encoderPinA[encoderIndex]);
if ((encoderALast[encoderIndex] == HIGH) && (encoderA == LOW)) {
if (digitalRead(encoderPinB[encoderIndex]) == LOW) {
//encoderPos[encoderIndex]--;
encoderPos[encoderIndex] = -1;
} else {
//encoderPos[encoderIndex]++;
encoderPos[encoderIndex] = 1;
}
Serial.print("Encoder ");
Serial.print(encoderIndex+1,DEC);
Serial.print("=");
Serial.println (encoderPos[encoderIndex]);
}
encoderALast[encoderIndex] = encoderA;
}
void loop() {
updatePosition(0);
updatePosition(1);
updatePosition(2);
updatePosition(3);
}
// using direct access to digitalread code in order to process faster
int val[4];
int encoderPos = 0;
boolean encoderALast[4] ={LOW, LOW, LOW, LOW};
// remember the previous pin state
void setup() {
Serial.begin(9600);
}
void loop() {
for (int ii=0; ii<4;ii++) {
encoder_direction(ii);
}
}
int encoder_direction(int m) {
boolean encoderA;
boolean encoderB;
if(m == 0) {
encoderA = (PIND & _BV (7)) == 0;
// digitalRead(7) encoderPinA
encoderB = (PINB & _BV (4)) == 0;
// digitalRead(12) encoderPinB
}
if (m == 1) {
encoderA = (PINB & _BV (0)) == 0;
// digitalRead(8) encoderPinA
encoderB = (PIND & _BV (5)) == 0;
// digitalRead(5) encoderPinB
}
if (m == 2) {
encoderA = (PINC & _BV (1)) == 0;
// digitalRead(A1) encoderPinA
encoderB = (PIND & _BV (4)) == 0;
// digitalRead(4) encoderPinB
}
if (m == 3) {
encoderA = (PIND & _BV (2)) == 0;
// digitalRead(2) encoderPinA
encoderB = (PINC & _BV (2)) == 0;
// digitalRead(A2) encoderPinB
}
if ((encoderALast[m] == HIGH) && (encoderA == LOW)) {
if (encoderB == LOW) {
//encoderPos --;
encoderPos = -1;
} else {
//encoderPos ++;
encoderPos = 1;
}
Serial.print(" Encoder ");
Serial.print(m+1);
Serial.print(" = ");
Serial.println(encoderPos);
}
encoderALast[m] = encoderA;
}
-
I don't think its possible using only 2 detector and detect only based on the order of activation.duck– duck12/23/2016 04:23:08Commented Dec 23, 2016 at 4:23
-
when this is in the loop works fine. But, now I want to use function. The problem is I can not retrieve correct value from function's output.zalt– zalt12/23/2016 04:48:59Commented Dec 23, 2016 at 4:48
2 Answers 2
I have a page about rotary encoders - in that I show how you can detect encoder changes (and direction) using one interrupt pin and another pin, like this:
// Wiring: Connect common pin of encoder to ground.
// Connect pin A (one of the outer ones) to a pin that can generate interrupts (eg. D2)
// Connect pin B (the other outer one) to another free pin (eg. D5)
volatile bool fired;
volatile bool up;
const byte encoderPinA = 2;
const byte encoderPinB = 5;
// Interrupt Service Routine for a change to encoder pin A
void isr ()
{
if (digitalRead (encoderPinA))
up = digitalRead (encoderPinB);
else
up = !digitalRead (encoderPinB);
fired = true;
} // end of isr
void setup ()
{
pinMode (encoderPinA, INPUT_PULLUP); // enable pull-ups
pinMode (encoderPinB, INPUT_PULLUP);
attachInterrupt (digitalPinToInterrupt (encoderPinA), isr, CHANGE); // interrupt 0 is pin 2
Serial.begin (115200);
} // end of setup
void loop ()
{
static long rotaryCount = 0;
if (fired)
{
if (up)
rotaryCount++;
else
rotaryCount--;
fired = false;
Serial.print ("Count = ");
Serial.println (rotaryCount);
} // end if fired
} // end of loop
In the main loop you just need to test the boolean fired
to see if the encoder changed, and up
to see if it went up (otherwise it went down).
I receive random of these values ...
I think with your code you need to detect a change - that is, if the encoder pin is now HIGH
but was previously LOW
.
Solution for 4 encoders
The problem is I am working on four encoders ...
I have reworked my code to use pin-change interrupts instead of external interrupts.
// Wiring: Connect common pin of encoder to ground.
volatile bool fired;
const byte ENCODERS = 4;
typedef struct
{
int aPin; // which Arduino pin for the "A" side
int bPin; // which Arduino pin for the "B" side
// values below are calculated at run-time
volatile byte * aPort; // which processor port the A side is plugged into
volatile byte * bPort; // which processor port the B side is plugged into
byte aBitMask; // which bit in the A port
byte bBitMask; // which bit in the B port
byte whichInterrupt; // which pin-change interrupt port (0, 1, 2)
byte oldValue; // old value of A port (to see if it changed)
int count; // current encoder counter
} encoder;
volatile encoder encoders [ENCODERS] =
{
// A B pins (eg. D4 and D8, D5 and D9 and so on)
{ 4, 8 },
{ 5, 9 },
{ 6, 10 },
{ 7, 11 },
}; // end of encoders
void checkForPinChange (const byte which)
{
for (byte i = 0; i < ENCODERS; i++)
{
if (encoders [i].whichInterrupt == which)
{
byte newValue = *(encoders [i].aPort) & encoders [i].aBitMask;
if (newValue != encoders [i].oldValue)
{
bool up;
byte bPort = *(encoders [i].bPort) & encoders [i].bBitMask;
if (newValue)
up = bPort;
else
up = !bPort;
fired = true;
if (up)
encoders [i].count++;
else
encoders [i].count--;
encoders [i].oldValue = newValue;
} // end of if value has changed
} // end of if this is the right interrupt number
} // end of for each encoder
} // end of checkForPinChange
// handle pin change interrupt for D8 to D13 here
ISR (PCINT0_vect)
{
checkForPinChange (PCIE0);
} // end of PCINT0_vect
// handle pin change interrupt for A0 to A5 here
ISR (PCINT1_vect)
{
checkForPinChange (PCIE1);
} // end of PCINT1_vect
// handle pin change interrupt for D0 to D7 here
ISR (PCINT2_vect)
{
checkForPinChange (PCIE2);
} // end of PCINT2_vect
void setup ()
{
Serial.begin (115200);
Serial.println ("Starting ...");
PCIFR |= bit (PCIF0) | bit (PCIF1) | bit (PCIF2); // clear any outstanding interrupts
for (byte i = 0; i < ENCODERS; i++)
{
pinMode (encoders [i].aPin, INPUT_PULLUP);
pinMode (encoders [i].bPin, INPUT_PULLUP);
// convert pin number to port, mask, etc. for efficiency
// Input port
encoders [i].aPort = portInputRegister (digitalPinToPort (encoders [i].aPin));
encoders [i].bPort = portInputRegister (digitalPinToPort (encoders [i].bPin));
// Which bit in the port to test
encoders [i].aBitMask = digitalPinToBitMask (encoders [i].aPin);
encoders [i].bBitMask = digitalPinToBitMask (encoders [i].bPin);
// Which interrupt number (0, 1, 2)
encoders [i].whichInterrupt = digitalPinToPCICRbit (encoders [i].aPin);
// activate this pin-change interrupt bit (eg. PCMSK0, PCMSK1, PCMSK2)
volatile byte * ICRmaskPort = digitalPinToPCMSK (encoders [i].aPin);
*ICRmaskPort |= bit (digitalPinToPCMSKbit (encoders [i].aPin));
// enable this pin-change interrupt
PCICR |= bit (digitalPinToPCICRbit (encoders [i].aPin));
} // end of for each encoder
} // end of setup
void loop ()
{
if (fired)
{
for (byte i = 0; i < ENCODERS; i++)
{
char buf [10];
sprintf (buf, "%5d", encoders [i].count);
Serial.print (buf);
}
Serial.println ();
fired = false;
} // end if fired
} // end of loop
The code is general enough to handle 1 to x encoders (eg. 10 encoders) - within reason! You need to have 2 pins spare per encoder (the third pin goes to ground). One of the pins needs to be capable of pin-change interrupts (on the Uno that is all of them).
The code sets up to detect an interrupt on the "A" side of the encoder and tests the value on the "B" side to see if it was turned clockwise or counter-clockwise.
I am getting results like this (with 2 encoders connected):
16 -19 1 1
17 -19 1 1
18 -19 1 1
19 -19 1 1
20 -19 1 1
21 -19 1 1
22 -19 1 1
23 -19 1 1
23 -20 1 1
23 -21 1 1
23 -22 1 1
23 -23 1 1
23 -24 1 1
23 -25 1 1
The other two are showing as "1" (that is, not turned yet).
-
I see. The problem is I am working on four encoders, Therefore, I trying to realize by status change detecting.zalt– zalt12/23/2016 07:06:39Commented Dec 23, 2016 at 7:06
-
It's always helpful to give as much detail as you can in the question. Then we don't have to guess, and we don't give an answer which doesn't suit your situation.12/23/2016 07:43:13Commented Dec 23, 2016 at 7:43
-
And if I may say, your posted code in the question doesn't have four encoders in it.12/23/2016 07:43:43Commented Dec 23, 2016 at 7:43
-
Nick, I think it would make sense to update your code to say
pinMode(encoderPinA, INPUT_PULLUP);
instead ofdigitalWrite (encoderPinA, HIGH); // enable pull-ups
James Waldby - jwpat7– James Waldby - jwpat712/23/2016 07:46:43Commented Dec 23, 2016 at 7:46 -
ok, I got to different code for four encoders.zalt– zalt12/23/2016 07:48:51Commented Dec 23, 2016 at 7:48
Using polling to read multiple encoders may work if you program quite carefully and if you dedicate the processor just to reading encoders; but generally, interrupt-driven encoder processing has a higher likelihood of working ok. The example below uses pin-change interrupts to read from three rotary encoders that are attached to PD2...PD7, ie, six consecutive bits of port D. It can be extended to more encoders by adding another pin-change ISR to handle another interrupt vector, or by using PD0 and PD1 as encoder inputs. (However, PD0 and PD1 are RX and TX on the Uno I'm testing with, and I'm using Serial for test output.)
Example: rotors3.ino
/* Rotors3 -- JW, December 2016 -- Uses 4-state state-machine
* implementation of rotary encoding for KY-040 rotary knobs. The
* state-machine picture in a Feb 4, 2014 7:40 PM post at
* https://e2e.ti.com/support/microcontrollers/hercules/f/312/t/318762
* by Anthony Seely shows counts increasing on transitions 10 -> 11
* -> 01 -> 00 -> 10 and decreasing on transitions the other way.
* The ISR in this program decodes those transitions using logical
* operations on variables `changes` and `wayflag`. Transitions
* between 00 and 11 or 10 and 01 are invalid and ignored.
*/
volatile byte abOld; // Old state
volatile byte changedCount; // Change-indication
volatile int count[3]; // current rotary count
void setupRotors3(byte ioMode, byte enablePCI) {
pinMode( 2, ioMode); // 2, PD2 PCINT 18
pinMode( 3, ioMode); // 3, PD3 PCINT 19
pinMode( 4, ioMode); // 4, PD4 PCINT 20
pinMode( 5, ioMode); // 5, PD5 PCINT 21
pinMode( 6, ioMode); // 6, PD6 PCINT 22
pinMode( 7, ioMode); // 7, PD7 PCINT 23
if (enablePCI) {
PCMSK2 |= 0xfc; // Set mask for bits of interest
PCIFR |= 0x04; // Clear PC interrupts if any
PCICR |= 0x04; // Enable PC interrupts
}
abOld = changedCount = count[0] = count[1] = count[2] = 0;
}
void setup() {
setupRotors3(INPUT_PULLUP, true);
Serial.begin(115200);
Serial.println("Starting Rotary Encoder Test");
}
// On interrupt, read input pins, compute new states, and adjust
// counts. This code depends on encoder data appearing like ddccbbaa,
// ie data is in properly paired consecutive port bits.
ISR(PCINT2_vect) {
byte abNew = PIND & 0xfc; // Read pins 2, 3, 4, 5, 6, 7,
byte changes = abNew^abOld; // Use XOR to flag changed bits
char wayflag = 2*abNew^abNew; // Use XOR to get state-bits
byte test2;
for (byte i=0; i<3; ++i) {
// For PD2-7, low 2 bits aren't relevant. Move the shifts to
// end of for loop if low 2 bits carry encoder info.
wayflag >>= 2; // Discard low state-bits
changes >>= 2; // Discard low change-bits
test2 = changes & 3; // Get 2 bits of interest
if (test2==1) {
count[i] += 1-(wayflag&2);
changedCount = true;
} else
if (test2==2) {
count[i] -= 1-(wayflag&2);
changedCount = true;
}
}
abOld = abNew; // Save new state
}
void loop() {
if (changedCount) {
changedCount = false; // Race condition can be ignored
for (byte i=0; i<3; ++i) {
Serial.print(count[i]); // Announce counts
Serial.print(" ");
}
Serial.println(millis()); // Announce change-time
}
}
Note, setupRotors3()
and a few other statements in the program were generated using the ISR-framework-generating Sketch from an answer to an earlier question. If you need to use other input pins than those shown in the program above, you can use the framework generator to produce a setup routine and port-I/O statements for them.
Following is some output from the program as above, from when I turn two of the three 60-counts-per-turn encoders as fast as I can by hand, for half a turn each at the same time (about 4 RPS, or 240 RPM).
-2 1 0 1127
-2 2 0 1133
-2 3 0 1148
-2 4 0 1155
-3 4 0 1165
-3 5 0 1166
-4 5 0 1169
-4 6 0 1170
-5 6 0 1174
-5 7 0 1177
-6 7 0 1178
-6 8 0 1181
-7 8 0 1184
-7 9 0 1187
-8 9 0 1189
-8 10 0 1190
-9 10 0 1196
-9 11 0 1197
-10 12 0 1200
-10 12 0 1201
-11 12 0 1205
-11 13 0 1207
-12 13 0 1210
-12 14 0 1211
-12 15 0 1215
-13 15 0 1216
-13 16 0 1220
-14 16 0 1221
-15 16 0 1224
-15 17 0 1226
-16 17 0 1229
-16 18 0 1230
-17 18 0 1234
-17 19 0 1235
-18 19 0 1239
-18 20 0 1241
-19 20 0 1243
-19 21 0 1247
-20 21 0 1248
-21 21 0 1251
-21 22 0 1252
-22 22 0 1255
-22 23 0 1258
-23 23 0 1259
-24 23 0 1263
-24 24 0 1264
-25 24 0 1266
-25 25 0 1269
-26 25 0 1271
-27 25 0 1275
-27 26 0 1275
-28 26 0 1280
-28 27 0 1281
-29 27 0 1284
-29 28 0 1288
-30 28 0 1291
-31 28 0 1301
-31 29 0 1341
-32 29 0 1346
-32 30 0 1385
-33 30 0 1389
-
Looks like we had similar ideas. I didn't spot your answer until I had rewritten my code. :) We even have similar debugging output.12/24/2016 01:30:52Commented Dec 24, 2016 at 1:30