//Include is used to call libraries with either #include <name> or "name"
//They allow use to use pre-written functions
#include <Wire.h> // it allow us to comunicate with the screen and time module
#include <LiquidCrystal_I2C.h> // to control the LCD screen
#include <RTClib.h> // To control the real time module (in this case DS1307)
#include <ESP32Servo.h> // to control the servo
//Configuration
LiquidCrystal_I2C lcd(0x27, 20, 4); //(Adress LCD, columns of the screen, rows on screen)
RTC_DS1307 rtc; //no need of parenthesis the I2C address is fixed internally
Servo ServoCross; // Creates a servo object (I name it cross because I changed the horn to be a cross)
//The pin of each component, I asked ChatGPT which connections were better for each one
//The const int (constat integer) is to define a value that cant change later on
const int pinServo = 18; // Pin tyoe Output PWM capable (Pulse width modulation)
const int pinBuzzer = 19; //Output (PWD)
const int pinLED = 5; //Output
//Inputs (when we prees the button the ESP32 reads the signal either LOW or HIGH)
const int btnConf = 27; //Changes if we are introducing the hours (H) or minutes (M) or if its on standby
const int btnUp = 14; // The numbers go up
const int btnDown = 12; // The numbers go down
//Control pins for the bipolar stepper motor
const int stepPin = 32; // Contros the steps by readyn the pulses HIGH and LOW (each pulse is one step)
const int dirPin = 33; // Controls direction (HIGH is clockwise while LOW is the opposite)
// Star Wars Imperial Song for the alarm
int melodia[] = { // Int for whole numbers withou decimals can be positve or negative
440, 440, 440, //Number of Hertz; I asked ChatGPT for the Hz equivalent of the notes similar to the imperial march song
349, 523, 440,
349, 523, 440,
659, 659, 659,
698, 523, 415, 349
};
int duraciones[] = {
75, 75, 75, // reduced duration for faster melody
50, 25, 100,
50, 25, 125,
75, 75, 75,
50, 40, 75, 125 // The number of durations are the same as the number of notes we have
};
const int totalNotas = 16; //Total of notes
// Melody variables, without them we wouldnt know the next note or the program would read the same note
int notaActual = 0; // The note that is currently playing
unsigned long tiempoNota = 0; // The time the note started, unsigned long is use for postive whole numbers
// Clock and alarm variables
int alarmaHora = 0; //Hour
int alarmaMinuto = 0; //Minute
bool editandoAlarma = false; //Bool means true or false, true if edits the alarm
bool editandoHora = true; // True if we are editing the hour, false if it is the minute
bool sonando = false; // true when the alarm activates
bool showThatsAll = false; // true to display "that's all for now" after stopping
unsigned long debounceTiempo = 0; //Prevents button bouncing (false/multiple triggering)
// Servo variables
int posServo = 0; //Position of the servo (angle from 0 to 180 degrees)
unsigned long tiempoServo = 0; //Timing of its movement
//Stepper variables
int pasosStepper = 0; // Counts how many steps it moves
bool dirStepper = true; //Direction of rotation
//Main difference between stepper (open loop) and servo (closed loop)
//function to reset the stepper
void resetStepper() {
pasosStepper = 0;
dirStepper = true;
}
//Set up funtion (runs once at the beginning, help us define the prevous variables when the programm starts)
void setup() {
Serial.begin(115200); // INICIA COMUNICACIÓN REMOTA SIMPLE
//pin mode help us define if the pins are inputs or outputs
pinMode(pinBuzzer, OUTPUT);
pinMode(pinLED, OUTPUT);
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
pinMode(btnConf, INPUT_PULLUP); //with the pull up the button pins have internal resistors
pinMode(btnUp, INPUT_PULLUP); //This way we wont need physical resitors
pinMode(btnDown, INPUT_PULLUP);
ServoCross.attach(pinServo); //attach the pin (in this case 18) to control a servo
ServoCross.write(0); // so it starts at 0 degrees
lcd.init(); //Initialices the LCD screen
lcd.backlight(); //Turn on LCD
rtc.begin(); //start the Rtc module
}
//Runs repeadtly forever
void loop() {
DateTime now = rtc.now(); //Current real time
manejarBotones(); // A function that check the buttons status
pantallaPrincipal(now); // the function that show the hour
verificarAlarma(now);// to check if the alarm must sound
}
//Buttons
void manejarBotones() { //each if in here is a diferent task, indepent from eachother
//When we use the buttons the programm need to check several things at the same time
if (millis() - debounceTiempo < 30) return; // faster button change
// Edit or change mode of edition (editing alarm)
if (digitalRead(btnConf) == LOW) { //Conf button is the green one, when pressed is LOW
editandoAlarma = !editandoAlarma; //Changes between the alarm conf or standby
if (editandoAlarma) {
editandoHora = !editandoHora; //changes between changing the hour or the minutes in the alarm conf
}
debounceTiempo = millis();
}
if (editandoAlarma) { //Here we include the actions of the other buttons activated by editing the alarm
//Add numbers
if (digitalRead(btnUp) == LOW) { //button up is the blue one, if pressed is on low
if (editandoHora) {
alarmaHora = (alarmaHora + 1) % 24; //Adds one number and if we pass the 24 (hrs) resets to 0
} else {
alarmaMinuto = (alarmaMinuto + 1) % 60; //Same but for minututes
}
debounceTiempo = millis(); //Prevents bouncing
}
//Substract numbers
if (digitalRead(btnDown) == LOW) { //red button
if (editandoHora) {
alarmaHora--;
if (alarmaHora < 0) alarmaHora = 23; //same but substract one number at the time
} else {
alarmaMinuto--;
if (alarmaMinuto < 0) alarmaMinuto = 59;
}
debounceTiempo = millis();
}
}
}
// LCD Screen
void pantallaPrincipal(DateTime t) { //parameter that prints the rtc current time
lcd.setCursor(0, 0); //the starting point of the cursor
lcd.print("HORA: "); // Writes the word time on the lcd
imprimirDosDigitos(t.hour()); lcd.print(":"); //prints 2 digits of the time (hours)
imprimirDosDigitos(t.minute()); lcd.print(":"); //same but for minutes
imprimirDosDigitos(t.second()); //prints the seconds (real time)
lcd.setCursor(0, 1); // starts at the first column second row
lcd.print("ALARMA: "); //prints the word alarm
imprimirDosDigitos(alarmaHora);
lcd.print(":");//the word alarm is followed by the stablished one
imprimirDosDigitos(alarmaMinuto);
if (editandoAlarma) {
lcd.print(editandoHora ? " <Hrs>" : " <Min>"); // what it shows if we are editing the alarm
} else {
lcd.print(" "); //clear the extra part if not editing
}
lcd.setCursor(0, 3); //third row for alarm messages
if (sonando) {
lcd.print(" !Take your pills!"); // show alarm message
showThatsAll = true; // flag to show "that's all" after stop
} else if (showThatsAll) {
lcd.print(" That's all for now"); // show message after alarm stops
showThatsAll = false; // reset flag
} else {
lcd.print(" "); //clear row 3
}
}
// Alarm
void verificarAlarma(DateTime now) { //checks if the current times (DateTimeNow) matches the alarm
if (now.hour() == alarmaHora &&
now.minute() == alarmaMinuto &&
now.second() == 0) { //the exact second to start the alarm
//If the alarm is active (true)
sonando = true;
notaActual = 0; //it will start playing the melody on the first note
tiempoNota = millis(); // record the start time of the note
tone(pinBuzzer, melodia[0] / 4, duraciones[0] - 15); // starts the buzzer, divides the number to make the sound deeper
// remote communication
Serial.print("Alarma activada a las ");
Serial.print(alarmaHora);
Serial.print(":");
Serial.println(alarmaMinuto);
}
if (sonando) { //if the alarm is active
ejecutarAlarmaImperial(); //runs the melody we did before
if (digitalRead(btnConf) == LOW || //if any of the buttons is pressed the alarm goes off
digitalRead(btnUp) == LOW || // the two lines mean or, so if the blue button is pressed OR the green one it will stop
digitalRead(btnDown) == LOW) { // with out the two line we woul lead to press the three buttons at the same time
detenerAlarma();
}
}
}
// Alarm song and led
void ejecutarAlarmaImperial() {
if (millis() - tiempoNota >= duraciones[notaActual]) {
notaActual++;
if (notaActual >= totalNotas) {
notaActual = 0;
}
tone(pinBuzzer, melodia[notaActual] / 2, duraciones[notaActual] - 25);
digitalWrite(pinLED, !digitalRead(pinLED)); //So the led follow the melody
tiempoNota = millis();
}
if (millis() - tiempoServo >= 100) { //moves the servo every 100ms
posServo += 20; // faster movement
if (posServo >= 180) posServo = 0;
ServoCross.write(posServo);
tiempoServo = millis();
}
digitalWrite(dirPin, dirStepper); //Moves the stepper step by step, changes direction after 100 steps
digitalWrite(stepPin, HIGH);
delayMicroseconds(400);
digitalWrite(stepPin, LOW);
delayMicroseconds(400);
pasosStepper++;
if (pasosStepper >= 100) {
dirStepper = !dirStepper;
pasosStepper = 0;
}
}
// Stop the alarm
void detenerAlarma() {
sonando = false; //stops the buzzer
noTone(pinBuzzer);
digitalWrite(pinLED, LOW); //turn off the led
ServoCross.write(0); //resets the servo
notaActual = 0; //resents the melody
resetStepper(); //resets the stepper
//Remote communication
Serial.println("Alarma detenida, gracias por tomar tu medicamento!");
}
//Format
void imprimirDosDigitos(int numero) {
if (numero < 10) lcd.print('0'); // makes numbers smaller than 10, two digit numbers by adding a cero at the start
lcd.print(numero);
}