How to Implement Emergency Stop and Safety Features in a DIY Sim-Racing FFB Pedal

The DIY Sim-Racing FFB Pedal implements emergency stop and safety features through a three-layer hardware-software architecture that monitors a dedicated GPIO pin, triggers a force-stop state machine transition, and physically cuts servo power via servoIdleAction() to guarantee zero torque during fault conditions.

The chrgri/diy-sim-racing-ffb-pedal firmware protects users and hardware through a robust safety system built around a stepper-driven force-feedback servo state machine. Implementing emergency stop and safety features requires configuring a hardware guard pin, handling the emergency logic in the main control loop, and ensuring fail-safe power-down procedures execute immediately when faults are detected. This guide covers the exact implementation found in both the ESP32 and V3 firmware variants.

Safety Architecture Overview

The firmware implements emergency stop and safety features through three distinct layers working in concert:

  • Hardware Guard – A pull-up configured push-button on EMERGENCY_PIN (GPIO 6 on V2 boards) provides immediate physical interruption capability
  • Emergency-Stop Handler – The main control loop continuously monitors button state during SERVO_CONNECTED mode and transitions to SERVO_FORCE_STOP status when triggered
  • Fail-Safe Power-Down – The servoIdleAction() routine disables the servo power supply or enters sleep mode while managing brake resistor states

Configuring the Emergency Stop Pin

Define the emergency input pin in your board configuration header. In Firmware_for_V3/PedalFirmware/include/Main.h at line 362, the pin is declared as:

#define EMERGENCY_PIN 6          // Connected to emergency-stop button

The ESP32 implementation uses an identical definition at line 369 in ESP32/include/Main.h. Both firmware variants initialize this pin during startup with internal pull-up activation to ensure the line remains high when the button is open.

Initialize the pin in your setup routine as shown in ESP32/src/Main.cpp (lines 776-777):

pinMode(EMERGENCY_PIN, INPUT_PULLUP);

The V3 firmware performs the same initialization at lines 354-355 in Firmware_for_V3/PedalFirmware/src/Main.cpp.

Implementing Emergency Detection Logic

The control loop checks the emergency button state every iteration while the servo remains active. When the button pulls EMERGENCY_PIN low, the firmware executes a synchronized safety sequence.

In ESP32/src/Main.cpp (lines 1852-1869), the detection block appears as:

if ((stepper->servoStatus == SERVO_CONNECTED) &&
    (stepper->servoStatus != SERVO_FORCE_STOP) &&
    (digitalRead(EMERGENCY_PIN) == LOW))
{
    stepper->servoIdleAction();          // Power-down
    stepper->servoStatus = SERVO_FORCE_STOP;
    // Audible & visual cue
    Buzzer.single_beep_tone(770, 100);
    delay(300);
    pixels.setPixelColor(0, 0xff, 0x00, 0x00); // red LED
    pixels.show();
    Serial.println("Servo force Stoped.");
}

The V3 firmware contains identical logic at lines 1215-1230. The SERVO_FORCE_STOP state prevents subsequent motion commands until system reset, while servoIdleAction() handles the actual power removal.

Fail-Safe Power-Down Implementation

The servoIdleAction() method in Firmware_for_V3/PedalFirmware/src/StepperWithLimits.cpp (lines 29-46) provides hardware-specific power management:

bool StepperWithLimits::servoIdleAction()
{
    bool returnValue_b = false;
    #ifdef SERVO_POWER_PIN
        // Turn off the servo's power
        gpio_set_level((gpio_num_t)SERVO_POWER_PIN, 0);
        delay(500);               // Let the driver fully discharge
        returnValue_b = true;
    #endif

    #ifndef SERVO_POWER_PIN
        setServoToSleep_b = true; // Driver-only sleep mode
        returnValue_b = true;
    #endif
    return returnValue_b;
}

This implementation exists in both firmware variants. When SERVO_POWER_PIN is defined, the code physically cuts the servo power rail, guaranteeing zero torque output. Boards without dedicated power control pins instead force the driver into low-power sleep mode via setServoToSleep_b.

