|
|
View previous topic :: View next topic |
Author |
Message |
acornjelly Guest
|
rotary encoder grey code problem |
Posted: Thu Aug 30, 2007 10:31 am |
|
|
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
|
|
|
RLScott
Joined: 10 Jul 2007 Posts: 465
|
Re: rotary encoder grey code problem |
Posted: Fri Aug 31, 2007 10:53 am |
|
|
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
|
|
Posted: Sun Sep 02, 2007 1:33 pm |
|
|
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
|
|
Posted: Sun Sep 02, 2007 2:43 pm |
|
|
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
|
|
Posted: Mon Sep 03, 2007 5:21 am |
|
|
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
|
global vs timer interrupt |
Posted: Tue Apr 15, 2008 7:17 pm |
|
|
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
|
|
Posted: Tue Apr 15, 2008 8:25 pm |
|
|
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
|
|
Posted: Wed Apr 16, 2008 12:58 am |
|
|
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
|
|
Posted: Wed Apr 16, 2008 4:18 am |
|
|
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 |
|
|
|
|
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
|