Pages: 1 2 [3] 4   Go Down
Author Topic: Calculating RPM from encoder by timing pulses  (Read 10819 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

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

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

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!

Code:
//
//
// 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
}
Logged

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

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

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

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

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

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks
I'll give that a try
Logged

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

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

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

Logged

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

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

Hi Martin,

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

Here are two Excel docs from the time - http://www.iol.ie/~foundation/dyno_run.xlsx and http://www.iol.ie/~foundation/dyno_run2.xlsx

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
« Last Edit: November 07, 2010, 11:25:23 am by Zenith63 » Logged

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

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

The app you need to try is Excel 2007/2010 smiley, 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
Logged

South Africa
Offline Offline
Newbie
*
Karma: 0
Posts: 20
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

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

Hi!

Im pretty new with arduino and dynos and I'm actually trying to datalog an eddy-current dyno.

I've looked at Zenith Excel sheet and that's exactly what I want to achieve!

I have some questions about the arduino; Since I have to control the eddy-current brake with a pwm and maybe a PID (from the arduino ) do you think I can still log the values of both sensors ( speed and torque ) of the brake or I'll have to get another arduino doing it ?

To have a sheet like you got, did you control the rpm of the bike yourself or did the dyno did it to ensure each value of rpm ?

Lwis
« Last Edit: January 12, 2011, 09:48:37 pm by elwiss » Logged

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