Small engine dyno, help needed with rpm logging

Hi!

I have built a small inertia type engine dyno.
It is for a supermilage car project.

It works by having the rear wheel on a set of rollers, and measuring the rpm with a hall effect sensor and an arduino duemilanove.
The arduino then sends the recorded data to my computer where I have written a software in processing to receive data and calculate power output.

However i think the arduino is missing interrupts, because when i change the " if (rpmcount >= 50) " to a lower value it doesnt work very well.
And a high value does not give as frequent rpm readings.

How can i solve this? The arduino needs to be faster.
Im also later going to read injector pulsewidth with Pulsein()

Here is my code:

volatile byte rpmcount;

 unsigned int rpm;

 unsigned long timeold;
 int dtime;

 void setup()
 {
   Serial.begin(115200);
   attachInterrupt(0, rpm_fun, RISING);

   rpmcount = 0;
   rpm = 0;
   timeold = 0;
   dtime=0;
 }

 void loop()
 {
   if (rpmcount >= 50) { 
     
     dtime = millis()-timeold;
     rpm = 30000/(dtime)*rpmcount;
     timeold = millis();
     rpmcount = 0;
     Serial.print(rpm);
     Serial.print(" ");
     Serial.println(dtime);
     
   }
 }

 void rpm_fun()
 {
   rpmcount++;
   
 }

dyno3.JPG

A few thoughts:

If the loop() execution interval ever gets so long relative to the interrupt interval that the rpmcount exceeds 255, the counter will overflow. Do you know what values you're actually getting in practice?

In loop() this code does not execute instantaneously: if an interrupt occurs while it is executing, the interrupt could be lost:

     rpm = 30000/(dtime)*rpmcount;
     timeold = millis();
     rpmcount = 0;

To avoid that possibility, you could disable interrupts, copy rpmcount to a local variable, zero it and then resume interrupts.

However, I think you could also avoid it without disabling interrupts by keeping a static/global variable that records the previous value of rpmcount and subtract the two to get the delta count, similar to the way you subtract the current millis() from previous millis() to get the interval.

The name rpmcount is poorly chosen since it does not hold a count of rpms - it actually holds a count of revolutions.

Hi!

I changed some things.
The maximum expexted rotational speed is 6000rpm, or 100hz.
So i guess the loop needs to be faster than 1/100 second.

// Variables
volatile int revcount = 0;
unsigned int rpm = 0;
unsigned long timeold = 0;
unsigned int dtime = 0;

 void setup()
 {
   Serial.begin(115200);
   attachInterrupt(0, revInterrupt, RISING);

   
 }

 void loop()
 {
   if (revcount >= 50) { 
     
     dtime = millis()-timeold;
     rpm = 30000/(dtime)*revcount;
     timeold = millis();
     revcount = 0; // Reset revcount

     Serial.print(rpm); //Print current rpm
     Serial.print(" "); //Print space
     Serial.println(dtime); //Print elapsed time
     
     
   }
 }

 void revInterrupt()
 {
   revcount++;
   
   
 }

As far as I can see you haven't adopted either of the suggestions I made, and you seem to have made the problem substantially worse by putting print statements in between reading and zeroing the revcount.

Ok, i corrected the mistake.

rpmcount is now revcount.

I have changed the revcount to int, so overflow wont be a problem.

I dont understand how to implement the second suggestion you made.

However, I think you could also avoid it without disabling interrupts by keeping a static/global variable that records the previous value of rpmcount and subtract the two to get the delta count, similar to the way you subtract the current millis() from previous millis() to get the interval.

I mean something along these lines:

// incomplete, untested
volatile unsigned byte revcount; // written in interrupt, read in loop
unsigned byte lastrevcount;
unsigned long lastmillis;

void loop()
{
    byte newrevcount = revcount;
    byte deltarevcount = newrevcount - lastrevcount;
    lastrevcount = newrevcount;

    unsigned long newmillis = millis();
    unsigned long deltamillis = newmillis - lastmillis;
    lastmillis = newmillis;

    // on to calculate speed from delta revcount and deltamillis ...
}

I've stuck with revcount as a byte because it makes it possible to take a local copy of the volatile variable atomically; if it is an int that requires two byte read instructions so you would need to disable interrupts while it is copied. Suggest you find out how many interrupts occur per loop() in the worst case - if it is conceivable that it would be anywhere near 255 then you would need to change those byte variables to int, and and code to disable interrupts before assigning newrevcount = revcount and enabling them afterwards.

I dont really see how that would make a difference, also when will the serial communication happen?

frejohg:
I dont really see how that would make a difference, also when will the serial communication happen?

How what will make a difference?

I'm suggesting a different way of collecting the revcount values which is not susceptible to lost counts, which is the problem you asked for help on. I don't see that serial comms is related to this.

