Files
2022-02-12 01:03:46 +01:00

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;
}
}