Finding time it takes to complete loop for gyro integration

Hello all,

I’ve been working on a project that involves integrating a gyro to find angular displacements of the system. Im at a snag right now because I can’t seem to get millis() to work from my purposes.

Im doing a simple loop operation to assign the current time of the loop to “timeFromStart” and subtracting the “timeLastRead” in an effort to find the time since the last time I passed that step in the loop.

Anyone have any thoughts about why the the output that I’m seeing is 0 for all of my outputs in the serial monitor?

Here is the code with the segments concerning the timing issue. This is the entire code but the issue that I’m facing is in the recordAngDispX() function.

#include <Wire.h>
float gyroXCal, gyroYCal, gyroZCal;

long accelX, accelY, accelZ;
float gForceX, gForceY, gForceZ;

long gyroX, gyroY, gyroZ;
float rotX, rotY, rotZ;

unsigned long timeStart = millis();
unsigned long timeLastRead,elapsed; //Approximate time from start for first angular disp. calc
float recordAngleDispX, angleDispX=0;

//----------------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Wire.begin();
  setupMPU();

  //Calibrate Gyro
  Serial.println("Calibrating Gyroscope Sensor");
  for(int cal_int = 0; cal_int<2000; cal_int ++){ //Loop for 1000 iterations 
    if(cal_int % 200 == 0)Serial.print("."); //Prints "." every 125 readings of gyro values
    recordGyroRegisters();
    gyroXCal += rotX; //equivalent to the expression x = x + y;
    gyroYCal += rotY;
    gyroZCal += rotZ;
  }
  gyroXCal /= 2000; //Find average of 2000 readings, Syntax:equivalent to the expression x = x / y; 
  gyroYCal /= 2000;
  gyroZCal /= 2000;
  
Serial.print("Gyro X Calibration Int: ");
  Serial.println(gyroXCal);
  Serial.print(" Gyro Y Calibration Int: ");
  Serial.println(gyroYCal);
  Serial.print(" Gyro Z Calibration Int: ");
  Serial.println(gyroZCal);

}
//----------------------------------------------------------------------------------------
void loop() {
  recordAccelRegisters();
  recordGyroRegisters();
  recordAngDispX(); //Angular Displacement about z body axis to pass to servo PID controller
  printData();
  delay(100);
}
//----------------------------------------------------------------------------------------
void setupMPU(){
  Wire.beginTransmission(0b1101000); //I2C address of MPU, 0b1101000 for ADO low, 0b1101001 for AD0 high (Section 9.2 Data sheet)
  Wire.write(0x6B); //Accessing register 6B-Power Management (Section 4.28)
  Wire.write(0b00000000); //Setting sleep register to 0
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1B); //Accessing register 1B-gyroscope configuration (
  Wire.write(0x00000000); //Setting the gyroscope to full scale +/- 250 rad/s
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1C); //Accessing the register 1C-accelerometer configuartion
  Wire.write(0b00000000); //Setting the accelerometer to +/- 2gs
  Wire.endTransmission();
}

void recordAccelRegisters() {
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B); // Accessing register for accel readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); // Requesting accel register readings from 3B-40
  while(Wire.available()<6);
    accelX = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelX
    accelY = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelY
    accelZ = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelZ
    processAccelData();
}

void processAccelData() {
  gForceX = accelX/16384.0; //Convert the read accel data using the LSB/g conversion in the data sheet specific to +/- 2g setting (sensitivity setting)
  gForceY = accelY/16384.0;
  gForceZ = accelZ/16384.0;
}

void recordGyroRegisters() {
  Wire.beginTransmission(0b1101000);
  Wire.write(0x43); // Accessing register for accel readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); // Requesting gyro register readings from 43-48
  while(Wire.available()<6);
    gyroX = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelX
    gyroY = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelY
    gyroZ = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelZ
    processGyroData();
}

