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 grey code problem

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







rotary encoder grey code problem
PostPosted: Thu Aug 30, 2007 10:31 am     Reply with quote

Has anyone successfully implimented a function to read from a rotary encoder (grey code). I'm sure it should be simple, as I read you just have to XOR the first bit from the last reading, and second from the first, then that'll tell you what direction you're turning in. But I'm having problems.

I have this, surely it should work? I've got a 20ms delay as a bodge debounce and am calling this function from interupts


int1 oldrotdata, newrotdata, answer = 0x00;
int oldrotdat, newrotdat = 0x00;



int rot_det()
{

if (input(rota)) bit_set(newrotdat,0); // set bit 0 of variable to match input
else bit_clear(newrotdat,0);

if (input(rotb)) bit_set(newrotdat,1); // set bit 1 of variable to match input
else bit_clear(newrotdat,1);

if (oldrotdat == newrotdat) // if the encoder hasn't moved
return(0x03); // report so
else
oldrotdat = newrotdat; // else store previous state

newrotdata = input(rota); // check input, set to variable
answer = newrotdata ^ oldrotdata; // XOR bit 0 and old bit 1
oldrotdata = input(rotb); // read bit 1 of rot encoder, after calculation
return(answer); // return 1 for clockwise, 0 for anti
}
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Aug 30, 2007 12:25 pm     Reply with quote

See this CCS example file:
Quote:
c:\program files\picc\examples\ex_encod.c


Use Google to search for CCS code. #use delay is unique to CCS.
Example of search strings:
Quote:

"grey code" "#use delay" -patent
or
"gray code" "#use delay" -patent


Sample hits:
http://courses.ece.uiuc.edu/ece445/projects/summer2006/project5_appendix.doc
http://www.engin.swarthmore.edu/academics/courses/e90/2004_5/ek/Ku_E90_Report_final_alldone.pdf
RLScott



Joined: 10 Jul 2007
Posts: 465

View user's profile Send private message

Re: rotary encoder grey code problem
PostPosted: Fri Aug 31, 2007 10:53 am     Reply with quote

acornjelly wrote:
Has anyone successfully implimented a function to read from a rotary encoder (grey code). I'm sure it should be simple, as I read you just have to XOR the first bit from the last reading, and second from the first, then that'll tell you what direction you're turning in....


That does not work for general Gray Code (absolute encoders). It does work for quadrature phase incremental encoders, though. Is that what you are using?

Robert Scott
Real-Time Specialties
acornjelly
Guest







PostPosted: Sun Sep 02, 2007 1:33 pm     Reply with quote

Sorry, yes you're right. I'm using a quaz phaze thingy, not Gray code. I can't work out why it isn't working, it seems simple on paper
Ttelmah
Guest







PostPosted: Sun Sep 02, 2007 2:43 pm     Reply with quote

Do you want to 'full' quadrature decoding (all four edges), or just one of the subsets of this?
What is the pitch of your encoder (lines per turn)?
What is the maximum rotational rate?
What is your PIC?

The problem is that if you are using (for example), the interrupt on change ability to 'see' the edges, and the normal handlers for this, there are literally dozens of 'extra' instructions involved in saving the registers, before the handler is reached, and a similar number on the return. Unless your PIC is very fast, or the rotational rate very low (or the number of lines similarly low), you need to write a high speed 'global' handler to deal with the interrupts and avoid problems.
This routine
Code:

//This is the decoder for a change in the quadrature inputs. Called on
//port B change
//interrupt, using the fast return stack.
void quad(void) {
   static int old;
   static int new;
   static int value;
   //Here I have an edge on one of the quadrature inputs
   new=input_b();
   //Now I have to decode the quadrature changes. There are four
   // possibilities:
   //I can have a rising or falling edge, on each of the two inputs. I have to
   //look at the state of the other bit, and increment/decrement according
   //to this.
   value=new^old;
   //'value', now has the bit set, which has changed
   if (value & 0x10) {
      //Here the low bit has changed
      if (new & 0x10) {
         //Here a rising edge on A
         if (new & 0x20) --position;
         else ++position;
      }
      else {
         //Here a falling edge on A
         if (new & 0x20) ++position;
         else --position;
      }
   }
   else {
      //Here the high bit (B) must have changed
      if (new & 0x20) {
         //Here a rising edge on B
         if (new & 0x10) ++position;
         else --position;
      }
      else {
         //Here a falling edge on B
         if (new & 0x10) --position;
         else ++position;
      }
   }
   old=new;
   bchanged=0;
}

'position', is a global value containing the current shaft position.
Now, this code looks long, but if you track it through, each possible path, for the different input combinations, involves only reading the port, an XOR, two logic tests, and then incrementing/decrementing the position variable.
Used with the normal interrupt handlers, and reasonable speed on the PIC, this should work fine. However I used it with a minimised 'global' handler, that doesn't save most registers, and like this, it happily handled a servo at up to 5000RPM, on a 128 line encoder.

