kenemon 29 Posted July 21, 2011 Share Posted July 21, 2011 Hi All, have any of you peeps tried to hook up a rotary encoder to the LP? I picked up a few cheap ones with the intent of using them to make a wind direction indicator. Thinking analogically I figured I could work with the msp430, and use them similar to a potentiometer or joystick. I would use the resistance "zones" with the LP using the ADC or comparitor. I dissected one of the encoders, and removed the spring so it would rotate more freely. Hoping it still would allow the contacts to function, and noticed the contacts were staggered copper pads. This jogged my memory, reminding me this would be a more troublesome piece of code. Correct me if i am wrong please. looking at the spec sheets, the device is normally prefaced with a debounce chip. I figured this could be handled just as easy with software. I understand the switch creates square wave pulses on two pins. By comparing the timing of the waves and the quantity, the relative change in the position of the post is estimated. I am wondering If this "estimation" can be relied upon to judge the rotational position in a consistent manner. Do these type of devices suffer from drift and calibration issues? thanks in advance for reading Quote Link to post Share on other sites
oPossum 1,083 Posted July 21, 2011 Share Posted July 21, 2011 Quadrature or gray encoder? Quote Link to post Share on other sites
kenemon 29 Posted July 21, 2011 Author Share Posted July 21, 2011 quadrature Quote Link to post Share on other sites
bluehash 1,581 Posted July 21, 2011 Share Posted July 21, 2011 You will have relative position by counting one of the pulses. Absolute positioning can be achieved by keeping the last counts in memory. The only error you will have is the size of the copper pads... Direction can be detected by looking at the two pulses and see which comes first - A or B. kenemon 1 Quote Link to post Share on other sites
zeke 693 Posted July 21, 2011 Share Posted July 21, 2011 This is the code I used to test out the Bourns PEC11L-4220F-S0015 rotary encoder /* Rotary Encoder example Simple program to test a PEC11L-4220F-S0015 rotary encoder with pushbutton. Compiled using Code Composer Studio V4 Setup: +----------------+ LED -- | P1.0 P2.6 | -- NC LED -- | P1.1 P2.7 | -- Push Button LED -- | P1.2 | LED -- | P1.3 | LED -- | P1.4 P1.7 | -- Encoder B LED -- | P1.5 P1.6 | -- Encoder A +----------------+ Function: * Every time the encoder is pushed the LED on P1.0 will be toggled * The LED on P1.1 to P1.5 will move left/right according to the encoder * The script uses the internal pullup resistors */ #include #define CLI() _disable_interrupts() #define SEI() _enable_interrupts() #define dint() _disable_interrupts() #define eint() _enable_interrupts() unsigned char encoder_pos = BIT3; // Set starting pos to P1.3 unsigned char push_pos = 0; #pragma vector=PORT2_VECTOR __interrupt void p1_encoder_int(void) { dint(); if( P2IFG & 0x80 ) { push_pos ^= BIT0; P1OUT = BIT7 | BIT6 | encoder_pos | push_pos; // output P1 with push status and encoder pos } P2IFG = 0x00; eint(); } #pragma vector=PORT1_VECTOR __interrupt void p1_encoder_move(void) { dint(); if( P1IFG & 0x40 ) { if ( P1IN & BIT7 ) { // b is high -> counter clockwise if ( encoder_pos > BIT1 ) { // only move down if not already at bottom encoder_pos = encoder_pos >> 1; } } else { // b is low -> clockwise if ( encoder_pos < BIT5 ) { encoder_pos = encoder_pos << 1; } } P1OUT = BIT7 | BIT6 | encoder_pos | push_pos; // output P1 with push status and encoder pos } P1IFG = 0x00; eint(); } int main(void) { WDTCTL = WDTPW | WDTHOLD; // Setup port P1SEL = 0x00; // select io function for all ports (this is actually default for P1) P1REN = 0xC0; // select pullup resistors for pin 1.7 and 1.6 P1DIR = 0x3F; // iioo oooo P1OUT = 0xC8; // need to set P1.6 and P1.7 to high so PULLUP resistor will be selected P1IES = 0x00; // select low to high transition for interrupts on all port pins (this actually the default) P1IE = 0x40; // enable interrupt for P1.6 to reader status of encoder every time Encoder sigal A goes high P1IFG = 0x00; // clear interrupt register P2SEL = 0x00; // selection io function for all ports (default is actually XIN.XOUT for P2.7/2.6) P2REN = 0x80; // select pullup resistors for pin 2.7 P2DIR = 0x7F; // iooo oooo P2OUT = 0x80; // need to set P2.7 to high so PULLUP resistor will be selected P2IES = 0x00; // select low to high transition for interrupts on all port pins P2IE = 0x80; // enable interrupt P2.7 for push button P2IFG = 0x00; // clear interrupt register eint(); for (; { // loop forever } } kenemon 1 Quote Link to post Share on other sites
nobody 47 Posted July 21, 2011 Share Posted July 21, 2011 My version (based on msp430f2274 but portable to other series...) Advace: - It is controlled by rising and falling edge of signal. - Simplified by using the lookup table (look here). (it's only test code - not optimized yet...) I know - it is not necessary to test switch thru interrupts - but this is preparation to another extensions (measure time of pushing, recognize long and short click, ...) #include "io430.h" #include "font5x7.h" #include "stdio.h" #include "intrinsics.h" #include "putchar.c" #define ENC_A BIT6 // P2.6 encoder A #define ENC_B BIT7 // P2.7 encoder B #define ENC_SHIFT 6 // Encoder testing with (P2IN >> 6) #define ENC_SW BIT5 // P2.5 encoder switch volatile int Encoder_Value = 0; // Encoder value volatile unsigned char Encoder_SW = 0; // Encoder switch value push = 1, free = 0; int main( void ) { // int n, i; WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer to prevent time out reset BCSCTL1 = CALBC1_8MHZ; // Set DCO to 8MHz DCOCTL = CALDCO_8MHZ; // (don't matter for this demo) // **Initialize system for encoder** P2SEL &= ~(ENC_A + ENC_B + ENC_SW); // Set back (XOUT/XIN) and Rosc to port function P2DIR &= ~(ENC_A + ENC_B + ENC_SW); // Input function P2REN |= ENC_A + ENC_B + ENC_SW; // Enable resistor P2OUT |= ENC_A + ENC_B + ENC_SW; // Pullup resistor P2IES |= ENC_A + ENC_B + ENC_SW; // Interrupt when 1 -> 0 (dynamically changed!!!!) P2IE |= ENC_A + ENC_B + ENC_SW; // Interrupt enable __enable_interrupt(); while(1) { if(Encoder_SW) Encoder_Value = 0; printf("Encoder=%d, \f", Encoder_Value); // I implemented my version of "putchar.c", // so I can use printf to an LCD display } } #pragma vector = PORT2_VECTOR __interrupt void Port2_Interrupt(void) { const signed char table[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; static unsigned char ab = 0; // the old value of the sensor ports // **switch test** if(P2IFG & ENC_SW) { P2IFG &= ~ENC_SW; if(P2IN & ENC_SW) // switch released { Encoder_SW = 0; P2IES |= ENC_SW; // Interrupt when 1 -> 0 } else // switch pushed { Encoder_SW = 1; P2IES &= ~ENC_SW; // Interrupt when 0 -> 1 } } // **encoder test** if(P2IFG & (ENC_A + ENC_) { P2IFG &= ~(ENC_A + ENC_; ab = ab << 2; // move the old data left two places ab |= ((P2IN >> ENC_SHIFT) & 0x03); // OR in the two new bits Encoder_Value += table[(ab & 0x0F)]; // get the change from the 16 entry table if(Encoder_Value > 32760) // Overflow? Encoder_Value = 32760; if(Encoder_Value < -32760) // Underflow? Encoder_Value = -32760; if(P2IN & ENC_A) // 1 P2IES |= ENC_A; // Interrupt when 1 -> 0 else // 0 P2IES &= ~ENC_A; // Interrupt when 0 -> 1 if(P2IN & ENC_ // 1 P2IES |= ENC_B; // Interrupt when 1 -> 0 else // 0 P2IES &= ~ENC_B; // Interrupt when 0 -> 1 } } kenemon 1 Quote Link to post Share on other sites
Mac 67 Posted July 21, 2011 Share Posted July 21, 2011 This is the code I used to test out the Bourns PEC11L-4220F-S0015 rotary encoder With all four AB transition states between detent positions on that PEC11 encoder, you've come up with simplest and most elegant solution I've seen. kenemon 1 Quote Link to post Share on other sites
RobG 1,892 Posted July 21, 2011 Share Posted July 21, 2011 I don't think this code handles all transitions, just one out of four for each direction. How are you handling denounce zeke? That's one thing that is driving me nuts with my encoder and will affect your code. Quote Link to post Share on other sites
zeke 693 Posted July 22, 2011 Share Posted July 22, 2011 I don't think this code handles all transitions, just one out of four for each direction. How are you handling denounce zeke? That's one thing that is driving me nuts with my encoder and will affect your code. To be honest, I'm not debouncing anything yet. It's on my todo list. For the push button, I was going to simplify things with an RC because I'm so dang tired of coding all the negative path combos that may happen. For the AB inputs, I'm not sure how I might debounce that. One way I've done it in the past is to create a four bit sampler. Whether that is polled or interrupt driven depends on how it might be utilized in the code. Polled is more laid back and cool about it. Interrupt driven is all hyperactive and ADHD-like approach. Until I decide how much work I'm going to do, I'm just turning the digital POT slower Quote Link to post Share on other sites
nobody 47 Posted July 22, 2011 Share Posted July 22, 2011 With all four AB transition states between detent positions on that PEC11 encoder, you've come up with simplest and most elegant solution I've seen. My code (above) do it. For the AB inputs, I'm not sure how I might debounce that. For this I also use RC debouncing. It's work perfectly. A----[ 4k7 ]---------> (internal pullup in MSP430) | | = 10nF | | --- B----[ 4k7 ]---------> (internal pullup in MSP430) | | = 10nF | | --- SW-------------------> (internal pullup in MSP430) | | = 100nF | | --- kenemon and zeke 2 Quote Link to post Share on other sites
Mac 67 Posted July 22, 2011 Share Posted July 22, 2011 That method looks pretty good too. Another method uses quadrature direction = new A ^ old B = ~(new B ^ old A). I've used this method in the past. I would poll the encoder at some debounce interval and simply set "new press" switch flags (up, down, or left, right, etc.) based on quadrature direction. unsigned char newenc = 0; unsigned char oldenc = 0; unsigned char swflag = 0; #define old_A (oldenc & BIT7) #define new_B (newenc & BIT6) newenc = P1IN & 0xC0; // newenc = AB------ if(newenc != oldenc) // if changed { if(old_A ^ new_ // direction, 0 or 1 swflag |= sw1mask; // sw1 "new press" bit else // else swflag |= sw2mask; // sw2 "new press" bit oldenc = newenc; // update encoder latch } If I was using the PEC11 encoder with detents for a control, I would use an additional qualifier in my code to ignore all but the last transition into the detent position (new AB = 00 or 11, depending on code). It was kind of neat in that I could gently move the encoder back and forth between the detents and nothing would happen until I finally let the encoder fall into either detent position. Regards, Mike oPossum and kenemon 2 Quote Link to post Share on other sites
RobG 1,892 Posted July 22, 2011 Share Posted July 22, 2011 Here's my implementation. To cover all transitions, I am using pairs of pins with opposite edge detection. I am using external pullups and caps for denouncing since doing it in software would be very tricky and not reliable. My encoder is PEC11-4215K-S0024 and as Mike pointed it out, with detents it is hard to get single steps. After removing detent plate from my encoder, it works perfectly. #include "msp430g2231.h" #define A1PIN BIT1 #define A2PIN BIT2 #define B1PIN BIT6 #define B2PIN BIT7 #define EINT P1IE |= A1PIN|A2PIN; P2IE |= B1PIN|B2PIN #define DINT P1IE &= ~(A1PIN|A2PIN); P2IE &= ~(B1PIN|B2PIN) #define CLRPFG P1IFG &= ~(A1PIN|A2PIN); P2IFG &= ~(B1PIN|B2PIN) int counter = 0; void main(void) { WDTCTL = WDTPW + WDTHOLD; BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P2SEL &= ~(B1PIN|B2PIN); P2DIR = 0; P1DIR &= ~(A1PIN|A2PIN); P1IES &= ~A1PIN; P1IES |= A2PIN; P2IES &= ~B1PIN; P2IES |= B2PIN; CLRPFG; _bis_SR_register(GIE); EINT; while(1) { // do whatever _bis_SR_register(LPM0_bits); // sleep } } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { DINT; if((P1IFG & A1PIN) ^ (P2IN & B1PIN)) { counter--; } else { counter++; } CLRPFG; EINT; _bic_SR_register_on_exit(LPM0_bits); } // Port 2 interrupt service routine #pragma vector=PORT2_VECTOR __interrupt void Port_2(void) { DINT; if((P2IFG & B1PIN) ^ (P1IN & A1PIN)) { counter++; } else { counter--; } CLRPFG; EINT; _bic_SR_register_on_exit(LPM0_bits); } PEC11 +-------------------+ +-----+ | MSP430G2231 | | | | P2.6+-----+-----+---------+---------+ A | | | | | | | | | | | | +-+ | | | P2.7+-----+ |100n | | | | | | +---+ | |1K | | | | +---+ +-+ | | | | | | | | | | | GND | GND | | | | +---+ | +---+ C | | | | +---+ | | | | |100n | Vcc | | | | +---+ +-+ | | | | +---+ | | | | | P1.1+-----+ | | |1K | | | | | | +-+ | | | | | | | | | | P1.2+-----+-----+---------+---------+ B | | | | | +-------------------+ +-----+ And here's the code which includes LCD display: #include "msp430g2231.h" #include "adc_lcd.h" #define A1PIN BIT1 #define A2PIN BIT2 #define B1PIN BIT6 #define B2PIN BIT7 #define EINT P1IE |= A1PIN|A2PIN; P2IE |= B1PIN|B2PIN #define DINT P1IE &= ~(A1PIN|A2PIN); P2IE &= ~(B1PIN|B2PIN) #define CLRPFG P1IFG &= ~(A1PIN|A2PIN); P2IFG &= ~(B1PIN|B2PIN) void binaryToASCIIScale(unsigned int n, unsigned char * digits, unsigned int scale); // asm prototype void sendChars(unsigned char chars[], unsigned char length); // asm prototype void send(unsigned char data, unsigned char registerSelect); // asm prototype unsigned char d[5] = {0,0,0,0,0}; void sendDataArray(unsigned char data[], char length); char charIndex = 0; char valueIndex = 0; int counter = 2000; void main(void) { WDTCTL = WDTPW + WDTHOLD; BCSCTL1 = CALBC1_1MHZ; DCOCTL = CALDCO_1MHZ; P1OUT &= ~(CLOCKPIN + DATAPIN); P1OUT |= ENABLEPIN; P1DIR |= ENABLEPIN + CLOCKPIN + DATAPIN; P2SEL &= ~(B1PIN|B2PIN); P2DIR = 0; P1DIR &= ~(A1PIN|A2PIN); P1IES &= ~A1PIN; P1IES |= A2PIN; P2IES &= ~B1PIN; P2IES |= B2PIN; CLRPFG; initDisplay(); _bis_SR_register(GIE); EINT; while(1) { sendInstruction(0x80); binaryToASCIIScale(counter, d, 0); sendChars(d, 5); _bis_SR_register(LPM0_bits); // sleep } } // Port 1 interrupt service routine #pragma vector=PORT1_VECTOR __interrupt void Port_1(void) { DINT; if((P1IFG & A1PIN) ^ (P2IN & B1PIN)) { counter--; } else { counter++; } CLRPFG; EINT; _bic_SR_register_on_exit(LPM0_bits); } // Port 2 interrupt service routine #pragma vector=PORT2_VECTOR __interrupt void Port_2(void) { DINT; if((P2IFG & B1PIN) ^ (P1IN & A1PIN)) { counter++; } else { counter--; } CLRPFG; EINT; _bic_SR_register_on_exit(LPM0_bits); } zeke and kenemon 2 Quote Link to post Share on other sites
zeke 693 Posted July 22, 2011 Share Posted July 22, 2011 I didn't know if RC's could be put on the AB lines because I didn't do the timing calcs yet which would answer the question "how fast can a human make AB transition?" tau = RC = 1000 * 0.0000001 = 0.0001 seconds or 100 microseconds. In my understanding, a human reaction time is about 300 milliseconds so, Yup, that RC is fast enough. Depending on the speed of the processor and it's resultant ISR servicing times, you may be able to optimize the RC more. RobG, you've managed to put an great hardware safety guard on your C code Excellent! Quote Link to post Share on other sites
zeke 693 Posted July 23, 2011 Share Posted July 23, 2011 This is the code I used to test out the Bourns PEC11L-4220F-S0015 rotary encoder With all four AB transition states between detent positions on that PEC11 encoder, you've come up with simplest and most elegant solution I've seen. @Mac: Please don't heap praise on me. I stand on the shoulders of others on this topic. In this case, I located the sample code on this blog entry. I only adapted it to CSS from mspgcc. I would have posted the author's name but it isn't there. Everything is posted by the username "admin". Not very helpful in giving attribution. But, then again, if whois searches can be trusted then the author is Michael Epple. I hope he doesn't mind me posting his name. If not, I'll remove it when he requests it. Quote Link to post Share on other sites
kenemon 29 Posted July 23, 2011 Author Share Posted July 23, 2011 Hey RobG, can you elaborate a bit on the LCD? where can i find that include, and how are you wiring it up please. thanks Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.