M5StickC Intervalometer
Intervalometer for the Canon DSLR using an M5StickC I had laying around, as well as the Relay module for the device.
Here's the source code. M5StickC board and ESP32 libraries must be installed in the Arduino IDE
#include <M5StickC.h>
int cur_val_front = 0;
int last_val_front = 0;
int cur_val_side = 0;
int last_val_side = 0;
int cursor_selection = 0; // 0 = minutes, 1 = seconds, 2 = shots, 3 = start, 4 = flash, 5 = reset
bool is_running = false;
int flash_on = false;
int minute_count = 0;
int second_count = 0;
int shot_count = 0; // user selected shot amount, 0 = unlimited shots
int relay_delay_interval = 1; // delay between relay closes in seconds
int seconds_until_open = 0; // used to display countdown of seconds until relay opens
int relay_counter = 0; // used to display count of relay cycles
int shot_counter = 0; // used to display count of shots left
uint32_t target_time = 0; // for next 1 second timeout
//screen coord vals
int min_lbl_x = 0;
int min_lbl_y = 2;
int min_cnt_x = 55;
int min_cnt_y = 2;
int sec_lbl_x = 80;
int sec_lbl_y = 2;
int sec_cnt_x = 125;
int sec_cnt_y = 2;
int shot_lbl_x = 0;
int shot_lbl_y = 22;
int shot_cnt_x = 55;
int shot_cnt_y = 22;
int flash_lbl_x = 80;
int flash_lbl_y = 22;
int flash_cnt_x = 125;
int flash_cnt_y = 22;
int start_lbl_x = 20;
int start_lbl_y = 52;
int start_rect[] = { 0, 48, 82, 24 };
int clr_lbl_x = 105;
int clr_lbl_y = 52;
int clr_rect[] = { 88, 48, 52, 24 };
int cnt_w = 20; // width of char erase rect
int cnt_h = 15; // height of char erase rect
void setup() {
M5.begin();
M5.Lcd.setRotation(3);
M5.Lcd.setTextFont(2);
M5.Lcd.setTextSize(1);
init_select_screen();
// set buttons for input
pinMode(37, INPUT); // front 'M5' button
pinMode(39, INPUT); // side button
//disable the speak noise
pinMode(32, OUTPUT);
}
void loop(void) {
cur_val_front = digitalRead(37);
cur_val_side = digitalRead(39);
if (is_running == false){ // on selection menu
if (cur_val_front != last_val_front) {
if (cur_val_front == 0) { // button pressed
if (cursor_selection == 0) { // minutes
minute_count++;
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(min_cnt_x, min_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(min_cnt_x, min_cnt_y);
M5.Lcd.print(String(minute_count));
}
else if (cursor_selection == 1) { // seconds
second_count++;
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(sec_cnt_x, sec_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(sec_cnt_x, sec_cnt_y);
M5.Lcd.print(String(second_count));
}
else if (cursor_selection == 2) { // shots
shot_count++;
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(shot_cnt_x, shot_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(shot_cnt_x, shot_cnt_y);
M5.Lcd.print(String(shot_count));
}
else if (cursor_selection == 3) { // flash
flash_on = (flash_on) ? 0 : 1;
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(flash_cnt_x, flash_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(flash_cnt_x, flash_cnt_y);
M5.Lcd.print(String(flash_on));
}
else if (cursor_selection == 4) { // start
is_running = true;
init_timer_screen();
// close relay
digitalWrite(32, HIGH);
target_time = millis();
} else { // reset
minute_count = 0;
second_count = 0;
shot_count = 0;
flash_on = 0;
M5.Lcd.setTextColor(RED, BLACK);
// update minutes
M5.Lcd.fillRect(min_cnt_x, min_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(min_cnt_x, min_cnt_y);
M5.Lcd.print(String(minute_count));
// update secs
M5.Lcd.fillRect(sec_cnt_x, sec_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(sec_cnt_x, sec_cnt_y);
M5.Lcd.print(String(second_count));
// update shots
M5.Lcd.fillRect(shot_cnt_x, shot_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(shot_cnt_x, shot_cnt_y);
M5.Lcd.print(String(shot_count));
// update flash
M5.Lcd.fillRect(flash_cnt_x, flash_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(flash_cnt_x, flash_cnt_y);
M5.Lcd.print(String(flash_on));
}
}
last_val_front = cur_val_front;
}
if (cur_val_side != last_val_side) {
if (is_running == false) { // on selection menu
if (cur_val_side == 0) { // button pressed
cursor_selection++;
if (cursor_selection > 5) {
cursor_selection = 0;
}
if (cursor_selection == 0) { // minutes
// display CLR as normal
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(clr_rect[0], clr_rect[1], clr_rect[2], clr_rect[3], BLACK);
M5.Lcd.setCursor(clr_lbl_x, clr_lbl_y);
M5.Lcd.print("CLR");
M5.Lcd.drawRect(clr_rect[0], clr_rect[1], clr_rect[2], clr_rect[3], RED);
// display minutes inv
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(min_cnt_x, min_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(min_cnt_x, min_cnt_y);
M5.Lcd.print(String(minute_count));
}
else if (cursor_selection == 1) { // seconds
// display minutes as normal
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(min_cnt_x, min_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(min_cnt_x, min_cnt_y);
M5.Lcd.print(String(minute_count));
// display secs inv
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(sec_cnt_x, sec_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(sec_cnt_x, sec_cnt_y);
M5.Lcd.print(String(second_count));
}
else if (cursor_selection == 2) { // shots
// display seconds as normal
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(sec_cnt_x, sec_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(sec_cnt_x, sec_cnt_y);
M5.Lcd.print(String(second_count));
// display shots inv
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(shot_cnt_x, shot_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(shot_cnt_x, shot_cnt_y);
M5.Lcd.print(String(shot_count));
}
else if (cursor_selection == 3) { // flash
// display shots normal
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(shot_cnt_x, shot_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(shot_cnt_x, shot_cnt_y);
M5.Lcd.print(String(shot_count));
// display flash inv
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(flash_cnt_x, flash_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(flash_cnt_x, flash_cnt_y);
M5.Lcd.print(String(flash_on));
}
else if (cursor_selection == 4) { // start
// display flash normal
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(flash_cnt_x, flash_cnt_y, cnt_w, cnt_h, BLACK);
M5.Lcd.setCursor(flash_cnt_x, flash_cnt_y);
M5.Lcd.print(String(flash_on));
// display START inv
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(start_rect[0], start_rect[1], start_rect[2], start_rect[3], RED);
M5.Lcd.setCursor(start_lbl_x, start_lbl_y);
M5.Lcd.print("START");
}
else { // reset
// display START as normal
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.fillRect(start_rect[0], start_rect[1], start_rect[2], start_rect[3], BLACK);
M5.Lcd.setCursor(start_lbl_x, start_lbl_y);
M5.Lcd.print("START");
M5.Lcd.drawRect(start_rect[0], start_rect[1], start_rect[2], start_rect[3], RED);
// display CLR inv
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.fillRect(clr_rect[0], clr_rect[1], clr_rect[2], clr_rect[3], RED);
M5.Lcd.setCursor(clr_lbl_x, clr_lbl_y);
M5.Lcd.print("CLR");
}
}
}
last_val_side = cur_val_side;
}
}
else { // timer display is running
// stuff to do every second
if (target_time < millis()) {
each_second_interval();
target_time += 1000;
}
if (cur_val_front != last_val_front) {
if (cur_val_front == 0) { // stop button is pressed
// open relay
digitalWrite(32, LOW);
is_running = false;
init_select_screen();
}
last_val_front = cur_val_front;
}
}
}
void init_select_screen() {
M5.Lcd.setTextFont(2);
cursor_selection = 0;
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.setCursor(min_lbl_x, min_lbl_y);
M5.Lcd.print("MINS: ");
M5.Lcd.setCursor(sec_lbl_x, sec_lbl_y);
M5.Lcd.print("SECS: ");
M5.Lcd.setCursor(shot_lbl_x, shot_lbl_y);
M5.Lcd.print("SHOTS: ");
M5.Lcd.setCursor(flash_lbl_x, flash_lbl_y);
M5.Lcd.print("FLASH: ");
M5.Lcd.setCursor(start_lbl_x, start_lbl_y);
M5.Lcd.print("START");
M5.Lcd.drawRect(start_rect[0], start_rect[1], start_rect[2], start_rect[3], RED);
M5.Lcd.setCursor(clr_lbl_x, clr_lbl_y);
M5.Lcd.print("CLR");
M5.Lcd.drawRect(clr_rect[0], clr_rect[1], clr_rect[2], clr_rect[3], RED);
M5.Lcd.setCursor(sec_cnt_x, sec_cnt_y);
M5.Lcd.print(String(second_count));
M5.Lcd.setCursor(shot_cnt_x, shot_cnt_y);
M5.Lcd.print(String(shot_count));
M5.Lcd.setCursor(flash_cnt_x, flash_cnt_y);
M5.Lcd.print(String(flash_on));
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.setCursor(min_cnt_x, min_cnt_y);
M5.Lcd.print(String(minute_count));
}
void init_timer_screen() {
M5.Lcd.setTextFont(4);
// init all timer values
seconds_until_open = (minute_count * 60) + second_count;
// reset relay/shot display counters
relay_counter = 0;
shot_counter = shot_count;
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(RED, BLACK);
// display timer, relay/shot counter
M5.Lcd.setCursor(0, 2);
M5.Lcd.print("Timer:");
M5.Lcd.setCursor(110, 2);
M5.Lcd.print(String(seconds_until_open));
M5.Lcd.setCursor(0, 26);
M5.Lcd.print("Shots:");
M5.Lcd.setCursor(110, 26);
if (shot_count == 0) { // when zero, unlimited, so relay counter is shown instead
M5.Lcd.print(String(relay_counter));
}
else {
M5.Lcd.print(String(shot_counter));
}
// display stop button
M5.Lcd.setTextColor(BLACK, RED);
M5.Lcd.setCursor(40, 54);
M5.Lcd.print("STOP");
}
void flash_screen() {
for (int i = 0; i < 4; i++) {
M5.Lcd.fillScreen(RED);
delay(100);
M5.Lcd.fillScreen(BLACK);
delay(100);
}
}
void each_second_interval() {
if (seconds_until_open == 0) {
// open relay
digitalWrite(32, LOW);
// update relay counter
relay_counter++;
// update shot counter
shot_counter--;
}
else if (seconds_until_open == (0 - relay_delay_interval)) {
// close relay
digitalWrite(32, HIGH);
//reset interval
seconds_until_open = (minute_count * 60) + second_count;
}
M5.Lcd.setTextColor(RED, BLACK);
// update seconds until next open
M5.Lcd.fillRect(110, 2, 40, 25, BLACK);
M5.Lcd.setCursor(110, 2);
M5.Lcd.print(String(seconds_until_open));
// update relay/shot counter
M5.Lcd.fillRect(110, 26, 40, 25, BLACK);
M5.Lcd.setCursor(110, 26);
if (shot_count == 0) { // when zero, unlimited, so relay counter is shown instead
M5.Lcd.print(String(relay_counter));
}
else {
M5.Lcd.print(String(shot_counter));
}
// if shots are limited and number of shots reached, stop and return to select screen
if (shot_count != 0 && shot_counter == 0) {
is_running = false;
delay(1000);
if (flash_on) {
flash_screen();
}
init_select_screen();
}
seconds_until_open--;
}
