Development steps

Project planning and development stages

Step 5 - Add buttons

Back to project development

Add buttons

  • Add buttons and program different screens;

The ESP32-C3 Series does not have touch sensors so I will have to add buttons of some sort.

Here is a comparison of the different ESP32 in Espressif site.

In this step I need to add buttons to the device. I will use just two pins and because I have used up all the ESP32 Pins I tried to use multiple buttons with just one analog pin.

That didn't work out as expected and I decided to not waste more time and just remove the LED and use two pins for two buttons.

I will use GPIO 2, 4 for this.

I will setup 2 buttons each with a 10K resistors:

Schematic

Prototype:

Now I need to integrate the buttons with the rest of the code.


#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "pitches.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <elapsedMillis.h>
#include <Bounce2.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
static const int RXPin = 20, TXPin = 21;
static const uint32_t GPSBaud = 9600;

const int SDPin = 5;
bool sdCardAvailable;

const int BuzzerPin = 3;

const int ButtonPin1 = 2;  // Button 1 pin
const int ButtonPin2 = 4;  // Button 2 pin

const float maxDistance = 20;
float initialLatitude = 42.61127351795643;
float initialLongitude = -5.59774217193631;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
TinyGPSPlus gps;
String gpstext;
int gpscount = 0;
int gpsttlcount = 30;
float distance;
unsigned long gpsStartTime;
bool waitingForGPS = false;
bool newData = false;

SoftwareSerial ss(RXPin, TXPin);

elapsedMillis timeElapsed;
bool stopwatchStarted = false;
bool stopwatchPaused = true;
unsigned long stopwatchStartMillis = 0;
unsigned long stopwatchElapsedMillis = 0;
bool stopwatchReset = false;

Adafruit_BME280 bme;
#define SEALEVELPRESSURE_HPA (1013.25)

#include "melody.h"
#include "icons.h"

#define DEBUG 1
#ifdef DEBUG
#define DebugPrintln(str) Serial.println(str)
#define DebugPrint(str) Serial.print(str)
#define DebugPrintf(str, str2) Serial.printf(str, str2)
#else
#define DebugPrintln(str)
#define DebugPrint(str)
#define DebugPrintf(str, str2)
#endif

// Initialize debouncers
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();

// Initialize panel index
int panelIndex = 0;

void initDisplay() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    DebugPrintln(F("SSD1306 allocation failed"));
    for (;;)
      ;
  }
  delay(500);
  display.clearDisplay();
  display.setTextColor(WHITE);
}

// Initialize SD card
void initSDCard() {
  if (!SD.begin(SDPin)) {
    sdCardAvailable = false;
    DebugPrintln("SD Card failed, or not present!");
  } else {
    // We're ready to go
    sdCardAvailable = true;
    DebugPrintln("SD card initialized!");
  }
}

void initBME() {
  bool status = bme.begin(0x76);
  if (!status) {
    DebugPrintln("No valid BME280 sensor!");
    while (1)
      ;
  }
}

// Write to the SD card
void writeFile(fs::FS &fs, const char *path, const char *message) {
  DebugPrintf("Writing file: %sn", path);
  File dataFile = fs.open(path, FILE_WRITE);
  if (!dataFile) {
    DebugPrintln("Failed to open file for writing");
    return;
  }
  if (dataFile.print(message)) {
    DebugPrintln("File written");
  } else {
    DebugPrintln("Write failed");
  }
  dataFile.close();
}

// Append data to the SD card
void appendFile(fs::FS &fs, const char *path, const char *message) {
  DebugPrintf("Appending to file: %s", path);
  DebugPrintln();
  File dataFile = fs.open(path, FILE_APPEND);
  if (!dataFile) {
    DebugPrintln("Failed to open file for appending");
    return;
  }
  if (dataFile.print(message)) {
    DebugPrintln("Message appended");
  } else {
    DebugPrintln("Append failed");
  }
  dataFile.close();
}

