Buttons

Wiring and reading momentary push-buttons on an ESP32 for cockpit panels, overhead switches, and control inputs.

Button Types

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.

TypeExampleCockpit use
Momentary (NO)12 mm tactile, illuminated pushbuttonAutopilot engage, ALT hold, heading select
Momentary (NC)Panel-mount normally-closedRarely used; same wiring, logic inverted
LatchingAlternate-action pushbuttonAvionics master, battery (when toggle is not used)
Illuminated30 mm annunciator with built-in LEDWarning lights that also serve as reset buttons

Pull-up vs pull-down

ConfigurationIdle statePressed stateNotes
Internal pull-up (recommended)HIGH (3.3 V)LOW (GND)No extra resistor; INPUT_PULLUP in code
External pull-downLOW (GND)HIGH (3.3 V)10 kΩ resistor to GND; INPUT in code
Wiring
GPIO 23 GND Momentary Button (NO) INPUT_PULLUP LOW = pressed HIGH = released

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.

Button legConnect to
Leg 1GPIO pin (any digital input)
Leg 2GND
Arduino Code — Basic Read

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
    }
}
Software Debouncing

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.

Multi-Button Panel

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.

Cockpit Applications
ApplicationButton typeNotes
MCP / FCU pushbuttons12 mm illuminated momentaryLED driven separately by a second GPIO or shift register
Autopilot engage30 mm panel-mount momentaryMap to autopilot command dataref toggle
Overhead annunciatorsIlluminated push-to-resetShared GPIO: button input + LED output via transistor
PTT (push-to-talk)Momentary NO, yoke-mountUse GPIO interrupt for lowest possible latency
Go-around / TOGAMomentary, throttle-mountSingle press sends one-shot command to simulator