Best Wishes
acornjelly
Guest







PostPosted: Mon Sep 03, 2007 5:21 am     Reply with quote

Fantastic. I haven't had a chance to try it yet but what you say makes complete sense. I can run my code within a while(1) in the main program so it's obviously the way I've handled the interrupts. All new to me, I'll put this in my code when I find time and let you know how I get on. Thanks so much for the help
cybanical



Joined: 10 Apr 2008
Posts: 6
Location: Oakland, CA

View user's profile Send private message

global vs timer interrupt
PostPosted: Tue Apr 15, 2008 7:17 pm     Reply with quote

Quote:
However I used it with a minimised 'global' handler, that doesn't save most registers, and like this, it happily handled a servo at up to 5000RPM, on a 128 line encoder.


If one uses a 'global' interrupt handler, is it still possible to define other interrupt handlers; ie. could I have both a global interrupt to count pulses and also a timer interrupt that might be used to calculate velocity after a given time?

Also, I imagine the int_global has precedence over any other interrupts? I would hate to have my int_timer1 cause me to miss a pulse count, but I'm worried that calculating velocity inside the int_global function, quad(), might also slow down my overall pulse calculations?
_________________
cybanical == cyber+mechanical
KU5D



Joined: 10 Feb 2008
Posts: 46
Location: Asheville, North Carolina

View user's profile Send private message

PostPosted: Tue Apr 15, 2008 8:25 pm     Reply with quote

Global interrupt doesn't really have anything to do with priority. Basically that allows you to enable your subsequent interrupts. Depending on your chip, you could have many possible interrupts...

#INT_RDA for your hardware usart
#INT_EXT for some external interrupt
#INT_RTCC for some housekeeping interrupt

...so on and so forth. Once you set these up, you use global to enable them. I have a project now that runs smoothly with something like five interrupts on a 4525. From what I've been able to learn (mostly from folks here), interrupts are by default prioritized in the order they are declared (someone correct me if I am wrong). However, if you have one that is most important above all else, you can do a #device HIGH_INTS=TRUE at the top of your main and then pick your priority interrupt and do something like #INT EXT FAST at the top of the interrupt service routine to give it priority. Using FAST rather than HIGH will still have the compiler manage the stack for you...if you use HIGH then you have to manage the stack for yourself.

Another thing to consider is the canned delay_xx() functions. These will put the PIC into do-nothing mode and mask all your interrupts, so they should be avoided in an interrupt-driven program at all costs. Best bet is to set up a background housekeeper timer interrupt and use it to set flags to clock your delays rather than the canned delay_xx() stuff.
_________________
Confidence is the feeling you have right before you fully understand the situation...
cybanical



Joined: 10 Apr 2008
Posts: 6
Location: Oakland, CA

View user's profile Send private message

PostPosted: Wed Apr 16, 2008 12:58 am     Reply with quote

hmm.

So, I'm using Ttelmah's quadrature code that uses a Global interrupt and I'm trying to include a tachometer by using CCP2 interrupt on pinB3 to capture every 4th rising edge. I've read through http://www.ccsinfo.com/forum/viewtopic.php?t=29963 and I thought I was following the same pattern, however i get the following error when i attempt to compile.

Quote:
error: duplicate interrupt function


I can post code, but I figure this is a common error that someone will be able to point out? Maybe i just need to step away from the code for a while to see the obvious answer?

thanks,
_________________
cybanical == cyber+mechanical
Ttelmah
Guest







PostPosted: Wed Apr 16, 2008 4:18 am     Reply with quote

There seems to be a big misunderstanding here about what #int_global is/does....

The #INT_GLOBAL interrupt routine definition, is really nothing to do with the INT_GLOBAL flag definition needed to enable all the interrupts (except in a rather 'roundabout' way).
On the basic PIC (ignore the hardware interrupt priority on the PIC18 for now), there is just _one_ interrupt vector. When _any_ interrupt occurs, if two flags are enabled (the one for this specific interrupt, and the global flag), then the processor performs a call to this _one_ address. The #INT_GLOBAL definition, defines the code to put at this point.
Normally, CCS does the handler at this point for you automatically. Their handler, saves _every_ global register, and a copy of the scratch memory. Then it tests all interrupt flags that have a defined handler, and jumps to the code for the first one it finds that is enabled, and whose flag is set. When this has finished, it then clears the interrupt flag, restores all the memory it saved, and returns to the main program.
Now, the problem here, is that this takes _time_. The INT_GLOBAL handler supplied by CCS, typically adds anything from 60 to 100 instructions to the time needed to process an interrupt (depending on how many interrupts are enabled).
The only connection between the #INT_GLOBAL handler, and the #INT_GLOBAL 'enable', is that both affect _all_ the interrupts.