void processGyroData() {
  rotX = gyroX/131.0; //Convert the read gyro data using the LSB/g conversion in the data sheet specific to +/- 2g setting (sensitivity setting)
  rotY = gyroY/131.0;
  rotZ = gyroZ/131.0;
}

void recordAngDispX(){
  elapsed = timeStart - timeLastRead;
  angleDispX = (timeLastRead)*(rotX-gyroXCal) + angleDispX; //Calculating angle by performing rectangular integration, [deg/s*s + deg]
  timeLastRead = timeStart;
}

void printData() {
  Serial.print("Gyro (deg/s)");
  Serial.print(" X=");
  Serial.print(rotX-gyroXCal); //Displays Gyro values adjusted with the calibration value from initial state
  Serial.print(" Y=");
  Serial.print(rotY-gyroYCal); 
   Serial.print(" Z=");
  Serial.print(rotZ-gyroZCal);
  //Serial.print(" Accel (g)");
  //Serial.print(" X=");
  //Serial.print(gForceX);
  //Serial.print(" Y=");
  //Serial.print(gForceY);
  //Serial.print(" Z=");
  //Serial.println(gForceZ);
  Serial.print(" Time Total: ");
  Serial.print(timeStart);
    Serial.print(" Time Last Read: ");
  Serial.print(timeLastRead);
  Serial.print(" X Rotation: ");
  Serial.print(angleDispX);
  Serial.print(" Elapsed Time: ");
  Serial.println(elapsed);
}

I appreciate all the help!
Tripp

I can get the serial monitor to display the angular displacement about one axis pretty accurately if I write "timeLastRead" = .13 s which tells me that the loop takes about .03 s plus the .1s delay built in if this helps at all with troubleshooting.

Please modify your post and use the code button </> so your code looks like this and is easy to copy to a text editor. See How to use the Forum

I can’t figure what you are trying to do. It seems that you just use the time in the function recordAngDispX(). Can you explain what that calculation is intended to do?

Do you just want to know how many millisecs have elapsed between calls to the function or are you trying to minimize the time between calls. If the latter, get rid of the delay()s and all the printing. Serial.print() is slow.

If you just want the time between calls to the function all you need is something like

void recordAngDispX(){
  timeNow = millis();
  timeInterval = timeNow - timeLastRead;
  timeLastRead = timeNow;

  angleDispX = (timeInterval)*(rotX-gyroXCal) + angleDispX; //Calculating angle by performing  integration, [deg/s*s + deg]
}

However I can’t see where you are actually reading the value - maybe that is where timeNow should be recorded.

…R

void recordAngDispX(){
  timeLastRead = timeFromStart/1000 - timeLastRead;
  angleDispX = (timeLastRead)*(rotX-gyroXCal) + angleDispX; //Calculating angle by performing  integration, [deg/s*s + deg]
  timeLastRead = timeFromStart;
}

Seems nonsensical to me. You are mixing values in seconds with milliseconds, and surely timeLastRead
should not be inverted in polarity like that?

perhaps you intend:

void recordAngDispX(){
  unsigned long time = millis();
  unsigned long elapsed = time - timeLastRead ;
  angleDispX += elapsed * (rotX-gyroXCal) ; // Integrate angle
  timeLastRead = time;
}

Thank you both for you response. Robin2, I went back and read those instructions. Thank you for pointing those out to me!

To answer your question, the purpose of the recordAngDispX() function is to find the time between calls of the function in the loop and use that time to perform the integration of the gyro sensor and output the angular displacement since the last read of the function.

Yes I am mixing milliseconds and seconds but once millis() is divided by 1000 everything is in seconds and that what unit I was intending to convert everything to.

I see what you did in your suggested code MarkT. I'm going to play around with the code for a couple hours before I come back and see if I can figure it out with yalls suggestions. I see you both came to the same conclusion.

Thank you,
Tripp

