
// shutter tester version 2.9.7 for Arduino Nano. 31 March 2023
// https://www.photrio.com/forum/threads/build-a-shutter-tester-for-focal-plane-shutters-cheap-easy-it-works.197756/



// *********************************************************************************************************
// newer laser sensors may have a reversed output. They can be changed here by swapping the two active lines
const bool seen    = true;     // un-comment this line (older sensors)
const bool blocked = false;    // AND this line        (older sensors)

// const bool seen    = false;  // OR this line       (newer sensors)
// const bool blocked = true;   // AND this line      (newer sensors)
// *********************************************************************************************************


#include <Wire.h>               // built in library
#include <LiquidCrystal_I2C.h>  // by Frank de Brabander
#include <LibPrintf.h>          // https://github.com/embeddedartistry/arduino-printf

LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address. Normally 0x27 for a 20 char 4 line display

#define DEBUG 0  // add debugging print commands & toggle on & off
#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 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 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

long int shCurtainspeedMilliS1;  // curtain 1 speed in Milliseconds
long int shCurtainspeedMilliS2;  // curtain 2 speed in Milliseconds
long int shCurtainSpeedMicroS1;  // curtain 1 speed in Microseconds
long int shCurtainSpeedMicroS2;  // curtain 2 speed in Microseconds
const int shutterBounce = 1000;  // time in mS for shutter shutterBounce 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

int SSfracV1;  // 1/Shutter Speed Fraction of second rounded
int SSfracV2;  // 1/Shutter Speed Fraction of second rounded

String StringFrac   = "1/";     // symbol used in print
String StringFracV1 = "0";      // string to store vulgar fracton, for neat printing
String StringFracV2 = "0";      // string to store vulgar fracton, for neat printing

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 singleLaserFlag;     // flag set if only one laser being used, calculated in program
bool shutterBounceFlag1;  // flag set if shutter shutterBounce detected.
bool shutterBounceFlag2;  // flag set if shutter shutterBounce detected.
bool sensorReversedFlag;  // flag set is sensors triggered in wrong order

//variables used in ISR
volatile int risingLaser1;       // increases each time isr is called & detects shutter open on  first sensor
volatile int fallingLaser1;      // increases each time isr is called & detects shutter close on first sensor
volatile int risingLaser2;       // increases each time isr is called & detects shutter open on  second sensor
volatile int fallingLaser2;      // increases each time isr is called & detects shutter close on second sensor
volatile bool laserChangeFlag2;  // flag set in isr, set when isr called, used to  determine single laser use

volatile int sensorIn1 = 2;  // name input pins
volatile int sensorIn2 = 3;  // name input pins


