|
|
View previous topic :: View next topic |
Author |
Message |
Sergeant82d
Joined: 01 Nov 2009 Posts: 55 Location: Central Oklahoma
|
Found a great rotary encoder routine |
Posted: Wed Dec 30, 2009 10:38 pm |
|
|
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
|
Got it fixed... |
Posted: Thu Dec 31, 2009 11:38 am |
|
|
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
|
|
Posted: Fri Jan 29, 2010 4:42 pm |
|
|
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
|
|
Posted: Thu Mar 11, 2010 2:13 am |
|
|
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
|
|
Posted: Thu Mar 11, 2010 6:21 am |
|
|
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
|
|
Posted: Thu Mar 11, 2010 6:36 am |
|
|
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
|
|
Posted: Fri Mar 12, 2010 4:24 pm |
|
|
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 |
|
|
|
|
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
|