Ok i get your point now. I was focusing on the speed of which the code is being processed.

I will try this later today!

Hello!

It didnt work.

Here is the code i wrote:

// Variables
volatile byte revcount = 0;
int rpm = 0;
long timeold = 0;
int dtime = 0;


long lastrevcount=0;
long lastmillis=0;
long deltarevcount=0;
long newrevcount=0;
long newmillis=0;
long deltamillis=0;



 void setup()
 {
   Serial.begin(115200);
   attachInterrupt(0, revInterrupt, RISING);

   
 }


void loop()
{
    newrevcount = revcount;
    deltarevcount = newrevcount - lastrevcount;
    lastrevcount = newrevcount;

  newmillis = millis();
    deltamillis = newmillis - lastmillis;
    lastmillis = newmillis;
 if (deltarevcount>1) { 
     
    
     rpm = 30000/(deltamillis)*deltarevcount;
     
     
     Serial.print(rpm); //Print current rpm
     Serial.print(" "); //Print space
     Serial.println(deltamillis); //Print elapsed time

   
}
 
 
  
     
}
 

 void revInterrupt() {
   revcount++;
   
   
 }

That code doesn't make sense at all, does it?

Your 'if (deltarevcount>xx)' is intended to wait until several revolutions have occurred. To get any sort of smooth output from it I suggest that xx needs to be a lot more than one. Also you should only update lastrevcount and do the millis calculations when you exceed that threshold and decide to reevaluate the speed.

Finally, all the time values should be unsigned long and all the revolution values should be the same type as revcount.

OK!

So i tried a new way.
Now i just let the interrupt increase revcount, when it is above a certain value (20), the arduino prints
revcount and the elapsed time since the last print.
I let the processing program handle all the calculations, hz, rpm, power and so on.

Since i dont have my dyno at home, i wrote a code that sends fake revcount and time readings with a delay of 1ms to my laptop.
The processing software can read and handle all the data from the fake readings at this high speed.

So this way i have basicly minimized the work for the arduino and let the laptop do it instead.
3ghz is faster than 16mhz right?

Ill try this tomorrow.

Fake serial print code

/*
VeraDyno 
Fredrik Johansson 2013

Purpose:
Send rpmcounts and millis to processing

*/

void setup() {

 Serial.begin(115200); //Faster baud?
 
}

void loop() {
  

  
  Serial.print("20");
  Serial.print(" ");
   Serial.println("550");
  delay(1);
  
     Serial.print("20");
  Serial.print(" ");
   Serial.println("500");
  delay(1);
    Serial.print("20");
  Serial.print(" ");
   Serial.println("450");
  delay(1);
  
   Serial.print("20");
  Serial.print(" ");
   Serial.println("400");
  delay(1);
  
      Serial.print("20");
  Serial.print(" ");
   Serial.println("350");
  delay(1);
  
  Serial.print("20");
  Serial.print(" ");
   Serial.println("300");
  delay(1);
  
  
  
    Serial.print("20");
  Serial.print(" ");
   Serial.println("250");
  delay(1);
  
     Serial.print("20");
  Serial.print(" ");
   Serial.println("200");
  delay(1);
  
      Serial.print("20");
  Serial.print(" ");
   Serial.println("150");
  delay(1);
  
  
    Serial.print("20");
  Serial.print(" ");
   Serial.println("100");
  delay(1);
  delay(10000);
}

The real code for the arduino:

// Variables

unsigned long lastmillis=0;
unsigned long deltamillis=0;
volatile byte revcount = 0;

 void setup()
 {
   Serial.begin(115200);
   attachInterrupt(0, revInterrupt, RISING);
 
 }
void loop()
{
   
  
 if (revcount>20) {
    
    deltamillis = millis() - lastmillis;
         lastmillis = millis();
       
     
     Serial.print(revcount); //Print current hz
     Serial.print(" "); //Print space
     Serial.println(deltamillis); //Print elapsed time
revcount=0;
   
}
      
}
 

 void revInterrupt() {
   revcount++;
   
   
 }

The processing code:

/*
Veradyno
 Fredrik Johansson 2013
 */



import processing.serial.*;
Serial port;
PrintWriter output;
PImage img;

// Variables
//Data
String[] splitdata;

long hz=0;
long hzold=0;
float watt;
float wattold=0;
float rpm;
float rpmold;
int revcount = 0;
int revcountold=0;
float rads2;
float rads1;
long maxhzrulle=100;

int dtime=1; // Time between readings
int timeold=1; // To keep time of readingss
String fileExtension = ".txt";

// Colors
long bgcolor=40;
long bgcolor2=60;
long txtcolor=240;

// Time and date
long year = year();
long month = month();
long day = day();
long hour = hour();
long minute = minute();
long second = second();

float inertia = 0.054405996665378;



