Hi All,
I have been digging around for a week or two trying to get info on getting 8 rotary encoders to work on a MIDI controller project. Several folks have commented that HW interrupts are required, but there are only two on the UNO and six on the MEGA 2560, I need 8.
Note: This project was developed on a MEGA 2560, but should work on the UNO. BTW if you are now to this stuff please search YouTube for Arduino timer interrupts and muxs to get the background you'll need. This isn't really that hard!
This scenario supports 8 rot encoder using 3 generic digital outputs and 2 generic digital inputs, -NO- HW interrupt pins required.
First step is to wire the "A" pins from the 8 encoders to a 74HC151 8-1 mux. Do the same with the "B" pins to a second mux, be careful to get the A-B wires in the same order on the muxs. Then take the outputs of the two muxs and connect them to two generic digital input pins.
Second step is to wire the S0-S1-S2 address pins of the muxs to three generic digital output pins. S0A and S0B can be connected to the same output pin and so on.
Using the Timer1 SW interrupt lib, define an interrupt function. In this example we use ScanEncoders. I have the interrupt frequency set to 1ms (1000us) and it appears to be working fine. Note: that if you put Serial.prints in this function to debug, the encoders can become mildly unresponsive. So make sure you keep the code tight and snappy in the ScanEncoder interrupt routine.
Timer1.initialize(1000); //micro sec
Timer1.attachInterrupt(ScanEncoders);
In function ScanEncoders set up a For loop to count 0-7 (000-111) and set the S0-S1-S2 "address" lines of both muxs (only 3 output pins required) to scan all 8 inputs of the muxs.
For each time through the loop read the A and B inputs (only 2 input pins required). Here's where I departed from most conventional thinking... instead of looking for the transitions to know the direction of the encoder spinning, looking for the values of the A and B inputs and compare them to the last read of those inputs.
I saw a post mentioning that the A & B pulses are a gray code (only 1 bit changes at a time)... 00-01-11-10 repeating. I noticed that with a quick translation mapping 11 to 10 and 10 to 11 that this becomes 2-bit counting... 00-01-10-11 == 0-1-2-3. If the count goes up your moving clockwise and if count down then counter-clockwise (anti-clockwise if you must) ;-). Once you know this, you can inc or dec the variable that represents the virtual position of the encoders.
Hope this helps someone out there... I have watched so many excellent videos and learned so much from all the great examples... I'm just trying to give back a little!
some prepping...
#define EncMux1InPin 49
#define EncMux2InPin 50
// these address lines control A & B Mux's at the same time
#define EncMuxAdr1Pin 46 //digital outputs to control button mux address line 1
#define EncMuxAdr2Pin 47 //digital outputs to control button mux address line 2
#define EncMuxAdr3Pin 48 //digital outputs to control button mux address line 3
//note: Tie the strobe pin to ground.
// for MUX CODE - manually binary count 0-7 to control 8 inputs
unsigned int a_bin[]={LOW,HIGH,LOW,HIGH,LOW,HIGH,LOW,HIGH};
unsigned int b_bin[]={LOW,LOW,HIGH,HIGH,LOW,LOW,HIGH,HIGH};
unsigned int c_bin[]={LOW,LOW,LOW,LOW,HIGH,HIGH,HIGH,HIGH};
//Encoder variables
volatile int gEncVirtPosVal[8] = {0,0,0,0,0,0,0,0}; //used to store the Enc's virtual rotational value
volatile int gLastEncVirtPosVal[8] = {0,0,0,0,0,0,0,0}; //used to store the Enc's last virtual rotational value
volatile byte gLastEncVal[8] = {0,0,0,0,0,0,0,0}; //used to store the previous Enc's Input 2 bit (A&B) values to compare to current Enc's values
The function called by the timer every 1ms
void ScanEncoders() {
byte EncAInput = 0; //used to read enc output A
byte EncBInput = 0; //used to read enc output B
byte EncVal; // used store the "counting" value of the pins A & B gray code
boolean jump; //a hack to get to the next loop
for(int i=0;i<8;i++) {
jump=false;
//select mux input by using the manual binary count 0-7 from lookup table a/b/c_bin arrays
digitalWrite(EncMuxAdr1Pin,a_bin*);*
digitalWrite(EncMuxAdr2Pin,b_bin*);
digitalWrite(EncMuxAdr3Pin,c_bin);
_ // read encoder input values from both Enc MUXs*
* EncAInput = !digitalRead(EncMux1InPin);
EncBInput = !digitalRead(EncMux2InPin); *_
* //Enc A & B is gray code, we convert gray code to 2-bit counting --> 00=0, 01=1, 11=2, 10=3*
* if (EncAInput == 0 & EncBInput == 0) EncVal = 0;*
* else if (EncAInput == 0 & EncBInput == 1) EncVal = 1;*
* else if (EncAInput == 1 & EncBInput == 1) EncVal = 2;*
* else EncVal = 3; // (EncAInput == 1 & EncBInput == 0)*
* //determine encoder's spin direction*
_ if (EncVal == gLastEncVal*) jump=true; //do nothing*
else if (EncVal > gLastEncVal) gEncVirtPosVal = gEncVirtPosVal*+1; // encoder moved clockwise so inc encoder "value"*
else gEncVirtPosVal = gEncVirtPosVal-1; // (EncVal < gLastEncVal*) encoder moved anti-clockwise so dec encoder "value"*_
* if (jump==false) {*
* //Do stuff here based on changed encoder values*
gEncVirtPosVal = constrain(gEncVirtPosVal*,0,127); //bound the Enc position values, !!! not quit right for data > 1 byte*
_ gLastEncVal = EncVal; // save the current encoder value (physical position) to compare the next time scan funtion is called
gLastEncVirtPosVal = gEncVirtPosVal*; // save the current virtual encoder position/value in 'Last' in order to compare at the next scan*
* } *_
* }*
}