Pages: 1 [2] 3 4   Go Down
Author Topic: Calculating RPM from encoder by timing pulses  (Read 10290 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I've never actually tried to figure out the acceleration, it wouldn't be easy to do.  I'll get the dyno down to the house during the week and take some measurements, then get back to you.

In the meantime I'll program up what you're suggesting as well.  I should be able to try the different options and see how the graphs look.
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I look forward to hearing about the results
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Should I expect to get some spurios data through the serial port at 115200 baud?  For example the following is form the middle of a run of about 4000 samples

Cnt1     Cnt2     Millis      pulseIn RPM
6231      10084      35119      904      5309
6232      10085      35123      922      5206
6233      10086      35127      929      5167
623                        
6336      10189      35566      1043      4602
6337      10190      35570      879      5460
6338      10191      35574      1041      4611

Note the one in the middle that is missing half its data.  Sometimes I see lines with missing EOL characters or lines half missing like this one.  It happens both in my Processing app and in the Arduino Serial Monitor.  They could be filtered pretty easily I'm sure, just wondering what they might be telling me?

Thanks!
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here's one from further up the page on the same run

5416      9269      32019      189      25402                        
5417      9270      32022      201      23885                        
5418      9271      32026      187      256735544      9397      32468      293      16384
5545      9398      32473      303      15843                        
5546      9399      32476      298      16107                        
5547      9400      32480      300      16000                        


Odd huh?
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It could be a serial buffer is sometimes overflowing. Try reducing the number of chars sent per sample or increase the time between samples.
Logged

Manchester (England England)
Offline Offline
Brattain Member
*****
Karma: 618
Posts: 33945
Solder is electric glue
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Are you using a PC? If so the problem could be at the receiving end I have seen a lot of PCs that can't cope with data arriving so fast.
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Zenith, not sure if you are still trying to send at intervals proportional to the rotation rate, but if so I really think something along the lines of post#12 is the best way to solve the problem if you can't reduce the number of characters sent for each sample.

But no point banging on about this, I will watch with interest and see how you get on.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Yep using a PC with Windows XP.  It's probably not a big deal, when it comes around to the final software I'll just code something to watch out for it and use the slowest baud rate I can.

mem - I definitely agree with you on sampling at a given time and will do this for the final version, I think it will make graphing etc. much more straightforward anyway.  For the time being though I'm trying to test the various parts, so just sending every sample (without getting into the possibility of slight errors in the timings I mentioned above) seems the most robust way of doing it.  I don't trust the encoder, am not too sure if the Arduino is accurate enough, not too sure of my external counter dividing down the pulses etc etc. so I'm trying to elimate all possible problem areas.

Which leads me to the next bit.  I got some nice looking graphs of the increasing RPM as the bike accelerated the drum, however when you zoom in on it it the data is quite sporadic, it jumps up and down quite a bit.  This doesn't cause a problem for a simple RPM graph, but once you start subtracting one value from the previous one to determine acceleration you get very erratic data.  In the list of samples you see swings of 50-100 microseconds constantly.  As I hope the pulseIn command is a lot more accurate then that, the only thing I can blame is the encoder.  I've been a bit suspicious of it from the start anyway so I've ordered a Hall-effect ferrous metal gear sensor (SNDH-T4L-G01) which should arrive later in the week.  These are the sensors people tend to use building dynos, I was just hoping an encoder would work because they're so simple to work with.

Thanks for the help so far, I'll be back to update whenever the new sensor arrives in.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Just thought I'd update this a bit now that I'm finally getting somewhere!



Hopefully that pic above works, it shows the bike starting in 1st gear, shifting up 3 gears, doing a bit of an acceleration run in 4th then slowing and again in 5th and finally a big run up in 6th gear.  1300 RPM of the drum equates to about 12000 engine RPM, or 60MPH wheel speed.  The drum is let coast down for a while, then you see the graph drop off more sharply as I hit the brake to slow it a bit faster.  The X axis is the nuber of samples by the way, so that run was about 1200 samples.

So success at last!  I've bolted an old clutch basket to the drum which has a 63 tooth gear on it, then using the Hall effect sensor mentioned above to pickup the teeth.  The output from the sensor is connected to a 74HC4040AP binary ripple counter to divide down the pulses, I'm taking the 2^6 output, so for every 64 pulses from the sensor I get 1 long pulse.  The output from the counter is connected to one of the digital interrupt pins on the Arduino so I can time the pulses.  From the Arduino the data is sent to the PC, graphed live onscreen with Processing and also saved to a CSV for now.

What I'm using for timing on the Arduino is Don's code from http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1226257074/5 .  On every rising edge from the binary counter I grab the current hpticks().  The value is stuck in a small array (buffer).  The loop() function is constantly checking this buffer to see if there is any data in there, if there is it sends it over serial to the PC.  The output of one line would be something like "1,8534563,54332" where 1 is the current size of the buffer (so I can watch for overflows if the buffer is filling faster then the serial port is sending), 8534563 is the result from hpticks() and 54332 is the time in microseconds this pulse took.  This way I can tell if I'm overwhelming the serial port, as per the problem discussed a few posts earlier.  It also allows the PC to "slowdown" during a run if something happens and not end up missing values, so a bit of a safety margin.  Out of interest I hooked this up to a 555 timer, at 115200 baud I was able to pulse the interrupt all the way down to every 2000 microseconds and have no problems with the buffer filling or even getting 1 slot ahead of the sending, so the Arduino should be capable of sending faster again, I just didn't have the resistors or capacitors to speed the 555 up any more smiley.  At 1 pulse per RPM and a max RPM of about 1200-1300 I didn't really need it to be any faster then 50,000 microseconds anyway.

On the PC side I'll write a proper graphing program at some stage, but for now I just used some Processing (pretty cool piece of kit actually, certainly much faster then trying to work in C++ from scratch!) to grab the values from the serial port (code from the David Mellis article on here) and push it into a CSV so I could graph it in Excel.  Last night I also got it to draw the graph live as the run is done, which is pretty cool to watch and gives you a good idea if things are working or not.  Here's a screenshot from the middle of the same run as graphed above.




I've had an intermittent problem over the last couple of days with almost random values being read into the CSV file.  While doing a run earlier it turned out that moving the USB cable around where it connects to the laptop made the problem worse or cured it depending on where you moved it to.  I changed cable but the problem is the same, so I can only assume it's a loose USB port or something.  Worth keeping in mind if anybody has weird problems in future.  I *think* the odd line is getting lost on it's way across because the amount of error is always out by one full RPM of the drum.  Had this been TCP the packet would have been sent again, and I thought this was the case for serial as well, but maybe something is missing in the USB-Serial implementation?  Regardless I'll try another laptop or something tomorrow.  Not sure what I can do longterm though as I can't guarantee every laptop will be perfect and all other devices work fine in this one.  Any thoughts?

That's it for now, if anybody has any thoughts or questions let me know.  Have to say I'm delighted with how the Arduino has worked out, I'm constantly looking for more things I can use one for now to give me an excuse to buy a couple of the other types smiley.

Thanks,
Philip
« Last Edit: December 30, 2008, 06:46:32 pm by Zenith63 » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

A bit more testing today and it seems the problem is not with the USB port at all, I was using an old extension lead to power a lab power supply which was powering my sensor and the binary counter.  Moving the extension lead caused the counter to be incremented randomly.  Got a new extension lead and now I only see one or two spurious values per run.  Think I need to look at some shielding or better grounding...
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Just to show where this is going, here's a graph of two runs I did yesterday on the dyno.  In theory the two graphs should match up completely, but that never really happens, too many variables.  For instance you can see in one of the runs at the start I rolled the throttle for one of them, while the other I just went straight to full throttle.  Also there was one of the earlier mentioned 'spurious' values up at about 12200 on one of the runs, so there's an odd peak.

X axis is engine RPM.  Y axis is uncorrected horsepower, which isn't quite right at the moment, should be 5-6 HP higher, I think my drum is heavier then weighed or something...

Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6255
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It's looking good, have you posted the Arduino code somewhere?
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 38
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Here it is.  I usually remove most of the serial.prints when doing runs, they're just there for debugging.  The only important one is sending the microsecond length of the pulse.

Code:
/*
/  Times TTL pulses (rising-rising edge, interrupt driven) on pin 2 and stores them to a buffer,
/   then sends them over the serial port as microsecond figures in an interruptable loop.
/
/  Philip Harrison December 2008 (All hpticks code by Don Kinzer [http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1226257074/5])
*/

#define mTimeBufferSize 64 // MUST only be 2^x value, such as 2, 4, 8, 16, 32, 64, 128
#define timer0TicksToMicroseconds(t)  ((t) * 64L / (F_CPU / 1000000L))  // Function to convert the timer ticks counted by the hpticks() function to microseconds.  Only works with CPU frequencies of 2^x numbers, such as 8MHz or 16MHz

volatile unsigned long mTimesBuffer[mTimeBufferSize]; // Buffer to store values before they are sent over serial.
volatile unsigned long mTimesBufferStorePtr=0; // Pointer into the buffer showing where we are currently storing values to.  Note this pointer does not loop over, it counts up continually.
volatile unsigned long mTimesBufferSendPtr=0; // Pointer into the buffer showing where we are currently sending values over serial from.  Note this pointer does not loop over, it counts up continually.

unsigned long lastSent = 0;


extern "C"
{
  // Defined in wiring.h
  extern unsigned long timer0_clock_cycles;
  extern unsigned long timer0_millis;
};

// Define the timer interrupt flag register if necessary
#if !defined(TIFR0)
  #if defined(TIFR)
    #define TIFR0 TIFR
  #else
    #error AVR device not supported
  #endif
#endif


unsigned long hpticks(void)
{
  uint8_t sreg = SREG;
  cli();
  
  // Get the current value of TCNT and check for a rollover/pending interrupt
  uint16_t t0 = (uint16_t)TCNT0;
  if ((TIFR0 & _BV(TOV0)) && (t0 == 0)) t0 = 256;

  // Get a snapshot of the other timer values
  unsigned long clock_cycles = timer0_clock_cycles;
  unsigned long millis = timer0_millis;
  SREG = sreg;

  // Compute the number of Timer0 ticks represented by the data
  unsigned long timer0_ticks = (((millis * (F_CPU / 1000L)) + clock_cycles) / 64) + t0;

  return timer0_ticks;
}


void setup()
{
  Serial.begin(115200);
  pinMode(2, INPUT);
  attachInterrupt(0, onChangeX, RISING);
}


void loop()
{
  if(mTimesBufferSendPtr < mTimesBufferStorePtr)
  {
    // The send pointer is behind the store pointer so there must have been a value written that we can send.
    
    Serial.print(mTimesBufferStorePtr - mTimesBufferSendPtr); // Send how far the send pointer is behind the store pointer, if this gets higher then mTimeBufferSize a buffer overflow has occured and the data will not be accurate
    Serial.print(",");
    Serial.print(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)]); // Send the next hpticks count in the buffer, buffer is incremented later.  Note hpticks() is not time, it needs to be converted
    Serial.print(",");
    Serial.println(timer0TicksToMicroseconds(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)] - lastSent)); // Subtract the previous hpticks from this hpticks, then convert this to microseconds to get the pulse time for this pulse

    lastSent = mTimesBuffer[mTimesBufferSendPtr++ & (unsigned long)(mTimeBufferSize-1)];
  }
}