void setup() {
  // GUI---------------------------
  background(bgcolor); //Background color
  size(100, 210); // Picture size
  img = loadImage("logo.JPG"); // Image 
  image(img, 270, 0);
  size(800, 600); //Screen size
  frame.setTitle("Vera Dyno"); // Title of window


  textSize(32);
  fill(txtcolor);
  text("Vera dyno", 10, 50); //Header
  fill(bgcolor2);
  strokeWeight(0);
  rect(50, 100, 610, 450); // Plot window

  fill(255);

  fill(txtcolor);
  textSize(20);
  text("Rpm:", 670, 130); // Measurements
  text("Millis:", 670, 170); // Measurements

  textSize(18);
  text("0", 17, 555);
  text("Watt", 4, 335);
  text("1000", 3, 120);

  text("0", 45, 580);
  text("Rpm", 320, 580);
  text(maxhzrulle, 600, 580);

  // Serial communication
  port = new Serial(this, "COM6", 115200); //port baud 9600
  port.bufferUntil('\n'); // buffer until prlonged line prlongln

  //Logging file specification

  output = createWriter(year + "-" + month + "-" + day + " " + hour + "_" + minute + "_" + second + ".txt");
  output.print("HZ "); // Headers for logging file
  output.println("WATT");
}




void draw() {
}

void serialEvent (Serial port) {

  splitdata = splitTokens(port.readStringUntil('\n'));

revcount= int(splitdata[0]);
dtime = int(splitdata[1]);
revcount=revcount*1000;
 hz=(revcount/dtime);


  if (hz>hzold) {


    rads2 = (hz)*2*3.14;
  rads1 = (hzold)*2*3.14;

  float  E2=0.5*inertia*(rads2*rads2);
   float E1=0.5*inertia*(rads1*rads1);
    watt=((E2-E1)/dtime)*1000;
   
    rads1=rads2;
    //    // plotting
        rpm=hz*60;
        rpm=map(rpm, 0, 5000, 0, 250);
float  watt2=map(watt, 0, 50000, 0, 100);
       strokeWeight(3);
        stroke(0);
       line(rpmold+50, 550-wattold, rpm+50, 550-watt2);
rpmold=rpm;
wattold=watt2;
   
    output.print(hz);  
    output.print(" ");
    output.println(watt); //Prlonging to file
    output.flush(); // Writes the remaining data to the file
    hzold=hz;
  }
}

Sorry to jump in,

But I think many dyno newbies who discovered arduino will have the same questions I have and maybe Fredrik has too :-).
I also googled for this but did not found a satisfying answer or explanation for the posibilties.

I have seen low speed 1 second delay rpm fan samples but I am curious if the following could be achieved anyway before building and prototype one?
What are the limits of an 16 Mhz arduino based inertia dyno setup recording rpm and time sending those two variables to a PC at the maximum baudrate?
1] If a dyno pull is a maximum 5 seconds.
2] If the rpm of the inertia flywheel or rollers is a maximum 11000 rpm.
3] How many data points could be losless transfered for the 3 to 5 seconds pull to create a nice graph?
4] Is there a limit on the arduino based interupts request that could reduce the resolution or speed of rpm recording?

Any guidance on the questions would be highly apriciated,

Paco

Another way is also to save the data to an array, and after the pull is run send over the results.

I think most dyno users will like to view the rpm while running a pull.
So live rpm viewing would be nice.
Also switching off the pull by max rpm setting would be usefull to have, just in case something fails.

Paco

After googling some more i found the following links related to high speed rpm logging.

http://forum.arduino.cc/index.php?topic=69126.30
http://forum.arduino.cc/index.php/topic,22994.0.html
http://forum.arduino.cc/index.php/topic,16447.0.html
http://forum.arduino.cc/index.php?topic=22298.0

most are ending in 2011 and final results are not mentioned of the project..... :frowning:

I also found this:
http://www.pjrc.com/teensy/td_libs_FreqMeasure.html

added: http://siggiorn.com/arduino-serial-manager/

As in general, small dynos will be Inertia types, the rpm range will be for very small modelcar engine upto 40000 rpm at the crankshaft.
With a gear down to 10000 rpm there still is a 200 hz to measure.

Paco

I tried the new way. It didnt give me accurate results. No One seems To be able to measure this with good results.

Well if FreqMeasure can handle up to 1kHz, then 40,000RPM (667Hz) should be no problem. I think your problem may be related to the serial prints. To test this theory, you might try temporarily commenting them out and just storing the data in an array, then printing the array after the run. If this solves the problem but you still want realtime serial output, then take a look at this:

http://siggiorn.com/arduino-serial-manager/

Tylernt,

Thanks for posting the suggestion and link.
I think the libary can be used for all serial prints regardless it has something to do with rpm measurement.

Paco