/******************************************************************************
Project Description
This project is a Ground Control Station for a model rocket or high-altitude
flight system.
It uses an ESP32 microcontroller, pushbuttons, LEDs, and an LCD to allow
the operator to send specific commands such as arming the rocket,
choosing telemetry mode, and deploying parachutes.
Each button press is confirmed visually on the LCD and through LED
indicators, while the system also displays simulated telemetry data such
as altitude, speed, and battery status in real time.
How It Runs
Power the ESP32 and the LCD initializes displaying Telemetry Mode.
Press any control button (e.g., ARM ROCKET, DEPLOY MAIN PARACHUTE).
The Signal LED lights to show the command was detected.
The LCD and Serial Monitor display the command name.
The Action LED lights briefly to indicate the command execution completed.
Use the potentiometer to adjust battery level to simulate battery changes
If no buttons are pressed, the system automatically updates and shows:
Altitude
Speed
Battery voltage and percentage
This cycle continues until another button is pressed.
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// === LCD setup ===
#define SDA_PIN 21
#define SCL_PIN 22
LiquidCrystal_I2C lcd(0x27, 16, 2); // Address 0x27, 16x2 LCD
// === Pin Assignments ===
const int resetBtn = 34;
const int chooseMqttBtn = 35;
const int chooseBeaconBtn = 32;
const int deployMainBtn = 33;
const int deployDrogueBtn = 25;
const int armRocketBtn = 26;
const int signalLED = 18; // LED for "signal received"
const int actionLED = 17; // LED for "action completed"
const int batteryPin = 36; // ADC input for battery simulation
// === Button arrays ===
const int buttonPins[6] = {
resetBtn,
chooseMqttBtn,
chooseBeaconBtn,
deployMainBtn,
deployDrogueBtn,
armRocketBtn
};
const char* buttonNames[6] = {
"RESET SYSTEM",
"CHOOSE MQTT MODE",
"CHOOSE BEACONS MODE",
"DEPLOY MAIN PARACHUTE",
"DEPLOY DROGUE PARACHUTE",
"ARM ROCKET"
};
// === Debounce variables ===
bool buttonState[6] = {0};
bool lastButtonState[6] = {0};
unsigned long lastDebounceTime[6] = {0};
const unsigned long debounceDelay = 200; // ms
unsigned long lastTelemetryTime = 0;
const unsigned long telemetryInterval = 1000; // 1 second
void setup() {
// Init I2C with custom pins
Wire.begin(SDA_PIN, SCL_PIN);
// Init LCD
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Ground Station");
lcd.setCursor(0, 1);
lcd.print("Initializing...");
delay(1500);
lcd.clear();
// Set up buttons with internal pull-ups
for (int i = 0; i < 6; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
// Set up LEDs
pinMode(signalLED, OUTPUT);
pinMode(actionLED, OUTPUT);
digitalWrite(signalLED, LOW);
digitalWrite(actionLED, LOW);
Serial.begin(115200);
Serial.println("=== GROUND STATION READY ===");
lcd.setCursor(0, 0);
lcd.print("Telemetry Mode");
}
void loop() {
bool buttonPressed = false;
for (int i = 0; i < 6; i++) {
int reading = digitalRead(buttonPins[i]);
if (reading != lastButtonState[i]) {
lastDebounceTime[i] = millis();
}
if ((millis() - lastDebounceTime[i]) > debounceDelay) {
if (reading != buttonState[i]) {
buttonState[i] = reading;
if (buttonState[i] == LOW) { // Button pressed
buttonPressed = true;
Serial.println();
Serial.println("====================================");
Serial.print(">>> SIGNAL RECEIVED: ");
Serial.println(buttonNames[i]);
Serial.println("====================================");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SIGNAL RECEIVED");
lcd.setCursor(0, 1);
lcd.print(buttonNames[i]);
digitalWrite(signalLED, HIGH);
performAction(i);
// After action, return to telemetry mode
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Telemetry Mode");
}
}
}
lastButtonState[i] = reading;
}
// Show telemetry & battery every second if no button is pressed
if (!buttonPressed && millis() - lastTelemetryTime > telemetryInterval) {
lastTelemetryTime = millis();
displayTelemetry();
}
}
void performAction(int index) {
// Signal LED on
digitalWrite(signalLED, HIGH);
delay(500);
// Action LED on
digitalWrite(actionLED, HIGH);
digitalWrite(signalLED, LOW);
// Show on LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ACTION COMPLETE");
lcd.setCursor(0, 1);
lcd.print(buttonNames[index]);
delay(1000); // Keep message visible
// Turn off Action LED
digitalWrite(actionLED, LOW);
// Serial log
Serial.println("------------------------------------");
Serial.print(">>> ACTION COMPLETE: ");
Serial.println(buttonNames[index]);
Serial.println("------------------------------------");
}
void displayTelemetry() {
// Random altitude and speed for simulation
lcd.setCursor(0, 1);
lcd.print("Alt:");
lcd.print(random(100, 999));
lcd.print("m Spd:");
lcd.print(random(10, 99));
lcd.print("m/s");
// Read battery voltage
int raw = analogRead(batteryPin);
float voltage = (raw / 4095.0) * 3.7; // pot simulates up to 3.7V
float actualVoltage = voltage * 2; // scale for 2S battery (7.4V max)
int percentage = (actualVoltage / 7.4) * 100;
if (percentage > 100) percentage = 100;
if (percentage < 0) percentage = 0;
// Show battery info on first row
lcd.setCursor(0, 0);
lcd.print("Batt:");
lcd.print(percentage);
lcd.print("% ");
lcd.print(actualVoltage, 2);
lcd.print("V");
}