void onChangeX()
{
  mTimesBuffer[mTimesBufferStorePtr & (unsigned long)(mTimeBufferSize-1)] = hpticks(); // Put the current ticks count into the next space in the buffer
  mTimesBufferStorePtr++;
}

I'm away for a week so forgive me if I don't reply, but I'm interested to here if you have any thoughts...
« Last Edit: January 03, 2009, 02:27:37 pm by Zenith63 » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 17
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I wouldn't mind checking out your Processing code too.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 5
'merica
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi, I'm also building a dyno using similar ideas.  One difference being that I am measuring the Engine RPM as well as roller speed.  The reason for this is to account for tire slip on the rollers.  With this setup, there isn't any account for slip.  In testing phases, I put a 1400cc bike on there and ran up to a roller rpm of close to 4000.  The bike has about 200 ponies, so the tires were pretty gummy after a few runs.  Something to think about...

I've tried many ways to go about this, but I think a good way would be in a fashion similar to the what mem posted in reply 12.  I did actually try using that sketch but the inaccuracy of the millis made that sketch not perform to my expecations.  Anybody up for modifying that code to be more accurate at measuring periods?

Here's what I'm currently doing to measure teeth.  The spark measuring has been taken out to reduce the code, but it's pretty much identical to measuring gear teeth.  If anyone can suggest how this could be more accurate please let me know.  To summarize what's going on, I'm taking the average of the periods occurring over a known time interval.  So to calculate acceleration rate at the computer, I take a change in rates (teeth/sec or rad/sec or whatever) then divide it by the known interval.  I'm actually still in the testing phases right now so I don't have much data readily available.  Except I can tell you that using the below code, I wouldn't recommend measuring an input frequency above 4kHz.  I tried it all the way up to 12kHz, but yeah, the readings were all over the place. I'm planning to reduce down my gear teeth using a binary counter.  I have a 168 tooth flywheel, and plan to divide down the number of teeth with a binary counter to improve accuracy of time measurements between pulses.  

Code:
/*------------------------------------------------------------------------------------*/
// Measures periods of a square wave over .05 seconds, averages them, then sends them
// serially. So to calculate an acceleration rate of a gear every .05 seconds, you would
// need to account for the inaccuracy of timing exactly .05 seconds, therefore a truer time
// between measurements is calculated by taking a snapshot of timer0 ticks right before
// detaching interrupts.  At low frequency it's pretty accurate at calculating periods,
// but at higher frequencies, not so much.  If using an high ppr encoder, consider
// a counting scheme, or else divide down the pulses.
//
// Daniel Golden - dgolden11@bellsouth.net
//
// hpticks() by Don Kinzer
//     Taken from : http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1226257074/5#5
/*------------------------------------------------------------------------------------*/

#include <Metro.h>                    // a timing library

volatile double teeth;                // number of gearteeth that occurred
volatile double GTpwACC;              // accumulated periods between gearteeth
volatile boolean ignrGearTooth = true;       // ignore first gear tooth of sequence
volatile boolean lastCountedTeeth = false;   // last function was as freq counter (teeth)

Metro serialMetro = Metro(50);        // instantiate a timing instance (50ms)

extern "C"{                           // defined in wiring.h
  extern unsigned long timer0_clock_cycles;
  extern unsigned long timer0_millis;
}