The #INT_GLOBAL used in the quadrature example, is done to allow really _fast_handling of a quadrature encoder. The difference between perhaps 600RPM, and something approaching 8000RPM, on the original test system. However doing this, implies you can no longer have any other interrupt definitions at all. If you do need another interrupt, then the code to detect this (test the bit), has to be added _by you_ to the INT_GLOBAL handler. Also, you then have to save any other registers _this_ handler needs, and the maximum interrupt frequency supported without missing a character, will _drop depending on the duration you will be in this other handler.
Now, as an example, this is the 'real' #INT_GLOBAL handler from the original system. With this, I could still manage over 3000RPM (my actual target), and have some other interrupts in use:
Code:

#int_global
void myint(void) {
   static int INT_scratch[10];
#ASM
   //Here for maximum speed, I test the RB interrupt - since it is allways
   //enabled, I don't have to test the enable bit
   BTFSS   INTCON,RBIF
   GOTO    NXT
#endasm
   quad();              //Quadrature handler.
#asm
   GOTO    FEXIT        //Use the fast stack for exit
NXT:
   //Now I save the registers for the other interrupt handlers
   MOVFF   FSR0L,INT_scratch
   MOVFF   FSR0H,INT_scratch+1
   MOVFF   FSR1L,INT_scratch+2
   MOVFF   FSR1H,INT_scratch+3
   MOVFF   FSR2L,INT_scratch+4
   MOVFF   FSR2H,INT_scratch+5
   MOVFF scratch,INT_scratch+6
   MOVFF   scratch1,INT_scratch+7
   MOVFF   scratch2,INT_scratch+8
   MOVFF   scratch3,INT_scratch+9
   //Test for the external interrupt (power fail).
   BTFSS    INTCON,INT0E
   GOTO     NEXTA
   BTFSC    INTCON,INT0
#endasm
   pfail();
#asm
NEXTA:
   //Now for Timer 2
   BTFSS     PIE1,TMR2
   GOTO      NEXT1
   BTFSC     PIR1,TMR2
#endasm
   TIMER2_isr();
#asm
NEXT1:
   //Receive data available
   BTFSS     PIE1,RCIE
   GOTO      NEXT2
   BTFSC     PIR1,RCIE
#endasm
   RDA_isr();
#asm
NEXT2:
   //Transmit buffer empty
   BTFSS     PIE1,TXIE
   GOTO      EXIT
   BTFSC     PIR1,TXIE
#endasm
   TBE_isr();
#asm
EXIT:
   //Restore registers
   MOVFF   INT_scratch,FSR0L
   MOVFF   INT_scratch+1,FSR0H
   MOVFF   INT_scratch+2,FSR1L
   MOVFF   INT_scratch+3,FSR1H
   MOVFF   INT_scratch+4,FSR2L
   MOVFF   INT_scratch+5,FSR2H
   MOVFF   INT_scratch+6,scratch
   MOVFF   INT_scratch+7,scratch1
   MOVFF   INT_scratch+8,scratch2
   MOVFF   INT_scratch+9,scratch3
   //Here the 'fast' exit.
FEXIT:
   RETFIE  1
   nop
#ENDASM
}

This potentially calls a timer2 handler, serial transmit, serial receive, and a 'power fail' routine, which writes data to the EEPROM, after checking for the quadrature change.
You don't us the #int definitions for any of the handlers, just have the routines called from the int_global handler. So (for instance), the serial received hndler is just:
Code:

void RDA_isr(void) {
    //Here a RS232 receive character has arrived - store value, and set
    //flag. Also clear 'overrun' if flagged.
    rflag=true;
    rchr=RCREG;
    if (OERR) {
        CREN=false;
        CREN=true;
    }
    clear_interrupts(INT_RDA);
}

This manages to be about 20 instructions shorter than the CCS int_global version (I don't save any registers that are not used in the handlers), still giving significantly better performance.

Now, key to understand, is that if you use #INT_GLOBAL to define a handler, you cannot have any other #INT routine defined. However this _does not_ stop you from using other interrupts, it just means that _you_ have to handle checking the flags, and saving the registers.

In this particular case, what was wanted (really fast handling of the quadrature event), could have been much easier handled, using the high priority hardware interrupt on the 18F chips. The standard INT_GLOBAL handler could have then been left in place to handle all the other events (you would use the #fast definition, which effectively does the same as int_global for the low priority interrupts, allowing a handler, where _you_ have to do all the work, but without the massive overhead of the CCS approach). Unfortunately, this was on a 18F452, which has a hardware problem (as do quite a few other chips), if the high priority interrupt comes from a source that is asynchronous to the processor clock, precluding use of the high priority hardware ability....

Best Wishes
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