Jump to content
Sign in to follow this  
kenemon

Rotary Encoder and the LP

Recommended Posts

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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
   }

}

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
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 :lol:

Share this post


Link to post
Share on other sites
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
             |
             |
            ---      

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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!

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...