Fun with Interrupts : Decoding Quadrature Encoders on TI Launchpad MSP-EXP430G2

Print Friendly, PDF & Email

This is one of those programming assignments I had undertaken to learn more about interrupts and timers. I have used a detented quadrature encoder before and used it in a small project . However the routine for these earlier trysts with encoder were not 100% original and somehow I had a feeling that I had not grasped the routine.
Although there are number of routines available online to use these encoders, nothing gives you a kick like the one you get re-inventing the wheel.
The encoder which I am using is actually a knob having detents (clicks) at every turn increment. The knob also has a keyswitch  akin to a centre-click on mice.

Sureelectronics  Rotary Encoder Switch With Keyswitch

Sureelectronics Rotary Encoder Switch With Keyswitch

This is those kind of knobs which you see on Hi-Fi audio systems/radio for volume or frequency controls.   This is the link to the product page. The output of these encoders is 2 bit gray code. One of the benefit using these encoders is that they can replace push buttons and if coded well can be used for menu operations.

The encoder outputs the data on two channel (CH A and CH B ); the output signal sequence depends on two factors viz bias (active high or active low) and direction of rotation.  In my test routine I have catered for an active high configuration .i.e the common pin is connected to GND and CH A, CH B and Button pins are connected directly to MCU pins which have been pulled up. So, if you take a multi-meter  and measure voltage on CH A , CH B or Button pin , you will read VCC with respect to Ground. The subsequent explanation is based on the abovementioned configuration.

As I mentioned above, at steady state the CH A , CH B or Button pins will read VCC. However when the knob is turned or pressed the pins will be pulled low momentarily and again pulled. Illustrated below is the signal sequence for rotation in both direction

So, as is evident from the above two diagrams, to determine direction of rotation we just have to interpret the change in bit values.  A transition of bit value from 1 to 0 would indicate a CCW rotation or a transition from 3 to 2 would indicate a clockwise rotation.

The code is sufficiently commented for ease of understanding. Please free to post a comment for your suggestions and queries

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
 /* ----------------------------------------------------------------------
 * Demo program routine to read/interpret a detented rotary encoder
 * Coded by Ishan Karve (ishan at the rate gmail dot com)
 * 14 Feb 2012
 * Platform : TI MSP-EXP430G2 Launchpad running MSP430G2553
 * Encoder  :  Rotary Encoder Switch With Keyswitch
 *             http://www.sureelectronics.net/goods.php?id=221
 * Software Suite : Code Compose Studio, Version: 4.2.3.00004
 * ----------------------------------------------------------------------
 *
 * Routine Caters for
 *      Clockwise Rotation.
 *      Counter Clockwise Rotation
 *      Single Button Click
 *      Double Button Click
 * To Do       
 *      Click and Turn (CW or CCW)
 */


#include <msp430g2553.h>
#include <stdbool.h>
//Defines for Launchpad LEDs
#define LED1    BIT6
#define LED0    BIT0
//define for quarature encoder
#define CH_B    BIT0    //P2.0  Encoder Pin
#define CH_A    BIT1    //P2.1  Encoder Pin
#define BUTTON  BIT2    //P2.2  Encoder Pin
#define COMM    BIT5    //P1.5  Encoder Pin
//function declaration
void pattern_match();                   //determine direction of rotation
void TX(char *tx_message);              //Serial UART TX
static char *i2a(unsigned i, char *a, unsigned r);  //convert int to char
char *itoa(int i, char *a, int r);          //convert int to char
void ADC_init(void);

//variable declaration
char buffer[5];
int a=0;        //variable stores interrupt edge
int b=0;        //variable stores interrupt edge
int cval=3;     //store current value to determine rotation
int pval=3;     //stores previous value to determine rotation direction
int tr_state=0;     //variable to store transition state  
int num=0;      //arbitary number. will increment or decrement iff tr_satate=4
int tr;         //holds trigget state for variable tr_state
int pnum=0;    
int inc_fact=1;     //this variable holds the increment factor
int click=0;        //holds button click count
int sclick=false;   //flag holds single click, has to be reset in program loop
int dblclk=false;   //flag holds double click, has to be reset in program loop
void main(){
    //stop watchdog
    WDTCTL = WDTPW + WDTHOLD;
 
    //setup internal DCO
    BCSCTL1 = CALBC1_1MHZ;            // Set DCO to 1MHz
    DCOCTL = CALDCO_1MHZ;  
   
    //Setup output ports
    P1DIR |= (LED0+LED1+COMM);
    P1OUT &=~(LED0+LED1+COMM);
   
    //setup interrupts for quadrature encoder
    P2DIR&= ~(CH_A + CH_B + BUTTON);            //set ports as inputs
    P2IE |=  (CH_A + CH_B + BUTTON);        //enable interrupts for Quad Encoder
   
    //active-high configuration
    P2REN |=(CH_A + CH_B + BUTTON);         //enable pullups on respective ports
    P2OUT |=(CH_A + CH_B + BUTTON);
   
    P2IFG&=~(CH_A + CH_B + BUTTON);         //clear interrupt flag
    P2IES&=~(CH_A + CH_B + BUTTON);         //Interrupt on Hi->Lo transition
   
    //Setup USCI for UART communication at 9800-N-1
    P1SEL = BIT1 + BIT2;                    // Set P1.1 to RXD and P1.2 to TXD
    P1SEL2 = BIT1 + BIT2;
    UCA0CTL1 |= UCSSEL_2;                   // Have USCI use SMCLK AKA 1MHz main CLK
    UCA0BR0 = 104;                          // Baud: 9600, N= CLK/Baud, N= 10^6 / 9600
    UCA0BR1 = 0;                            // Set upper half of baud select to 0
    UCA0MCTL = UCBRS_1;                     // Modulation UCBRSx = 1
    UCA0CTL1 &= ~UCSWRST;                   // Start USCI
    TX("Rotary Encoder Demo\r\n");
   
    /* setup timer to watch for double click
     * the double clik period is 0.5 second
     * mcu is running at 1 MHZ, hence we will divide (prescale)
     * the clock by 8 to get a frequency of 125 KHZ. Clock source
     * is SMCLK
     *
     * interrupt_period = (ticks/timer_clock_freq)
     * hence for a period of 0.5 second no of ticks = 62500
     */

     
     TACTL= TASSEL_2 + ID_3 + TACLR;    //SMCLJK + divide by 8 + clear counter
     TACCTL0=CCIE;              //enable timer
     TACCR0=62500;              //set timer interrupt count
     //note timer is not enabled. timer will be enabled in button interrupt routine

    _enable_interrupt();
    for (;;)
    {
        if (num!=pnum){
        itoa(num, buffer, 10);
        TX(buffer);
        TX("\r\n");
        pnum=num;
        }
    }
}

void pattern_match(){
 /* Transition sequence is as follows (Active High Config)
  *         --Clockwise--  
  * CH_A    ->  H L L H H
  * CH_B    ->  H H L L H
  *     ---Counter-Clockwise---
  * CH_A    ->  H H L L H
  * CH_B    ->  H L L H H
  *
  * If the above pattern is stored in a 2 bit array with CH_A as Bit 0 and CH_B as Bit 1
  * The value shall be as follows (integer)
  * CW      -> 3-2-0-1-3
  * CCW     -> 3-1-0-2-3
  */

    int cw[]={3,2,0,1,3};
    int ccw[]={3,1,0,2,3};
    int i;
    for (i=1;i<5;i++){
        if (pval==cw[i-1] && cval==cw[i])
        {
            if (tr){tr_state++;} else (tr=1);
            if (tr_state>=5)
            {
                num=num+inc_fact;
                tr_state=0;                                            
            }
           
        }
    }
       
    for (i=1;i<5;i++){
        if (pval==ccw[i-1] && cval==ccw[i])
        {
            if (!tr){tr_state++;} else (tr=0);
            if (tr_state>=5)
            {
                num=num-inc_fact;              
                tr_state=0;
            }
           
        }
    }
}
static char *i2a(unsigned i, char *a, unsigned r)
{
    if (i/r > 0) a = i2a(i/r,a,r);
    *a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i%r];
    return a+1;
}

char *itoa(int i, char *a, int r)
{
    if ((r < 2) || (r > 36)) r = 10;
    if (i < 0)
    {
        *a = '-';
        *i2a(-(unsigned)i,a+1,r) = 0;
    }
    else *i2a(i,a,r) = 0;
    return a;
}

void TX(char *tx_message)
{
    unsigned int i=0; //Define end of string loop int
    char *message; // message variable
    unsigned int message_num; // define ascii int version variable
    message = tx_message; // move tx_message into message
    while(1)
    {
        if(message[i]==0) // If end of input string is reached, break loop.
        {break;}
        message_num = (int)message[i]; //Cast string char into a int variable
        UCA0TXBUF = message_num; // write INT to TX buffer
        i++; // increase string index
        __delay_cycles(10000); //transmission delay
        if(i>50) //prevent infinite transmit
        {
            P1OUT |= LED1;
            break;  
        }
    } // End TX Main While Loop
} // End TX Function

// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
    pval=cval;      //store current_value
//disable all interrupts
    P2IE&=~(CH_A + CH_B + BUTTON);
//check interrupt flags to find which interrupt was triggred
   
    //Check for CH_A interrupt
    if ((P2IFG & CH_A)==CH_A)
    {
        if ((P2IN & CH_A)==CH_A) {a=1;}else {a=0;}  //read pin state
        if ((P2IN & CH_B)==CH_B) {b=1;}else {b=0;}      //read pin state
        cval=(a << 0 ) | (b <<1);              
        P2IES^=CH_A;                    //toggle interrupt edge
           
    }
   
    //Check for CH_B interrupt
    if ((P2IFG & CH_B)==CH_B)
    {
        if ((P2IN & CH_A)==CH_A) {a=1;}else {a=0;}  //read pin state
        if ((P2IN & CH_B)==CH_B) {b=1;}else {b=0;}      //read pin state
        cval=(a << 0 ) | (b <<1);
        P2IES^=CH_B;    //toggle interrupt edge
       
    }
   
   
   
   
    //Check for Buttton interrupt also caters for double click
    if ((P2IFG & BUTTON)==BUTTON)
    {
        click++;   
        if (click>=2)
        {
            click=0;
            dblclk=true;    //set flag true, has to be set false once action is taken on flag
            P1OUT ^= (LED0+LED1);   //toggle both leds
        }      
        TACTL |= MC_1;          //enable timer
       
    }
    pattern_match();
    //clear all interrupts
    P2IFG&=~(CH_A + CH_B + BUTTON);
    //enable all interrupts
    P2IE|=(CH_A + CH_B + BUTTON);

 }
 

#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer0(void)
{
    if (click==1){sclick=true;P1OUT ^= LED0;}
    TACTL |= MC_0;              //disable timer
    click=0;
   
   
}

You can download the project files here or source c file

Ishan Karve

About Ishan Karve

Ishan Karve is just an every day normal guy next door who happens to be an Electronics Engineer by profession and dabbles with PHP, Javascript, C++ and python. His interests vary as seasons change.. they change from astronomy to soul searching. This site is just a reflection of what he does to keep his mind engaged when he is not occupied by work and family. He is an extremely objective guy and is always ready for some good arguments.. of course over a glass of 40% proof alcohol.
This entry was posted in Default. Bookmark the permalink.

