// shutter tester version 2.9.3 for Arduino Nano. 19 March 2023
//Adapted to virtual Wokwi UNO model by ic-racer
// "To use the model, single click the top sensor and a slider appears. Move the slider from dark to lght and back to dark. This simulates the focal plane
// shutter moving across the sensor.
// Next, quickly , click on the bottom sensor and, again, move its slider from dark to light and back to dark. This simulates
// the focal plane shutter crossing the second sensor.
// Due to the way the simulation handles the interrupts, the first readings might be odd. Try it again and it should be OK.
// Also, due to the delay required to trigger the two sensors in this simulation, the bounce and single mode are disabled"--ic-racer
// https://www.photrio.com/forum/threads/build-a-shutter-tester-for-focal-plane-shutters-cheap-easy-it-works.197756/
#include <Wire.h> // built in library
#include <LiquidCrystal_I2C.h> // by Frank de Brabander
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address. Normally 0x27 for a 20 char 4 line display
#define DEBUG 0
#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
// define variables. Vars ending 1 are first laser or first curtain. Vars ending 2 are second laser or second curtain.
unsigned long Start1; // Arduino microsecond clock time when laser1 seen (uncovered by shutter curtain)
unsigned long Stop1; // Arduino microsecond clock time when the laser1 blocked (by shutter curtain)
unsigned long Start2; // Arduino microsecond clock time when laser2 seen (uncovered by shutter curtain)
unsigned long Stop2; // Arduino microsecond clock time when the laser2 blocked (by shutter curtain)
unsigned long SSmicro1; // Shutter Speed in Microseconds calculated from laser1
unsigned long SSmicro2; // Shutter Speed in Microseconds calculated from laser2
unsigned long shCurtainspeedMilliS1; // curtain 1 speed in Milliseconds
unsigned long shCurtainspeedMilliS2; // curtain 2 speed in Milliseconds
unsigned long shCurtainSpeedMicroS1; // curtain 1 speed in Microseconds
unsigned long shCurtainSpeedMicroS2; // curtain 1 speed in Microseconds
unsigned long shutterBounce = 1000; // how long to wait for shutter shutterBounceFlag check
float SSmillis1; // Shutter Speed in Millieseconds
float SSmillis2; // Shutter Speed in Millieseconds
int SSmillisInt1; // Shutter Speed in Millieseconds rounded
int SSmillisInt2; // Shutter Speed in Millieseconds rounded
float SSsec1; // Shutter Speed in Seconds
float SSsec2; // Shutter Speed in Seconds
float SSfrac1; // 1/Shutter Speed to give Fraction of second
float SSfrac2; // 1/Shutter speed to give Fraction of second
int SSfracV1; // 1/Shutter Speed Fraction of second rounded up
int SSfracV2; // 1/Shutter Speed Fraction of second rounded up
bool firedFlag1; // flag set when the shutter has been firedFlag1, Laser 1
bool firedFlag2; // flag set when the shutter has been firedFlag2, Laser 2
bool singleLaserMode; // flag set if only one laser being used, calculated in program
bool shutterBounceFlag; // flag fet if shutter shutterBounceFlag detected.
//variables used in ISR
volatile int risingLaser1; // flag set in isr, set to 1 when the voltage INCREASES in the interrupt (shutter open)
volatile int fallingLaser1; // flag set in isr, set to 1 when the voltage DECREASES in the interrupt (shutter close)
volatile int risingLaser2; // flag set in isr routine, set to 1 when the voltage INCREASES in the interrupt (shutter open)
volatile int fallingLaser2; // flag set in isr, set to 1 when the voltage DECREASES in the interrupt (shutter close)
volatile int laserChangeFlag2; // flag set in isr, set when isr called, used to determine single laser use
void setup() { // This part of the program is run exactly once on boot
//define & setup input pins
pinMode(2, INPUT); // Laser 1 input
pinMode(3, INPUT); // Laser 2 input
attachInterrupt(digitalPinToInterrupt(2), CLOCK1, CHANGE); // run the ISR CLOCK1, every time the voltage on pin 2 changes.
attachInterrupt(digitalPinToInterrupt(3), CLOCK2, CHANGE); // run the ISR CLOCK2, every time the voltage on pin 3 changes.
// initialize the lcd
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Camera Shutter ");
lcd.setCursor(0, 1);
lcd.print(" Tester ");
lcd.setCursor(0, 2);
lcd.print("Version 2.9.3 Wokwi ");
lcd.setCursor(0, 3);
lcd.print(" 21-March-2023 ");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Laser2 Laser1 ");
LCDdisplay();
//start serial monitor (output to PC screen)
Serial.begin(9600);
Serial.println("ready");
clearVars();
} //end void setup
void loop() { // main program starts here
// first laser
if (risingLaser1 == 1) { // rising count increased in ISR, first laser seen, (shutter opening)
Start1 = micros(); // set the variable Start1 to current microseconds (detected shutter open)
risingLaser1++; // increase rising count, so that this code isn't used again during shutter cycle
}
if (fallingLaser1 == 1) { // falling count increased in ISR, first laser changed to not seen, (shutter closing)
Stop1 = micros(); // set the variable Stop1 to current microseconds (detected shutter closing)
fallingLaser1++; // increase falling count, so that this code isn't used again during shutter cycle
firedFlag1 = true; // set the firedFlag1 flag to 1, to allow calculation of shutter speed.
}
// second laser
if (risingLaser2 == 1) { // rising count increased in ISR, second laser seen, (shutter opening)
Start2 = micros(); // set the variable Start2 to current microseconds (detected shutter open)
risingLaser2++; // increase falling count, so that this code isn't used again during shutter cycle
}
if (fallingLaser2 == 1) { // falling count increased in ISR, first laser changed to not seen, (shutter closing)
Stop2 = micros(); // set the variable Stop2 to current microseconds (detected shutter closing)
fallingLaser2++; // increase the falling flag count, so that this code isn't used again during shutter cycle
firedFlag2 = true; // set the firedFlag2 flag, to allow calculation of shutter speed
}
// if shutter cycle complete, run the subroutiens
if (firedFlag1 == true && firedFlag2 == true) {
shutterBounceCheck();
processData();
serialDisplay();
LCDdisplay();
clearVars();
}
// if only 1 laser has operated,after 1 second, invoke single laser mode.
else if (firedFlag1 == true && laserChangeFlag2 == false && (micros() - Stop1 >= 10000000)) { //TEN TIMES LONGER FOR SIMULATION
singleLaserMode = true;
// shutterBounceCheck(); Will not work for single laser mode.
processData();
serialDisplay();
LCDdisplay();
clearVars();
}
} // jump back to void loop as shutter cycle not completed.
// Subrouties.
void shutterBounceCheck() { // wait for a while to see if shutter bounces open
for (uint32_t tStart = millis(); (millis() - tStart) < shutterBounce;) {
if (digitalRead(3) == HIGH) {
return; // bounce detection disabled in simulation
shutterBounceFlag = true;
} // end_if
} // end_for_loop
} // end_shutterBounceCheck
// Do the maths
void processData() {
// do the maths calculations for first laser
SSmicro1 = (Stop1 - Start1); // calculate shutter open/close in microseconds
SSmillis1 = (Stop1 - Start1) / 1000.0; // calculate shutter open/close in millieseconds
SSmillisInt1 = (int(SSmillis1 + 0.5)); // convert millis1 to rounded int
SSsec1 = (float)SSmicro1 / 1000000.0; // convert shutter SSmicro1 to seconds
SSfrac1 = (float)(1.0 / SSsec1); // inverse of theSSsec1, to give vulgar fraction
SSfracV1 = (int(SSfrac1 + 0.5)); // inverse of theSSsec1, to give vulgar fraction rounded up
// do the maths calculations for second laser
if (singleLaserMode == false) { // do the maths for shutter curtain speed if two lasers used
SSmicro2 = (Stop2 - Start2); // calculate shutter open/close in microseconds
SSmillis2 = (Stop2 - Start2) / 1000.0; // calculate shutter open/close in millieseconds
SSmillisInt2 = (int(SSmillis2 + 0.5)); // convert millis2 to rounded int
SSsec2 = (float)SSmicro2 / 1000000.0; // convert shutter SSmicro2 to seconds
SSfrac2 = (float)(1.0 / SSsec2); // inverse of theSSsec2, to give vulgar fraction
SSfracV2 = (int(SSfrac2 + 0.5)); // inverse of theSSsec1, to give vulgar fraction rounded up
// do the maths for shutter curtain speed as two lasers used
shCurtainSpeedMicroS1 = (Start2 - Start1); // Microseconds
shCurtainSpeedMicroS2 = (Stop2 - Stop1); // Microseconds
shCurtainspeedMilliS1 = (Start2 - Start1) / 1000; // miliseconds
shCurtainspeedMilliS2 = (Stop2 - Stop1) / 1000; // miliseconds
Serial.print(Start2);
Serial.print("minus");
Serial.print(Start1);
}
} // end processData
// Display results of laser 1 to serial monitor
void serialDisplay() {
Serial.println();
Serial.print("Laser1 Start: "); // display actual Arduino microsecond clock
Serial.println(Start1);
Serial.print("Laser1 Stop : ");
Serial.println(Stop1);
Serial.print("shutter Speed Microseconds : ");
Serial.println(SSmicro1); // display shutter SSMs in microseconds
Serial.print("shutter Speed Milliseconds : ");
Serial.println(SSmillisInt1); // display shutter SSMs in milliseconds
Serial.print("shutter Speed Seconds : ");
Serial.println(SSsec1, 3); // display shutter SSMs in seconds to 3 decimal places
if (SSsec1 < 1) { // test if shutter SSMs less than 1 second, if so, print in fractions
Serial.print("shutter Speed fraction : 1/");
Serial.println(SSfrac1); // display shutter SSMs in fractions
Serial.print("shutter Speed fraction : 1/");
Serial.println(SSfracV1); // display shutter SSMs in fractions
}
// Display results of laser 2 to serial monitor
if (singleLaserMode == false) {
Serial.println();
Serial.print("Laser2 Start: "); // display actual Arduino microsecond clock
Serial.println(Start2);
Serial.print("Laser2 Stop : ");
Serial.println(Stop2);
Serial.print("shutter Speed Microseconds : ");
Serial.println(SSmicro2); // display shutter SSMs in microseconds
Serial.print("shutter Speed Milliseconds : ");
Serial.println(SSmillisInt2); // display shutter SSMs in milliseconds
Serial.print("shutter Speed Seconds : ");
Serial.println(SSsec2, 3); // display shutter SSMs in seconds to 3 decimal places
if (SSsec2 < 1) { // test if shutter SSMs less than 1 second, if so, print in fractions
Serial.print("shutter Speed fraction : 1/");
Serial.println(SSfrac2); // display shutter SSMs in fractions
Serial.print("shutter Speed fraction : 1/");
Serial.println(SSfracV2); // display shutter SSMs in fractions
}
}
if (singleLaserMode == false) {
Serial.println();
Serial.print("First Curtain Speed Microseconds : ");
Serial.println(shCurtainSpeedMicroS1); // display first curtain travel time between lasers
Serial.print("First Curtain Speed Milliseconds : ");
Serial.println(shCurtainspeedMilliS1); // display first curtain travel time between lasers mS
Serial.print("Second Curtain Speed Microseconds : ");
Serial.println(shCurtainSpeedMicroS2); // display second curtain travel time between lasers
Serial.print("Second Curtain Speed Milliseconds : ");
Serial.println(shCurtainspeedMilliS2); // display second curtain travel time between lasers mS
}
if (shutterBounceFlag == true) {
Serial.println();
Serial.print("Houston, we have shutter Bounce(s)! ");
Serial.println(risingLaser2 - 2); // val increases by 1 each time ISR sees laser. 1 added in loop to stop re-calc.
}
Serial.println();
} //end_serialDisplay
// output to LCD
void LCDdisplay() {
// print 'Bou' on line 0 if shutter bounce detected
if (shutterBounceFlag == true) {
lcd.setCursor(17, 0);
lcd.print("Bou");
} else {
lcd.setCursor(17, 0);
lcd.print(" ");
}
// print miliseconds on line 1 (lines numbered 0, 1, 2, 3)
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(9, 1);
lcd.print(SSmillisInt1);
lcd.setCursor(0, 1);
lcd.print(SSmillisInt2);
lcd.setCursor(18, 1);
lcd.print("mS");
// print seconds on line 2, fraction or decimal
lcd.setCursor(0, 2);
lcd.print(" ");
if (SSsec1 < 1) { // if first laser measurement is < 1 second, print as fraction
lcd.setCursor(9, 2);
lcd.print("1/");
lcd.print(SSfracV1);
lcd.setCursor(18, 2);
lcd.print(" S");
} else {
lcd.setCursor(9, 2);
lcd.print(SSsec1);
lcd.setCursor(18, 2);
lcd.print(" S");
}
if (SSsec2 < 1 && singleLaserMode == false) { // if second laser measurement is < 1 second, print as fraction
lcd.setCursor(0, 2);
lcd.print("1/");
lcd.print(SSfracV2);
} else {
lcd.setCursor(0, 2);
lcd.print(SSsec2);
}
// print curtain speed on line 3,
lcd.setCursor(0, 3);
lcd.print(" ");
lcd.setCursor(0, 3);
lcd.print("C1 ");
if(shCurtainspeedMilliS1>10000){
lcd.print("shutter reversed ");
return;
}
lcd.print(shCurtainspeedMilliS1);
lcd.setCursor(9, 3);
lcd.print("C2 ");
lcd.print(shCurtainspeedMilliS2);
lcd.setCursor(18, 3);
lcd.print("mS");
delay(500);
} // end_LCDdisplay
// clear vars, helps to show errors for debug
void clearVars() {
noInterrupts();
Start1 = 0;
Stop1 = 0;
Start2 = 0;
Stop2 = 0;
SSmicro1 = 0;
SSmicro2 = 0;
shCurtainspeedMilliS1 = 0;
shCurtainspeedMilliS2 = 0;
shCurtainSpeedMicroS1 = 0;
shCurtainSpeedMicroS2 = 0;
SSmillis1 = 0;
SSmillisInt1 = 0;
SSmillis2 = 0;
SSmillisInt2 = 0;
SSsec1 = 0;
SSsec2 = 0;
SSfrac1 = 0;
SSfrac2 = 0;
SSfracV1 = 0;
SSfracV2 = 0;
firedFlag1 = false;
firedFlag2 = false;
singleLaserMode = false;
risingLaser1 = 0;
risingLaser2 = 0;
fallingLaser1 = 0;
fallingLaser2 = 0;
laserChangeFlag2 = false;
shutterBounceFlag = false;
EIFR = bit(INTF0);
EIFR = bit(INTF1);
interrupts();
Serial.println("Ready Again...");
Serial.println();
Serial.println();
} // end clearVars
// Interrupt Routines
void CLOCK1() { // interrupt called everytime the voltage on pin 2 changes, laser 1,
if (digitalRead(2) == LOW) { //reversed from HIGH to LOW to work on UNO/Wokwi
risingLaser1++; // if the voltage on pin 2 is high, increase rising count.
}
if (digitalRead(2) == HIGH) { //reversed from LOW to HIGH to work on UNO/Wokwi
fallingLaser1++; // If the voltage on pin 2 is low, increase falling count
}
}
void CLOCK2() { // interrupt called everytime the voltage on pin 3 changes
if (digitalRead(3) == LOW) { //reversed from HIGH to LOW to work on UNO/Wokwi
risingLaser2++; // if the voltage on pin 3 is high, increase rising count
laserChangeFlag2 = true;
}
if (digitalRead(3) == HIGH) { //reversed from LOW to HIGH to work on UNO/wokwi
fallingLaser2 = true; // If the voltage on pin 3 is low, increase falling count
laserChangeFlag2 = true;
}
}
// end ISR