I understand that interrupt service routines can't reliably call functions that involve timing such as delay() or millis(), so I am trying to implement the code below as a workaround for this.
I am building a photogate consisting of two phototransistors each configured as shown in the diagram. Initially light will be shining on both phototransistors, but a moving object will block the light shining on one phototransistor then the other. I would like to measure the time between the two interruptions of light. I tried implementing this with loops initially and I can't seem to make measurements any shorter than 35 milliseconds using loops. This is why I am trying to use interrupts.
The Vout pin in this diagram reads 5v when light is shining on the phototransistor and about 1.4v when shaded. I would like to use the fall from 5v to trigger interrupts.
The code below does not output anything to the serial monitor. I'm not all that familiar with interrupts and can't find any errors myself. I am positive that my circuit works because I was able to use it with the 'loop' version of this sketch, but I can't seem to make the 'interrupt' version work.
Thanks for your help.
int gatePin1 = 0; //interrupt on digital pin 2
int gatePin2 = 1; //interrupt on digital pin 3
volatile int gateState1 = 0;
volatile int gateState2 = 0;
int initialTime = 0;
int finalTime = 0;
int index = 0;
void setup ( )
{
attachInterrupt ( gatePin1, gateInt1, FALLING );
attachInterrupt ( gatePin2, gateInt2, FALLING );
}
void gateInt1 ( ) // ISR for gatePin1
{
gateState1 = 1;
}
void gateInt2 ( ) // ISR for gatePin2
{
gateState2 = 1;
}
void loop ( )
{
while ( gateState1 != 1 )
{
// do nothing - wait for interrupt
}
initialTime = millis ( );
while (gateState2 != 1 )
{
// do nothing - wait for interrput
}
finalTime = millis ( );
Serial.println ( finalTime - initialTime );
while ( index != 1 )
{
// stay in loop until reset
}
}
I have a similar circuit working with interrupts. The risetime and fall time of your circuit is very slow. Make it have a sharp fall time using a comparator. I use the LM339 with a referenve voltage half way between VOH and VOL (high level and low voltage level). The comparator uses Hysteresis to make a sharp edge. Instead of millis() , I use micros().
You might do better to use booleans instead of ints for GateState. A change of name to something like GateTriggered might then be appropriate. This would be functionally equivalent to your current code, but would make the intent more clear to the reader. You also never have to write, "if (gateState == 1)", since boolean true and false are equivalent to numerical 1 and 0, and any non-zero value evaluates to true in C/C++. In other words, you can write, "if (gateState)", or, "if (!gateState)".
I have two suggestions for debugging. First, put in Serial printouts for each step of the logic, including inside the "do nothing" while() loops. This will allow you to track the logic exactly. Second, consider carefully whether interrupts are really necessary. They are a PITA to debug sometimes.
codytaylor:
I understand that interrupt service routines can't reliably call functions that involve timing such as delay() or millis()
You can call millis(), micros() or even delayMicroseconds(), but not delay().
[ You probably shouldn't be calling delayMicroseconds() unless its for very short delays().]
delay() waits for a timer0 interrupt, but if you are already inside an interrupt
handler then global interrupts are by default blocked, so you get deadlock.
Same goes for serial I/O which waits for other interrupts to run.
A perfectly standard idiom is calling micros() or millis() in an ISR and then storing that
value in some volatile variable for the rest of the sketch to examine (you need
to disable interrupts, copy the variable, re-enable interrupts, then examine the copy).
Thank you for all of the input, I really appreciate it.
I added Serial.begin and made the time variables of unsigned long type, but I am still not seeing anything on the serial monitor. I am now using a comparator circuit to sense the light levels as suggested. I am wondering if the change in the voltages is enough for the interrupt pins to sense. With my current circuit and the phototransistor uncovered (light) the interrupt pin reads 3.10v and with the phototransistor covered (no light) the pin reads 3.79v. I wonder if this is enough of a change in voltage for CHANGE or RISING to detect.
The code I am currently using is below:
int gatePin1 = 0; //interrupt on digital pin 2
int gatePin2 = 1; //interrupt on digital pin 3
volatile int gateState1 = 0;
volatile int gateState2 = 0;
unsigned long initialTime = 0;
unsigned long finalTime = 0;
int index = 0;
void setup ( )
{
Serial.begin ( 9600 );
attachInterrupt ( gatePin1, gateInt1, CHANGE );
attachInterrupt ( gatePin2, gateInt2, CHANGE );
}
void gateInt1 ( ) // ISR for gatePin1
{
gateState1 = 1;
Serial.println ( "gate1" ); //debugging
}
void gateInt2 ( ) // ISR for gatePin2
{
gateState2 = 1;
Serial.println ( "gate2" ); //debugging
}
void loop ( )
{
while ( gateState1 != 1 )
{
Serial.println ( "gate2" ); // do nothing - wait for interrupt //debugging
}
initialTime = millis ( );
while (gateState2 != 1 )
{
Serial.println ( "gate2" ); // do nothing - wait for interrput //debugging
}
finalTime = millis ( );
Serial.println ( finalTime - initialTime );
while ( index != 1 )
{
Serial.println ( "gate2" ); // stay in loop until reset
}
}
You should NOT be calling Serial.print() in an ISR. Serial.print() needs to have interrupts enabled to make room in the serial buffer, if it gets full. Interrupts are disabled during an ISR.
"I am now using a comparator circuit to sense the light levels as suggested. I am wondering if the change in the voltages is enough for the interrupt pins to sense. With my current circuit and the phototransistor uncovered (light) the interrupt pin reads 3.10v and with the phototransistor covered (no light) the pin reads 3.79v. I wonder if this is enough of a change in voltage for CHANGE or RISING to detect. "
The comparator should have its output go from 0 to 5 volts. You need to fix that first. The comparator has a digital output for a '1' and '0' to be sent to the Arduino. The comparator may require a resistor at its output to pull up an open collector. Please tell me which comparator you are using and give the schematic.
Okay, I worked out my circuit trouble and am not getting values of 0v and 5v as I should from the comparator. With a working trigger circuitry, and seemingly functional code I have begun testing and am getting strange results.
I am using an LM339 comparator quad packaged. I am using two identical copies of the circuit below. One for each interrupt pin.
As I mentioned I am trying to build a photogate. So far I have tested this by dropping a ball from a specific height as I can calculate how fast the ball should be traveling when it passes through the gates. I am questioning the results because I am getting two values repeatedly. The numbers I am getting repeatedly are 7280µs and 14560µs. I noticed that 14560µs is twice 7280µs. Surely this is not a coincidence. I tried to keep the code as minimal as possible in hopes of making it as fast as possible, but I have a suspicion that the loops in my code are a limiting factor. Even though interrupts are being used, the loops are checking whether an interrupt has happened and is therefore making the data only as accurate as the length of time it takes a loop to process. Might this be possible or is something else happening? Am I asking too much of an Arduino?
The code I have been using:
int gatePin1 = 0; //interrupt on digital pin 2
int gatePin2 = 1; //interrupt on digital pin 3
volatile int gateState1 = 0;
volatile int gateState2 = 0;
unsigned long initialTime = 0;
unsigned long finalTime = 0;
int index = 0;
void setup ( )
{
Serial.begin ( 9600 );
attachInterrupt ( gatePin1, gateInt1, FALLING );
attachInterrupt ( gatePin2, gateInt2, FALLING );
}
void gateInt1 ( ) // ISR for gatePin1
{
gateState1 = 1;
//Serial.println ( "gate1" ); //debugging
}
void gateInt2 ( ) // ISR for gatePin2
{
gateState2 = 1;
//Serial.println ( "gate2" ); //debugging
}
void loop ( )
{
while ( gateState1 != 1 )
{
Serial.println ( "gate1" ); // do nothing - wait for interrupt //debugging
}
initialTime = micros ( );
while (gateState2 != 1 )
{
Serial.println ( "gate2" ); // do nothing - wait for interrput //debugging
}
finalTime = micros ( );
Serial.println ( finalTime - initialTime );
while ( index != 1 )
{
// stay in loop until reset
}
}
ok, I checked the source and see that micros will read the timer counter registers and calculate the value returned, while millis simply returns a variable value. I take it that means while you are still in ISR, the timer counters continue to increment, right? otherwise, micros will still return the same value.
Yes it does. It returns the contents of the hardware register, which is constantly increasing, plus an adjusted overflow count (from when it overflows).
So calling micros() is perfectly valid, and will give a good result. From memory the resolution is 4 µS because of the prescaler, however that is still pretty good.
I am a little unclear of whether or not calling micros() in the ISR will yield accurate data.
As I understand it, micros() can be called in the ISR but will only be accurate for 1 millisecond. Is this true?
Nick, I would like to implement your suggestion, but I am reluctant if it is only good for times less than one millisecond. I'm sure that I am misunderstanding something:
I will definitely be getting rid of the Serial.prints in the loops.
I am a little unclear of whether or not calling micros() in the ISR will yield accurate data.
As I understand it, micros() can be called in the ISR but will only be accurate for 1 millisecond. Is this true?
I don't understand this. You can call millis() in the ISR. You can't call it more than once and expect to get different values, but you are not doing that.
You can call micros(), if the resolution of millis() is not good enough. Once you have a time for each interrupt, you can determine the time between them, whether that time is in milliseconds or microseconds.