View previous topic :: View next topic |
Author |
Message |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
Counting Pulses Method 'Tachometer' |
Posted: Tue Nov 18, 2014 9:53 am |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
temtronic
Joined: 01 Jul 2010 Posts: 9178 Location: Greensville,Ontario
|
|
Posted: Tue Nov 18, 2014 2:39 pm |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
rnielsen
Joined: 23 Sep 2003 Posts: 852 Location: Utah
|
|
Posted: Tue Nov 18, 2014 3:18 pm |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
|
Posted: Tue Nov 18, 2014 4:49 pm |
|
|
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. |
|
![](templates/subSilver/images/spacer.gif) |
asmboy
Joined: 20 Nov 2007 Posts: 2128 Location: albany ny
|
|
Posted: Tue Nov 18, 2014 6:40 pm |
|
|
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. |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Tue Nov 18, 2014 7:24 pm |
|
|
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 ? |
|
![](templates/subSilver/images/spacer.gif) |
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Wed Nov 19, 2014 3:00 am |
|
|
Try changing this
Code: | loops = 10; //reset | to this
with the appropriate change to your RPM calculation.
See if that tells you anything.
Mike |
|
![](templates/subSilver/images/spacer.gif) |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
|
Posted: Wed Nov 19, 2014 3:16 am |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
|
Posted: Wed Nov 19, 2014 6:15 am |
|
|
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). |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Nov 19, 2014 8:58 am |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
|
Posted: Wed Nov 19, 2014 10:04 am |
|
|
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
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 |
|
![](templates/subSilver/images/spacer.gif) |
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Nov 19, 2014 10:17 am |
|
|
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. |
|
![](templates/subSilver/images/spacer.gif) |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
|
Posted: Wed Nov 19, 2014 10:29 am |
|
|
Hi PCM,
Let me have a think about this tomorrow - thanks for all your help as always. |
|
![](templates/subSilver/images/spacer.gif) |
Mike Walne
Joined: 19 Feb 2004 Posts: 1785 Location: Boston Spa UK
|
|
Posted: Wed Nov 19, 2014 11:29 am |
|
|
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 |
|
![](templates/subSilver/images/spacer.gif) |
carl
Joined: 06 Feb 2008 Posts: 240 Location: Chester
|
|
Posted: Wed Nov 19, 2014 11:42 am |
|
|
thanks mike,
totally understand and agree.
the logic is not correct. will look at this again tomorrow.
carl |
|
![](templates/subSilver/images/spacer.gif) |
|