|
|
View previous topic :: View next topic |
Author |
Message |
asdf85
Joined: 03 Jan 2011 Posts: 34
|
PIC unable to keep up with rotary encoder? |
Posted: Wed Jan 19, 2011 12:22 am |
|
|
Hi,
I've been trying to get the PIC to be able to read the inputs of a quadrature encoder.
If I rotate it slowly, the pic is able to read the correct values. However if I rotate it fast (I'm rotating it manually, and I guess its roughly about 3 rpm), it seems to miss alot of counts. 1 revolution has 1000 pulses.
Is the problem due to the interrupt unable to happen as quick as the input pulses? I'm using a pic16f873 with a 4mhz crystal.
Here is my code
Code: |
#include "16F873.h"
#include <stdio.h>
#include <stdlib.h>
#fuses XT,NOPROTECT,NOLVP,NOWDT,NODEBUG,NOBROWNOUT
#use delay(clock=4000000)
#use RS232(baud=19200,errors,xmit=PIN_C6,rcv=PIN_C7,parity=n,STREAM=no,stop=2,enable=pin_b1)
#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1 (SPI_L_TO_H)
#define SPI_MODE_2 (SPI_H_TO_L)
#define SPI_MODE_3 (SPI_H_TO_L | SPI_XMIT_L_TO_H)
#define DATA PIN_C5 // SPI1 hardware SPI data out
#define LOAD PIN_B2 // software SPI
#define CLK PIN_C3 // SPI1 hardware clock
//===============================
int d0=0;int d1=0;int d2=0;int d3=0;int16 a=0;int b=0;
int1 preva=0;int1 prevb=0;
int16 counter=0;int pata=0b00000000;int patb=0b00000000;
int y=0;
int1 ina=0;int1 inb=0;
int1 temp=0;int dir=0;
void write(char address, char dis_data)
{
output_low(LOAD);
delay_us(10);
spi_write(address); // spi1 write to default pins
spi_read();
delay_us(10); // used for scope write but not necessary
spi_write(dis_data); // spi1 write to default pins
spi_read();
output_high(LOAD);
delay_us(10);
output_low(LOAD);
}
void initdriver()
{
write(0x0C,0x01); // Shutdown 0C00 - normal mode 0C01
write(0x0F,0x00); // test mode off 0F00 - on0F01
write(0x0B,0x03); // scan limit - 00 = 1 column
write(0x09,0xFF); // decode mode on - FF all 8 digits have decode on
write(0x0A,0x0F); // intensity - 0xXF max intensity
write(0x00,0x00); // No Operation
}
void display(int16 number)
{
d3=number/1000;
a=number-(d3*1000);
d2=a/100;
b=a-(d2*100);
d1=b/10;
d0=b-(d1*10);
write(0x04,d3);
write(0x03,d2);
write(0x02,d1);
write(0x01,d0);
}
void main()
{
setup_spi(SPI_MASTER | SPI_MODE_0 | SPI_CLK_DIV_16);
initdriver();
port_b_pullups(TRUE);
delay_us(200);
output_high(pin_a0);
output_high(pin_a5);
EXT_INT_EDGE(L_to_H);
clear_interrupt(int_rb);
enable_interrupts(GLOBAL);
enable_interrupts(INT_rb);
output_low(pin_a1);
while(1)
{ display(counter);
// output_toggle(pin_a0);
delay_ms(100);
}
}
#INT_rb
void rb_isr(void)
{ ina=input(pin_b4);
inb=input(pin_b5);
pata=pata<<1;
bit_clear(pata,4);
if(ina==1)
{ bit_set(pata,0);
}
else
{ bit_clear(pata,0);
}
patb=patb<<1;
bit_clear(patb,4);
if(inb==1)
{ bit_set(patb,0);
}
else
{ bit_clear(patb,0);
}
if(pata==6&&patb==12)
{ counter++;
}
else if(pata==12&&patb==6)
{ counter--;
}
}
|
|
|
|
asdf85
Joined: 03 Jan 2011 Posts: 34
|
|
Posted: Wed Jan 19, 2011 1:38 am |
|
|
I've tested my previous code and it can correctly read up to 2500 pulses per second.
I've changed the code to use the external interrupt on rb0. This can read accurately up to 5000pulses per second.
Here is the code
Code: |
void main()
{ // 4 mhz clock, no prescaler, set timer 0
// to overflow in 100us
setup_spi(SPI_MASTER | SPI_MODE_0 | SPI_CLK_DIV_16);
initdriver();
port_b_pullups(TRUE);
delay_us(200);
output_high(pin_a0);
output_high(pin_a5);
EXT_INT_EDGE(L_to_H);
clear_interrupt(int_ext);
enable_interrupts(GLOBAL);
enable_interrupts(INT_ext);
output_low(pin_a1);
while(1)
{ display(counter);
// output_toggle(pin_a0);
// delay_ms(1000);
}
}
#INT_ext
void rb_isr(void)
{ ina=input(pin_b4);
//inb=input(pin_b5);
if(ina==0)
{ counter++;
}
else
{ counter--;
}
}
|
I'm hoping to at least be able to read 10k pulses per second. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Wed Jan 19, 2011 2:51 am |
|
|
I'm unable to recognize a reliable quadrature encoder processing algorithm in one of the both codes.
A state-of-the-art algorithm counts every A or B edge, tolerates glitches and detects a speed overrun from the input states. In so far, it's basically necessary to use the input change interrupt, or poll the inputs (for slow encoder applications).
Generally, you have the problem of interrupt processing time. Thus the interrupt function must be coded effectively. I don't understand, why you are operating a 20 MHz CPU at 4 MHz.
The simple edge interrupt decoder is effective, but it's not exact. It's supposed to loose steps on direction reversal. Possibly you don't mind.
In both routines, you're only counting one of four steps. It would be easier, to use an encoder with fewer pulses. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Wed Jan 19, 2011 3:38 am |
|
|
Do you need to use any other interrupts?.
If not, then you need to consider going the 'int_global' route:
Code: |
signed int32 position;
int save_w;
#locate save_w=0x7f
int save_status;
#locate save_status=0x20
#byte status = 3
//This is the decoder for a change in the quadrature inputs. Called on
//port B change interrupt, with encoder connected to B4, and B5.
void quad(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;
}
#INT_GLOBAL
void isr() {
#asm
//store current state of processor
MOVWF save_w
SWAPF status,W
BCF status,5
BCF status,6
MOVWF save_status
#ASM
//Here for maximum speed, I test the RB interrupt - since it is always
//enabled, I don't have to test the enable bit
BTFSS INTCON,RBIF
GOTO FEXIT //Add other handlers here if needed
#endasm
quad(); //Quadrature handler.
#asm
BCF RBIF
FEXIT:
// restore processor and return from interrupt
SWAPF save_status,W
MOVWF status
SWAPF save_w,F
SWAPF save_w,W
#endasm
}
|
Will need the register defines for PORTB, RBIF etc., added.
Not an exact match for your code, but close (position, rather than counter etc..).
The decoder, is slightly more efficient than your code, but the key saving, is that it only saves the bare minimum registers needed. Should double the response speed with care.
You may find that adding pull up resistors helps a little. The internal pullups are quite low powered, and as pulse rates rise, you may find these are not good enough to give fast edges. Worth studying the waveform.
Best Wishes |
|
|
asdf85
Joined: 03 Jan 2011 Posts: 34
|
|
Posted: Wed Jan 19, 2011 3:56 am |
|
|
actually, the 1st code does check for all 4 steps. Im shifting the bits on each state change and checking to see if it matches the intended waveform pattern(doing this with pata and patb variables).
Initially i could only find a 4mhz crystal lying around. but i just ran out and got my self a 20mhz crystal.
With the 20mhz crystal, the 2nd code works. but the first still does not.
However, as you pointed out, the 2nd code just checks one of the 4 steps, so im afraid the results may be innacurate. Because I noticed when using the 4mhz crystal, it started decreasing(its shud be increasing) the count once i rotated it at a faster speed.
Ttelmah, i need to use RDA and timer interrupts too. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Wed Jan 19, 2011 4:07 am |
|
|
You _will_ have problems then.
You can add handlers for other interrupts, but what happens, if you have just started handling one of these, and a RB edge arrives?. The code has to do all the operations associated with the second interrupt, restore the registers this needs, then be called a second time for the RB interrupt. Ouch....
Choices:
Go for a faster CPU clock
Add an external change detector, and feed the CCP channels off this.
Add a full external quadrature counter.
Switch to a 18 PIC, and make the RB interrupt 'high priority'.
Best Wishes |
|
|
bkamen
Joined: 07 Jan 2004 Posts: 1615 Location: Central Illinois, USA
|
|
Posted: Wed Jan 19, 2011 9:43 am |
|
|
Ttelmah wrote: |
Choices:
Go for a faster CPU clock
Add an external change detector, and feed the CCP channels off this.
Add a full external quadrature counter.
Switch to a 18 PIC, and make the RB interrupt 'high priority'.
|
Or use a PIC with a Quadrature decoder like the 18Fxx31.
http://ww1.microchip.com/downloads/en/DeviceDoc/39616d.pdf
-Ben _________________ Dazed and confused? I don't think so. Just "plain lost" will do. :D |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Wed Jan 19, 2011 10:02 am |
|
|
Quote: | i need to use RDA and timer interrupts too |
This changes everything.
Quote: | the 1st code does check for all 4 steps. |
I don't see, how. It checks for a signal pattern, that occurs only once per 4 steps (hopefully).
The code presented by Ttelmah does a state-of-the-art quadrature encoding. It can be possibly speeded up by using a state table. |
|
|
scottc
Joined: 16 Aug 2010 Posts: 95
|
|
Posted: Wed Jan 19, 2011 10:22 am |
|
|
Is the encoder one of the Mechanical types that has 3 pins
gnd, A - B
Thanks Scott |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19504
|
|
Posted: Wed Jan 19, 2011 11:23 am |
|
|
FvM wrote: | Quote: | i need to use RDA and timer interrupts too |
This changes everything.
Quote: | the 1st code does check for all 4 steps. |
I don't see, how. It checks for a signal pattern, that occurs only once per 4 steps (hopefully).
The code presented by Ttelmah does a state-of-the-art quadrature encoding. It can be possibly speeded up by using a state table. |
A state table, works out slower. I tried it...
If you run through the possible 'routes', it only takes a handful of instructions to go through each possibility. With the table, the total timing is about 'neck and neck', _but_ you then have to also save the registers used to access the table, which makes it worse (in the given example, with minimum register saves).
I have to say though, that assuming the code outside, is looping quickly doing whatever it does, one has to remember, that you don't need an interrupt handler to service serial (just ensure you are polling the receive interrupt at least as frequently as a character time), and the same applies to timer interrupts.
If you only need to do some very small operation with the timer, then add a test for this interrupt flag in the global code, and do this operation. However otherwise, just poll the interrupt flag in the main code. You are using interrupt flags, but not interrupt handlers. Then the only interrupt using the hardware handlers becomes the quadrature system, and you can get the required speed.
I just compiled my code (a few bugs), but with these fixed, it takes a total of 36 instruction times 'worst case' to handle any quadrature state (including the interrupt call/return). Potentially means this can handle 30000 edges per second.
Best Wishes |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Thu Jan 20, 2011 6:05 am |
|
|
Quote: | A state table, works out slower. I tried it... | Yes, I believe. It's however faster on a 8051, that has a fast MOVC ROM lookup instruction.
Quote: | Potentially means this can handle 30000 edges per second. | Good! |
|
|
asdf85
Joined: 03 Jan 2011 Posts: 34
|
|
Posted: Thu Jan 20, 2011 6:55 pm |
|
|
Sorry, I'm actually not from an electronics background. What exactly is handlers for interrupts?
scott, the encoder has 6 pins, its Koyo TRD-J 1000 RZW. |
|
|
gpsmikey
Joined: 16 Nov 2010 Posts: 588 Location: Kirkland, WA
|
|
Posted: Thu Jan 20, 2011 7:00 pm |
|
|
Interrupt Handler = ISR = Interrupt Service Routine -- the code that gets control any time an interrupt goes off. If you are using interrupts, you MUST have a handler to take control when that interrupt goes off or it will wander off into never never land.
mikey _________________ mikey
-- you can't have too many gadgets or too much disk space !
old engineering saying: 1+1 = 3 for sufficiently large values of 1 or small values of 3 |
|
|
asdf85
Joined: 03 Jan 2011 Posts: 34
|
|
Posted: Thu Jan 20, 2011 11:29 pm |
|
|
oh okay, its the same thing as isr |
|
|
asdf85
Joined: 03 Jan 2011 Posts: 34
|
|
Posted: Fri Jan 21, 2011 3:00 am |
|
|
May i also know if its OKAY to be enabling and disabling interrupts in an interrupt service routine? Thanks |
|
|
|
|
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
|