CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Found a great rotary encoder routine

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Sergeant82d



Joined: 01 Nov 2009
Posts: 55
Location: Central Oklahoma

View user's profile Send private message

Found a great rotary encoder routine
PostPosted: Wed Dec 30, 2009 10:38 pm     Reply with quote

Following a link on another web site, I found this site: http://www.circuitsathome.com/mcu/programming/vigorius-stirring-redefined-part-2-electronics

Oleg over there has developed what must be the most elegant routine for reading a quadrature encoder ever written... It was written for the Arduino, but I was able to port it over to CCS C, and I present it here for discussion and a bit of assistance.

Code:


#include <18F4620.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP,BROWNOUT,PUT,NOPBADEN,DEBUG
#use delay(clock=20M)      //Inex NX-877 Plus II Development Board
#use rs232(baud=115200,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#include<Flex_LCD_2x20.c>

signed char enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
int16 old_AB = 0;
int16 enc_value = 0;
int16 old_enc_value = 0;

void encoder0Loop(void)
{
   for(enc_value >= 0;;)         // Keep the number positive
   {
      old_AB <<= 2;                                   // Remember previous state by shifting the lower bits up
      old_AB |= ( input_b() & 0x03 );                 // AND the lower 2 bits of port b,
                                                      // then OR them with var old_AB to set new value
      enc_value += enc_states[( old_AB & 0x0f )];     // the lower 4 bits of old_AB are
                                                      // then the index for enc_states

      if ( enc_value == old_enc_value )
      {
      return;
      }

      if( enc_value >= 30 )   // Arbitrary max value for testing purposes
      {
         enc_value = 30;
      }

      printf("Encoder Value = %3ld\n\r",enc_value);
      printf(lcd_putc,"\fEncoder 0 = %ld\n",enc_value);
      old_enc_value = enc_value;
      // set whatever variable you need to increment to enc_value here...

   }        // End of For
}           // End of encoder0Loop

////////////////////////////////////////////////////////////////////////////////////////////

#int_TIMER0
void timer0_isr()
   {
      output_toggle(PIN_A2);     // let me know it's alive...
//      T0_flag_1 = 1;          // Set a flag
//      T0_flag_2++;            // Increment a counter
   }

////////////////////////////////////////////////////////////////////////////////////////////


void main(void)
{
   port_b_pullups(true);
   setup_adc_ports(NO_ANALOGS);
   ENABLE_INTERRUPTS(global);
   ENABLE_INTERRUPTS(INT_TIMER0);
   SETUP_TIMER_0(RTCC_INTERNAL | RTCC_DIV_32);

   printf("\n\n\rRotary Encoder Test 2\n\n\r");
   printf(lcd_putc,"\fRotary Encoder\nTest 2");

   while (true)
   {
      encoder0Loop();
   }     // end of while loop

}        // end of main



It works absolutely great - NO debouncing required! NO Interrupts required! You change the pins (for now, on the same port - maybe one of you can update that) by ANDing in the proper value in this line:

Code:

old_AB |= ( input_b() & 0x03 );      // AND the lower 2 bits of port b,
                                     // then OR them with var old_AB to set new value


I have only one problem with it: When it reaches zero (0), it wraps around back to the highest value and continues to decrease from there. I was able to successfully set the max value, and keep the value positive, but I can't figure out how to set a minimum and have it stay there like it does at the maximum. Think volume or speed control knobs.

Of course, if YOU need negative numbers, simply take out the "for" loop line at the top of encoder0Loop.

Brad
Sergeant82d



Joined: 01 Nov 2009
Posts: 55
Location: Central Oklahoma

View user's profile Send private message

Got it fixed...
PostPosted: Thu Dec 31, 2009 11:38 am     Reply with quote

Okay, here is my "final" version of this great routine. It maxes out at whatever value (signed int16 for me) that you set, and goes down to zero when turning the other way. My encoders give four counts per click, so I divide by 4 at the end just to keep my increments aligned with the knob rotation; you may not want that.

I hope you find it useful - I sure do.

Code:

#include <18F4620.h>
#fuses HS,NOWDT,NOPROTECT,NOLVP,BROWNOUT,PUT,NOPBADEN,DEBUG
#use delay(clock=20M)      //Inex NX-877 Plus II Development Board
#use rs232(baud=115200,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
#include<Flex_LCD_2x20.c>

/////////////////////////////Encoder Vars//////////////////////////////////////
signed char enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
int16 enc_position = 0;
signed int16 enc_value = 0;
int16 old_enc_value = 0;

/////////////////////////////Working Vars//////////////////////////////////////
int16 my_enc_val = 0;

