A rotary encoder is a digital device in the shape of a knob. You can turn it in either clockwise direction or counter-clockwise direction infinite turns, without any limit. This is what a potentiometer can’t do. In the old days most knobs are potentiometers or rotary switches, such as those on stove top, washers and dryers. Potentiometer is much easier in the concept to sense than a rotary encoder so here we discuss how to sense a digital rotary encoder.
How does it work?
A simple rotary encoder has two channels or switches. If you turn the encoder’s shaft, the two switches will open and close repeatedly much like two tactile buttons. Depending on the direction of the rotation, the sequence of the open and close will be different. Say we have two channels A and B that open and close as we turn the encoder and if we turn the encoder clockwise, we will see:
A open->B open->A close->B close
If we turn counter clockwise, we see:
B open->A open->B close->A close
So to tell which way the encoder is turning, we just need to tell which sequence occurs. There are many ways to tell which sequence occurs but I will focus on the easiest way, which is also efficient enough.
First, you combine the status of the switches A and B into a 2-bit binary number, A being the low bit and B being the high bit. Let’s define open as 1, and close as 0. Here is how to combine the status bits:
stat=(digitalRead(EncoderChnB)*2) + digitalRead(EncoderChnA);
When both channels are closed, we get 0. When both are open, we get 3. When only a is open, we get 1. When only b is open, we get 2.
Now we can translate the sequences into numbers:
A open->B open->A close->B close ==>> 1,3,2,0
B open->A open->B close->A close ==>> 2,3,1,0
Note that in the first sequence when B opens after A opens, they are both open, so that corresponds to 3.
Now we can make an array to store both sequences with a pointer:
byte stat_seq[]={0,1,3,2,0,1,3,2,0};
byte stat_seq_ptr=4;
The pointer initially points to a status of both switches closes, which is a detent position for the encoder. You turn your encoder and it clicks into detent positions. If your detent position is when both switches are open, you may use {3,2,0,1,3,2,0,1,3} instead.
Now, every time you read the two channels, you compare the stat with the array element before and after the current pointer’s position. If the stat represent the array element after the current pointer, move the pointer there, same for the other way around. When the array pointer hits the left end of the array, with a 0, you have completed one CCW turn, report this turn and reset the pointer to the middle, 4. Same for CW turn when the pointer reaches the right end of the array.
This should be easier to understand than other ways of tracking the encoder and is much more robust. Here is the complete code:
The function returns the number of CW or CCW turns stored in its buffer. Every time you call it to ask number of CW turns, it returns the turns and decrements its internal count by one. All you need to do is to keep calling this function every loop by supplying which direction you want to read back. If the read back is greater than zero, a turn is made by the user. You can decide what to do, maybe advance in a menu. If the return is zero, there is no turn in that direction, you need to do nothing. The green section is sensing the encoder and the blue section is responding the the caller.
byte read_encoder(byte direction) // 1 for asking CW counts, -1 for asking CCW counts. Returns counts and decrements the counts till 0.
{
 static byte stat_seq[]={3,2,0,1,3,2,0,1,3}; // For always on switches use {0,1,3,2,0,1,3,2,0};
 static byte stat_seq_ptr=4;
 static byte cw_counter=0;
 static byte ccw_counter=0;
 byte stat_int=(digitalRead(EncoderChnB)<<1) | digitalRead(EncoderChnA);
 if (stat_int==stat_seq[stat_seq_ptr+1])
 {
   stat_seq_ptr++;
   if (stat_seq_ptr==8)
   {
     stat_seq_ptr=4;
     cw_counter++;
   }
 }
 else if (stat_int==stat_seq[stat_seq_ptr-1])
 {
   stat_seq_ptr–;
   if (stat_seq_ptr==0)
   {
     stat_seq_ptr=4;
     ccw_counter++;
   }
 }
if (direction==1)
 {
   if (cw_counter>0)
   {
     cw_counter–;
     return cw_counter+1;
   }
   else return 0;
 }
 else if (direction==255)
 {
   if (ccw_counter>0)
   {
     ccw_counter–;
     return ccw_counter+1;
   }
   else return 0;
 }
 else return 0;
}
Like this:
Like Loading...