Additional Safety Mechanisms

Beyond the emergency stop button, the firmware implements several automatic protection systems:

Servo Idle Timeout – After servoIdleTimeout milliseconds of inactivity, servoIdleAction() automatically powers down the servo to prevent overheating.

Brake Resistor Control – When the servo enters idle or emergency states, the firmware disables the high-current braking resistor (controlled via BRAKE_RESISTOR_PIN in StepperWithLimits.cpp lines 1111-1114) to eliminate unnecessary heat generation.

Watchdog-Style Lifeline – The main loop monitors stepper->getLifelineSignal() (referenced at line 1640 in Main.cpp). If communication fails, the system automatically transitions to SERVO_FORCE_STOP status.

Custom Implementation Examples

Adding a Redundant Emergency Button

Extend the safety system with a secondary button on GPIO 10:

#define EMERGENCY_PIN_2 10   // Second button, also pulled-up

pinMode(EMERGENCY_PIN_2, INPUT_PULLUP);

// Extend the check in the main loop
if ( (digitalRead(EMERGENCY_PIN) == LOW) ||
     (digitalRead(EMERGENCY_PIN_2) == LOW) )
{
    // Emergency-stop handling
    stepper->servoIdleAction();
    stepper->servoStatus = SERVO_FORCE_STOP;
}

Enabling Visual and Audible Feedback

Ensure your build configuration includes the feedback peripherals:

#define EMERGENCY_PIN 6
#define USING_LED                // Enable visual feedback
#define USING_BUZZER             // Enable audible feedback

These definitions activate the buzzer tone and red LED indicators shown in the emergency handler examples.

Software-Controlled Reset

To clear a SERVO_FORCE_STOP condition without power cycling, trigger an ESP32 restart:

ESP.restart();   // Clears stop state and reinitializes system

This approach mirrors the wake-up sequence implementation found at line 1828 in the ESP32 Main.cpp.

Summary

  • Hardware Configuration – Define EMERGENCY_PIN (typically GPIO 6) and configure with INPUT_PULLUP to detect button presses via active-low signaling
  • State Management – Monitor servoStatus for SERVO_CONNECTED before checking emergency inputs, then transition to SERVO_FORCE_STOP to lock out motion commands
  • Power Cutoff – Implement servoIdleAction() to physically disable servo power via SERVO_POWER_PIN or enter sleep mode when hardware power control is unavailable
  • Feedback Systems – Integrate buzzer tones and LED indicators to provide immediate user notification during emergency events
  • Automatic Protection – Leverage built-in idle timeouts and lifeline monitoring to catch communication failures without manual intervention

Frequently Asked Questions

How does the emergency stop button wiring work?

The firmware expects a normally-open push-button connected between EMERGENCY_PIN (GPIO 6) and ground. The internal pull-up resistor keeps the pin high when the button is released; pressing the button pulls the line low, triggering the detection logic. This active-low configuration provides fail-safe behavior in case of wire disconnections.

What happens when the emergency stop is triggered?

When triggered, the firmware immediately calls servoIdleAction() to cut power or sleep the servo driver, sets servoStatus to SERVO_FORCE_STOP to block further motion commands, activates the buzzer with a 770Hz tone, illuminates the status LED red, and prints a debug message. The system remains in this safe state until a hardware or software reset occurs.

How do I reset the pedal after an emergency stop?

The SERVO_FORCE_STOP state persists until you power-cycle the controller or execute ESP.restart() for ESP32-based boards. The firmware does not provide automatic reset from this state to prevent accidental restarts while the emergency condition may still exist. Always verify the physical button has released before resetting.

What is the difference between power-cut and sleep mode?

When SERVO_POWER_PIN is defined, servoIdleAction() performs a hard power cut by setting the control pin low and waiting 500ms for driver discharge, guaranteeing zero holding torque. Without this pin defined, the system sets setServoToSleep_b = true, placing the stepper driver into a low-power sleep state that may retain minimal holding current depending on the driver hardware configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →