Close

Added a code contribution from Ryan Hashiro to the files section

A project log for Arduino controlled photogrammetry 3D-scanner

A completely automated way to take photo's and convert them into a 3D-object

brian-brockenBrian Brocken 09/20/2020 at 19:370 Comments

Added a code contribution from Ryan Hashiro to the files section on this page.

Code:

/*

  Original code By Brian Brocken
  Contribution By Ryan Hashiro: 
  1. Added library calls and setups for an LCD with I2C backpack.
  2. Added fast value change routines for the number of photogrammetry pics, motor speed, and cinematic turns.  Holding joystick deflection for > 1 sec enters fast change mode.  Change rate is proportional to amount of deflection.  For the motor speed change is fixed at the slow speed since there's only 17 values to go through.
  3. Added cancel routines for the photogrammetry and cinematic modes.  Holding the joystick button down for > 2 sec kicks you out of the operation modes.

*/

// #include <LiquidCrystal.h>  // use this library for the standard (non I2C) LCD
#include <LiquidCrystal_I2C.h>  // RH - use this library for LCD displays with I2C backpack
#include <Wire.h>  // RH - needed for the LCD I2C library.  SCL connects to pin A5.  SDA connects to pin A4.  Jumper the LED backlight pin on the I2C board
#include <Stepper.h>
#include <Servo.h>

// LiquidCrystal lcd(1, 2, 4, 5, 6, 7);  // Use these pins for the 1602 lcd
LiquidCrystal_I2C lcd(0x27, 16, 2);  // RH - use this for I2C LCD.  Assumes default address of 0x27

const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = A0; // analog pin connected to X output
const int Y_pin = A1; // analog pin connected to Y output

int MenuNr = 0;   // Menu number
int PhotoNr = 2;  // The amount of photos that have to be taken
bool Flag1 = 0;   // This flag is only active during 1 program cycle (prevents constantly adding/subtracting 1 to the menu number when the joystick is pushed to the side) 
bool Flag2 = 0;   // This flag is only active during 1 program cycle (prevents constantly adding/subtracting 2 to the photo number when the joystick is pushed up or down)
bool Flag3 = 0;   // This flag is only active during 1 program cycle (prevents constantly adding/subtracting 1 to the RPM when the joystick is pushed up or down)
bool Flag4 = 0;   // This flag is only active during 1 program cycle (prevents constantly adding/subtracting 1 to the turn number when the joystick is pushed to the side)
bool Flag5 = 0;   // This flag is only active during 1 program cycle (prevents constantly adding/subtracting 1 to the RPM when the joystick is pushed up or down)
bool Flag6 = 0;   // This flag is only active during 1 program cycle to clear the lcd
int SwMenu = 0;   // Switch menu (Sub menu's in the main menu's)
bool BtnFlag = 0; // This flag is only active during 1 program cycle (prevents constantly adding of 1 to SwMenu when button is pressed)

// RH - added variables for fast change & cancel modes
int FastChng = 0;  // indicates fast change value mode.  0 = off, 1 = delay mode, 2 = fast changing mode
const unsigned long FastDelay = 1000;  // delay mode time (before values change fast)
const unsigned long ShortInt = 100;  // short fast change interval
const unsigned long LongInt = 300;  // long fast change interval
const unsigned long BtnDelay = 2000;  // delay for button press to cancel operations.  Note this is an approximate delay, since stepper motor
                                     // suspends all program execution until motor finishes its move
unsigned long SetTime = 0; // time value for fast change & button cancel modes.  Used to calculate time intervals
bool BtnCancelFlag = 0; // This flag is used to detect when button is pressed for canceling operations
bool MaxSwMenu = 0;  // This flag is used for detecting when the maximum SwMenu is reached
bool CinCancelFlag = 0;  // This flag is used to trigger cinematic cancel.  1 = cancel cinematic operation
int StepPoll = 480;  // number of motor steps to poll for cinematic cancel (at 15 rpm)
int Cntr = 0;  // step counter for cinematic motor cancel
// RH - end of added variables

const int stepsPerRevolution = 2048;  // change this to fit the number of steps per revolution
int FullRev = 14336;                  // 1 full revolution of the big gear -> Small-Big gear ratio is 7:1
int rolePerMinute = 15;               // Adjustable range of 28BYJ-48 stepper is 0~17 rpm
int PhotoTaken = 0;                   // Amount of photo's that have been taken
int StepPerPhoto;                     // Amount of steps per photo (calculated -> see MenuNr 0/SwMenu 2)
int TurnNr = 1;                       // Amount of turns
int CurrentTurn = 0;                  // Stores current turn number
int Steps = 0;                        // Amount of individual steps the stepper motor has to turn