Does it matter whether or not the declaration of the timeStart variable is inside or outside the function loop? Im trying yalls suggested code but is throwing an error that the variable isn't declared in scope and its highlighting the serial.print(timeStart) when I declare it inside the function.

Tripp

trippi25:
Does it matter whether or not the declaration of the timeStart variable is inside or outside the function loop? Im trying yalls suggested code but is throwing an error that the variable isn't declared in scope and its highlighting the serial.print(timeStart) when I declare it inside the function.

You need to post the latest version of your code so we can see exactly what you have done.

Most of the time for Arduino programs I just make all my variables global - I know many others disapprove of that but I find it makes it easy to see what I have (or don't have) when they are all declared at the top of the program.

...R

Robin2,

Thank you again for the advice with the forum. Below is the latest version of the code that I’m working with. As of right now with everything declared at the top of the code outside of the function loop Im getting zeros across the board for timeStart, timeLastRead and elapsed but the logic of the loop makes sense to me and units check out.

How would declaring them in the loop change how the program operates? Specifically if I put:

void recordAngDispX(){
  unsigned long timeStart = millis();
  elapsed = timeStart - timeLastRead;
  angleDispX =+ (elapsed/1000)*(rotX-gyroXCal); //Calculating angle by performing rectangular integration, [deg/s*s + deg]
  timeLastRead = timeStart;
}

Does this mean that every time the function is called, that timeStart is redeclared and starts the timer over? Im just trying to wrap my head around how is different to declare it outside of the loop than inside and why I’m seeing different results.

This is the code that results in zeros across the board for the variables:

#include <Wire.h>
float gyroXCal, gyroYCal, gyroZCal;

long accelX, accelY, accelZ;
float gForceX, gForceY, gForceZ;

long gyroX, gyroY, gyroZ;
float rotX, rotY, rotZ;

unsigned long timeStart = millis();
unsigned long timeLastRead = 0,elapsed; 
float recordAngleDispX, angleDispX=0;

//----------------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Wire.begin();
  setupMPU();

  //Calibrate Gyro
  Serial.println("Calibrating Gyroscope Sensor");
  for(int cal_int = 0; cal_int<2000; cal_int ++){ //Loop for 1000 iterations 
    if(cal_int % 200 == 0)Serial.print("."); //Prints "." every 125 readings of gyro values
    recordGyroRegisters();
    gyroXCal += rotX; //equivalent to the expression x = x + y;
    gyroYCal += rotY;
    gyroZCal += rotZ;
  }
  gyroXCal /= 2000; //Find average of 2000 readings, Syntax:equivalent to the expression x = x / y; 
  gyroYCal /= 2000;
  gyroZCal /= 2000;
  
Serial.print("Gyro X Calibration Int: ");
  Serial.println(gyroXCal);
  Serial.print(" Gyro Y Calibration Int: ");
  Serial.println(gyroYCal);
  Serial.print(" Gyro Z Calibration Int: ");
  Serial.println(gyroZCal);

}
//----------------------------------------------------------------------------------------
void loop() {
  recordAccelRegisters();
  recordGyroRegisters();
  recordAngDispX(); //Angular Displacement about z body axis to pass to servo PID controller
  printData();
  delay(100);
}
//----------------------------------------------------------------------------------------
void setupMPU(){
  Wire.beginTransmission(0b1101000); //I2C address of MPU, 0b1101000 for ADO low, 0b1101001 for AD0 high (Section 9.2 Data sheet)
  Wire.write(0x6B); //Accessing register 6B-Power Management (Section 4.28)
  Wire.write(0b00000000); //Setting sleep register to 0
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1B); //Accessing register 1B-gyroscope configuration (
  Wire.write(0x00000000); //Setting the gyroscope to full scale +/- 250 rad/s
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1C); //Accessing the register 1C-accelerometer configuartion
  Wire.write(0b00000000); //Setting the accelerometer to +/- 2gs
  Wire.endTransmission();
}

void recordAccelRegisters() {
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B); // Accessing register for accel readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); // Requesting accel register readings from 3B-40
  while(Wire.available()<6);
    accelX = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelX
    accelY = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelY
    accelZ = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelZ
    processAccelData();
}

void processAccelData() {
  gForceX = accelX/16384.0; //Convert the read accel data using the LSB/g conversion in the data sheet specific to +/- 2g setting (sensitivity setting)
  gForceY = accelY/16384.0;
  gForceZ = accelZ/16384.0;
}

void recordGyroRegisters() {
  Wire.beginTransmission(0b1101000);
  Wire.write(0x43); // Accessing register for accel readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); // Requesting gyro register readings from 43-48
  while(Wire.available()<6);
    gyroX = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelX
    gyroY = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelY
    gyroZ = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelZ
    processGyroData();
}

void processGyroData() {
  rotX = gyroX/131.0; //Convert the read gyro data using the LSB/g conversion in the data sheet specific to +/- 2g setting (sensitivity setting)
  rotY = gyroY/131.0;
  rotZ = gyroZ/131.0;
} 

void recordAngDispX(){
  elapsed = timeStart - timeLastRead;
  angleDispX =+ (elapsed/1000)*(rotX-gyroXCal); //Calculating angle by performing rectangular integration, [deg/s*s + deg]
  timeLastRead = timeStart;
}

void printData() {
  Serial.print("Gyro (deg/s)");
  Serial.print(" X=");
  Serial.print(rotX-gyroXCal); //Displays Gyro values adjusted with the calibration value from initial state
  Serial.print(" Y=");
  Serial.print(rotY-gyroYCal); 
   Serial.print(" Z=");
  Serial.print(rotZ-gyroZCal);
  //Serial.print(" Accel (g)");
  //Serial.print(" X=");
  //Serial.print(gForceX);
  //Serial.print(" Y=");
  //Serial.print(gForceY);
  //Serial.print(" Z=");
  //Serial.println(gForceZ);
  Serial.print(" Time Total: ");
  Serial.print(timeStart);
  Serial.print(" Time Last Read: ");
  Serial.print(timeLastRead);
  Serial.print(" X Rotation: ");
  Serial.print(angleDispX);
  Serial.print(" Elapsed Time: ");
  Serial.println(elapsed);
}

Thank you again. Im already learning a lot.
Tripp

Hi,

When I read this,

void recordAngDispX(){
  unsigned long time = millis();
  unsigned long elapsed = time - timeLastRead ;
  angleDispX += elapsed * (rotX-gyroXCal) ; // Integrate angle
  timeLastRead = time;
}

I see that "elapsed" is the time required to calculate the angle. That, in millis() is 0.
EDIT : I am wrong here. "elapsed" is the time between calls.

Since the sensor is read once per loop, would'nt it be simpler to measure time in the loop section?

void loop() {
  time = millis();
  elapsed= time - timeLastRead ;
  timeLastRead = time;
  recordAccelRegisters();
  recordGyroRegisters();
  recordAngDispX(); //Angular Displacement about z body axis to pass to servo PID controller
  printData();
  delay(100);
}

Declare

unsigned long time, elapsed, timeLastRead;

In the top of the sketch to make them global.

trippi25:

void recordAngDispX(){

unsigned long timeStart = millis();
 elapsed = timeStart - timeLastRead;
 angleDispX =+ (elapsed/1000)(rotX-gyroXCal); //Calculating angle by performing rectangular integration, [deg/ss + deg]
 timeLastRead = timeStart;
}




Does this mean that every time the function is called, that timeStart is redeclared and starts the timer over? Im just trying to wrap my head around how is different to declare it outside of the loop than inside and why I'm seeing different results.

If you need to access timeStart outside the function then it should be declared at the top of the program as a global variable. But it still needs to get the latest value of millis() inside your function. Actually it would make more sense to call it timeNow. Meaningful variable names make code much easier to understand. So like this...

void recordAngDispX(){
  timeNow = millis();
  elapsed = timeNow - timeLastRead;
  angleDispX =+ (elapsed/1000)*(rotX-gyroXCal); //Calculating angle by performing rectangular integration, [deg/s*s + deg]
  timeLastRead = timeNow;
}

...R

Hi jbellavance, that is definitely one way to do it but I’d prefer to keep the timing sequence in the integration function if I can.

You’re correct that elapsed is the time that is used to calculate the angle. I took your advice and kept all the declaration at the top of the program and I have isolated the problem down to what I believe is the transition between the calculate of elapsed and angleDispX. This is where I’m currently at and what has provided me the most accurate outputs so far but the angle still reports 0.

void recordAngDispX(){
  timeStart = millis(); //[ms]
  elapsed = (timeStart - timeLastRead); //Calc. elasped time since last time elapsed was calculated (~elapsed time since gyro was read last), [ms]=[ms-ms]
  angleDispX =+ (elapsed/1000)*(rotX-gyroXCal); //Calculating angle by performing rectangular integration, [deg/s*s + deg]
  timeLastRead = timeStart; //Re-assign time last read to the value of time from the start of the program at this moment [ms]=[ms]
}

timeStart is declared an unsigned long and is in milliseconds
elapsed is the subtraction of two unsigned long values in milliseconds
angleDispX needs the value of elapsed to be in seconds to match units with the gyro outputs but if its simply divided by 1000 then the return of angleDispX is recorded as zero.

elapsed is non-zero, rotX-gyroXCal is non zero so the only thing I can think is that the variable type is restricting the precision of the output but its already a float so I’m not sure what could be the issue.

Most up to date code:

#include <Wire.h>
float gyroXCal, gyroYCal, gyroZCal;

long accelX, accelY, accelZ;
float gForceX, gForceY, gForceZ;

long gyroX, gyroY, gyroZ;
float rotX, rotY, rotZ;

unsigned long timeStart, timeLastRead = 0, elapsed;
float recordAngleDispX, angleDispX;

//----------------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Wire.begin();
  setupMPU();

  //Calibrate Gyro
  Serial.println("Calibrating Gyroscope Sensor");
  for(int cal_int = 0; cal_int<2000; cal_int ++){ //Loop for 1000 iterations 
    if(cal_int % 200 == 0)Serial.print("."); //Prints "." every 125 readings of gyro values
    recordGyroRegisters();
    gyroXCal += rotX; //equivalent to the expression x = x + y;
    gyroYCal += rotY;
    gyroZCal += rotZ;
  }
  gyroXCal /= 2000; //Find average of 2000 readings, Syntax:equivalent to the expression x = x / y; 
  gyroYCal /= 2000;
  gyroZCal /= 2000;
  
Serial.print("Gyro X Calibration Int: ");
  Serial.println(gyroXCal);
  Serial.print(" Gyro Y Calibration Int: ");
  Serial.println(gyroYCal);
  Serial.print(" Gyro Z Calibration Int: ");
  Serial.println(gyroZCal);

}
//----------------------------------------------------------------------------------------
void loop() {
  recordAccelRegisters();
  recordGyroRegisters();
  recordAngDispX(); //Angular Displacement about z body axis to pass to servo PID controller
  printData();
  delay(100);
}
//----------------------------------------------------------------------------------------
void setupMPU(){
  Wire.beginTransmission(0b1101000); //I2C address of MPU, 0b1101000 for ADO low, 0b1101001 for AD0 high (Section 9.2 Data sheet)
  Wire.write(0x6B); //Accessing register 6B-Power Management (Section 4.28)
  Wire.write(0b00000000); //Setting sleep register to 0
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1B); //Accessing register 1B-gyroscope configuration (
  Wire.write(0x00000000); //Setting the gyroscope to full scale +/- 250 rad/s
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1C); //Accessing the register 1C-accelerometer configuartion
  Wire.write(0b00000000); //Setting the accelerometer to +/- 2gs
  Wire.endTransmission();
}

void recordAccelRegisters() {
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B); // Accessing register for accel readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); // Requesting accel register readings from 3B-40
  while(Wire.available()<6);
    accelX = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelX
    accelY = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelY
    accelZ = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelZ
    processAccelData();
}

void processAccelData() {
  gForceX = accelX/16384.0; //Convert the read accel data using the LSB/g conversion in the data sheet specific to +/- 2g setting (sensitivity setting)
  gForceY = accelY/16384.0;
  gForceZ = accelZ/16384.0;
}

void recordGyroRegisters() {
  Wire.beginTransmission(0b1101000);
  Wire.write(0x43); // Accessing register for accel readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); // Requesting gyro register readings from 43-48
  while(Wire.available()<6);
    gyroX = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelX
    gyroY = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelY
    gyroZ = Wire.read()<<8|Wire.read(); //Store the first two bytes in accelZ
    processGyroData();
}

void processGyroData() {
  rotX = gyroX/131.0; //Convert the read gyro data using the LSB/g conversion in the data sheet specific to +/- 2g setting (sensitivity setting)
  rotY = gyroY/131.0;
  rotZ = gyroZ/131.0;
} 

void recordAngDispX(){
  timeStart = millis(); //[ms]
  elapsed = (timeStart - timeLastRead); //Calc. elasped time since last time elapsed was calculated (~elapsed time since gyro was read last), [ms]=[ms-ms]
  angleDispX =+ (elapsed/1000)*(rotX-gyroXCal); //Calculating angle by performing rectangular integration, [deg/s*s + deg]
  timeLastRead = timeStart; //Re-assign time last read to the value of time from the start of the program at this moment [ms]=[ms]
}

void printData() {
  Serial.print("Gyro (deg/s)");
  Serial.print(" X=");
  Serial.print(rotX-gyroXCal); //Displays Gyro values adjusted with the calibration value from initial state
  Serial.print(" Y=");
  Serial.print(rotY-gyroYCal); 
   Serial.print(" Z=");
  Serial.print(rotZ-gyroZCal);
  //Serial.print(" Accel (g)");
  //Serial.print(" X=");
  //Serial.print(gForceX);
  //Serial.print(" Y=");
  //Serial.print(gForceY);
  //Serial.print(" Z=");
  //Serial.println(gForceZ);
  Serial.print(" Time Total: ");
  Serial.print(timeStart);
  Serial.print(" Time Last Read: ");
  Serial.print(timeLastRead);
  Serial.print(" X Rotation: ");
  Serial.print(angleDispX);
  Serial.print(" Elapsed Time: ");
  Serial.println(elapsed);
}

Thank you all,
Tripp

Robin2,

That makes more sense. I was assuming that if it timeStart was declared to equal millis() than it would follow it inside the loop. I fixed this in my post above.

Also i like the idea of calling it timeNow, that could be a little bit more intuitive than timeStart.

Tripp

I think I found why.

Replace

  angleDispX =+ (elapsed/1000)*(rotX-gyroXCal);

with

  angleDispX += (float(elapsed)/1000)*(rotX-gyroXCal);

The =+ operator was abandonned in the 70’s, I have read on the net.
Now, (A =+ B) is compiled as (A = +B).

Casting elapsed to a float (from unsigned long) should do the trick

Jacques

That did the trick! I know better than to write =+, just didn't see it but I would never had known you could cast a variable as another type in a line like that.

Thank you Jacques and other who helped!

Tripp

Glad you're ok.

See you again in the forum

Jacques

p.s.

I usually use the following construct for my chronometers:
chrono = millis();
elapsed = millis() - chrono

That saves me one variable.