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

Rotary Encoder
Goto page 1, 2  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Frozen01



Joined: 23 Apr 2009
Posts: 32

View user's profile Send private message

Rotary Encoder
PostPosted: Fri Sep 09, 2016 2:16 pm     Reply with quote

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

View user's profile Send private message AIM Address

PostPosted: Fri Sep 09, 2016 2:35 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Sep 09, 2016 4:23 pm     Reply with quote

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: 9221
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Fri Sep 09, 2016 4:44 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Sep 09, 2016 5:24 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Sep 09, 2016 5:37 pm     Reply with quote

OK got the oscillator setting figured out, have to test it and see if it changed now...
temtronic



Joined: 01 Jul 2010
Posts: 9221
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Fri Sep 09, 2016 6:08 pm     Reply with quote

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

View user's profile Send private message AIM Address

PostPosted: Sat Sep 10, 2016 6:59 am     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Wed Sep 14, 2016 3:17 pm     Reply with quote

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

View user's profile Send private message AIM Address

PostPosted: Wed Sep 14, 2016 3:48 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Nov 18, 2016 12:38 pm     Reply with quote

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

View user's profile Send private message AIM Address

PostPosted: Fri Nov 18, 2016 1:02 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Nov 18, 2016 1:59 pm     Reply with quote

that should read & 0x30
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Fri Nov 18, 2016 2:22 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Nov 18, 2016 2:38 pm     Reply with quote

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();
}
}
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
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