Stepper myStepper(stepsPerRevolution, 9, 11, 10, 12);  // Use these pins for the stepper motor

Servo Servo1;  // Define Servo1 as a servo

void setup() {
  //lcd.begin(16, 2);                   //Lcd setup for standard (non I2C) LCD connection
  lcd.init();                         // RH - I2C LCD init command
  lcd.backlight();                    // RH - I2C LCD turn backlight on
  pinMode(SW_pin, INPUT);             //Set pushbutton as input
  digitalWrite(SW_pin, HIGH);         //Set SW_pin High
  
  myStepper.setSpeed(rolePerMinute);  //Set RPM of steppermotor

  Servo1.attach(3);                   //Attach servo to pin 3
  Servo1.write(90);                   //Move servo to mid possition

  lcd.setCursor(4, 0);                //Startup screen start
  lcd.print("Welcome!");              //      """""      //
  lcd.setCursor(1, 1);                //      """""      //
  lcd.print("Software: V1.6");        //      """""      //
  delay(3000);                        //      """""      //
  lcd.clear();                        //      """""      //
  lcd.setCursor(0, 0);                //      """""      //
  lcd.print("Designed by");           //      """""      //
  lcd.setCursor(0, 1);                //      """""      //
  lcd.print("Brian Brocken");         //      """""      //
  delay(2000);                        //      """""      //
  lcd.clear();                        //Startup screen end
}

