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 support@ccsinfo.com

PIC unable to keep up with 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
asdf85



Joined: 03 Jan 2011
Posts: 34

View user's profile Send private message

PIC unable to keep up with rotary encoder?
PostPosted: Wed Jan 19, 2011 12:22 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 1:38 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 2:51 am     Reply with quote

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: 19221

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 3:38 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 3:56 am     Reply with quote

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: 19221

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 4:07 am     Reply with quote

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: 1611
Location: Central Illinois, USA

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 9:43 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 10:02 am     Reply with quote

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

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 10:22 am     Reply with quote

Is the encoder one of the Mechanical types that has 3 pins

gnd, A - B

Thanks Scott
Ttelmah



Joined: 11 Mar 2010
Posts: 19221

View user's profile Send private message

PostPosted: Wed Jan 19, 2011 11:23 am     Reply with quote

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... Very Happy

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

View user's profile Send private message

PostPosted: Thu Jan 20, 2011 6:05 am     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Jan 20, 2011 6:55 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Jan 20, 2011 7:00 pm     Reply with quote

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

View user's profile Send private message

PostPosted: Thu Jan 20, 2011 11:29 pm     Reply with quote

oh okay, its the same thing as isr
asdf85



Joined: 03 Jan 2011
Posts: 34

View user's profile Send private message

PostPosted: Fri Jan 21, 2011 3:00 am     Reply with quote

May i also know if its OKAY to be enabling and disabling interrupts in an interrupt service routine? Thanks
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