void setup() {
  // Rest of your setup code...
  debouncer1.attach(ButtonPin1);
  debouncer1.interval(50);
  debouncer2.attach(ButtonPin2);
  debouncer2.interval(50);
  Serial.begin(9600);
  initDisplay();
  initBME();
  initSDCard();
  pinMode(BuzzerPin, OUTPUT);
  ss.begin(GPSBaud);

  if (sdCardAvailable == true) {
    File dataFile = SD.open("/gpslog.csv");
    if (!dataFile) {
      DebugPrintln("File doesn't exist");
      DebugPrintln("Creating file...");
      writeFile(SD, "/gpslog.csv", "");
    } else {
      DebugPrintln("File already exists");
    }
    dataFile.close();
  }
}
void loop() {
  debouncer1.update();
  debouncer2.update();



  if (debouncer1.fell() && debouncer2.fell() && panelIndex == 2) {
    // Reset the stopwatch
    Serial.println("both");
    stopwatchStarted = false;
    stopwatchPaused = true;
    stopwatchStartMillis = 0;
    stopwatchElapsedMillis = 0;
  } else {
    if (debouncer1.fell()) {
      panelIndex++;
      if (panelIndex > 2) {  // We have three panels
        panelIndex = 0;
      }
    }

    // Check if Button2 is pressed
    if (debouncer2.fell()) {
      if (panelIndex == 2) {  // Stopwatch Panel
        if (!stopwatchStarted) {
          stopwatchStarted = true;
          stopwatchPaused = false;
          stopwatchReset = false;
          stopwatchStartMillis = millis();
        } else {
          if (stopwatchPaused) {
            stopwatchPaused = false;
            stopwatchStartMillis = millis() - stopwatchElapsedMillis;
          } else {
            stopwatchPaused = true;
            stopwatchElapsedMillis = millis() - stopwatchStartMillis;
          }
        }
      }
    }
  }

  // Check if we are currently waiting for GPS data.
  if (waitingForGPS) {
    // If we have been waiting for less than a second...
    if (millis() - gpsStartTime < 1000) {
      handleGPS();
    } else {
      // We have waited for a second, so stop waiting.
      waitingForGPS = false;
    }
  } else {
    // If we are not currently waiting for GPS data, start.
    waitingForGPS = true;
    gpsStartTime = millis();
  }

  // Update the display.
  updateDisplay();
}

void handleGPS() {
  // GPS data handling
  boolean newData = false;
  while (ss.available() > 0) {
    if (gps.encode(ss.read())) {
      // start gps file
      // See if we have a complete GPS data string
      if (displayInfo() != "0") {
        // Get GPS string
        gpstext = displayInfo();
        // Check GPS Count
        // DebugPrintln(gpscount);
        if (gpscount == gpsttlcount) {
          DebugPrintln(gpstext);
          if (sdCardAvailable == true) {
            appendFile(SD, "/gpslog.csv", gpstext.c_str());
          }
        }
        // Increment GPS Count
        gpscount = gpscount + 1;
        if (gpscount > gpsttlcount) {
          gpscount = 0;
        }
      }
      // End GPS file
      newData = true;
      float distance = getDistance(gps.location.lat(), gps.location.lng(), initialLatitude, initialLongitude);
      // DebugPrint("Distance:");
      // DebugPrintln(distance);
      if (distance > maxDistance) {
        //DebugPrintln("outside");
      } else {
        //DebugPrintln("inside");
        playBuzzer();
      }
    }
  }
}

void updateDisplay() {

  // Panel update logic
  display.clearDisplay();
  display.setCursor(0, 0);
  int displaydistance = getDistance(gps.location.lat(), gps.location.lng(), initialLatitude, initialLongitude);

  switch (panelIndex) {
    case 0:
      display.setTextSize(1);
      display.setCursor(0, 0);
      display.print("hike");
      display.setCursor(40, 0);
      display.print(String(gps.time.hour()) + ":" + String(gps.time.minute()) + " " + String(gps.date.day()) + "/" + String(gps.date.month()) + "/" + String(gps.date.year()));
      display.setTextSize(1);
      display.setCursor(0, 10);
      display.print("Distance: ");
      display.print(String(displaydistance / 1000) + " km");
      display.setCursor(0, 20);
      display.println("Speed: " + String(gps.speed.kmph()) + " km/h");
      display.setTextSize(1);
      display.setCursor(0, 50);
      display.print("SAT:");
      display.setCursor(25, 50);
      display.print(gps.satellites.value());
      display.setTextSize(1);
      display.setCursor(70, 50);
      display.print("ALT:");
      display.setCursor(95, 50);
      display.print(gps.altitude.meters(), 0);
      display.display();
      // Show panel 0
      if (newData == true) {
      }
      break;
    case 1:
      // Show panel 1
      displayWeather();
      break;
    case 2:
      // Show panel 2 (Stopwatch)
      displayStopwatch();
      break;
  }
  display.display();

  // Reset newData for the next loop iteration
  if (newData == true) {
    newData = false;
  }
}