/////////////////////////////Encoder Function - Repeat as required/////////////
void encoder0Loop(void)
{
      enc_position <<= 2;                       //remember previous state by shifting the lower bits up 2
      enc_position |= ( input_b() & 0x03 );     // AND the lower 2 bits of port b, then OR them with var old_AB to set new value
      enc_value += enc_states[( enc_position & 0x0f )];     // the lower 4 bits of old_AB & 16 are then the index for enc_states
     
      if ( enc_value == old_enc_value ) {
      return;
      }
         if( enc_value <= 0 ) {
         enc_value = 0;
      }

         if( enc_value >= 400 ) {               // Arbitrary max value for testing purposes
         enc_value = 400;
      }
      my_enc_val = enc_value/4;                 // This is the value you will pass to whatever needs the encoder data - change as required
      printf("Encoder 0 = %4ld\n\rValue = %3ld%%\n\n\r",enc_value,my_enc_val);
      printf(lcd_putc,"\fEncoder 0 = %ld\nValue = %3ld%%\n",enc_value,my_enc_val);
      old_enc_value = enc_value;


}           // End of encoder0Loop
////////////////////////////////////////////////////////////////////////////////////////////

#int_TIMER0
void timer0_isr()
   {
      output_toggle(PIN_A2);     // let me know it's alive...
//      T0_flag_1 = 1;          // Set a flag
//      T0_flag_2++;            // Increment a counter
   }

////////////////////////////////////////////////////////////////////////////////////////////


void main(void)
{
   port_b_pullups(true);
   setup_adc_ports(NO_ANALOGS);
   ENABLE_INTERRUPTS(global);
   ENABLE_INTERRUPTS(INT_TIMER0);
   SETUP_TIMER_0(RTCC_INTERNAL | RTCC_DIV_32);

   printf("\n\n\rRotary Encoder Test 2\n\n\r");
   printf(lcd_putc,"\fRotary Encoder\nTest 2");

   while (true)
   {
      encoder0Loop();
   }     // end of while loop

}        // end of main


Brad
Jaimearctico
Guest







PostPosted: Fri Jan 29, 2010 4:42 pm     Reply with quote

Hi Brad...

I have a question, how does the routine recognize the state of the pin?
I don't really understand this line:
Code:

enc_value += enc_states[( enc_position & 0x0f )]

I mean, I expect something like with an "If" asking about the state of the pin.

Thanks.
slipknotcc



Joined: 13 Feb 2010
Posts: 8

View user's profile Send private message

PostPosted: Thu Mar 11, 2010 2:13 am     Reply with quote

Hey,
Your code worked great but I have a few questions. Here's my code:

Code:

#include <16f877a.h>
#device ICD = TRUE
#fuses HS,NOWDT,NOLVP
#use delay(clock=20000000)     

#include "flex_LCD.c"

/////////////////////////////Encoder Vars//////////////////////////////////////
signed char enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
int16 enc_position = 0;
signed int16 enc_value = 0;
int16 old_enc_value = 0;

/////////////////////////////Working Vars//////////////////////////////////////
 

/////////////////////////////Encoder Function - Repeat as required/////////////
void encoder0Loop(void)
{
      enc_position <<= 2;                       //remember previous state by shifting the lower bits up 2
      enc_position |= ( input_e() & 0x03 );     // AND the lower 2 bits of port b, then OR them with var old_AB to set new value
      enc_value += enc_states[( enc_position & 0x0f )];     // the lower 4 bits of old_AB & 16 are then the index for enc_states
     
      if ( enc_value == old_enc_value ) {
      return;
      }
         if( enc_value <= 0 ) {
         enc_value = 0;
      }

         if( enc_value >= 624 ) {               // Max Value of Encoders
         enc_value = 624;
      }
   
      printf(lcd_putc,"\fEncoder 0 = %u\n",enc_value);
      old_enc_value = enc_value;


}           // End of encoder0Loop
////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////


void main(void)
{
   lcd_init();
   printf(lcd_putc,"\fRotary Encoder\nTest 2");

   while (true)
   {
      encoder0Loop();
   }     // end of while loop

}        // end of main


As you can see, I changed the port to E and I raised the limit to 624. But the real problem is that no matter what I set the limit too it maxes out at 255. Where is my problem?

Thanks,
Cory
jbmiller



Joined: 07 Oct 2006
Posts: 73
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Thu Mar 11, 2010 6:21 am     Reply with quote

I _think_ it's in the line that reads and stores the encoder pins. You might have to 'cast' the result as a 16bit value.I _think_ the compiler sees the ports as 8 bits which maxes out at 255.
I am surely NOT a C expert but it'd be easy to try.

Jay
mindstorm88



Joined: 06 Dec 2006
Posts: 102
Location: Montreal , Canada

View user's profile Send private message

PostPosted: Thu Mar 11, 2010 6:36 am     Reply with quote

try this
Code:

enc_value += (int16)enc_states[( enc_position & 0x0f )];     // the lower 4 bits of old_AB & 16 are then the index for
slipknotcc



Joined: 13 Feb 2010
Posts: 8

View user's profile Send private message

PostPosted: Fri Mar 12, 2010 4:24 pm     Reply with quote

Hey again,

One other problem. When the motor spins in reverse the encoder doesn't go negative. Now I know that this needs to be changed:

Code:

if( enc_value <= 0 ) {
         enc_value = 0;
      }


But I just don't know what to change it to. I know that everything will have to be changed to signed int16. But I just don't know what to do with that if statement.

Thanks again,
Cory
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group