View previous topic :: View next topic |
Author |
Message |
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
Rotary Encoder |
Posted: Fri Sep 09, 2016 2:16 pm |
|
|
PCWH Version 5.061
PIC18F45K20
Continuing on my learning journey with CCS, and programming, and PICs, I am working to get a rotary encoder working on a PIC 44-pin demo board. I have the encoder connected to D4 and D5. I need to get these working on these pins as the final application will use these pins.
I need to be able to count up and down depending on the direction the encoder is spun. Here is my code:
Code: | #include <main.h>
#include <FLEX_LCD.C>
BYTE const sequence[4] = {0,32,48,16};
BYTE seq_index;
BYTE encoder;
BYTE encoder_old;
BYTE error_count;
#int_TIMER0
void timer0_isr()
{
output_toggle(POWER_LED); // let me know it's alive...
}
void main()
{
set_tris_d(0b01110000);
ENABLE_INTERRUPTS(global);
ENABLE_INTERRUPTS(INT_TIMER0);
SETUP_TIMER_0(RTCC_INTERNAL | RTCC_DIV_4);
short output;
long count,max_count;
error_count=0;
lcd_init(); // Always call this first.
lcd_putc("\fREADY....\n");
delay_ms(1000);
lcd_init();
while(1){
seq_index=(seq_index+1)&3;
encoder=input_d() & 0x30;
if (encoder==sequence[seq_index] & sequence[(seq_index-1)&3]==encoder_old ) {
count++;
printf(lcd_putc,"\fCount= %lu\nMax = %lu",count,max_count);
}
if (encoder==sequence[seq_index] & sequence[(seq_index+1)&3]==encoder_old ) {
count--;
printf(lcd_putc,"\fCount= %lu\nMax = %lu",count,max_count);
}
encoder_old=encoder;
}
} |
This sort of works, but it "misses" a lot of the changes. Right now I am running off of the 4MHz internal clock.
I am sure there is a better way for me to do this.... |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Fri Sep 09, 2016 2:35 pm |
|
|
what is the make and model of your encoder?
where are your FUSES ?
what is the circuit composed off -for connecting your encoder to the pic ? |
|
|
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
|
Posted: Fri Sep 09, 2016 4:23 pm |
|
|
Fuses:
Code: | #FUSES NOBROWNOUT //No brownout reset
#FUSES NOPBADEN //PORTB pins are configured as digital I/O on RESET
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST //Extended set extension and Indexed Addressing mode disabled (Legacy mode) |
Circuit has 10K pullup on the A and B outputs.
Encoder is this one from sparkfun:
https://www.sparkfun.com/products/9117 |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9244 Location: Greensville,Ontario
|
|
Posted: Fri Sep 09, 2016 4:44 pm |
|
|
Several 'problems' can account for the missing counts..
1) using the printf() and an LCD module. LCD modules are 'slow' compared to the encoder...so the encoder can move ,while the PIC is updating the LCD module
2) clock speed..yeesh 4 MHz ! WHY so slow !!! It should be at 20MHz or faster unless you have a specific reason to be that s-l-o-w....
3)ideally the encoder 'function' should be in an ISR. That would allow the PIC to update the LCD at a faster rate.
4) signal conditioning on the encoder data pins. That's a mechanical encoder(aka metal switch) and has 'debounce' problems or 'noise'. 10K maybe be to high( I've used 4K7 in the past) as well as a small cap to 'clean up' the signal. Normally you'ld feed that signal into a Schimdt(sp) input gate or opamp with hysterisis to send a CLEAN signal to the PIC. One of those GIGO ( garbage In , garbage Out) situations. Unless you've got good, clean, sharp square waves going into the PIC you can easily get miscounts, errors, etc.
5) speed of encoder rotation. Somewhere in the datasheet , it'll say how fast you can turn the knob. exceed that and you will get miscounts !
just some points to ponder.
Jay |
|
|
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
|
Posted: Fri Sep 09, 2016 5:24 pm |
|
|
temtronic wrote: | Several 'problems' can account for the missing counts..
1) using the printf() and an LCD module. LCD modules are 'slow' compared to the encoder...so the encoder can move, while the PIC is updating the LCD module |
OK is there anything I can do to compensate for the "slow" encoder?
temtronic wrote: | 2) clock speed..yeesh 4 MHz ! WHY so slow !!! It should be at 20MHz or faster unless you have a specific reason to be that s-l-o-w.... |
It is 4MHz since I am currently using the demo board that does not have any crystals on it. Still new to PICs, the datasheet shows it is selectable to 16MHz, how to I set this? Is it just the #use delay line? seems like there would be some other setting.
temtronic wrote: | 3)ideally the encoder 'function' should be in an ISR. That would allow the PIC to update the LCD at a faster rate. |
Can this be done with pins D4 and D5? All the examples seem to use port B.
temtronic wrote: | 4) signal conditioning on the encoder data pins. That's a mechanical encoder(aka metal switch) and has 'debounce' problems or 'noise'. 10K maybe be to high( I've used 4K7 in the past) as well as a small cap to 'clean up' the signal. Normally you'ld feed that signal into a Schimdt(sp) input gate or opamp with hysterisis to send a CLEAN signal to the PIC. One of those GIGO ( garbage In , garbage Out) situations. Unless you've got good, clean, sharp square waves going into the PIC you can easily get miscounts, errors, etc. | Did not have any 4.7K in the "supply" box, so I used 10K since I have alot of them. I will see if I can locate a lower value. If not I will have to order some.
Thanks for all the help! |
|
|
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
|
Posted: Fri Sep 09, 2016 5:37 pm |
|
|
OK got the oscillator setting figured out, have to test it and see if it changed now... |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9244 Location: Greensville,Ontario
|
|
Posted: Fri Sep 09, 2016 6:08 pm |
|
|
that PIC can go up to 64MHz with the correct fuse/clock selection ....some reading is required though..
4k7 is easy... parallel 2 10K resistor and you have 5K, use 3 and you get 3K3.
you really need an oscilloscope to 'play with PICs'. even a 25 year old analog scope works fine, mine has for that long !
Jay |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Sat Sep 10, 2016 6:59 am |
|
|
simple 2 bit code - like yours written for port B -works at 4 mhz fine
using pin B0, B1
Code: |
unsigned int8 old,new,direction=1;
signed int16 clock=0,index=0;
/////////////////////////////////////////////////////////////////
// incremental 2 bit
/////////////////////////////////////////////////////////////////
// iniitialize code for incremental
// usually done in main()
old=input_B()&3;
#INLINE
void handle_incremental(void){
new=input_B()&3;
if(new==old) return;
IF(!NEW){ // at zero index position set direction
if(2==old) { direction=1; ++index; } // fix index count
else { direction=0; --index; }
} // end if (!new)
// adjust total clock tix of encoder
// 4 per index count
if (direction) ++clock;
else --clock;
old=new; // old is the new , new
} |
|
|
|
guy
Joined: 21 Oct 2005 Posts: 297
|
|
Posted: Wed Sep 14, 2016 3:17 pm |
|
|
IMHO if it works but not stable then the biggest problem is debouncing - while moving from one state to the next there may be fluctuations of the previous state. One option, as mentioned, is by adding a resistor+capacitor as low pass filter. Another is to add code that filters the inputs, for example for each input read it and shift into a byte. Only if the whole byte is 1's or 0's treat it as stable.
untested code:
Code: | byte fltrA=255;
byte fltrB=255;
short AA=1;
short BB=1;
while(1) {
fltrA<<=1; // shift left the shift register, put 0 as default new bit
if(input(PIN_D4)) bit_set(fltrA,0); // if input is not 0 set the new bit
if(fltrA==255) AA=1; // filter is 11111111
if(fltrA==0) AA=0; // filter is 00000000
fltrB<<=1; // shift left the shift register, put 0 as default new bit
if(input(PIN_D5)) bit_set(fltrB,0); // if input is not 0 set the new bit
if(fltrB==255) BB=1; // filter is 11111111
if(fltrB==0) BB=0; // filter is 00000000
delay_ms(1); // tweak this so you don't miss counts but do filter out noise
// use AA and BB to determine stable encoder state.
} |
|
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Wed Sep 14, 2016 3:48 pm |
|
|
Quote: | IMHO if it works but not stable then the biggest problem is debouncing |
This would be quite true for some super cheap encoders -except
for the safety overlap of states in all the recent design mechanical encoders i used the original code with. They use optical interruption with Schmidt trigger sensors. They can't glitch.
they are technically called :
"Rotary Encoder- 4 pulse 2-bit gray code"
reading about "gray code" explains why the simple posted code works robustly as is- since no invalid states are mechanically possible -
short of being so broken that there are less than 4 "adjacent" states as the encoder wheel turns
originally i used the code such as below with cheapie non gray -non optical units but abandoned it as being irrelevant after state testing example parts on a 480
rpm gear motorshaft. the extra code was realized to be a waste of space and time.... if worried -this is a simpler fix for your fears
Code: |
void handle_incremental(void){
int8 count=5;
byte temp;
temp=input_B()&3;
if(temp==old) return; // no state change
// ONLY delay if any change seen
DO{ //ok state change recorded
delay_us(100);
temp=input_B()&3;
if(temp==old) return; // was a glitch -get lost
count--; // we only get to here on steady "new value"
}While( count) // re read 5 times in .5 msec -still steady
new=temp; // done wasting time
.......................
|
The Gray code actually follows a very simple state machine, and at any given state, it can only change to one of two other values.
hence the "==2" test ...
Position Bit A Bit B
0 0 0
1/4 1 0
1/2 1 1
3/4 0 1
1 0 0 |
|
|
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
|
Posted: Fri Nov 18, 2016 12:38 pm |
|
|
OK after some time away from this, I am back working on this project. I changed encoders to a Bourns incremental encoder (EMS22Q51-B28-LS4). My above code works great with no missed changes. Now my problem is if put that exact routine into a function, it falls back to missing changes. Also I now have a 20MHz crystal as the clock.
Doing some searching I found this thread:
https://www.ccsinfo.com/forum/viewtopic.php?t=41115
I implemented that routine, but changed it for my pins here:
Code: | void encoder0Loop(void)
{
enc_position <<= 2; //remember previous state by
// shifting the lower bits up 2
enc_position |= ( input_d() & 0x0 ); // AND the lower 2 bits of
//port d, then OR them with var
//old_AB to set new value
enc_value += enc_states[( enc_position & 0xf0 )]; // 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/256; // This is the value you will pass to
//whatever needs the encoder data - change as required
printf(lcd_putc,"\fEncoder 0 = %ld\nValue = %3ld%%\n",enc_value,my_enc_val);
old_enc_value = enc_value;
} |
That goes wy to fast and seems to have points where if I stop rotating the encoder it still thinks it is movig. |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Fri Nov 18, 2016 1:02 pm |
|
|
what is this ???
Code: |
( input_d() & 0x0 )
|
what do you get when you AND anything with ZERO ???
you encoder position calc line is futzed at a minimum..
you are not detecting the encoder change you think you are. |
|
|
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
|
Posted: Fri Nov 18, 2016 1:59 pm |
|
|
that should read & 0x30 |
|
|
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Fri Nov 18, 2016 2:22 pm |
|
|
i simply don't understand much of anything about
why you do it the way you are trying to do it.
as in why you don't simply compare raw_read(masked) encoder with raw_stored encoder of the one step masked value?
that is the the easiset test there is - and then ONLY do calcs to re-jigger the bits when they don't match .
or encode_value - earlier declared as a byte
but then used in a signed notation compare ??
Code: |
if( enc_value <= 0 ) {
// then this:
enc_value/256;
// the compiler MIGHT optimize for you but how about
enc_value >>=8;
|
if enc_value is still a BYTE
then above is always ALSO going to be ZERO too.
you have too many missing bits of code and declarations for me to comment further on where you are in the worst trouble.
also let me say that the port_B code i showed can easily be handled on interrupt on change on port_b in 18F family parts BTDTGTTS. |
|
|
Frozen01
Joined: 23 Apr 2009 Posts: 32
|
|
Posted: Fri Nov 18, 2016 2:38 pm |
|
|
here is my test complete test code:
Code: | #include <main.h>
#include <FLEX_LCD420.C>
unsigned int8 direction=1;
signed int16 index=0;
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;
int16 my_enc_val = 0;
#int_TIMER0
void timer0_isr()
{
output_toggle(POWER_LED); // let me know it's alive...
}
void encoder0Loop(void)
{
enc_position <<= 2; //remember previous state by
// shifting the lower bits up 2
enc_position |= ( input_d() & 0x30 ); // AND the lower 2 bits of
//port d, then OR them with var
//old_AB to set new value
enc_value += enc_states[( enc_position & 0xf0 )]; // 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/256; // This is the value you will pass to
//whatever needs the encoder data - change as required
printf(lcd_putc,"\fEncoder 0 = %ld\nValue = %3ld%%\n",enc_value,my_enc_val);
old_enc_value = enc_value;
}
void main()
{
set_tris_d(0b01110000);
ENABLE_INTERRUPTS(global);
ENABLE_INTERRUPTS(INT_TIMER0);
SETUP_TIMER_0(RTCC_INTERNAL | RTCC_DIV_16);
printf(lcd_putc,"\fRotary Encoder\nTest 2");
while(1)
{
encoder0Loop();
}
} |
|
|
|
|