// define the timer interrupt flag register if necessary
#if !defined(TIFR0)
  #if defined(TIFR)
    #define TIFR0 TIFR
  #else
    #error AVR device not supported
  #endif
#endif

// converting ticks to microseconds
#define ticksToMicros(t)  ((t) * 64L / (F_CPU / 1000000L))

/*------------------------------------------------------------------------------------*/
void setup()
{
  TCCR0A = 0x00;                      // timer0 in normal mode
  TCCR0B = 0x03;                      // Prescalar 64 => FREQ = 16MHz/64 = 250kHz = 4us
  TIMSK0 = 0x01;                      // enable timer/counter0 overflow interrupts
  TIFR0 |= _BV(TOV0);                 // timer0 interrupt flag enable
  
  Serial.begin(115200);               // setup serial communication
  
  attachInterrupt(0, MsrTthPW, RISING);  // attach interrupt to PWM pin 2 of Arduino Mega
}

/*------------------------------------------------------------------------------------*/
void loop()                           // main program loop
{    
  if (serialMetro.check() == 1) {     // if it's been 50 ms
    CalcAndSend();                    // calculate and send periods
  }
}


/*------------------------------------------------------------------------------------*/

void MsrTthPW()
{
  static double CNT;                  // timer0 count in ticks
  static double lastCNT;              // previous timer0 count in ticks
  
  CNT = hpticks();                    // get Timer0 counter value (ticks)
  if (ignrGearTooth == false){        // skip first tooth
    GTpwACC += CNT - lastCNT;         // add to accumulator (ticks)
    teeth += 1;                       // increment tooth sensed
    lastCNT = CNT;                    // store last timer count (ticks)
  }else{
    lastCNT = CNT;                    // get base value for first tooth
    ignrGearTooth = false;            // calculate PW on next tooth
  }
}

