Calculating RPM from encoder by timing pulses

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

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

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

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.

/*
/  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...

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

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.

/*------------------------------------------------------------------------------------*/
// 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. :slight_smile:

Hi Golden,

My plan was definitely to measure engine RPM down the road, I was trying to phase things a bit to avoid being overwhelmed by too many things to troubleshoot. Turns out it didn't work, I kinda lost interest in it in the end. Having said that we're only talking max 50hp motocross bikes so I doubt tire slippage would be a major factor for me.

I gave mem's post some thought but I was concerned with the idea of 'interval sampling' because you're measuring teeth here, not constant speed. You can't say at exactly time X tell me what speed the drum is doing. You have to say at time X wait for the next tooth to go by, time it and see what speed you're at. But that waiting period can be any length of time up to almost one full tooth, leading to a pretty big margin or error in my opinion. Maybe I misunderstood it, I'm not sure.

Did you have a look at my code and give it a try? In the last few runs I did it seemed to be quite accurate and reliable. My remaining problem was about once per run I seemed to be missing a tooth so seeing spikes on the graph. I'm pretty much inclined to blame the sensor for this at this stage, but as I say I haven't looked at any of this in a while. I was only using one of the sensor outputs, my plan was to use the phase shifted A and B outputs from the sensor to do some signal confirmation using a few logic gates. I already have the sensor output going through a ripple counter to divide down the number of pulses so it shouldn't be a big deal to add this check.

I was away skiing for a week and had this on my mind, came back the thread and see I never posted the Processing code, so just in case it's any use to anybody here it is. At this stage I can't remember who I copied what from or how well this is commented so please keep this in mind if it doesn't work first time!

//
//
// Stream serial data to CSV + live graph
//
// Philip Harrison, December 2008
//
//

import processing.serial.*;

//
// Edit following values only
//
boolean drawGraph = true; // Should we draw a graph live or just spool the data into the CSV file?

boolean simulateRunFromTextFile = true; // Set TRUE to load the values from a text file instead of the serial port
int simulationSpeed = 1; // Speed values appear for the simulation, lower is faster
String simulationInputFile = "text3.txt"; // Note this path is relative to the Processing sketch folder so you'll need to put the file in there

String outputFilePath = "c:\\data\\lines.csv";


int windowWidth = 1024; // Height and width of the graph window.  Can be any size, graph will scale.
int windowHeight = 768;

int numPointsWide = 50; // The number of points to show on the X axis.  As the number is reached the earliest values are pushed off, so you only see a snaphost of the data values, the latest 'numPointsWide'.

//
// End editable values
//



Serial port;

String buff = "";
String[] buffs = new String[50000];
int currBuffPtr=0;

int NEWLINE = 10;
PrintWriter output;

ArrayList values = new ArrayList(numPointsWide); // Store the last numPointsWide values received so we can graph them.

String importedLines[]; // Used to import values from a file if doing a simulated run

Double mMaxYValue;
Double mMinYValue;

int currLineNum = 0;

int currSimulationCnt = simulationSpeed;


void setup()
{
  size(windowWidth, windowHeight);

  println("Available serial ports:");
  println(Serial.list());
  
  // Uses the first port in this list (number 0).  Change this to
  // select the port corresponding to your Arduino board.  The last
  // parameter (e.g. 9600) is the speed of the communication.  It
  // has to correspond to the value passed to Serial.begin() in your
  // Arduino sketch.
  // If you know the name of the port used by the Arduino board, you
  // can specify it directly like this.
  //port = new Serial(this, "COM1", 9600);
  port = new Serial(this, "COM3", 115200);  

  if(simulateRunFromTextFile)
    importedLines = loadStrings(simulationInputFile);

  output = createWriter(outputFilePath);

  // Create the array of points for graphing
  for(int i = 0; i < numPointsWide; i++)
  {
    values.add(i,(new Double(0.0)));
  }
   
   mMaxYValue =(double)0.0;
   mMinYValue=(double)0.0;
}