void loop() {

  int XValue = analogRead(X_pin);     // Read the analog value from The X-axis from the joystick
  int YValue = analogRead(Y_pin);     // Read the analog value from The Y-axis from the joystick
  int SwValue = digitalRead(SW_pin);  // Read the digital value from The Button from the joystick

  if (MenuNr < 0){  //This sets the min number of menu's
    MenuNr = 0;
  }
  else if ( MenuNr > 2){  //This sets the max numbers of menu's
    MenuNr = 2;
  }
  
  if (XValue < 400 && Flag1 == 0 && SwMenu == 0){  //if the joystick is pushed to the right side and flag1 is 0 then 1 will be added to the menu number (purpose of the flag -> see comment Flags above)
    MenuNr = MenuNr + 1; 
    Flag1 = 1;
    lcd.clear();
  }

  if (XValue > 600 && Flag1 == 0 && SwMenu == 0){  //if the joystick is pushed to the left side and flag1 is 0 then 1 will be subtracted from the menu number (purpose of the flag -> see comment Flags above)
    MenuNr = MenuNr - 1;
    Flag1 = 1;
    lcd.clear();
  }
  
  if (XValue > 399 && XValue < 599 && Flag1 == 1){  //if joystick is at neutral possition, flag1 is set to 0 (purpose of the flag -> see comment Flags above)
    Flag1 = 0;
  }


  if (SwValue == 0 && BtnFlag == 0 && MaxSwMenu == 0){  //if the button is pressed and the flag is 0 -> add 1 to menu 
    SwMenu = SwMenu + 1;
    BtnFlag = 1;
    BtnCancelFlag = 0;
    lcd.clear();
  }

  if (SwValue == 1 && BtnFlag == 1){  //if the button is not pressed and the flag is 0 -> Reset the flag (purpose of the flag -> see comment Flags above)
    BtnFlag = 0;
  }
  

//***********************************************Menu0***********************************************//

  if (MenuNr == 0){  //Menu0 program

    if (SwMenu == 0){ //Menu 0 selected
      lcd.setCursor(0, 0);
      lcd.print("Photogrametry");
    }
  
    if (SwMenu == 1){ //entered menu 0
      lcd.setCursor(0, 0);
      lcd.print("Nr of photo's");
      lcd.setCursor(7, 1);
      lcd.print(PhotoNr);

      if (FastChng == 0) {  // RH - if not in fast change mode, update set time
        SetTime = millis();
      }
      
      if (YValue < 400 && Flag2 == 0){ //joystick up -> Add 2 to number of photo's
        PhotoNr = PhotoNr + 2;
        Flag2 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 600 && Flag2 == 0){ //joystick down -> Subtract 2 from number of photo's
        PhotoNr = PhotoNr - 2;
        Flag2 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 399 && YValue < 599 && Flag2 == 1){  //if the Y-axis is back at it's neutral possition -> Flag3 = 0 -> Prevents constant adding or subtracting of 2
        Flag2 = 0;
        FastChng = 0;
      }

      if (YValue < 400 && FastChng == 1) {  // RH - if joystick up is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue > 600 && FastChng == 1) {  // RH - if joystick down is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue < 400 && FastChng == 2) {  // RH - we are in fast change mode, so we want to update the values quickly, proportional between
        if ((millis() - SetTime) > (LongInt - (400 - YValue) * (LongInt - ShortInt) / 400)) {  // ShortInt and LongInt based on joystick deflection
          PhotoNr = PhotoNr + 2;
          SetTime = millis();
          lcd.clear();
        }
      }

      if (YValue > 600 && FastChng == 2) {  // RH - same for joystick down in fast change mode
        if ((millis() - SetTime) > (LongInt - (YValue - 600) * (LongInt - ShortInt) / 400)) {
          PhotoNr = PhotoNr - 2;
          SetTime = millis();
          lcd.clear();
        }
      }

      if (PhotoNr < 2){    //Min allowable Nr of photo's
        PhotoNr = 2;
      }
      if (PhotoNr > 200){  //Max allowable Nr of photo's
        PhotoNr = 200;
      }
    }

    if (SwMenu == 2){ //Program started

      MaxSwMenu = 1;
      
      lcd.setCursor(0, 0);
      lcd.print("Program started");
      lcd.setCursor(0, 1);
      lcd.print("Photo Nr: ");
      lcd.print(PhotoTaken);
      
      StepPerPhoto = FullRev / PhotoNr;  //Calculate amount of steps per photo

      if (PhotoTaken < PhotoNr){          
      myStepper.setSpeed(rolePerMinute);  //Set motor speed
      myStepper.step(StepPerPhoto);       //move the calculated amount of steps
      PhotoTaken = PhotoTaken + 1;        //Add 1 to photos taken
      lcd.setCursor(0, 1);
      lcd.print("Photo Nr: ");            //Taking photo's
      lcd.print(PhotoTaken);
      Servo1.write(30);
      delay(300);
      Servo1.write(90);
      delay(1000);
      }

      if (PhotoTaken == PhotoNr){  //If the amount of photos taken is equal to the amount of photos that have to be taken -> Program finished
        lcd.setCursor(0, 0);
        lcd.print("Program finished");
        delay(3000);
        lcd.clear();  //Rest parameters
        PhotoTaken = 0;
        PhotoNr = 2;
        SwMenu = 0;
        MaxSwMenu = 0;
        Steps = 0;
      }

      // RH NOTE - cancelling works but delay is longer than expected since stepper motor movement is blocking
      if (SwValue == 0 && BtnCancelFlag == 0) {  // if button is pressed, start timing for cancel
        BtnCancelFlag = 1;
        SetTime = millis();
      }

      if (SwValue == 1 && BtnCancelFlag == 1) {  // RH - if button released before BtnDelay, then reset flag
        BtnCancelFlag = 0;
      }

      if (SwValue == 0 && BtnCancelFlag == 1 && ((millis() - SetTime) > BtnDelay)) {  // RH - button has been held for > BtnDelay, so cancel operation
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Program cancel");
        delay(3000);
        lcd.clear();
        PhotoTaken = 0;
        PhotoNr = 2;
        SwMenu = 0;
        MaxSwMenu = 0;
        Steps = 0;
        BtnCancelFlag = 0;
      }
    }
  }


//***********************************************Menu1***********************************************//

  if (MenuNr == 1){  //Menu1 program
    
    if (SwMenu == 0){  //Menu1 selected
      lcd.setCursor(0, 0);
      lcd.print("Cinematic");
      CinCancelFlag = 0;
      Cntr = 0;
    }

    if (SwMenu == 1){  //Entered menu1 - sub menu1
      lcd.setCursor(0, 0);
      lcd.print("motor speed");
      lcd.setCursor(7, 1);
      lcd.print(rolePerMinute);

      if (FastChng == 0) {  // RH - if not in fast change mode, update set time
        SetTime = millis();
      }
      
      if (YValue < 400 && Flag3 == 0){ // joystick up -> Add 1 RPM
        rolePerMinute = rolePerMinute + 1;
        Flag3 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 600 && Flag3 == 0){ // joystick down -> Subtract 1 RPM
        rolePerMinute = rolePerMinute - 1;
        Flag3 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 399 && YValue < 599 && Flag3 == 1){  //if the Y-axis is back at it's neutral possition -> Flag3 = 0 -> Prevents constant adding or subtracting of 1
        Flag3 = 0;
        FastChng = 0;
      }

      if (YValue < 400 && FastChng == 1) {  // RH - if joystick up is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue > 600 && FastChng == 1) {  // RH - if joystick down is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue < 400 && FastChng == 2) {  // RH - we are in fast change mode, so we want to update the values at the LongInt interval
        if ((millis() - SetTime) > LongInt) {  // (using fixed LongInt since not that many values to cycle through)
          rolePerMinute = rolePerMinute + 1;
          SetTime = millis();
          lcd.clear();
        }
      }

      if (YValue > 600 && FastChng == 2) {  // RH - same for joystick down in fast change mode
        if ((millis() - SetTime) > LongInt) {
          rolePerMinute = rolePerMinute - 1;
          SetTime = millis();
          lcd.clear();
        }
      }
      
      if (rolePerMinute < 1){  //Min allowable RPM
        rolePerMinute = 1;
      }
      if (rolePerMinute > 17){  //Max allowable RPM
        rolePerMinute = 17;
      }
    }

    if (SwMenu == 2){  //Entered menu1 - sub menu2
      lcd.setCursor(0, 0);
      lcd.print("Nr of turns");
      lcd.setCursor(7, 1);
      lcd.print(TurnNr);

      if (FastChng == 0) {  // RH - if not in fast change mode, update set time
        SetTime = millis();
      }
      
      if (YValue < 400 && Flag4 == 0){ // joystick up -> Add 1 turn
        TurnNr = TurnNr + 1;
        Flag4 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 600 && Flag4 == 0){ // joystick down -> Subtract 1 turn
        TurnNr = TurnNr - 1;
        Flag4 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 399 && YValue < 599 && Flag4 == 1){  //if the Y-axis is back at it's neutral possition -> Flag3 = 0 -> Prevents constant adding or subtracting of 1
        Flag4 = 0;
        FastChng = 0;
      }

      if (YValue < 400 && FastChng == 1) {  // RH - if joystick up is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue > 600 && FastChng == 1) {  // RH - if joystick down is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue < 400 && FastChng == 2) {  // RH - we are in fast change mode, so we want to update the values quickly, proportional between
        if ((millis() - SetTime) > (LongInt - (400 - YValue) * (LongInt - ShortInt) / 400)) {  // ShortInt and LongInt based on joystick deflection
          TurnNr = TurnNr + 1;
          SetTime = millis();
          lcd.clear();
        }
      }

      if (YValue > 600 && FastChng == 2) {  // RH - same for joystick down in fast change mode
        if ((millis() - SetTime) > (LongInt - (YValue - 600) * (LongInt - ShortInt) / 400)) {
          TurnNr = TurnNr - 1;
          SetTime = millis();
          lcd.clear();
        }
      }
      
      if (TurnNr < 1){  //Min allowable amount of turns
        TurnNr = 1;
      }
      if (TurnNr > 200){  //Max allowable amount of turns
        TurnNr = 200;
      }
    }

    if (SwMenu == 3){  //Program started
      MaxSwMenu = 1;
      StepPoll = 32 * rolePerMinute;  // RH - set polling rate StepPoll = number of steps per second (roughly)
      lcd.setCursor(0, 0);
      lcd.print("Program started");
      lcd.setCursor(0, 1);
      lcd.print("Turns done: ");
      lcd.print(CurrentTurn);

      if (CurrentTurn < TurnNr){ 
        myStepper.setSpeed(rolePerMinute);
        Cntr = 0;  // RH - initialize step counter
        while ((Cntr <= FullRev) && (CinCancelFlag == 0)) { 
          myStepper.step(StepPoll);  // RH - breaking up plate rotation into roughly 1 sec intervals to check for cancel
          Cntr = Cntr + StepPoll;
          SwValue = digitalRead(SW_pin);
          if (SwValue == 0 && BtnCancelFlag == 0) {  // RH - check if button is pressed, if so start timing for cancel
            BtnCancelFlag = 1;
            SetTime = millis();
          }
          if (SwValue == 1 && BtnCancelFlag == 1) {  // RH - if button released before BtnDelay, then reset button flag
            BtnCancelFlag = 0;
          }
          if (SwValue == 0 && BtnCancelFlag == 1 && ((millis() - SetTime) > BtnDelay)) {  // RH - button has been held for > BtnDelay, so cancel operation
            CinCancelFlag = 1;
            CurrentTurn = TurnNr;  
          }
        }
        if (CinCancelFlag == 0) {  // RH - operation not cancelled, so continue
          myStepper.step(FullRev + StepPoll - Cntr);  // RH - need to move remainder of steps for 1 full turn due to kickout from while loop
          CurrentTurn = CurrentTurn + 1;
          lcd.setCursor(0, 1);
          lcd.print("Turns done: ");
          lcd.print(CurrentTurn);
        }
      }

      if (CurrentTurn == TurnNr){  //If the current turn is equal to the amount of thurns that need to be turned -> program finished (RH - or cancelled)
        lcd.setCursor(0, 0);
        if (CinCancelFlag == 0) {
          lcd.print("Program finished");
        }
        else {
          lcd.clear();
          lcd.print("Program cancel");
        }
        delay(3000);
        lcd.clear();  //Reset
        CurrentTurn = 0;
        PhotoNr = 1;
        rolePerMinute = 15;
        SwMenu = 0;
        MaxSwMenu = 0;
        CinCancelFlag = 0;
        Steps = 0;
      }
    }
  }

//***********************************************Menu2***********************************************//

  if (MenuNr == 2){  //Menu2 selected
    
    if (SwMenu == 0){
      lcd.setCursor(0, 0);
      lcd.print("Manual control");
    }

     if (SwMenu == 1){  //Entered menu2
      lcd.setCursor(0, 0);
      lcd.print("motor speed");
      lcd.setCursor(7, 1);
      lcd.print(rolePerMinute);

      if (FastChng == 0) {  // RH - if not in fast change mode, update set time
        SetTime = millis();
      }
      
      if (YValue < 400 && Flag5 == 0){ // joystick up -> Add 1 RPM
        rolePerMinute = rolePerMinute + 1;
        Flag5 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 600 && Flag5 == 0){ // joystick down -> Subtract 1 RPM
        rolePerMinute = rolePerMinute - 1;
        Flag5 = 1;
        FastChng = 1;
        lcd.clear();
      }
      if (YValue > 399 && YValue < 599 && Flag5 == 1){  //if the Y-axis is back at it's neutral possition -> Flag3 = 0 -> Prevents constant adding or subtracting of 1
        Flag5 = 0;
        FastChng = 0;
      }

      if (YValue < 400 && FastChng == 1) {  // RH - if joystick up is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue > 600 && FastChng == 1) {  // RH - if joystick down is held for > the fast delay time, then enter fast change mode
        if ((millis() - SetTime) > FastDelay) {
          FastChng = 2;
          SetTime = millis();  // update the set time
        }
      }

      if (YValue < 400 && FastChng == 2) {  // RH - we are in fast change mode, so we want to update the values at the LongInt interval
        if ((millis() - SetTime) > LongInt) {  // (using fixed LongInt since not that many values to cycle through)
          rolePerMinute = rolePerMinute + 1;
          SetTime = millis();
          lcd.clear();
        }
      }

      if (YValue > 600 && FastChng == 2) {  // RH - same for joystick down in fast change mode
        if ((millis() - SetTime) > LongInt) {
          rolePerMinute = rolePerMinute - 1;
          SetTime = millis();
          lcd.clear();
        }
      }
      
      if (rolePerMinute < 1){  //Min allowable RPM
        rolePerMinute = 1;
      }
      if (rolePerMinute > 17){  //Max allowable RPM
        rolePerMinute = 17;
      }

      if (XValue < 400 ){  //if the joystick is pushed to the right side and the neutral flag is 0 then 1 will be added to the menu number (purpose of the flag -> see comment Flag1 above)
        Steps = Steps + 1;
        myStepper.setSpeed(rolePerMinute);
        myStepper.step(Steps);
        lcd.setCursor(14, 1);
        lcd.print("->");
        Flag6 = 1;
      }

      if (XValue > 600 ){  //if the joystick is pushed to the left side and the neutral flag is 0 then 1 will be subtracted from the menu number (purpose of the flag -> see comment Flag1 above)
        Steps = Steps + 1;
        myStepper.setSpeed(rolePerMinute);
        myStepper.step(-Steps);
        lcd.setCursor(0, 1);
        lcd.print("<-");
        Flag6 = 1;
      }

      if (XValue > 399 && XValue < 599){  //if the Y-axis is back at it's neutral possition -> Flag3 = 0 -> Prevents constant adding or subtracting of 1
        Steps = 0;
        if (Flag6 == 1){
          lcd.clear();
          Flag6 = 0;  //This flag is only active during 1 program cycle to clear the lcd
        }
      }
     }

     if (SwMenu == 2){  //Leave menu 2 when button is pressed
       lcd.clear();
       CurrentTurn = 0;
       PhotoNr = 1;
       rolePerMinute = 15;
       SwMenu = 0;
       Steps = 0;
     }
  }
}

Discussions