/*------------------------------------------------------------------------------------*/
void CalcAndSend()                    // send data (approx every .05 seconds)
{
  static double t;                    // temp geartooth variable
  static double ta;                   // temp geartooth accumulator variable
  static double t_prd;                // average geartooth period
  static double m_start;              // tick count at measurement start
  static double m_end;                // tick count as measurement end
  static double m_prd;                // measurement period (ticks)-->(micros) which
                                      // is used for calculating acceleration

  m_end = hpticks();                  // get measurment end time (ticks)
  detachInterrupt(0);                 // disable gear tooth interrupts
  
  // set temp variables
  t = teeth;                          // set temp toothcount variable
  ta = GTpwACC;                       // set temp geartooth pulse width accumulator variable
  
  // reset original variables  
  teeth = 0;                          // reset tooth count
  GTpwACC = 0;                        // reset geartooth pulse width accumulator (ticks)
  ignrGearTooth = true;               // ignore first tooth to get base time for measuring periods
  
  attachInterrupt(0, MsrTthPW, RISING);  // attach interrupt to start measuring pulses again
    
  m_prd = m_end - m_start;            // calculate measurement period (ticks)
  m_start = m_end;                    // set next measurment start time (ticks)

  m_prd = ticksToMicros(m_prd);       // convert ticks to micros
    
  if (t > 0) t_prd = ticksToMicros(ta) / t;  // If not DIV/0 calculate avg gear tooth period (usec)
    else t_prd = 0;                          // Else set it to zero
    
  Serial.print(t_prd);                // send tooth period
  Serial.print(",");                  // ,
  Serial.print(m_prd);                // send measurment period (to calc acceleration)
  Serial.println(";");                // ;

}

/*------------------------------------------------------------------------------------*/
// This routine returns a value representing the number of Timer0 ticks that have
// occurred since the device began running.  Timer0 ticks occur at a rate of
// F_CPU / 64.  This fact can be used to convert Timer0 ticks to microseconds, if
// desired.  Note, however, that the expression for doing so must be constructed
// carefully to avoid issues relating to integer truncation and overflowing a
// 32-bit value.  In general, it will be simpler to perform such conversions on
// the difference between two values returned by this routine when that difference
// is known or expected to be small compared to the range of a 32-bit value.
//
double hpticks(void)
{
  uint8_t sreg = SREG;
  cli();
  
  // get the current value of TCNT and check for a rollover/pending interrupt
  uint16_t t0 = (uint16_t)TCNT0;
  if ((TIFR0 & _BV(TOV0)) && (t0 == 0))
    t0 = 256;

  // get a snapshot of the other timer values
  double clock_cycles = timer0_clock_cycles;
  double mmillis = timer0_millis;
  SREG = sreg;

  // compute the number of Timer0 ticks represented by the data
  double timer0_ticks = (((mmillis * (F_CPU / 1000L)) + clock_cycles) / 64) + t0;

  return(timer0_ticks);
}



As I said, I'd like to hear any suggestions.   smiley
Logged

Pages: 1 [2] 3 4   Go Up
Jump to: