How to measure the velocities of a pair of carts using photo interrupters

I volunteer in a high school physics class that lacks lab equipment.
I built a track and a pair of carts to study collisions.

We need to measure the velocity of the carts before and after the collisions.
The carts carry a plastic rectangle of known length (0.1m) that blocks an infrared light beam in a photo interrupter. This change of state is monitored by an Arduino digital pin and causes interrupts at the onset of the blockage and at its termination. The times of these state changes (t1 and t2) are recorded at 4 microsecond resolution. For rectangle length L, the velocity V = L/(t2-t1).

The parts used are from SparkFun:
Photo Interrupter - GP1A57HRJ00F SEN-09299
SparkFun Photo Interrupter Board - GP1A57HRJ00F BOB-09322

The price for a pair of gates was about $7 plus $5 shiping.

Some soldering was needed to attach the interrupter to its board.

The code is given below:

int gateA=2;int gateB=3;
float shadeSize=0.1; //meters
boolean stateA=HIGH;boolean stateB=HIGH;
unsigned long tA1,tA2,tB1,tB2;// used to record micros()
void setup() {
Serial.begin (9600);
pinMode(gateA, INPUT); pinMode(gateB, INPUT);
attachInterrupt(digitalPinToInterrupt(gateA), intA, CHANGE);
attachInterrupt(digitalPinToInterrupt(gateB), intB, CHANGE);
}
void loop() {
}
void intA(){ //invoked when gateA changes state
boolean state;
state=digitalRead(gateA);
if (state==LOW){tA1=micros(); //Serial.println(tA1);
}else{ // change to HIGH is end of blockage
tA2=micros();
Serial.print(" A ");Serial.print(tA1);Serial.print(" ");Serial.print(tA2);Serial.print(" ");
Serial.print(tA2-tA1);Serial.print(" ");
Serial.println(shadeSize1e6/(tA2-tA1));//compute velocity in m/s
}
}
void intB(){ //invoked when gateB changes state
boolean state;
state=digitalRead(gateB);
if (state==LOW){tB1=micros(); //Serial.println(tA1); // change to LOW is start of blockage
}else{ // change to HIGH is end of blockage
tB2=micros();
Serial.print(" B ");Serial.print(tB1);Serial.print(" ");Serial.print(tB2);Serial.print(" ");
Serial.print(tB2-tB1);Serial.print(" ");
Serial.println(shadeSize
1e6/(tB2-tB1));//compute velocity in m/s
}
}

Remove all the slow stuff from int.routunes. note time, no more..

I understand your concern about long interrupt service routines that prevent other ISRs from occurring.

My defense is (1) the the events I am monitoring occur at tens of millisecond intervals and (2) I want to make the displays as understandable as possible for high school physics students.

That being said, I am considering a revised version of the code that takes the calculation and print statements out of the ISRs and simply sets a flag( one per interrupter gate) signalling that there is data (left in global variables) to be calculated and printed. The loop() routine would poll the flags and, if set, do the calculating and printing in the loop() routine.

Is this what you were hinting at Knut_ny?

Never print in an ISR. Printing depends on interrupts, which are blocked in an ISR.

Interrupts must generally be as short as possible.

There are at least two reasons not to call Serial operations in an ISR, actually:

  1. If the serial buffer fills up the serial code waits for it to drain, which cannot happen if you are
    in an ISR with serial interrupts blocked. Thus calling Serial in an ISR may or may not freeze
    up your Arduino depending on what was printed immediately before.

  2. Serial library may not declare all its variables volatile, so they cannot safely be used in an ISR.
    (whether this matters depends on which compiler optimizations are used, which can really set you
    up for a confusing time if the default optimizations get changed in a future release!!)

I thank knut_ny and jremington for their kind and useful suggestions.
Below is version two of the code incorporating their ideas. Also below the code is the test output on the serial console.

int gateA=2;int gateB=3;
// shade will block and unblock light on optical interrupter as cart moves
float shadeSize=0.1; //meters
boolean stateA=HIGH;boolean stateB=HIGH;
// high flag means times recorded need to be printed
boolean flagA=LOW;boolean flagB=LOW;
unsigned long tA1,tA2,tB1,tB2;// used to record micros()
void setup() {
Serial.begin (9600);
Serial.println("Gate");
Serial.println(" T1 T2 (T2-T1) Vel");
pinMode(gateA, INPUT); pinMode(gateB, INPUT);
attachInterrupt(digitalPinToInterrupt(gateA), intA, CHANGE);
attachInterrupt(digitalPinToInterrupt(gateB), intB, CHANGE);
}
void loop() {
if(flagA==HIGH){
Serial.print(" A ");Serial.print(tA1);Serial.print(" ");Serial.print(tA2);Serial.print(" ");
Serial.print(tA2-tA1);Serial.print(" ");
Serial.println(shadeSize1e6/(tA2-tA1),5);//compute velocity in m/s
flagA=LOW; // reset flag
}
if(flagB==HIGH){
Serial.print(" B ");Serial.print(tB1);Serial.print(" ");Serial.print(tB2);Serial.print(" ");
Serial.print(tB2-tB1);Serial.print(" ");
Serial.println(shadeSize
1e6/(tB2-tB1),5);// compute velocity in m/s
flagB=LOW; // reset flag
}
}
void intA(){ //invoked when gateA changes state
stateA=digitalRead(gateA);
if (stateA==LOW){tA1=micros(); //Serial.println(tA1);
}else{ // change to HIGH is end of blockage
tA2=micros(); flagA=HIGH;
}
}
void intB(){ //invoked when gateB changes state
stateB=digitalRead(gateB);
if (stateB==LOW){tB1=micros(); //Serial.println(tA1); // change to LOW is start of blockage
}else{ // change to HIGH is end of blockage
tB2=micros(); flagB=HIGH;

}
}

Serial console test output:

Gate
T1 T2 (T2-T1) Vel
A 12245436 12516724 271288 0.36861
B 16923200 17327128 403928 0.24757
A 134447788 135013676 565888 0.17671
B 137966412 138594208 627796 0.15929

Much better, but PLEASE use code tags when posting (</> button).

One thing to consider depending on what else you are doing (photography etc.) is false triggering by a strobe from the camera flash. Some years ago I built a race timer for the cub scouts with their derby cars. I used an IR diode under the track pointing up to the receiver over the track. I then used a 38khz carrier on the IR diode and the simple remote control receivers. In the software, I looked at the receive signal every ms. If 3 ms samples in a row were missing, then I considered it a valid detect (I had calculated that at the fastest speed, a car took 8ms to pass the sensor). I subtracted the 3ms back out of the time to get a correct time. By using 3 ms samples in a row, it took out the false triggers people were getting in other timers when people were taking pictures at the finish line (the strobes on the cameras were giving false triggers)

For more speed and smoothness, save your data in arrays and print after collection.
If you must print during collection, set the baud rate to maximum (I use 250000).

Also get rid of the floats. Measure lengths in micrometers, you measure time in microseconds.

1 cm = 10000 um, use unsigned long to not mix variable types in calculations.

Back in my time we had long exposure pictures of pool balls in motion, lit only by strobe light. There was a good budget but > 40 years ago even simple electronics was expensive.

With Arduino, you can strobe leds as fast as you want. :slight_smile: