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