Interfacing rotary encoder to AVR microcontroller

Recently I was working on a project where I used a rotary encoder. I thought I could share some thoughts on how rotary encoder should be interfaced to AVR MCU and how to write a code to read its value.

It is relatively easy to connect and program rotary encoders – only three wires are required to connect to the microcontroller (two for signal quadrature outputs) and one for reference GND). When encoder knob is turned in either direction, it generates a Grey code on the outputs which allows tracking turn speed and direction.

Rotary encoder allows including a convenient user interface option with a single knob. Many rotary encoders also come with an integrated button on the knob itself – so menu navigation becomes even more comfortable. In our example project, we are going to use a 12-step mechanical rotary encoder from SparkFun.

It is interfaced to ATMega32 board with graphical LCD.

rotarry endocer connection

You can find many projects on the internet where one of the rotary encoder pins is connected to a microcontroller interrupt pin. This enables easier detection of first encoder turn. The endless main loop for tracking is not recommended because it occupies too much of MCU resources for doing nothing.

Another logical solution is to use Timer which periodically generates interrupts so it could check if rotary encoder has been turned or button have been pressed.

Let us use the timer option as it allows the connecting encoder to any available microcontroller pins. The rotary encoder in our schematic is connected to Atmega32 as follows:

rotary encoder AVR schematic
  • Encoder A pin to atmega32 pin B2
  • Encoder pin B to MCU pin B3
  • Button to pin B4

We are going to use atemga32 internal pull-ups on these pins. Let us write a simple code library for reading rotary encoder. This will make code reusable and modular. First, we create two empty files rotary.c and rotary.h. In rotary.h we define AVR port pins and library function prototypes:

 #ifndef ROTARY_H
 #define ROTARY_H
  
 #include <avr/io.h>
  
 //define port where encoder is connected
 #define ROTPORT PORTB
 #define ROTDDR DDRB
 #define ROTPIN PINB
 //define rotary encoder pins
 #define ROTPA PB2
 #define ROTPB PB3
 #define ROTPBUTTON PB4
 //define macros to check status
 #define ROTA !((1<<ROTPA)&amp;ROTPIN)
 #define ROTB !((1<<ROTPB)&amp;ROTPIN)
 #define ROTCLICK !((1<<ROTPBUTTON)&amp;ROTPIN)
 //prototypes
 void RotaryInit(void);
 void RotaryCheckStatus(void);
 uint8_t RotaryGetStatus(void);
 void RotaryResetStatus(void);
 #endif 

In library source file, we declare following functions. First, initialize pins where rotary encoder is connected:

 void RotaryInit(void)
 {
 //set pins as input
 ROTDDR &amp;= ~((1<<ROTPA)|(1<<ROTPB)|(1<<ROTPBUTTON));
 //enable interrnal pullups;
 ROTPORT |= (1<<ROTPA)|(1<<ROTPB)|(1<<ROTPBUTTON);
 } 

Then we set selected port pins as inputs and enable internal pull-up resistors.

Let us write encoder check status function which sets an internal variable to some variable.

void RotaryCheckStatus(void)
 {
 //reading rotary and button
 //check if rotation is left
             if(ROTA &amp; (!ROTB)){
                         loop_until_bit_is_set(ROTPIN, ROTPA);
                         if (ROTB)
                                     rotarystatus=1;
                         //check if rotation is right
                         }else if(ROTB &amp; (!ROTA)){
                                     loop_until_bit_is_set(ROTPIN, ROTPB);
                                     if (ROTA)
                                                 rotarystatus=2;
                         }else if (ROTA &amp; ROTB){
                                     loop_until_bit_is_set(ROTPIN, ROTPA);
                                     if (ROTB)
                                                 rotarystatus=1;
                                     else rotarystatus=2;
                         }
             //check button status
             if (ROTCLICK)
                         {
                                     rotarystatus=3;
                         }
 } 
  • If knob was turned left – rotary status is set to 1
  • If right then the value is 2
  • If the button was pressed – state is set to 3

The two following two returns status and resets it:

//return button status
 uint8_t RotaryGetStatus(void)
 {
 return rotarystatus;
 }
 //reset status
 void RotaryResetStatus(void)
 {
 rotarystatus=0;
 } 

We are using a graphical LCD based on the ks0108 controller for displaying messages.

To check rotary encoder status, we are using Timer2 overflow interrupts. The microcontroller is running at 16MHz, so with prescaller of 256, the overflow interrupt occurs 122 times/s. The checking speed seems to be suitable for easy operation of the rotary encoder.

void Timer2_Start(void)
 {
             TCCR2|=(1<<CS22)|(1<<CS21); //prescaller 256 ~122 interrupts/s
             TIMSK|=(1<<TOIE2);//Enable Timer0 Overflow interrupts
 }

After the timer has been started, we can put RotarryCheckStatus function inside it and read encoder actions:

ISR(TIMER2_OVF_vect)
 {
 //reading rotary and button
 RotaryCheckStatus();
 } 
Rotary encoder status on GLCD display driven by AVR microcontroller

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.