265 lines
6.7 KiB
Arduino
265 lines
6.7 KiB
Arduino
/*
|
|
* PhotoStepper -- https://www.0x50.de/rschaten/photostepper
|
|
*
|
|
* PhotoStepper © 2022 by Ronald Schaten is licensed under CC BY-NC 4.0.
|
|
* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/
|
|
*
|
|
*/
|
|
|
|
// this project makes heavy use of the following libraries:
|
|
// - https://www.arduino.cc/reference/en/libraries/arduinomenu-library/
|
|
// - https://www.arduino.cc/reference/en/libraries/stepperdriver/
|
|
// - https://www.arduino.cc/reference/en/libraries/analogkeypad/
|
|
|
|
#include <Arduino.h>
|
|
#include <AnalogKeypad.h>
|
|
#include <BasicStepperDriver.h>
|
|
#include <menu.h>
|
|
#include <menuIO/liquidCrystalOut.h>
|
|
#include <menuIO/serialIO.h>
|
|
#include "Jogrunner.h"
|
|
#include "Scenerunner.h"
|
|
|
|
// analog keypad
|
|
#define PIN_BUTTON A0
|
|
#define KEY_RIGHT 0
|
|
#define KEY_UP 1
|
|
#define KEY_DOWN 2
|
|
#define KEY_LEFT 3
|
|
#define KEY_SELECT 4
|
|
// 2x16 LCD
|
|
#define PIN_LCD_DB4 4
|
|
#define PIN_LCD_DB5 5
|
|
#define PIN_LCD_DB6 6
|
|
#define PIN_LCD_DB7 7
|
|
#define PIN_LCD_RS 8
|
|
#define PIN_LCD_EN 9
|
|
#define PIN_LCD_BL 10
|
|
// stepper motor
|
|
#define PIN_STEPPER_DIR A1
|
|
#define PIN_STEPPER_STEP A2
|
|
// camera
|
|
#define PIN_CAMERA_FOCUS A3
|
|
#define PIN_CAMERA_SHUTTER A4
|
|
|
|
// motor configuration
|
|
#define STEPPER_STEPS 200
|
|
#define STEPPER_RPM 120
|
|
#define STEPPER_MICROSTEPS 1
|
|
#define STEPPER_ACCEL 2000
|
|
#define STEPPER_DECEL 1000
|
|
|
|
#define MAX_DEPTH 2
|
|
|
|
const int KeypadMap[] = {0, 96, 250, 402, 636};
|
|
const uint16_t KeypadHoldTimeMs = 1000;
|
|
const uint8_t KeypadAnalogPin = PIN_BUTTON;
|
|
AnalogKeypad keypad(KeypadAnalogPin, KeypadMap, countof(KeypadMap), KeypadHoldTimeMs);
|
|
|
|
LiquidCrystal lcd(PIN_LCD_RS, PIN_LCD_EN, PIN_LCD_DB4, PIN_LCD_DB5, PIN_LCD_DB6, PIN_LCD_DB7);
|
|
|
|
BasicStepperDriver stepper(STEPPER_STEPS, PIN_STEPPER_DIR, PIN_STEPPER_STEP);
|
|
|
|
enum State { STATE_MENU, STATE_JOG, STATE_RUN };
|
|
State state = STATE_MENU;
|
|
|
|
Jogrunner jogrunner(&lcd, &stepper, STEPPER_RPM);
|
|
Scenerunner scenerunner(&lcd, &stepper, PIN_CAMERA_FOCUS, PIN_CAMERA_SHUTTER, PIN_LCD_BL);
|
|
|
|
// ===
|
|
// Order of menu entries looks chaotic. It makes sense on screen.
|
|
// This is because serial IO (which is used since ArduinoMenu doesn't support
|
|
// analog keypad) doesn't implement setFieldMode().
|
|
|
|
result enter_jog() {
|
|
Serial.println("enter_jog()");
|
|
state = STATE_JOG;
|
|
jogrunner.start();
|
|
return proceed;
|
|
}
|
|
|
|
result enter_run() {
|
|
Serial.println("enter_run()");
|
|
state = STATE_RUN;
|
|
scenerunner.start();
|
|
return proceed;
|
|
}
|
|
|
|
TOGGLE(scenerunner.bReturn, setReturn, "Return: ", doNothing, noEvent, wrapStyle
|
|
,VALUE("On", true, doNothing, noEvent)
|
|
,VALUE("Off", false, doNothing, noEvent)
|
|
);
|
|
|
|
TOGGLE(scenerunner.bDarkenLcd, setLcdBlackout, "DarkenLCD: ", doNothing, noEvent, wrapStyle
|
|
,VALUE("On", true, doNothing, noEvent)
|
|
,VALUE("Off", false, doNothing, noEvent)
|
|
);
|
|
|
|
MENU(configuration, "Configuration", doNothing, anyEvent, wrapStyle
|
|
,FIELD(scenerunner.tSettle, "tSettle", "ms", 0, 2000, 100, 10, doNothing, noEvent, wrapStyle)
|
|
,EXIT("<Back")
|
|
,SUBMENU(setLcdBlackout)
|
|
,SUBMENU(setReturn)
|
|
,FIELD(scenerunner.tShutter, "tShutter", "ms", 0, 2000, 100, 10, doNothing, noEvent, wrapStyle)
|
|
,FIELD(scenerunner.tFocus, "tFocus", "ms", 0, 2000, 100, 10, doNothing, noEvent, wrapStyle)
|
|
);
|
|
|
|
MENU(scene, "Scene", doNothing, anyEvent, wrapStyle
|
|
,FIELD(scenerunner.nSteps, "nSteps", "steps", 0, 100, 10, 1, doNothing, noEvent, wrapStyle)
|
|
,EXIT("<Back")
|
|
,FIELD(scenerunner.distance, "distance", "mm", 0.1, 10, 1, 0.1, doNothing, noEvent, wrapStyle)
|
|
);
|
|
|
|
MENU(mainMenu, "PhotoStepper", doNothing, noEvent, wrapStyle
|
|
,OP("Jog", enter_jog, enterEvent)
|
|
,OP("Run", enter_run, enterEvent)
|
|
,SUBMENU(scene)
|
|
,SUBMENU(configuration)
|
|
);
|
|
|
|
MENU_OUTPUTS(out, MAX_DEPTH
|
|
,LIQUIDCRYSTAL_OUT(lcd, {0, 0, 16, 2})
|
|
,NONE
|
|
);
|
|
|
|
serialIn in(Serial);
|
|
NAVROOT(nav, mainMenu, MAX_DEPTH, in, out);
|
|
|
|
// ===
|
|
|
|
void ButtonHandler_menu(const ButtonParam& param) {
|
|
Serial.print("ButtonHandler_menu(");
|
|
Serial.print(param.button);
|
|
Serial.print(") - ");
|
|
switch (param.state) {
|
|
case ButtonState_Click:
|
|
Serial.println("Click");
|
|
switch (param.button) {
|
|
case KEY_UP:
|
|
nav.doNav(upCmd);
|
|
break;
|
|
case KEY_DOWN:
|
|
nav.doNav(downCmd);
|
|
break;
|
|
case KEY_SELECT:
|
|
nav.doNav(enterCmd);
|
|
break;
|
|
}
|
|
nav.doOutput();
|
|
break;
|
|
default:
|
|
Serial.println(param.state);
|
|
}
|
|
}
|
|
|
|
void ButtonHandler_jog(const ButtonParam& param) {
|
|
Serial.print("ButtonHandler_jog(");
|
|
Serial.print(param.button);
|
|
Serial.print(") - ");
|
|
switch (param.state) {
|
|
case ButtonState_Click:
|
|
Serial.println("Click");
|
|
switch (param.button) {
|
|
case KEY_SELECT:
|
|
state = STATE_MENU;
|
|
mainMenu.dirty = true;
|
|
break;
|
|
}
|
|
nav.doOutput();
|
|
break;
|
|
case ButtonState_Up:
|
|
Serial.println("Up");
|
|
jogrunner.stop();
|
|
break;
|
|
case ButtonState_Down:
|
|
Serial.println("Down");
|
|
switch (param.button) {
|
|
case KEY_UP:
|
|
jogrunner.forward();
|
|
break;
|
|
case KEY_DOWN:
|
|
jogrunner.backward();
|
|
break;
|
|
}
|
|
break;
|
|
case ButtonState_Hold:
|
|
Serial.println("Hold");
|
|
jogrunner.setSpeed(HIGH);
|
|
switch (param.button) {
|
|
case KEY_UP:
|
|
jogrunner.forward();
|
|
break;
|
|
case KEY_DOWN:
|
|
jogrunner.backward();
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
Serial.println();
|
|
}
|
|
}
|
|
|
|
void ButtonHandler_run(const ButtonParam& param) {
|
|
Serial.print("ButtonHandler_run(");
|
|
Serial.print(param.button);
|
|
Serial.print(") - ");
|
|
switch (param.state) {
|
|
default:
|
|
Serial.println();
|
|
stepper.stop();
|
|
stepper.setRPM(STEPPER_RPM);
|
|
state = STATE_MENU;
|
|
mainMenu.dirty = true;
|
|
nav.doOutput();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
while(!Serial);
|
|
Serial.println("PhotoStepper -- schatenseite.de");
|
|
|
|
// control backlight
|
|
pinMode(PIN_LCD_BL, OUTPUT);
|
|
digitalWrite(PIN_LCD_BL, HIGH);
|
|
|
|
// setup camera connection
|
|
pinMode(PIN_CAMERA_FOCUS, OUTPUT);
|
|
digitalWrite(PIN_CAMERA_FOCUS, LOW);
|
|
pinMode(PIN_CAMERA_SHUTTER, OUTPUT);
|
|
digitalWrite(PIN_CAMERA_SHUTTER, LOW);
|
|
|
|
stepper.begin(STEPPER_RPM, STEPPER_MICROSTEPS);
|
|
stepper.setSpeedProfile(stepper.LINEAR_SPEED, STEPPER_ACCEL, STEPPER_DECEL);
|
|
|
|
lcd.begin(16,2);
|
|
lcd.print("PhotoStepper");
|
|
lcd.setCursor(0, 1);
|
|
lcd.print("schatenseite.de");
|
|
delay(2000);
|
|
nav.doOutput();
|
|
}
|
|
|
|
void loop() {
|
|
switch (state) {
|
|
case STATE_MENU:
|
|
keypad.loop(ButtonHandler_menu);
|
|
break;
|
|
case STATE_JOG:
|
|
keypad.loop(ButtonHandler_jog);
|
|
stepper.nextAction();
|
|
break;
|
|
case STATE_RUN:
|
|
keypad.loop(ButtonHandler_run);
|
|
stepper.nextAction();
|
|
scenerunner.nextAction();
|
|
if (!scenerunner.isBusy()) {
|
|
state = STATE_MENU;
|
|
mainMenu.dirty = true;
|
|
nav.doOutput();
|
|
}
|
|
break;
|
|
}
|
|
}
|