// Finds the current max and min value in the array of points for graphing
//  Probably could optimise this as it must slow reading data
void setMaxMinYValues()
{
  double mTempMaxYValue = 0;
  double mTempMinYValue = 9999999; 
  
  for(int i = 0; i < numPointsWide; i++)
   {
     if(((Double)values.get(i)).doubleValue() > mTempMaxYValue)
       mTempMaxYValue = ((Double)values.get(i)).doubleValue();
     
     if(((Double)values.get(i)).doubleValue() < mTempMinYValue)
       mTempMinYValue = ((Double)values.get(i)).doubleValue();
   }
   
   mMaxYValue= mTempMaxYValue;   
   mMinYValue= mTempMinYValue;
}

void draw()
{
  background(53);
  stroke(255,255,255);
  fill(255,255,255);

  PFont font; // Setup a font
  font = loadFont("CourierNew-12.vlw"); 
  textFont(font, 12); 
  text("Press any key to end the run...", windowWidth/2-110, windowHeight-12);

  // If graph drawing enabled then do it  
  if(drawGraph)
  {
    setMaxMinYValues();
    double mVerticalScaleFactor = (double)windowHeight / (double)(mMaxYValue-mMinYValue);

    // Graph the stored values by drawing a lines between them.
    for (int i = 0; i < (numPointsWide-1); i++)
    {
      stroke(255);fill(255);
      line(i * (windowWidth/numPointsWide), (windowHeight)-(int)(((Double)values.get(i)-mMinYValue) * mVerticalScaleFactor), (i + 1) * (windowWidth/numPointsWide), (windowHeight)-(int)(((Double)values.get(i+1)-mMinYValue) * mVerticalScaleFactor));
      stroke(255,0,0);fill(255,0,0);
      ellipse((i+1) * (windowWidth/numPointsWide), (windowHeight)-(int)(((Double)values.get(i+1)-mMinYValue) * mVerticalScaleFactor), 5, 5); // Draw a small circle at each point to distinguish them
    }
  
    stroke(255,255,255); // Print all text in white
    fill(255,255,255);  
    text(mMinYValue.toString(),1,windowHeight-1); // Print the low RPM figure to the bottom left corner
    text(mMaxYValue.toString(),1, 13); // Print the high RPM figure to the top left corner
    text(((Double)values.get(numPointsWide-1)).toString(), windowWidth-50, ((windowHeight)-(int)(((Double)values.get(numPointsWide-1)-mMinYValue) * mVerticalScaleFactor))+13); // Print the current RPM figure near the latest point

    textFont(font, 60);
    text(Integer.toString(((Double)values.get(numPointsWide-1)).intValue()) + " RPM", 30, windowHeight/2+30);  // Print the big RPM number to the screen
  }
  
  // If this is a simulation run then add the next loaded value to the list of graphed points, otherwise read from the serial port
  if(simulateRunFromTextFile)
  {
    // This is a simulation so need to remove the oldest point from the array list and add the next one.  Only does this every 'simulationSpeed' number of draw loops to control speed
    if(currSimulationCnt-- == 0 && (currLineNum < importedLines.length))
    {
      values.remove(0);
      values.add(new Double((  (1/((((new Integer(importedLines[currLineNum++])).doubleValue())*(63.0/64.0))/1000000))*60   )));
      currSimulationCnt = simulationSpeed;
    }
  }
  else
  {
    // Not a simulation run so need to read data from the serial port
    while (port.available() > 0)
      serialEvent(port.read());    
  }

}

void serialEvent(int serial)
{
  if (serial != NEWLINE)
  {
    // Store all the characters on the line.
    buff += char(serial);
  } 
  else
  {
    // The end of each line is marked by two characters, a carriage
    // return and a newline.  We're here because we've gotten a newline,
    // but we still need to strip off the carriage return.
    buff = buff.substring(0, buff.length()-1);
     
    output.println(buff);

    values.remove(0);
    values.add(new Double((  (1/((((new Integer(buff)).doubleValue())*(63.0/64.0))/1000000))*60   )));

    // Clear the value of "buff"
    buff = "";
  }
}