void setup() {  // This part of the program is run exactly once on boot

  //define & setup input pins
  pinMode(sensorIn1, INPUT);     // Laser 1 input
  pinMode(sensorIn2, INPUT);     // Laser 2 input
  attachInterrupt(digitalPinToInterrupt(sensorIn1), CLOCK1, CHANGE);  // run the ISR CLOCK1, every time the voltage on pin 2 changes.
  attachInterrupt(digitalPinToInterrupt(sensorIn2), 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.7    ");
  lcd.setCursor(0, 3);
  lcd.print("   31-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);

  clearVars(); // clear all variables & flags - good housekeeping
}  //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 subroutines
  if (firedFlag1 == true && firedFlag2 == true) {
    shutterBounceCheck();
    processData();
   // serialDisplay();
    altDisplay ();
    LCDdisplay();
    clearVars();
  }

  // if only 1 laser has operated,after 1 second, invoke single laser mode.
  else if (firedFlag1 == true && laserChangeFlag2 == false && (micros() - Stop1 >= 1000000)) {
    singleLaserFlag = true;
    shutterBounceCheck();
    processData();
   // serialDisplay();
    altDisplay ();
    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, second laser
  for (uint32_t tStart = millis(); (millis() - tStart) < shutterBounce;) { // sit in a loop, to see if a bounce occurs
    if (risingLaser2 > 2) { // check to see if ISR has recorded sensor activations
      shutterBounceFlag2 = true;
    }
  } 

  if (risingLaser1 > 2) { // check to see if ISR has recorded sensor activations on first laser
    shutterBounceFlag1 = true;
  }
} 



//  Do the maths
void processData() {

  if ((Start2 < Start1) || (Stop2 < Stop1)) {  // error check lasers triggered in reverse (subtraction for micro-wrap)
    sensorReversedFlag = true;
  } else {
    sensorReversedFlag = false;
  }

  // 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
  SSfracV1 = (1.0 / SSsec1) + 0.5;         // inverse of theSSsec1, to give vulgar fraction rounded
  StringFracV1 = StringFrac + String(SSfracV1); // make a string for neat printing

  // do the maths calculations for second laser
  if (singleLaserFlag == 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
    SSfracV2 = (1.0 / SSsec2) + 0.5;        // inverse of theSSsec2, to give vulgar fraction rounded
    StringFracV2 = StringFrac + String(SSfracV2); // make a string for neat printing

    // 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



// Display results of laser 1 to serial monitor
void serialDisplay() {
  Serial.println();
  Serial.print(F("Laser1 Start: "));  // display actual Arduino microsecond clock
  Serial.println(Start1);
  Serial.print(F("Laser1 Stop : "));
  Serial.println(Stop1);

  Serial.print(F("shutter Speed Microseconds   : "));
  Serial.println(SSmicro1);  // display shutter SSMs in microseconds

  Serial.print(F("shutter Speed Milliseconds   : "));
  Serial.println(SSmillisInt1);  // display shutter SSMs in milliseconds

  Serial.print(F("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(F("shutter Speed fraction       : 1/"));
    Serial.println(SSfracV1);  // display shutter SSMs in fractions

    Serial.print(F("string  : ")); // print string value of fractions (calc in processData)
    Serial.print(StringFracV1);
    Serial.println();
  }


  // Display results of laser 2 to serial monitor
  if (singleLaserFlag == false) {
    Serial.println();
    Serial.print(F("Laser2 Start: "));  // display actual Arduino microsecond clock
    Serial.println(Start2);
    Serial.print(F("Laser2 Stop : "));
    Serial.println(Stop2);

    Serial.print(F("shutter Speed Microseconds   : "));
    Serial.println(SSmicro2);  // display shutter SSMs in microseconds

    Serial.print(F("shutter Speed Milliseconds   : "));
    Serial.println(SSmillisInt2);  // display shutter SSMs in milliseconds

    Serial.print(F("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(F("shutter Speed fraction       : 1/"));
      Serial.println(SSfracV2);  // display shutter SSMs in fractions

      Serial.print(F("string  : "));// print string value of fractions (calc in processData)
      Serial.print(StringFracV2);
      Serial.println();
    }
  }


  if (singleLaserFlag == false && sensorReversedFlag == false) {  // print curtain speeds
    Serial.println();
    Serial.print(F("First  Curtain Speed Microseconds : "));
    Serial.println(shCurtainSpeedMicroS1);  // display first curtain travel time between lasers
    Serial.print(F("First  Curtain Speed Milliseconds : "));
    Serial.println(shCurtainspeedMilliS1);  // display first curtain travel time between lasers mS

    Serial.print(F("Second Curtain Speed Microseconds : "));
    Serial.println(shCurtainSpeedMicroS2);  // display second curtain travel time between lasers
    Serial.print(F("Second Curtain Speed Milliseconds : "));
    Serial.println(shCurtainspeedMilliS2);  // display second curtain travel time between lasers mS
  }

  if (shutterBounceFlag2 == true && singleLaserFlag == false) {  //check for second curtain bounce,
    Serial.println();
    Serial.print(F("Houston, we have second curtain Bounce(s)!  "));
    Serial.println(risingLaser2 - 2);  // val increases by 1 each time ISR sees laser, + 1
  }

  if (shutterBounceFlag1 == true) {       // check for first curtain bounce
    Serial.println();
    Serial.print(F("Houston, we have first curtain Bounce(s)!  "));
    Serial.println(risingLaser1 - 2);  // val increases by 1 each time ISR sees laser, + 1
  }

  if (singleLaserFlag == false && sensorReversedFlag == true) { // report warnings
    Serial.println();
    Serial.print(F("Sensor or camera is reversed. "));
    Serial.println(F("Unable to calculate curtain speed  "));
  }
  if (Start2 < Stop1 && sensorReversedFlag == false) {          // test for flash sync
    Serial.println();
    Serial.println(F("Flash Sync (between the two lasers)"));
  }
  Serial.println();
}  //end_serialDisplay


void altDisplay() {   // print to screen in tabulated output, neater & easier to read
  printf("PARAMETER                 LASER2           LASER1 \n");
//  printf("Laser Start            %10li       %10li       \n", Start2, Start1);
//  printf("Laser Stop             %10li       %10li       \n", Stop2, Stop2);
  printf("\n");
  printf("Shutter speed Micro S  %10lu       %10lu       \n", SSmicro2, SSmicro1);
  printf("Shutter speed milli S  %10i       %10i         \n\n", SSmillisInt2, SSmillisInt1);
  printf("Shutter speed Seconds     %7.3f          %7.3f \n", SSsec2, SSsec1);

  printf("Shutter Speed Frac");
  if (SSsec2 < 1 && singleLaserFlag == false) {   // test if Laser2 less than 1 second, if so, print in fractions
    printf("         %6s", StringFracV2.c_str());  // display shutter SSMs in fractions
  }
  else {
    printf("               ");
  }

  if (SSsec1 < 1) {   // test if Laser1 less than 1 second, if so, print in fractions
    printf("           %6s \n\n", StringFracV1.c_str());  // display shutter SSMs in fractions
  }
  else {
    printf("\n\n");
  }

  if (singleLaserFlag == false && sensorReversedFlag == false) {  // print curtain speeds
    printf("Curtain Speed Micro S  %10li       %10li\n", shCurtainSpeedMicroS2, shCurtainSpeedMicroS1);
    printf("Curtain Speed Milli S  %10li       %10li\n\n", shCurtainspeedMilliS2, shCurtainspeedMilliS1);
  }

  if (shutterBounceFlag2 == true) {    // print shutter 2 bounce count.
    printf("Houston, we have second curtain Bounce(s)!  %i \n", risingLaser2 - 2 );
  }

if (shutterBounceFlag1 == true) {      // print shutter 1 bounce count.
    printf("Houston, we have first curtain Bounce(s)!  %i \n", risingLaser1 - 2 );
  }

  if (singleLaserFlag == false && sensorReversedFlag == true) { // print warning
    printf("Sensor or camera is reversed. Unable to calculate curtain speed \n");
  }

  if (Start2 < Stop1 && sensorReversedFlag == false) {
    printf("Flash Sync (between the two lasers)\n");
  }

  printf("\n");
}// end void altDisplay


// output to LCD
void LCDdisplay() {

  if (shutterBounceFlag2 == true || shutterBounceFlag2 == true) {// print 'Bou' on line 0 if shutter bounce detected
    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 && singleLaserFlag == 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,
  if (sensorReversedFlag == false) {
    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");
  }

  else {
    lcd.setCursor(0, 3);
    lcd.print("Unable to calc CuSpd");
  }
}  // end_LCDdisplay


// clear vars
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;

  SSfracV1 = 0;
  SSfracV2 = 0;

  String StringFracV1 = "0";
  String StringFracV2 = "0";

  firedFlag1 = false;
  firedFlag2 = false;
  singleLaserFlag = false;

  risingLaser1 = 0;
  risingLaser2 = 0;
  fallingLaser1 = 0;
  fallingLaser2 = 0;
  laserChangeFlag2 = false;
  shutterBounceFlag1 = false;
  shutterBounceFlag2 = false;
  sensorReversedFlag = false;

  EIFR = bit(INTF0);
  EIFR = bit(INTF1);
  interrupts();
  Serial.println("Ready...");
}  // end clearVars



//    Interrupt Routines
void CLOCK1() {  // interrupt called everytime the voltage on pin 2 changes, laser 1,
  if (digitalRead(sensorIn1) == seen) {
    risingLaser1++;  // if the voltage on pin 2 is high, increase rising count.
  }
  if (digitalRead(sensorIn1) == blocked) {
    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(sensorIn2) == seen) {
    risingLaser2++;           // if the voltage on pin 3 is high, increase rising count
    laserChangeFlag2 = true;  // set flag
  }
  if (digitalRead(sensorIn2) == blocked) {
    fallingLaser2 = true;  // If the voltage on pin 3 is low, increase falling count
  }
}

// end ISR