7 Responses to Fun with Interrupts : Decoding Quadrature Encoders on TI Launchpad MSP-EXP430G2

  1. Don Varela says:

    Thanks for the program! I connected it up to a AMT102V encoder. Used the USB power from TP1 (5V) on the Launchpad to power the encoder. Ran the A and B signal through a H11L2, which I connected the VCC 3.2v of the Launchpad on the output. Wish the Launchpad would use 5V logic. The encoder was set to 250 ppr. The output to the terminal would count correctly if I turned it real slow. I think its counting the pulses OK, just not updating the terminal. I plan on using some encoders from a PS2/USB mouse that has the ball in it. There are three (3) encoders in them and they are quads. Going to try and control an OWI-535 Robot arm with it. I am confused about the COMM connected to P1.5. I didn’t use a button, is this connection associated with that?
    Thanks again for the program!!!

    • Ishan Karve Ishan Karve says:

      You are most welcome.. Yes your observation is correct regarding the slack response of the pgme if you turn the encoder very fast.. COMM pin is actually the common pin on the encoder. it can be connected to GND or VCC.. depends on how you set up your interrupts.. positive or negative edge trigger.

      Thanks for passing by.

  2. peter says:

    hey, thanks for the code! Btw, my team and i am planing to decode two encoders. Can you give some advice on how to implement it?

  3. indu says:

    Hi,
    I am trying to decoder 2 encoders, I tried to include another 2 pins into the interrupt, but it does not respond. Could provide me some advice on this??
    Thanks!

    • Ishan Karve Ishan Karve says:

      PSE check following things
      Pins are configured for handling interrupt.
      U will have to do some changes in the interrupt handling routine especially in the bit masking if u are using the same port.
      If u are using different ports then ur timers would need to be configured accordingly.

      Is it possible for u to share our relevant part of our code

      • indu says:

        Hi,
        Thank you for your reply. The code we used is given below:

        #include
        #include
        //Defines for Launchpad LEDs
        #define LED1 BIT6
        #define LED0 BIT0
        //define for quarature encoder 1)Left 2)Right
        #define CH_B1 BIT0 //P2.0 Encoder Pin
        #define CH_A1 BIT2 //P2.2 Encoder Pin
        #define CH_B2 BIT3 //P2.3 Encoder Pin
        #define CH_A2 BIT5 //P2.5 Encoder Pin
        //function declaration
        void pattern_match(); //determine direction of rotation
        void ADC_init(void);

        //variable declaration
        char buffer[5];
        int a=0; //variable stores interrupt edge
        int b=0; //variable stores interrupt edge
        int cval_right=3; //store current value to determine rotation
        int pval_right=3; //stores previous value to determine rotation direction
        int cval_left=3; //store current value to determine rotation
        int pval_left=3; //stores previous value to determine rotation direction
        int tr_state_left=0; //variable to store transition state for left
        int tr_state_right=0; //variable to store transition state for right
        int num1=0; //arbitary number. will increment or decrement if tr_satate=4
        int num2=0; //arbitary number. will increment or decrement if tr_satate=4
        int tr_left; //holds trigget state for variable tr_state left
        int tr_right; //holds trigget state for variable tr_state right
        int inc_fact=1; //this variable holds the increment factor
        volatile int left_counter = 0, right_counter = 0;

        void main(){
        //stop watchdog
        WDTCTL = WDTPW + WDTHOLD;

        //setup internal DCO
        BCSCTL1 = CALBC1_1MHZ; // Set DCO to 1MHz
        DCOCTL = CALDCO_1MHZ;

        //Setup output ports
        P1DIR |= (LED0+LED1);
        P1OUT &=~(LED0+LED1);

        //setup interrupts for quadrature encoder
        P2DIR&= ~(CH_A1 + CH_B1 + CH_A2 + CH_B2); //set ports as inputs
        P2IE |= (CH_A1 + CH_B1 + CH_A2 + CH_B2); //enable interrupts for Quad Encoder

        //active-high configuration
        P2REN |=(CH_A1 + CH_B1 + CH_A2 + CH_B2 ); //enable pullups on respective ports
        P2OUT |=(CH_A1 + CH_B1 + CH_A2 + CH_B2 );

        P2IFG&=~(CH_A1 + CH_B1 + CH_A2 + CH_B2 ); //clear interrupt flag
        P2IES&=~(CH_A1 + CH_B1 + CH_A2 + CH_B2 ); //Interrupt on Hi->Lo transition
        }

        void pattern_match(){
        /* Transition sequence is as follows (Active High Config)
        * –Clockwise–
        * CH_A -> H L L H H
        * CH_B -> H H L L H
        * —Counter-Clockwise—
        * CH_A -> H H L L H
        * CH_B -> H L L H H
        *
        * If the above pattern is stored in a 2 bit array with CH_A as Bit 0 and CH_B as Bit 1
        * The value shall be as follows (integer)
        * CW -> 3-2-0-1-3
        * CCW -> 3-1-0-2-3
        */
        int cw[]={3,2,0,1,3};
        int ccw[]={3,1,0,2,3};
        int i;
        for (i=1;i=5)
        {
        num1=num1+inc_fact;
        tr_state_left=0;
        }
        left_counter++;

        }
        }

        for (i=1;i=5)
        {
        num1=num1-inc_fact;
        tr_state_left=0;
        }
        left_counter–;

        }
        }
        for (i=1;i=5)
        {
        num2=num2+inc_fact;
        tr_state_right=0;
        }
        right_counter++;

        }
        }

        for (i=1;i=5)
        {
        num2=num2-inc_fact;
        tr_state_right=0;
        }
        right_counter–;

        }
        }
        }

        // Port 2 interrupt service routine
        #pragma vector=PORT2_VECTOR
        __interrupt void Port_2(void)
        {
        pval_right=cval_right; //store current_value right encoder
        pval_left=cval_left; //store current_value left encoder

        //disable all interrupts
        P2IE&=~(CH_A1 + CH_B1 + CH_A2 + CH_B2 );
        //check interrupt flags to find which interrupt was triggred

        //Check for CH_A1 interrupt
        if ((P2IFG & CH_A1)==CH_A1)
        {
        if ((P2IN & CH_A1)==CH_A1) {a=1;}else {a=0;} //read pin state
        if ((P2IN & CH_B1)==CH_B1) {b=1;}else {b=0;} //read pin state
        cval_left=(a << 0 ) | (b <<1);
        P2IES^=CH_A1; //toggle interrupt edge

        }

        //Check for CH_B1 interrupt
        if ((P2IFG & CH_B1)==CH_B1)
        {
        if ((P2IN & CH_A1)==CH_A1) {a=1;}else {a=0;} //read pin state
        if ((P2IN & CH_B1)==CH_B1) {b=1;}else {b=0;} //read pin state
        cval_left=(a << 0 ) | (b <<1);
        P2IES^=CH_B1; //toggle interrupt edge

        }
        //Check for CH_A2 interrupt
        if ((P2IFG & CH_A2)==CH_A2)
        {
        if ((P2IN & CH_A2)==CH_A2) {a=1;}else {a=0;} //read pin state
        if ((P2IN & CH_B2)==CH_B2) {b=1;}else {b=0;} //read pin state
        cval_right=(a << 0 ) | (b <<1);
        P2IES^=CH_A2; //toggle interrupt edge

        }

        //Check for CH_B2 interrupt
        if ((P2IFG & CH_B2)==CH_B2)
        {
        if ((P2IN & CH_A2)==CH_A2) {a=1;}else {a=0;} //read pin state
        if ((P2IN & CH_B2)==CH_B2) {b=1;}else {b=0;} //read pin state
        cval_right=(a << 0 ) | (b <<1);
        P2IES^=CH_B2; //toggle interrupt edge

        }
        pattern_match();
        //clear all interrupts
        P2IFG&=~(CH_A1 + CH_B1 + CH_A2 + CH_B2);
        //enable all interrupts
        P2IE|=(CH_A1 + CH_B1 + CH_A2 + CH_B2);

        }

        When we run this code, the variables left_counter, right_counter does not change.
        Please advice us on this matter.

        Thanks.

  4. Fernando Dias says:

    Hi

    I did not understand the use of variable tr
    if (tr){tr_state++;} else (tr=1);
    I can not see it to be initialize.

    Many tanks in advance.
    Fernando Dias , UK

Leave a Reply

Your email address will not be published. Required fields are marked *