// Press a key to save the data
void keyPressed()
{ 
  output.flush(); // Write the remaining data
  output.close(); // Finish the file
  port.stop(); // Close the serial port to stop error at the end
  exit(); // Stop the program
}

Just to keep this up-to-date for anybody doing something similar in future.

Some of the Arduino code used above worked in 0012 but is no longer available in 0018, but there is now a micros() function which keeps track of the number of microseconds since the Arduino last reset, perfect for timing as I'm doing. So new Arduino code is -

/*
/  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 March 2010
*/

#define mTimeBufferSize 64 // MUST only be 2^x value, such as 2, 4, 8, 16, 32, 64, 128

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; // Used to hold the previous micros() value sent to the PC so the difference from current micros can be calculated

void setup()
{
  Serial.begin(115200); // Enable serial port communication at 115200 baud
  pinMode(2, INPUT); // Set digital pin 2 to an input pin
  attachInterrupt(0, onChangeX, RISING); // Setup interrupt 0 (which is on digital pin 2), triggered on a rising edge input, calling the onChangeX function
}


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.println(mTimesBuffer[mTimesBufferSendPtr & (unsigned long)(mTimeBufferSize-1)] - lastSent); // Subtract the previous micros from this micros to get the pulse time and send across serial port

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


// Function called on every interrupt
void onChangeX() 
{
  mTimesBuffer[mTimesBufferStorePtr & (unsigned long)(mTimeBufferSize-1)] = micros(); // Put the current micros() value into the next space in the buffer
  mTimesBufferStorePtr++; // Increment the buffer store pointer to the next location
}

I've been reading values (pulses being created from a 555 timer at 35Hz to simulate the drum being at full speed) from the Arduino for about an hour now with this new code and have not had one random spike, so it looks as though there was also a software issue somewhere in my code! See this thread for more info on the code and micros() - http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1226267355/90#90 .

Thanks to Mike in this thread - http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1266158883 - I've also added resistors to the LEDs I was using and also a decoupling capacitor to the power supply, both of which are very important apparently.

Hi to Zenith63
I have started using your most useful code with Arduino 0019 and a Duemilanove Board to build a pulse width monitoring system.
As a test I'm using a 555 followed by a Schmitt Trigger to ensure that I get a good clean rising pulse edge.

However, I get quite a wide variation in microsecond values in the CSV file derived from a constant input as follows:
49580
49592
49624
49636
49604
49676
49652
49660
49668
49676
49668
49668
49664
49668

I think that this variation is too much. Any ideas on how to reduce the variation.
Regards
Martin

I think your inaccuracy is in the 555 timer (check the datasheet to be sure). I think you would have better results by testing with a function generator.

Thanks
I'll give that a try

Function Generator gives much better results:
49956
49960
49956
49960
49956
49956
49960
49956
49960
49960
49956
49960
49956
49960
49956
49960
49956
49956
49960
49956
49956
49960
49960
49956
Thanks Golden, I'll pursue my project with more vigour now.
Martin

A Question to Zenith63 or anyone else who might be able to help.

The Processing Code which was posted by Zenith63 on 14 Feb 2010 plots a graph of microsecond "values" against Time.

I have been trying to modify the code to produce a graph of Torque and Horsepower against roller Speed (RPM or Kmh) to replicate the various graphs that are shown in this thread. I have some limited programming experience with VB6 but have managed to totally confuse myself in the process.

I understand that instantaneous Torque is equal to the rate of change of rpm and that HP is equal to instantaneous Torque * instantaneous RPM / K (K being a Constant) but coding this in Processing has got me !!

Can anyone assist please.
Thanks
Martin

