//
//
// This example and code is in the public domain and may be used without restriction and
// without warranty.
//
// Exmple sketch - Button Switch Using An External Interrupt
// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
// This sketch demonstrates the use of a simple button switch which is processed by
// an external interrupt process. It presents a very different and alternative approach
// for associating a button switch to an interrupt.
//
// The sketch is designed such that button status is only flagged as 'switched' AFTER
// 1. button is pressed AND then released, AND
// 2. elapse of the debounce period AFTER release
//
// Note that the associated button interrupt handler function and the button_read()
// function work together - the interrupt handler starts the on/off process and the
// button_read() function completes/concludes it. The interrupt handler can only restart AFTER
// button reading and debounce is complete. This ensures that only one interrupt trigger is
// processed at a time.
//
// The button switch is wired in a standard configuration with a 10K ohm pull down resister which
// ensures the digital interrupt pin is kept LOW until the button switch is pressed and
// raises it to HIGH (+5v).
//
// Operation of the button is demonstrated by toggling the in built LED on and off.
//
#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
#define LED LED_BUILTIN // digital pin connected to LED, for testing of switch code only
bool led_status = LOW; // start with LED off, for testing of switch code only
int button_switch = 13; // external interrupt pin
#define switched true // value if the button switch has been pressed
#define triggered true // controls interrupt handler
#define interrupt_trigger_type RISING // interrupt triggered on a RISING input
#define debounce 20 // time to wait in milli secs
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); // set the LCD address. Normally 0x27 for a 20 char 4 line display
volatile bool interrupt_process_status = {
!triggered // start with no switch press pending, ie false (!triggered)
};
bool initialisation_complete = false; // inhibit any interrupts until initialisation is complete
void shutterBounceCheck() { // wait for a while to see if shutter bounces open
for (uint32_t tStart = millis(); (millis() - tStart) < shutterBounce;) {
if (digitalRead(3) == HIGH) {
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
}
} // end processData
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 ");
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
void CLOCK1() { // interrupt called everytime the voltage on pin 2 changes, laser 1,
if (digitalRead(2) == HIGH) {
risingLaser1++; // if the voltage on pin 2 is high, increase rising count.
}
if (digitalRead(2) == LOW) {
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) == HIGH) {
risingLaser2++; // if the voltage on pin 3 is high, increase rising count
laserChangeFlag2 = true;
}
if (digitalRead(3) == LOW) {
fallingLaser2 = true; // If the voltage on pin 3 is low, increase falling count
// laserChangeFlag2 = true;
}
}
//
// ISR for handling interrupt triggers arising from associated button switch
//
void button_interrupt_handler()
{
if (initialisation_complete == true)
{ // all variables are initialised so we are okay to continue to process this interrupt
if (interrupt_process_status == !triggered) {
// new interrupt so okay start a new button read process -
// now need to wait for button release plus debounce period to elapse
// this will be done in the button_read function
if (digitalRead(button_switch) == HIGH) {
// button pressed, so we can start the read on/off + debounce cycle wich will
// be completed by the button_read() function.
interrupt_process_status = triggered; // keep this ISR 'quiet' until button read fully completed
}
}
}
} // end of button_interrupt_handler
bool read_button() {
int button_reading;
// static variables because we need to retain old values between function calls
static bool switching_pending = false;
static long int elapse_timer;
Serial.print(interrupt_process_status);
if (interrupt_process_status == triggered) {
// interrupt has been raised on this button so now need to complete
// the button read process, ie wait until it has been released
// and debounce time elapsed
button_reading = digitalRead(button_switch);
if (button_reading == HIGH) {
// switch is pressed, so start/restart wait for button relealse, plus end of debounce process
switching_pending = true;
elapse_timer = millis(); // start elapse timing for debounce checking
}
if (switching_pending && button_reading == LOW) {
// switch was pressed, now released, so check if debounce time elapsed
if (millis() - elapse_timer >= debounce) {
// dounce time elapsed, so switch press cycle complete
switching_pending = false; // reset for next button press interrupt cycle
interrupt_process_status = !triggered; // reopen ISR for business now button on/off/debounce cycle complete
return switched; // advise that switch has been pressed
}
}
}
return !switched; // either no press request or debounce period not elapsed
} // end of read_button function
void setup() {
pinMode(LED, OUTPUT);
pinMode(button_switch, INPUT);
attachInterrupt(digitalPinToInterrupt(button_switch),
button_interrupt_handler,
interrupt_trigger_type);
initialisation_complete = true; // open interrupt processing for business
//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 ");
lcd.setCursor(0, 3);
lcd.print(" 19-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 of setup function
void loop() {
// test buton switch and process if pressed
if (read_button() == switched) {
// button on/off cycle now complete, so flip LED between HIGH and LOW
led_status = HIGH - led_status; // toggle state
digitalWrite(LED, led_status);
}
// 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 >= 1000000)) {
singleLaserMode = true;
// shutterBounceCheck(); Will not work for single laser mode.
processData();
serialDisplay();
LCDdisplay();
clearVars();
}
}
// shutter tester version 2.9.3 for Arduino Nano. 19 March 2023
// https://www.photrio.com/forum/threads/build-a-shutter-tester-for-focal-plane-shutters-cheap-easy-it-works.197756/
//void setup() { // This part of the program is run exactly once on boot
//} //end void setup
//void loop() { // main program starts here
// jump back to void loop as shutter cycle not completed.
// Subrouties.
// Display results of laser 1 to serial monitor
// Interrupt Routines
// end ISR