Buttons
Wiring and reading momentary push-buttons on an ESP32 for cockpit panels, overhead switches, and control inputs.
Cockpit panels use two broad categories of push-button: momentary (spring-return, active only while held) and latching (stays pressed until pushed again). Most aircraft switches are momentary — the simulator state is held in software, not by the switch mechanism.
| Type | Example | Cockpit use |
|---|---|---|
| Momentary (NO) | 12 mm tactile, illuminated pushbutton | Autopilot engage, ALT hold, heading select |
| Momentary (NC) | Panel-mount normally-closed | Rarely used; same wiring, logic inverted |
| Latching | Alternate-action pushbutton | Avionics master, battery (when toggle is not used) |
| Illuminated | 30 mm annunciator with built-in LED | Warning lights that also serve as reset buttons |
Pull-up vs pull-down
| Configuration | Idle state | Pressed state | Notes |
|---|---|---|---|
| Internal pull-up (recommended) | HIGH (3.3 V) | LOW (GND) | No extra resistor; INPUT_PULLUP in code |
| External pull-down | LOW (GND) | HIGH (3.3 V) | 10 kΩ resistor to GND; INPUT in code |
With the internal pull-up enabled the GPIO pin is held at 3.3 V through the ESP32's ~45 kΩ internal resistor. Pressing the button connects the pin directly to GND, pulling the reading to LOW. One leg goes to the GPIO pin; the other leg goes to GND.
INPUT_PULLUP. The internal resistor value (~45 kΩ) prevents significant current draw when the button is held. No external resistor is required. | Button leg | Connect to |
|---|---|
| Leg 1 | GPIO pin (any digital input) |
| Leg 2 | GND |
Enable the internal pull-up and read the pin in the loop. The pin reads LOW when pressed and HIGH when released.
// Single button with internal pull-up — reads LOW when pressed
const int BTN_PIN = 23;
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP); // 3.3 V via ~45 kΩ internal resistor
}
void loop() {
if (digitalRead(BTN_PIN) == LOW) {
Serial.println("Button pressed");
delay(50); // simple debounce
}
} Mechanical contacts bounce — they open and close rapidly for a few milliseconds after a press. Without debouncing a single press may register as multiple events. The time-based approach below ignores transitions shorter than DEBOUNCE_MS.
// Software debounce — ignores bounces shorter than DEBOUNCE_MS
const int BTN_PIN = 23;
const int DEBOUNCE_MS = 20;
bool lastState = HIGH;
bool btnState = HIGH;
uint32_t lastChange = 0;
void setup() {
Serial.begin(115200);
pinMode(BTN_PIN, INPUT_PULLUP);
}
void loop() {
bool reading = digitalRead(BTN_PIN);
if (reading != lastState) {
lastChange = millis();
}
if ((millis() - lastChange) > DEBOUNCE_MS && reading != btnState) {
btnState = reading;
if (btnState == LOW) {
Serial.println("Button press (debounced)");
}
}
lastState = reading;
}For larger panels consider the Bounce2 library (Library Manager → Bounce2) which wraps this pattern cleanly for multiple buttons.
Iterate over an array of pins rather than duplicating code per button. This scales cleanly to a full overhead panel with dozens of inputs.
// Four buttons on a panel using an array — cockpit overhead style
const int BTN_PINS[] = { 23, 22, 21, 19 };
const char* BTN_NAMES[] = { "MASTER ALT", "AVIONICS", "BEACON", "STROBE" };
const int BTN_COUNT = 4;
bool lastStates[BTN_COUNT];
void setup() {
Serial.begin(115200);
for (int i = 0; i < BTN_COUNT; i++) {
pinMode(BTN_PINS[i], INPUT_PULLUP);
lastStates[i] = HIGH;
}
}
void loop() {
for (int i = 0; i < BTN_COUNT; i++) {
bool state = digitalRead(BTN_PINS[i]);
if (state == LOW && lastStates[i] == HIGH) {
Serial.print("Pressed: ");
Serial.println(BTN_NAMES[i]);
}
lastStates[i] = state;
}
delay(10);
}For more than ~16 buttons, use an I²C I/O expander (MCP23017 — 16 extra GPIOs per chip) to avoid running out of ESP32 pins.
| Application | Button type | Notes |
|---|---|---|
| MCP / FCU pushbuttons | 12 mm illuminated momentary | LED driven separately by a second GPIO or shift register |
| Autopilot engage | 30 mm panel-mount momentary | Map to autopilot command dataref toggle |
| Overhead annunciators | Illuminated push-to-reset | Shared GPIO: button input + LED output via transistor |
| PTT (push-to-talk) | Momentary NO, yoke-mount | Use GPIO interrupt for lowest possible latency |
| Go-around / TOGA | Momentary, throttle-mount | Single press sends one-shot command to simulator |