|
|
View previous topic :: View next topic |
Author |
Message |
williamho Guest
|
need help for rotary encoder |
Posted: Wed Jun 22, 2005 5:25 am |
|
|
Hi, I try someone's code to capture rotary encoder as below but
I will miss reading in fast mode.
Can anyone help?
Thanks.
#if defined(__PCH__)
#include <18F458.h>
#DEVICE ICD=TRUE
#fuses HS,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT,NOCPD,WRT
#use delay(clock=20000000)
#use rs232(baud=19200, parity=N, bits=8, xmit=PIN_C6, rcv=PIN_C7,stream=COM_1) // Jumpers: 8 to 11, 7 to 12
#endif
#include <lcd.c>
signed int16 count;
#INT_EXT
void gisr()
{
//output_high(PIN_C1);
output_bit(PIN_C0,input(PIN_B4));
if(input(PIN_B4)==1)
++count;
else --count;
//printf("Count = \%04ld \r",count);
printf(lcd_putc,"\fk = %04ld",count);
//output_low(PIN_C1);
}
void main() {
lcd_init();
printf("\n\rready\n\r");
count=0;
set_tris_b(0xFF);
ext_int_edge(0,L_TO_H);
printf("\n\rCount = 0000\r");
enable_interrupts(INT_EXT);
enable_interrupts(GLOBAL);
} |
|
|
sseidman
Joined: 14 Mar 2005 Posts: 159
|
|
Posted: Wed Jun 22, 2005 6:56 am |
|
|
First, you need an infinite loop in main.
Next, take the printf statements out of your isr. Instead, set a change flag in the isr, and poll it in main, printing and resetting if it is set. Depending on how fast your encoder is moving, don't expect great printing performance. Also, if your encoder is really flying, you'll miss plenty of counts.
The better approach is to put two counters in a triggered mode, use a cheap us digital chip to provide an "up" pulse and a "down" pulse to the timers, use timer interrupt to periodically poll the counts and reset them. My own experience is that this method, too, can miss counts if things are really zipping along.
If your encoder is going real fast, and you're early in a design stage, I highly recommend moving over to a uc that directly handles quadrature, like the 18F4431, or 18f2331 (if you need less IO and memory). The problems you'll encounter without such a device are a tad detailed (search the archives for "quadrature" if you want a review), and can be overcome using a decoder chip like the hctl2020, but that's more expensive than moving to the right PIC, and the programming will be difficult, as well
Scott
Scott |
|
|
Thomas Blake
Joined: 18 Jan 2004 Posts: 22 Location: Burbank CA
|
|
Posted: Wed Jun 22, 2005 11:35 am |
|
|
You can turn quadrature signals into tach and true direction easily enough with a flip flop. The real problem with mechanical encoders is that switch bounce can break the quadrature relationship. In some applications this isn't a problem, in others it is. If you must have absolute monotonicity then you might want to interpret the quadrature pulses as Gray code (although it will change a lot faster when you turn the knob!). |
|
|
williamho Guest
|
500 counts/rev at 1800 rpm encoder |
Posted: Wed Jun 22, 2005 10:26 pm |
|
|
Hi,
Thanks for your reply.
encoder 500 counts/rev at 1800 rpm is fast at average?
Thanks
William |
|
|
Ttelmah Guest
|
|
Posted: Thu Jun 23, 2005 4:02 am |
|
|
First, is the '500' figure, the encoder 'lines', or the encoder 'counts'. I suspect the former. A quadrature encoder, depending on how you decode it, can give up to four counts/line. Now others have pointed out that you must remove the printf from the encoder handler. The printf, just to actually 'output' the data, will take five character 'times', which at 19200bps, is about 2.5mSec. The rest of the interrupt handler, will take only a few dozen machine cycles, so this really is the part causing the main problem.
What is the circuitry attached between the encoder and the PIC?. The code you are using, looks as though it must have a partial 'decoder' present, otherwise the need to output a bit makes no sense.
As an example of just how fast the encoders can be handled:
Code: |
int16 position;
#int_global
void myint(void) {
static int old;
static int new;
static int value;
//Here I have an edge on one of the quadrature inputs
new=portb;
/*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;
#asm
//Here the 'fast' exit.
RETFIE 1
#ENDASM
}
|
Now this uses the low two bits on the high 'nibble' of port B to handle quadrature decoding with the two bits wired directly to the port, and the RB 'changed' interrupt to detect the edges. Because the code only uses simple 'tests', and addition, the only registers that have to be saved, are those handled by using the 'fast' interrupt return ability. However this has to be used 'manually', inside the normal int_global handler, because the chip does not support the asynchronous 'RB' event as a fast interrupt properly (on the 18F252, which this was originally written for)....
'bchanged', has to be defined using the #bit command to be the port b changed interrupt bit. So for the 18F252:
#bit bchanged=0xFF2.0
Now this code, on a chip at 20MHz, happily worked with edges at 10uSec intervals, corresponding to rotational rates with a 500 'line' encoder (2000 edges per rev), of 3000rpm.
You can go massively faster, by using external hardware. Some of the 'servo' application notes at MicroChip, show how to implement the decoder in hardware, and feed the output as counts, into the CTC modules, happily supporting tens of thousands of RPM, even on a higher line count encoder.
Seperately, I have to ask what you are actually trying to do?. Normally, at the main motor shaft, the innacuracies of any subsequent gearbox, makes high line counts fairly pointless. Hence it is common to find encoders fitted to motors, only having perhaps 16 lines. Conversely, using a high acciracy encoder on the output shaft, only has it rotating at a few RPM. Using a high accuracy encoder on the primary shaft, will normally involve the use of special low error and 'zero-backlash' gearing.
Best Wishes |
|
|
williamho Guest
|
|
Posted: Fri Jun 24, 2005 12:35 am |
|
|
Hi, thank for your reply.
I am try to use your code for my application as below:.
Changes that I have done :
set_tris_b(0x30);
new=input_b();
//new=portb;
// bchanged=0;
Total code become as below :-
#include <18F458.h>
#DEVICE ICD=TRUE
#fuses HS,NOWDT,NOPROTECT,NOLVP,PUT
#use delay(clock=40000000)
#use rs232(baud=9600, parity=N, bits=8, xmit=PIN_C6, rcv=PIN_C7,stream=COM_1) // Jumpers: 8 to 11, 7 to 12
#INT_RB
int16 position;
#int_global
void myint(void) {
static int old;
static int new;
static int value;
//Here I have an edge on one of the quadrature inputs
set_tris_b(0x30);
new=input_b();
//new=portb;
/*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;
#asm
//Here the 'fast' exit.
RETFIE 1
#ENDASM
}
void main()
{
signed int16 newcount=0;
printf("\n\rready\n\r");
enable_interrupts(INT_RB);
enable_interrupts(global);
do
{
if(newcount!=position){
newcount=position;
printf("\fcount = %5ld",newcount);
}
}while(TRUE);
}
result is only "ready" shown in screen.
I am using Omron optical encoder NPN output with 6 wires.
brown to +5v
blue to 0V
black (channel A) to B4
White (Channel B) to B5
Orange (channel Z) not use
I have been trying for 4 days trying to capture the rotary encoder but not working at all.
Someone recommended HCTL2020 or F4331. |
|
|
Ttelmah Guest
|
|
Posted: Fri Jun 24, 2005 3:21 am |
|
|
Get rid of the line saying #int_RB. This will bu&&er the code up completely.
The code replaces the _int global_ handler, and only supports the RB interrupt. If you have any other interrupts needed or specified, I can post a version that shows how to do this. You need INT_RB enabled, and INT_global enabled, but you _must not_ have an int_RB handler present.
If you want to use the input_b specification, then you _must_ have the line #use fast_io(B) present, or the compiler will add extra code to handle the TRIS, and will slow things down.
Do not set the TRIS in the interrupt handler. The port must already be set to have inputs on the two bits, or the interrupt will not trigger. Set the TRIS once at the start of main.
You _must_ have the bit definition for bchanged present, and this line _must_ be in the code. You have remmed it out.
Best Wishes |
|
|
Ttelmah Guest
|
|
Posted: Fri Jun 24, 2005 4:06 am |
|
|
I have put together a demo, which should show how it works:
Code: |
#include <18F458.h>
#use delay(clock=40000000)
#fuses H4,NOWDT,PUT,BROWNOUT,WRT,NOLVP,BORV42
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7)
#use fast_io(B)
/*
RB4,RB5=quadrature inputs
*/
signed int32 position;
//Note using int32, otherwise routine will run off the end very quickly.
//Also 'signed', so the unit can handle the shaft rotating backwards at
//the start...
#byte portb=0xF81
#bit bchanged=0xFF2.0
#int_global
void myint(void) {
static int old;
static int new;
static int value;
//Here I have an edge on one of the quadrature inputs
new=portb;
/*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;
#asm
//Here the 'fast' exit.
RETFIE 1
#ENDASM
}
//Main code loop
void main(void) {
signed int32 lastpos;
port_b_pullups(true);
//Note that many of the quadrature encoders _require_ pull-ups on
//their lines, having open collector outputs.
setup_adc_ports(NO_ANALOGS);
setup_spi(FALSE);
set_tris_B(0x30); //Input on bit 4,5 for quad
enable_interrupts(INT_RB);
enable_interrupts(global);
lastpos=position;
while (true) {
if(lastpos!=position){
lastpos=position;
printf("\fcount = %5ld",lastpos);
//I would recommend a delay here. The interrupt will still keep
//counting edges, but without a delay, the string will be 'non stop'
//for any reasonable movement rate.
}
}
}
|
Best Wishes |
|
|
williamho Guest
|
it works, thanks |
Posted: Fri Jun 24, 2005 5:22 am |
|
|
Thanks, it works.
Why the origin of the encoder remain the same even I reset the PIC?
Just want to know and it is OK for that.
Thanks a lot. |
|
|
kender
Joined: 09 Aug 2004 Posts: 768 Location: Silicon Valley
|
HCTL2020 |
Posted: Sun Aug 28, 2005 1:30 pm |
|
|
A have a problem with a rotary encoder skipping steps, and I want to solve it in hardware rather then frimware. I looked for HCTL2020, but couldn't find a place to buy them. Couldn't find the datasheet too.
Could you recommen where to look for these chips?
Thanks,
Nick |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
|
sseidman
Joined: 14 Mar 2005 Posts: 159
|
Re: HCTL2020 |
Posted: Mon Aug 29, 2005 6:23 am |
|
|
kender wrote: | A have a problem with a rotary encoder skipping steps, and I want to solve it in hardware rather then frimware. I looked for HCTL2020, but couldn't find a place to buy them. Couldn't find the datasheet too.
Could you recommen where to look for these chips?
Thanks,
Nick |
You may need to step up to the hctl2032, as the 2020 is listed as obsolete. I can't find any of these options in stock, though-- not digikey, arrow, or newark. Agilent was sold about a month ago, if I'm remembering right.
Are you early enough in design to switch to a 18fx331? The HCTL's run about $12-$25 each, and they seemed a little delicate to me-- I've blown two 2020's, and don't remember how (but they work absolutely great if you don't blow them up!). They also require their own clock. Using the PIC's with quadrature inputs really cuts down on your parts count, and works really nicely. I have a real fast count, and it doesn't miss anything.
Scott |
|
|
sseidman
Joined: 14 Mar 2005 Posts: 159
|
Re: HCTL2020 |
Posted: Mon Aug 29, 2005 6:25 am |
|
|
kender wrote: | A have a problem with a rotary encoder skipping steps, and I want to solve it in hardware rather then frimware. I looked for HCTL2020, but couldn't find a place to buy them. Couldn't find the datasheet too.
Could you recommen where to look for these chips?
Thanks,
Nick |
You may need to step up to the hctl2032, as the 2020 is listed as obsolete. I can't find any of these options in stock, though-- not digikey, arrow, or newark. Agilent was sold about a month ago, if I'm remembering right.
Are you early enough in design to switch to a 18fx331? The HCTL's run about $12-$25 each, and they seemed a little delicate to me-- I've blown two 2020's, and don't remember how (but they work absolutely great if you don't blow them up!). They also require their own clock. Using the PIC's with quadrature inputs really cuts down on your parts count, and works really nicely. I have a real fast count, and it doesn't miss anything.
Scott |
|
|
cybanical
Joined: 10 Apr 2008 Posts: 6 Location: Oakland, CA
|
Further information |
Posted: Mon Apr 14, 2008 11:09 pm |
|
|
Ttelmah wrote: | I have put together a demo, which should show how it works:
|
I'm just getting started with microcontrollers and for one of my first applications I am trying to write a quadrature encoder program to calculate both rotational position and angular velocity (rpm). Then I would like to perform a Digital-to-Analog conversion of my velocity signal, thereby producing an "analog tachometer" reading.
The premise: I have a motor with a broken tachometer and a controller which requires analog tach inputs. I am hoping to fake the tachometer signals (using the PIC) with this DAC program. The original motor spec is 9V/1000rpm, so I'm hoping to generate this to get my motor running again.
I got the PCWH student kit, so I'm trying to do this with the PIC18F4520 that came with the kit. Right now I'm practicing with a 2048 count, two channel optical encoder (which is a little annoying because there's a trigger every 0.04 degrees--I think).
I tried writing my own code, but I wasn't using interrupts and I think I was missing edges because the direction of my count was unreliable.
now I'm using Ttelmah's code and it works (thanks). However, I had a couple questions:
Could you explain this following bit of code? Code: | #byte portb=0xF81
#bit bchanged=0xFF2.0 | Is this just specifying an address to keep this data, or is this address specific to the port of the PIC18F458 you were using? If it's chip specific where in the data sheet could i find this information (ie, how would i know the address on the PIC18F4520?). I assume referencing the address like this is similar to a pointer reference directly to the portb value, and that's faster than calling an input(PIN_B4)? _________________ cybanical == cyber+mechanical |
|
|
Ttelmah Guest
|
|
Posted: Tue Apr 15, 2008 2:36 am |
|
|
The #bit, and #byte directives on CCS, allow you to define a pseudo variable, to access a specific address in the processor.
The addresses are in the data sheet.
They should be the same on all 18 family chips.
Using them for port I/O, is a bit of a 'left over'. On the current compiler, provided you have used fast_io, the standard 'input' command will work the same for reading the port. The equivalent command to access the whole port, would be "input_b()". In the past, the compiler was at times less well optimised, and this was the 'best' way to access the pins.
The 'bchanged' bit, accesses the interrupt flag set when the port bits have changed, and there isn't a 'standard' instruction to access this.
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
|