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.
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; } |











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!!!
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.
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?
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!
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
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.