#define EB_FAST_TIME 120
#define EB_NO_FOR
#include <EEPROM.h>
#include <GyverOLED.h>
#include <EncButton.h>
#include "GyverOLEDMenu.h"
#define PROJECTOR_AUTOFOCUS_ON 13
#define PROJECTOR_NEXT_SLIDE 12
#define LED_RED 11
#define LED_GREEN 10
#define LED_BLU 9
#define BTN_SINGLE_PIC 2
#define CAM_FOCUS 4
#define CAM_SHUTTER 3
EncButton eb(5, 6, 7, INPUT_PULLUP); // CLK, DT, SW
Button sb(BTN_SINGLE_PIC, INPUT_PULLUP);
GyverOLED<SSD1306_128x64, OLED_BUFFER> oled;
OledMenu<8, GyverOLED<SSD1306_128x64, OLED_BUFFER>> menu(&oled); // Where the first number is the number of items in the menu
boolean suspend_updates = false; // used to suspend the updates of the menu when a message is displayed
byte num_slides = 5; //standard cartrige contains 50 slides [default: 5]
boolean wait_slide = false; // if the projector is auto-focus it is possible to send signal when slide has lodged in [default: false]
byte slide_in_out = 16; // otherwise wait some time (in DECISECONDS to keep byte) [default: 16]
byte vibrations = 8; //wait for vibrations to stop after slide in (in DECISECONDS to keep byte) [default: 8]
byte focus = 16; //time to let the camera to autofocus (in DECISECONDS to keep byte) [default: 14]
byte shutter = 4; //time to let camera to take the pic (in DECISECONDS to keep byte) [default: 4]
void setup() {
oled.init();
Wire.setClock(400000L);
//load defaults from EEPROM
//readDefaults(); // commented in Wokwi
pinMode(PROJECTOR_AUTOFOCUS_ON, INPUT_PULLUP);
pinMode(PROJECTOR_NEXT_SLIDE, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLU, OUTPUT);
pinMode(BTN_SINGLE_PIC, INPUT_PULLUP);
pinMode(CAM_FOCUS, OUTPUT);
pinMode(CAM_SHUTTER, OUTPUT);
Serial.begin(9600); // open the serial port at 9600 bps:
menu.onChange(onItemChange, true);
//menu.onPrintOverride(onItemPrintOverride); // if needed make your own formatting of values
// add the menu items with name, increment, value (can be stored in a variabl)e, min val, max val)
// if only name, then it is only a menu item, can be used to run function
menu.addItem(PSTR("Capture batch")); // 0
menu.addItem(PSTR("Slides num."), GM_N_BYTE(1), &num_slides, GM_N_BYTE(1), GM_N_BYTE(255)); // 1
menu.addItem(PSTR("Auto slide in"), &wait_slide);
menu.addItem(PSTR("Slide_in/out"), GM_N_BYTE(2), &slide_in_out, GM_N_BYTE(2), GM_N_BYTE(40));
menu.addItem(PSTR("Autofocus time"), GM_N_BYTE(2), &focus, GM_N_BYTE(2), GM_N_BYTE(100)); // 4
menu.addItem(PSTR("Shutter time"), GM_N_BYTE(2), &shutter, GM_N_BYTE(2), GM_N_BYTE(100)); // 5
menu.addItem(PSTR("Vibes time"), GM_N_BYTE(0), &vibrations, GM_N_BYTE(2), GM_N_BYTE(40));
menu.addItem(PSTR("Save default")); // 7
eb.attach(cb);
sb.attach(sb_cb);
suspend_updates = true;
oled.clear();
oled.update();
//SplashScreen
oled.home();
oled.setScale(3);
oled.println("SLIDES");
oled.println("SCANNER");
oled.setScale(1);
oled.print("by F.Albertini 2024");
oled.update();
delay(5000);
suspend_updates = false;
//roll the encoder to exit the splash screen and show the menu
}
void writeDefaults() {
// save on EEPROM the default values
EEPROM.write(0, num_slides);
//wait_slide is boolean, write only in the bit 0 of the byteaddress 1
byte EEPROMbyte = EEPROM.read(1);
bitWrite(EEPROMbyte, 0, wait_slide);
bitWrite(EEPROMbyte, 1, 1); // write a 1 in second bit so even if waitslide = 0 the whole byte is > 0 (for reading)
EEPROM.update(1, EEPROMbyte);
// the rest are bytes
EEPROM.write(2, slide_in_out);
EEPROM.write(3, vibrations);
EEPROM.write(4, focus);
EEPROM.write(5, shutter);
//clean the screen, report the writing
menu.showMenu(false);
oled.home();
oled.print("Default values recorded"); // confirm writing
oled.update();
}
void readDefaults() {
//read defaults from memory
if (EEPROM.read(0)>0) {num_slides = EEPROM.read(0);}
if (EEPROM.read(1)>0) {wait_slide = bitRead(EEPROM.read(1), 0);}
if (EEPROM.read(2)>0) {slide_in_out = EEPROM.read(2);}
if (EEPROM.read(3)>0) {vibrations = EEPROM.read(3);}
if (EEPROM.read(4)>0) {focus = EEPROM.read(4);}
if (EEPROM.read(5)>0) {shutter = EEPROM.read(5);}
}
void onItemChange(const int index, const void* val, const byte valType) {
if (valType == VAL_ACTION) {
switch (index) {
case 0: // start the entire batch sequence
// call the funciton
capture_sequence(num_slides);
break;
case 7: //save teh current parmeters
writeDefaults();
break;
default:
break;
}
}
}
void sb_cb() {
switch (sb.action()) {
case EB_CLICK:
// start capture of a single image
capture_sequence(1);
break;
default:
break;
}
}
void cb() {
switch (eb.action()) {
case EB_TURN:
//if the menu was hidden, then show it
if((!menu.isMenuShowing) && (!suspend_updates)){
menu.showMenu(true);
}
else if((menu.isMenuShowing) && (!suspend_updates)){
//move up or down
if (eb.dir() == 1) {
menu.selectPrev(eb.fast());
} else {
menu.selectNext(eb.fast());
}
}
break;
case EB_CLICK:
menu.toggleChangeSelected();
break;
}
}
void setColor(int redValue, int greenValue, int blueValue) {
analogWrite(LED_RED, redValue);
analogWrite(LED_GREEN, greenValue);
analogWrite(LED_BLU, blueValue);
}
// colors of the RGB Led to simulate the phases:
// RED: slide move in/out
// BLUE: focus being taken
// GREEN: shutter being fired
// WHITE: projector remote control (Relay)
void capture_sequence(int Reps) {
// hode the menu, suspend updates to the encoder and show the progress screen
suspend_updates=true;
menu.showMenu(false);
setColor(0, 0, 0); // LED off
unsigned long Bat_t = 0; //total elapsed time of the batch job
// cycle for Reps time
for (int i=1; i<Reps+1; i++) {
// start the timer
unsigned long Start_t = millis();
// 1) send signal to the projector to feed one slide <------------ TBD: first check if a slide is already in
// signal projector via remote control (Optocoupler)
digitalWrite(PROJECTOR_NEXT_SLIDE, HIGH);
setColor(255, 255, 255); // White color on (remote control to projector)
delay(300); //impulse length in milliseconds
digitalWrite(PROJECTOR_NEXT_SLIDE, LOW);
// 2) wait for slide_out and in
setColor(255, 0, 0); // Red Color on (slide out)
delay(slide_in_out*100);
setColor(127, 0, 127); // Purple Color on (slide in)
// 3) if not auto_slide_in then wait for slide_in
if(wait_slide == true) {
// wait for signal from projector
while (digitalRead(PROJECTOR_AUTOFOCUS_ON) == LOW) {
// Do nothing
}
// wait for vibrations to stop
delay(vibrations*100);
// switch off slide_in led
setColor(0, 0, 0); // Red Color off (slide lodged)
}
else {
// wait for slide_in
delay(slide_in_out*100);
// wait for vibrations to stop
delay(vibrations*100);
// switch off slide_in led
setColor(0, 0, 0); // Red Color off;
}
// 4) send signal to camera for autofocus and wait autofocus
digitalWrite(CAM_FOCUS, HIGH);
setColor(0, 0, 255); // Blue color on
// give time to focus
delay(focus*100);
setColor(0, 0, 0); // Blue color off
// 5) send signal to camera for shutter and wait shutter
digitalWrite(CAM_SHUTTER, HIGH);
delay(300); //impulse length in milliseconds
digitalWrite(CAM_SHUTTER, LOW);
digitalWrite(CAM_FOCUS, LOW);
setColor(0, 255, 0); // Green color on
// Wait for the pic to be taken
delay(shutter*100);
setColor(0, 0, 0); // Green color on
// 6) Calculate the time elapsed and average
unsigned long End_t = millis();
Bat_t += (End_t - Start_t);
// 7) slide report
oled.clear();
oled.update();
oled.home();
oled.print("SLIDE: "); oled.print(i); oled.print(" of "); oled.println(Reps);
oled.print("TIME: "); oled.print((End_t - Start_t)/1000); oled.print(" AVG: "); oled.println((Bat_t / i)/1000);
oled.print("TOTAL: "); oled.print(Bat_t/1000); oled.print(" sec.");
oled.update();
// 8) Loop back or exit
}
//Restore the listening for changes
suspend_updates=false;
}
void loop() {
if (!suspend_updates){
eb.tick();
sb.tick();
}
}