Reading a rotary encoder with a pic
A while ago I went to see how I can use a rotary decoder. A rotary encoder, also called rotary decoder by some, looks a bit like a potentiometer. The difference is that a potentiometer has a beginning and end, but a rotary encoder you can rotate endlessly in either direction. This has nothing to do with changing a resistance value, but it will send pulses over two lines (A and B). When you can detect which line gives the first pulse can you tell if it is rotated clockwise or counterclockwise. The code of the PIC, AVR, Arduino, or any other micro controller can determine what pulse comes first and what has to be done with this information. That is, simply put, what I will discuss here.
Connections
I started working with the cheapest rotary encoder with a built-in switch (besides turning the knob it can also be pressed like a button) that I could find.
The 3 pins at the front are the data lines. Normally the middle pin is always the common, this you connect to ground. Left and right are the contacts A and B. It doesn't matter which one you will use as A or B, technically they are identical.
The 2 pins on the other side is the toggle switch. This I will not use today.
Furthermore, it is important to place two pull-up resistors of 10k on the lines A and B are (this value may be lower or higher) to ensure that the lines are high when the internal switches that provide the pulses are open.
There are also rotary encoders that have optical sensors instead of mechanical contacts. These are generally more expensive, and have a 4 connection for a supply voltage.
There are also differences in 'detent' and 'normal'. 'Detent' means you feel dents by every pulse cycle while turning. The 'normal' feels the same as when you turn a potentiometer.
The signal
What kind of pulses does a rotary encoder give? Both pin A and pin B will give a square wave when it's rotated. By watching which of the pins goes low first you can see if the encoder is rotated clockwise or counterclockwise.
Let's look at the picture below. While turning it goes thru four steps. My decoder is as said detent. Step 1 is thereby always a rest position (a dent), wherein both A and B are both 1. When I turn clockwise it goes to Step 2. I see that A = 0 and B = 1. If I had turned counterclockwise you can see in the drawing that A would be 1 and B = 0. So that's how you determine which way it's rotated. Another way would be to detect when A has a rising edge (at step 3/4) and again look if B is high or low.
Now the software
The pins A and B to connect to two inputs of the PIC. In order to read out the decoder reliable. Make sure that one of the pins has an IOC (Interrupt On Change) present. In my case I chose for pin A to be connected to the IOC pin of the PIC, but it's fine if you choose B, that does not matter. For the rest of this story we just keep going with pin A. Let us first define the pins with more usable names and define a variable that we need. I use MikroC as my compiler
1 2 3 4 56
//************* PINS ************************* sbit RotA at RA4_bit; sbit RotB at RA3_bit; //************* VARIABLES ********************unsigned short OldRotA;
Do not forget to enable IOC and set to correct pins to input
7 8 9 GIE_bit = 1; // Enable interrups RABIE_bit = 1; // IOC enabled IOCA4_bit = 1; // IOC at RA4 (RotA)
Okay, we now know that at rest A = 1 and B = 1. To keep the code simple, I just look when A goes down. You can see in the drawing that actually A goes to 0 at 2 moments during a pulse cycle. That is step 2 in clockwise or in step 3 in anti-clockwise. The difference is that in step 2, B is still high, but in step 3, he is low.
In short, if A = 0 and B = 0, then there is counterclockwise rotated ( or anti-clockwise).
But if A = 0 and B = 1 then there rotated clockwise.
This is pretty easy to convert to code. In my case I use the decoder to the change the variable voltage. To avoid overflow voltage can not be less than 0 and not greater than 720. In this MikroC code looks like this:
10 11 12 13 1415 16 17 18 1920 21 22 //******************** FUNCTIONS ************* void Interrupt(){ if (RABIF_bit){ // Only do something after falling edge if(OldRotA && RotA==0) { if(RotB && Voltage > 0) Voltage--; else if (!RotB && Voltage < 720) Voltage++; } OldRotA = RotA; RABIF_bit=0; } } //*******************************************
Maybe if (OldRotA && Rota == 0) some explanation needed. An IOC responds to every change of the pin. So if input RotA goes from 1 to 0 or from 0 to 1 will get an interrupt. I'm only interested in the transition from 1 to 0 (falling edge). Hence I look or RotA was first one (OldRotA == 1) and is now 0 (Rota == 0). If so then I look at RotB and whether the minimum or maximum is not reached yet. If it is a falling edge and the variable is within it's limits then it will increase or decrease accordingly.
Finally, the status of RotA is saved in OldRotA for next time.
It can be that simple. I use this code for a while now and it works well for low speed pics (till about 8Mhz). At higher speeds the debounce of the mechanical switched becomes a problem. Next time I will discuss a slightly extended version of the code where the function has become more universal and can be used for a variety of variables with different maximum and minimum values. More about this another time 🙂