What are you actually trying to do or build MartinP? I don't know a huge amount about Processing, I just picked up what I needed to to write the above code, but I don't believe it's really meant for any "production" type processes. Depending on what you're trying to do I think you're going to want to write a separate (VB or other language) app to draw graphs etc. Use Processing to prove you're getting sensible values from your hardware, then go and write an app to talk directly to the Arduino and work on the output as appropriate. For what it's worth, at the top of the Processing code above you can get it to export data to a CSV file, open this with Excel and you can mess around with the various formulas to generate change_in_RPM, then torque, then horsepower columns. One or two of the graphs I posted above are from an Excel spreadsheet I created to do this.

Hopefully that will answer some of your questions and point you in the right direction at least, come back to me if you need anything else, I'm happy to help...

Hi Zenith63
Thanks for your reply
I've been building Loading type dynos (with an eddy-current brake) for some years now. I use old style analog electronics. I've built and programmed my own data logging system but this merely logs Torque (from a loadcell) and RPM data by means of a push button at a steady state RPM.

I stumbled onto Arduino recently and thought that it might be suitable for a real time Inertia Dyno system. Most Inertia Dynos that I'm familiar with work using one pulse per roller revolution, usually with a 318.3 mm diam roller that conveniently gives 1.000 metre Roller circumference.

I'm not convinced that 1 pulse per rev (which equates to 3333 RPM or 55.5 Hz at 200km/h) is adequate for good resolution. I would like to use more pulses per rev (my analog system uses 72 giving 4000 Hz @ 200km/h).

I have been attempting to test the Arduino to see what it's upper limitation was in terms of Hz. I'm at this stage attempting to evaluate as a desktop excercise whether the Arduino could give me a smooth enough graph without resorting to graph smoothing etc.

That's why I was trying to see whether Processing could give me the kind of real time graphs that I want.

I have done a little work using Excel on the CSV file but that would have to be calculated after the Dyno run, not real time. I still haven't managed to get an Excel graph to produce accurate Torque and HP graphs using one pulse per rev.

I had thought that your posted graphs were "Real-time" and not generated later from the CSV file.

Your graphs look very realistic. Did you produce them from one pulse per rev? If you are willing to share it with me, I'd be most interested to know how you calculte your torque value in Excel from the Pulse width "Value" which is generated from the Arduino.

Thanks for your interest
Martin

Hi Martin,

Just had a big long post typed out then closed the window by accident >:(, so this one is gonna be shorter lol.

Here are two Excel docs from the time - Roundcube Webmail :: Welcome to Roundcube Webmail and Roundcube Webmail :: Welcome to Roundcube Webmail

The first is quite jumpy because I had issues with spurious results at the time (see posts above). Note I was using a 63 tooth gear on the drum, then feeding the sensor output to a binary counter to divide down the pulses by 64. The first column in the spreadsheet is direct from the Arduino and shows the time in milliseconds per pulse (64 teeth, or 1.015 drum revolutions). After that I just work towards power with each column. Any questions just ask!

I only went with (roughly) one pulse per revolution because I was trying to rule things out for my issue above, I could probably take a lot more now but never got around to trying the new code in-the-field. Best thing would be to get an accurate signal generator and just keep speeding it up until your margin of error exceeds the stated margin for that signal generator. Make sure to setup a light to indicate when the mTimeBufferSize buffer fills so you can see if you're overwhelming the serial port.

Philip

Hi Philip
Thanks very much for that. Excuse my ignorance but I'm having trouble opening your *.xml files after unzipping them. Which files contain the spreadsheets? I'm using OpenOffice but can't find a way to open the files?
Is there another App that I should try?

Thanks
Martin

The app you need to try is Excel 2007/2010 :), the files aren't compressed or anything. You could try the free Excel viewer from Microsoft - http://www.microsoft.com/downloads/en/details.aspx?familyid=1cd6acf9-ce06-4e1c-8dcf-f33f669dbc3a&displaylang=en

Hi Philip
I've opened your files. They make interesting reading. You've spurred me on to research my project further.

Thanks and Regards
Martin