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

Counting Pulses Method 'Tachometer'
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
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

Counting Pulses Method 'Tachometer'
PostPosted: Tue Nov 18, 2014 9:53 am     Reply with quote

Hi All,

I am a bit stuck, and hope you can help.
I am trying to measure accurate RPM (within 1%) when using a 13ppr quad encoder (so 52 edge detection per revolution). Max speed is 2000rpm. min Speed TBD.

I have tried the 'timing method' and 95% of the time it is within 1% (even with averaging algorithms), however I cannot improve on this, and so I thought that I would try the 'counting method' to see if any improvement was possible. Also I need to detect direction later on, so the counting method (with Ttelmah's encoder detection code) is likely to be preferable.

My results are more than 10% off with the counting method i.e. when encoder rotating at 2000rpm, the reading is 2200rpm. Can anyone suggest anything or see any obvious errors below?

The environmental factors are good (encoder, connected motor, receiver etc) as they have already been fully tested using the timing method.

the code is here:
Code:
#include "16f628a.h"
#fuses INTRC_IO, NOWDT, NOPROTECT, NOPUT, NOBROWNOUT, NOLVP
#use delay(clock=4000000)

#include "stdlib.h"

#byte PORTB=0x06
#byte encoder=0x06

#define DAV      PIN_B0  // ribbon cable line X5 - Data AVailable line to TRW
#define X4       PIN_A3  // ribbon cable line X4 - used for debugging purposes
#define SPI_REQ  PIN_A2  // ribbon cable line X3 - pos. request line from TRW PIC
#define SPI_SCO  PIN_A0  // ribbon cable line X2 - serial clock out to TRW PIC
#define SPI_SDO  PIN_A1  // ribbon cable line X1 - serial data out to TRW PIC

#define TEST      PIN_A6    // test pad on PC board

#define NumOfBits 16
//#byte NumOfBits =  16

////////////////////////////////////////////////////////////////////////////////
///// Global Variables
////////////////////////////////////////////////////////////////////////////////
long RPM, gl_counter;
int16 loops, meas_done, sensor_pulses;

////////////////////////////////////////////////////////////////////////////////
///// detect_rb_change() - interrupts on disk edges to update position counter
////////////////////////////////////////////////////////////////////////////////

#int_TIMER2
void  TIMER2_isr(void)
{
  if(loops)
    {loops--;}
  else
    {meas_done=true; }   
}


#INT_RB
void detect_rb_change() {

sensor_pulses++;
output_high(DAV);  // signal to TRW that new data is available
/*

   static int old;
   static int new;
   static int value;

// output_high(TEST); // test pin output
   output_high(DAV);  // signal to TRW that new data is available

   new=portb;
   value=new^old;

   //  'value', now has the bit set, which has changed
   if (value & 0x10) {
      // low bit has changed
      if (new & 0x10) {
         // rising edge on A
         if (new & 0x20) --gl_counter;
         else ++gl_counter;
      }
      else {
         // falling edge on A
         if (new & 0x20) ++gl_counter;
         else --gl_counter;
      }
   }
   else {
      // high bit (B) has changed
      if (new & 0x20) {
         // rising edge on B
         if (new & 0x10) ++gl_counter;
         else --gl_counter;
      }
      else {
         // falling edge on B
         if (new & 0x10) --gl_counter;
         else ++gl_counter;
      }
   }
   old=new;

//   output_low(TEST);     // test pin output
*/
} //  detect_rb_change()
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
///// sendData(long data)
////////////////////////////////////////////////////////////////////////////////
void sendData(long data) {   
  BYTE i;

  delay_ms(1);

  for(i=0; i<(NumOfBits); i++) {
    if((data & 1)==0) {
      output_low(SPI_SDO);
    }
    else {
      output_high(SPI_SDO);
    }
    data=data>>1;
    output_high(SPI_SCO);    // clock high
    delay_us(5);             // some delay here necessary
    output_low(SPI_SCO);     // clock low
 // delay_us(5);
   }
}
//////////////////////////////////////////////////////////////////////////////// 

                                         
////////////////////////////////////////////////////////////////////////////////
///// main()
////////////////////////////////////////////////////////////////////////////////

void main(){

  enable_interrupts(INT_RB);
  enable_interrupts(INT_TIMER2);// To create a measurement window
  PORT_B_PULLUPS(FALSE);

  setup_comparator(NC_NC_NC_NC);
  setup_ccp1(CCP_OFF);
       
  setup_timer_0(RTCC_INTERNAL);
  setup_timer_1(T1_DISABLED);
  setup_timer_2(T2_DIV_BY_16,195,16);// overflow every 49.92ms

    // unused runtime pins set as outputs to minimize noise susceptibility
  output_low(PIN_A4);
  output_low(PIN_A7);
  output_low(PIN_B1);
  output_low(PIN_B2);
  output_low(PIN_B3);
  output_low(PIN_B6); // also PRG clock
  output_low(PIN_B7); // also PRG data

  delay_ms(500);
  gl_counter = 0;
  sensor_pulses = 0;
  enable_interrupts(GLOBAL);
  meas_done=false;
  loops = 10;         // 49.92ms * 10 = 499.2ms
                 // 49.92ms * 20 = 998.4ms
                      // This is the measurement window time
  set_timer2(0);   
 
  while(true) {

while(meas_done){                     //0.5s Tick has occurred
   RPM = (((sensor_pulses/0.5)/52) * 60 );  //Calculate RPM: sensor_pulses / 0.5s / 52 * 60
   sendData( RPM);                     //send the data via external hardware
   output_low(DAV);                     //external control pin   
   meas_done=false;                     //reset
   loops = 10;                        //reset
   sensor_pulses = 0;                  //reset
   set_timer2(0);                      //reset
}



Thanks
Carl
temtronic



Joined: 01 Jul 2010
Posts: 9246
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Tue Nov 18, 2014 2:39 pm     Reply with quote

hmm....
RPM = (((sensor_pulses/0.5)/52) * 60 ); //Calculate RPM: sensor_pulses / 0.5s / 52 * 60

perhaps it's a 'math thing'.
multiplying(*2) then dividing(by 52) then multiplying(times 60) seems 'silly' to me and possibly prone to errors.
I'm pretty sure you can simplify the equation to a more 'robust' algorithm.

just thinking...it might be worth a few minutes to recode/recompile/retest and report back.....


Jay
rnielsen



Joined: 23 Sep 2003
Posts: 852
Location: Utah

View user's profile Send private message

PostPosted: Tue Nov 18, 2014 3:18 pm     Reply with quote

You didn't say if you need to know the direction of the encoder's rotation. If you don't, and the encoder has an index signal available, You could simply count the index pulse which would only happen once per revolution...

Ronald
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

PostPosted: Tue Nov 18, 2014 4:49 pm     Reply with quote

Thanks for replying.

Simplifying the maths doesn't make any difference.
Initially i just transmitted the 'sensor_pulses' alone, so no maths at all, and then this value was plugged into the rpm equation (manually with calculator), and the result is exactly the same.

The circuit design is already done, so ideally i do not want to make any hardware changes. Direction detection is required, but i can look at that later, however this does mean that the quad detection firmware (currently commented out) will be required.

I know i could use an index as the 'tick' instead of the timer, but the encoder design does not include an index, hence the reason for using the timer. and would it make a difference anyway?

I think trying to work out why the 'sensor_pulses' value is out is the key, but i can't work it out.
asmboy



Joined: 20 Nov 2007
Posts: 2128
Location: albany ny

View user's profile Send private message AIM Address

PostPosted: Tue Nov 18, 2014 6:40 pm     Reply with quote

what var type results from an int16 /.5 ??

does the compiler optimize the .LST file to treat it as a binary *2
or is it float ??

i too am puzzled by the multi-constant mess-o-math Jay metioned.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Tue Nov 18, 2014 7:24 pm     Reply with quote

Your rpm is 10% longer than it should be. What thing in your code is
associated with the number 10 ? Answer: Your loop counter.
Look closely at that. Is it really counting off 10 interrupts before it sets
meas_done=true ?
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 3:00 am     Reply with quote

Try changing this
Code:
  loops = 10;                        //reset
to this
Code:
  loops = 1;                        //reset
with the appropriate change to your RPM calculation.

See if that tells you anything.

Mike
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 3:16 am     Reply with quote

Preview
PostPosted: Wed Nov 19, 2014 3:13 am Post subject:
Hi PCM,

I will look into it, but potentially what could be the issue?

Could the two interrupts be in conflict?
so if the program is already in the int_rb routine, and the int_timer fires, would it go and do that interrupt (nested interrupts sort of), or would it be missed (or delayed) therefore increasing the time (so 0.5s becomes 0.55 etc)
I suppose, I could prioritise the timer interrupt (never done this before), so that the timer will always fire first, (even at the expense of missing a pulse, assuming the error is tolerable).

I will look into it and get back to you thanks.

ASM/Jay it is not the maths or type cast, as the result ended up being the same, when I only viewed 'sensor_pulses' alone. but I agree with your point and will type cast correctly when it is required (and simplify the equation) thanks

Hi Mike,

yes obvious plan thanks - I will try it.

Carl
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 6:15 am     Reply with quote

Its way out!

changed the loops to 'one' = tick every 49.92mS.
sensor_pulses = 180 at 2000rpm.

plugging 180 into the equation gives:

RPM = ((180/0.04992) / 52) * 60
RPM = 4160

either my count is way out for some reason, or the formula above is incorrect?

EDIT: sorry, if loops = 1, then that = tick every 100ms.
so:

RPM = ((180/0.09984) / 52) * 60
RPM = 2080.

Which is better (under 5%), and indicates that the 'loops' is going wrong somewhere when I increase the loops to 10 or 20. But this is exactly the opposite of what it is supposed to do isn't it (accumulated counts over longer time period = better resolution).
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 8:58 am     Reply with quote

You can investigate this three ways:

1. Look at the #int_timer2 routine and mentally check how many
interrupts it takes before meas_done goes TRUE.

2. Using a lined piece of paper, write down each interrupt, and the value
of loops after the interrupt has occurred, and when meas_done goes TRUE.

3. Strip your program down and test the loop frequency in hardware.
Sync on a pulse inside the isr with your oscilloscope (assuming your
scope has a built-in frequency counter. Mine does). I get about 1.8 Hz.
Your code expects 2.0 Hz. What do you have to do, to make it run at 2.0 Hz ?
Code:

#include <16F886.h>
#fuses INTRC_IO, NOWDT
#use delay(clock=4M)
#use rs232(baud=9600, UART1, ERRORS)

int16 loops, meas_done;


#int_TIMER2
void  TIMER2_isr(void)
{
  if(loops)
    {loops--;}
  else
    {
     meas_done=true;

     output_high(PIN_B1);  // *** Trigger pulse for scope
     delay_us(100);
     output_low(PIN_B1);
    }   
}


//===================================
void main()
{
setup_timer_2(T2_DIV_BY_16,195,16); // overflow every 49.92ms
set_timer2(0);   
enable_interrupts(INT_TIMER2); 
enable_interrupts(GLOBAL);

loops = 10;

while(TRUE)
  {
   if(meas_done)
     {
      meas_done = FALSE;
      loops = 10;
     }
  }

}


Last edited by PCM programmer on Wed Nov 19, 2014 10:08 am; edited 1 time in total
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 10:04 am     Reply with quote

Hi PCM,

Thanks so much for replying.
whilst you have written this, I have worked it out.

The way I was using the formula was incorrect.

I thought that 1 loop meant one timer period = 49.98ms
however 1 loop is 2 x timer periods
3 loops is 4 x timer periods etc

results measured and then recalculated as below:

Loops sensor_counts Equation RPM
0 94 (94/0.05/52 * 60) 2169
1 180 (180/0.1/52 * 60) 2076
2 266 (266/0.15/52 * 60) 2046
5 524 (524/0.3/52 * 60) 2015
10 961 (961/0.55/52 * 60) 2016
20 1824 (1824/1.05/52 * 60) 2004


So the resolution increases, when the time capture is increased Smile
Of course the drawback here is increased refresh rate, but I can alter this by increasing the encoder PPR to say 50ppr (200 edges). Wouldn't this have the same effect as extended time capture?

Thanks, I can now move onto figuring out how to detect the direction and transmit it along with the RPM at the same time - nice!

Thanks
Carl
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 10:17 am     Reply with quote

Your theory is that your loop doesn't run at 20 Hz (49.98ms period).
I made a test program to test your theory. The following program
produces a pulse on my scope that syncs at 20.017 Hz.
Code:

#include <16F886.h>
#fuses INTRC_IO, NOWDT
#use delay(clock=4M)
#use rs232(baud=9600, UART1, ERRORS)

int16 loops, meas_done;


#int_TIMER2
void  TIMER2_isr(void)
{
     output_high(PIN_B1);  // *** Trigger pulse for scope
     delay_us(100);
     output_low(PIN_B1);
}


//===================================
void main()
{
setup_timer_2(T2_DIV_BY_16,195,16);// overflow every 49.92ms
set_timer2(0);   
enable_interrupts(INT_TIMER2);// To create a measurement window
enable_interrupts(GLOBAL);

while(TRUE);

}


I think you still don't exactly get what's wrong. It's that your interrupt
loop takes 11 counts before meas_done is True. It should take 10 counts.
It's because your timer routine logic is wrong. You can either reduce
the loop counter to 9 or fix the logic. That's what you have to do.
And start using test programs like I do, to verify code in hardware.
Or use the other methods that I added to my previous post in an edit.
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 10:29 am     Reply with quote

Hi PCM,

Let me have a think about this tomorrow - thanks for all your help as always.
Mike Walne



Joined: 19 Feb 2004
Posts: 1785
Location: Boston Spa UK

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 11:29 am     Reply with quote

carl wrote:
Its way out!

changed the loops to 'one' = tick every 49.92mS.
sensor_pulses = 180 at 2000rpm.

plugging 180 into the equation gives:

RPM = ((180/0.04992) / 52) * 60
RPM = 4160

either my count is way out for some reason, or the formula above is incorrect?

EDIT: sorry, if loops = 1, then that = tick every 100ms.
so:

RPM = ((180/0.09984) / 52) * 60
RPM = 2080.

Which is better (under 5%), and indicates that the 'loops' is going wrong somewhere when I increase the loops to 10 or 20. But this is exactly the opposite of what it is supposed to do isn't it (accumulated counts over longer time period = better resolution).
No surprise.
When you set loops to 1 you actually go round twice!.
Hence your reading is roughly double what it should be.
The maths is not your biggest problem, yet.
What PCM_p and I are both saying is "Your logic is flawed".
Do what he tells you and work it out, one way or another.

Mike
carl



Joined: 06 Feb 2008
Posts: 240
Location: Chester

View user's profile Send private message

PostPosted: Wed Nov 19, 2014 11:42 am     Reply with quote

thanks mike,

totally understand and agree.
the logic is not correct. will look at this again tomorrow.

carl
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