void displayStopwatch() {
  if (stopwatchStarted && !stopwatchPaused) {

    unsigned long stopwatchElapsedSecs = stopwatchElapsedMillis / 1000;
    unsigned long hours = stopwatchElapsedSecs / 3600;
    unsigned long mins = (stopwatchElapsedSecs % 3600) / 60;
    unsigned long secs = stopwatchElapsedSecs % 60;

    String timeString = String(hours) + ":" + (mins < 10 ? "0" + String(mins) : String(mins)) + ":" + (secs < 10 ? "0" + String(secs) : String(secs));
    if (!stopwatchPaused) {
      stopwatchElapsedMillis = millis() - stopwatchStartMillis;
    }

    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    // Display the large icon (64x64) in the center at the top of the screen.
    display.drawBitmap((display.width() - 24) / 2, 2, epd_bitmap_stopwatch_solid, 24, 24, SSD1306_WHITE);

    // Center the text under the icon
    int textWidth = (display.width() - timeString.length() * 10) / 2;  // Calculate the text width to center it
    display.setCursor(textWidth, 40);
    display.println(timeString);
    display.display();
  } else {
    display.clearDisplay();
    // Display the large icon (64x64) in the center at the top of the screen.
    display.setTextSize(2);
    display.drawBitmap((display.width() - 24) / 2, 2, epd_bitmap_stopwatch_solid, 24, 24, SSD1306_WHITE);
    display.setCursor(30, 40);
    if (!stopwatchStarted) {
      display.println("START?");
    } else {
      display.println("PAUSED");
    }
    display.display();
  }
}

void displayWeather() {
  display.clearDisplay();
  // display temperature
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("Temperature: " + String(bme.readTemperature()) + "C");

  // display humidity
  display.setTextSize(1);
  display.setCursor(0, 10);
  display.print("Humidity: " + String(bme.readHumidity()) + " %");

  // display pressure
  display.setTextSize(1);
  display.setCursor(0, 20);
  display.print("Pressure: " + String(bme.readPressure() / 100.0F) + " hPa");


  display.display();
}

// Function to return GPS string
String displayInfo() {
  // Define empty string to hold output
  String gpsdata = "";
  // Get latitude and longitude
  if (gps.location.isValid()) {
    gpsdata = String(gps.location.lat(), 6);
    gpsdata += (",");
    gpsdata += String(gps.location.lng(), 6);
    gpsdata += (",");
  } else {
    return "0";
  }

  // Get Date
  if (gps.date.isValid()) {
    gpsdata += String(gps.date.year());
    gpsdata += ("-");
    if (gps.date.month() < 10) gpsdata += ("0");
    gpsdata += String(gps.date.month());
    gpsdata += ("-");
    if (gps.date.day() < 10) gpsdata += ("0");
    gpsdata += String(gps.date.day());
  } else {
    return "0";
  }

  // Space between date and time
  gpsdata += (" ");

  // Get time
  if (gps.time.isValid()) {
    if (gps.time.hour() < 10) gpsdata += ("0");
    gpsdata += String(gps.time.hour());
    gpsdata += (":");
    if (gps.time.minute() < 10) gpsdata += ("0");
    gpsdata += String(gps.time.minute());
    gpsdata += (":");
    if (gps.time.second() < 10) gpsdata += ("0");
    gpsdata += String(gps.time.second());
  } else {
    return "0";
  }
  // Return completed string
  return gpsdata;
}

// Calculate distance between two points
float getDistance(float flat1, float flon1, float flat2, float flon2) {
  // Variables
  float dist_calc = 0;
  float dist_calc2 = 0;
  float diflat = 0;
  float diflon = 0;
  // Calculations
  diflat = radians(flat2 - flat1);
  flat1 = radians(flat1);
  flat2 = radians(flat2);
  diflon = radians((flon2) - (flon1));
  dist_calc = (sin(diflat / 2.0) * sin(diflat / 2.0));
  dist_calc2 = cos(flat1);
  dist_calc2 *= cos(flat2);
  dist_calc2 *= sin(diflon / 2.0);
  dist_calc2 *= sin(diflon / 2.0);
  dist_calc += dist_calc2;
  dist_calc = (2 * atan2(sqrt(dist_calc), sqrt(1.0 - dist_calc)));
  dist_calc *= 6371000.0;  //Converting to meters
  return dist_calc;
}

void playBuzzer() {
  DebugPrintln("inside");
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.print("YOU HAVE");
  display.setCursor(0, 20);
  display.setTextSize(2);
  display.print("ARRIVED!");
  display.display();
  playBuzzer();
  delay(1000);
  for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
    divider = melody[thisNote + 1];
    if (divider > 0) {
      noteDuration = (wholenote) / divider;
    } else if (divider < 0) {
      noteDuration = (wholenote) / abs(divider);
      noteDuration *= 1.5;
    }
    tone(BuzzerPin, melody[thisNote], noteDuration * 0.9);
    delay(noteDuration);
    noTone(BuzzerPin);
  }
  delay(1000);
}