Rotary Encoder Tutorial

Rotary Encoder Tutorial

Rotary Encoders are the modern digital equivalent of the potentiometer. They have taken over from the potentiometer for use in stereos and many other applications due to their robustness, fine digital control and the fact that they can fully rotate without end stops.

Sparkfun Rotary Encoder

With a rotary encoder we have two square wave outputs (A and B) which are 90 degrees out of phase with each other. The number of pulses or steps generated per complete turn varies. The Sparkfun Rotary Encoder has 12 steps but others may have more or less. The diagram below shows how the phases A and B relate to each other when the encoder is turned clockwise or counter clockwise.

Rotary Encoder Phase Pulses

Every time the A signal pulse goes from positive to zero, we read the value of the B pulse. We see that when the encoder is turned clockwise the B pulse is always positive. When the encoder is turned counter-clockwise the B pulse is negative. By testing both outputs with a microcontroller we can determine the direction of turn and by counting the number of A pulses how far it has turned. Indeed, we could go one stage further and count the frequency of the pulses to determine how fast it is being turned. We can see that the rotary encoder has a lot of advantages over a potentiometer.

There are a number of methods we can use to read a rotary encoder with a microcontroller

  • Use a port change interrupt
  • Use the Capture Compare module to detect signal change. This is the best method if you also want to check how fast the encoder is being turned
  • Use of a timer to check the values of A and B pulse x times per second

Lets make an application

We will now use the rotary encoder in the simplest of applications, we will use it to control the brightness of an led by altering a pwm signal. We will use the easiest method to read the encoder, that is the use of a timer interrupt to check on the values.

We will use the sparkfun encoder as discussed above. The first thing is to determine how fast we need our timer to operate. If you imagine that at best we could turn the encoder through 180 degrees in 1/10th of a second, that would give us 6 pulses in 1/10th second or 60 pulse per second. In reality its never likely to be this fast. As we need to detect both high and low values this gives us a minimum frequency of 120Hz. Lets go for 200Hz just to be sure. (Note: as these units are mechanical switches, there is the possibility of switch bounce. Using a fairly low frequency allows us to effectively filter out any switch bounce)

Each time our timer interrupt triggers, we compare the value of our A pulse with its previous value. If it has gone from positive to zero, we then check the value of the B pulse to see if it is positive or zero. Depending on the outcome we can increment of decrement a counter. We then use this to control the PWM value to increase or decrease the brightness of the LED

We will use the Microchip C18 compiler for this application along with an 18F14K22 chip. This is an arbitrary chip that just happened to be available. Is has a built-in clock so we need minimal components. The schematic is shown below


Rotary Encoder Schematic

And the source code is shown below. You can also download it here

    Rotary Encoder Demo
    Use a rotary encoder to control the brightness of an LED
    Copyright HobbyTronics 2010

#include <p18f14k22.h>

#pragma config FOSC = IRC
#pragma config WDTEN = OFF
#pragma config BOREN = OFF
#pragma config PWRTEN = ON
#pragma config MCLRE = OFF
#pragma config LVP = OFF
#pragma config HFOFST = OFF
#pragma config PLLEN = OFF

//Define Interrupt Locations
void hi_interrupt(void);
void lo_interrupt(void);

#pragma code high_vector_section=0x8
void high_vector (void){
    _asm GOTO hi_interrupt _endasm
#pragma code low_vector_section=0x18
void low_vector(void){
    _asm GOTO lo_interrupt _endasm
#pragma code

#define ENCODER_A PORTCbits.RC0        // Encoder A Pin
#define ENCODER_B PORTCbits.RC1        // Encoder B Pin

unsigned char    encoder_counter=0;    // Used to control brightness of LED, values 0 to 25 (we dont need 250 increments)
unsigned char    encoder_A;
unsigned char    encoder_B;
unsigned char    encoder_A_prev=0;
unsigned char    flag_set_pwm=1;

#pragma interruptlow lo_interrupt
void lo_interrupt(void){
    // -----------------------
    // Low Priority Interrupts
    // ----------------------- 

    if (INTCONbits.TMR0IF)
        encoder_A = ENCODER_A;
        encoder_B = ENCODER_B;

        if((!encoder_A) && (encoder_A_prev)){

            // A has gone from high to low
            if(encoder_B) {
                // B is high so clockwise
                if(encoder_counter<25)    encoder_counter++;
            else {
                // B is low so counter-clockwise           
                if(encoder_counter>0) encoder_counter--;
            flag_set_pwm = 1;           // Set flag to indicate change to PWM needed
        encoder_A_prev = encoder_A;     // Store value for next time
        TMR0L = 178;                    // 200 Hz
        INTCONbits.TMR0IF = 0;          // Clear interrupt flag

#pragma interrupt hi_interrupt
void hi_interrupt(void)

void SetPWM(unsigned char pwm_width){
    // set pwm values
    // input of 0 to 25
    // PWM output is on P1A (pin 5)

    unsigned char pwm_lsb;
    pwm_width*=10;                         // change value from 0-25 to 0-250
    //10 Bits - 2 LSB's go in CCP1CON 5:4, 8 MSB's go in CCPR1L
    pwm_lsb = pwm_width & 0b00000011;      // Save 2 LSB
    CCPR1L = pwm_width >> 2;               // Remove 2 LSB and store 8 MSB in CCPR1L (only 6 bits as max duty value = 250)
    pwm_lsb = pwm_lsb << 4;                // Move 2 LSB into correct position
    CCP1CON = pwm_lsb + 0b00001100;        // duty lowest bits (5:4) + PWM mode


void main(void){

    OSCCON = 0b01110010;          // Int osc at 16 MHz
    RCONbits.IPEN = 1;            // Enable interrupt priority
    INTCONbits.PEIE = 1;          // interrupts allowed
    INTCONbits.GIE = 1;            
    INTCONbits.GIEH = 1;
    INTCONbits.GIEL = 1;

    T0CON = 0b11000111;           // 8 bit timer, prescaler 1:256, TMR0 on
    TMR0L = 178;                  // 200Hz
    INTCON2bits.TMR0IP = 0;       // interrupt priority 0
    INTCONbits.TMR0IE = 1;        // timer0 interrupt enabled
    // Clear the peripheral interrupt flags
    PIR1 = 0;

    ANSEL=0;                      // Digital
    ANSELH=0;                     // Digital
    ADCON0=0;                     // A2D Off
    CM1CON0=0;                    // Comparators off
    CM2CON0=0;                    // Comparators off

    TRISA = 0b00000000;           // Set Ports
    TRISB = 0b00000000;           //
    TRISC = 0b00000011;           // Encoder inputs on RC0 and RC1
    * PWM Register Values
    * Oscillator Frequency Fosc = 16000000
    * Clock Frequency Fclk = 4000000
    * PWM Freq = 250 - allows us to use a duty value of 0 to 250
    * Prescaler Value = 16
    * Postscaler Value = 16   
    * PR2 = 62
    * Maximum duty value = 250

    T2CON = 0b01111111;           // prescaler postscaler to give 250Hz + turn on TMR2;
    PR2 = 62;                     // gives 250Hz
    CCPR1L = 0b00000000;          // set duty MSB - initially 0 - off
    CCP1CON = 0b00001100;         // duty lowest bits + PWM mode   

        if(flag_set_pwm) {
            //Set PWM values