Signal Filtering Algorithms in the DIY Sim-Racing FFB Pedal: Kalman, Moving Average, and Digital FIR Filters

The DIY Sim-Racing FFB Pedal implements three primary signal filtering algorithms—Kalman filters (1st and 2nd order), moving-average filters, and digital FIR filters—to process raw sensor data before it reaches the force-feedback control loop.

The chrgri/diy-sim-racing-ffb-pedal repository delivers an open-source force-feedback pedal system built on the ESP32 platform. To convert noisy analog readings from load cells and rotary encoders into clean, responsive force-feedback signals, the firmware employs multiple stages of signal conditioning using distinct digital filter architectures.

Kalman Filters: Predictive State Estimation

The firmware provides Kalman filter implementations in both 1st-order and 2nd-order variants for optimal state estimation. These filters fuse noisy sensor measurements with predictive physical models to estimate the true pedal position, velocity, and acceleration.

Implementation and File Structure

The Kalman filter headers reside in ESP32/include/SignalFilter_1st_order.h and ESP32/include/SignalFilter_2nd_order.h, with corresponding implementations in ESP32/src/SignalFilter_1st_order.cpp and ESP32/src/SignalFilter_2nd_order.cpp. The repository also maintains the original Arduino Kalman library as a submodule at Arduino/libs/Kalman.

The 1st-order version tracks position and velocity, while the 2nd-order variant additionally estimates acceleration for applications requiring higher-order dynamics. The SimHub plugin interface in SimHubPlugin/UIFunction/GeneralSetting_KF.xaml.cs exposes a selector for choosing between the 1st and 2nd order variants.

Usage Example

// Initialize with sensor variance from calibration
KalmanFilter_1st_order *kalman = new KalmanFilter_1st_order(
    loadcell->getVarianceEstimate());

// Process measurement (command usually 0 for sensor-only mode)
float filteredForce = kalman->filteredValue(
    measurement,    // raw force reading
    command,        // current command
    1);             // model-noise scaling factor

// Retrieve estimated velocity
float velocity = kalman->changeVelocity();

Moving-Average Filters: Simple Temporal Smoothing

For coarse noise reduction requiring minimal computational overhead, the firmware utilizes moving-average filters. These finite-impulse-response approximations smooth data streams by averaging the last N samples, effectively implementing a low-pass filter with linear phase response.

Implementation Details

The core implementation lives in Common_Libs/MovingAverageFilter/src/MovingAverageFilter.h. The firmware instantiates these filters in multiple locations:

Configuration

The window size is determined at instantiation:

MovingAverageFilter forceAvg(100);  // Average last 100 samples
float smoothForce = forceAvg.process(rawForce);

Digital FIR Filters: Frequency-Domain Cleaning

The firmware employs Finite-Impulse-Response (FIR) digital filters for precise frequency-domain signal conditioning, specifically targeting mains-line interference and command-stream smoothing.

ADC Anti-Alias and Mains Rejection

For the ADS1220 load-cell ADC, the pedal firmware activates built-in FIR filtering to reject 50 Hz/60 Hz electrical noise. In Firmware_for_V3/PedalFirmware/src/LoadCell_ads1220.cpp, the initialization routine configures:

#include "ADS1220.h"

ADS1220 adc;
adc.begin();
// Enable 50/60 Hz notch filter to suppress mains hum
adc.setFIRFilter(ADS1220_50HZ_60HZ);

Command Smoothing FIR

The servo communication layer implements a configurable FIR filter for position command smoothing. This parameter, exposed as Pr2.23 ("Position command FIR filter") in the UI documentation at StepperParameterization/ServoParameterization.md, is programmed via Modbus in Firmware_for_V3/PedalFirmware/src/isv57communication.cpp at line 259:

// pr_2_00+23 corresponds to FIR command-smoothing time slot
modbus.checkAndReplaceParameter(slaveId, pr_2_00 + 23, smoothingTime10us);

Filter Integration and Signal Flow

The signal filtering algorithms operate in cascading stages depending on the physical input:

Load-Cell Signal Chain:

  1. Raw ADC reading → FIR filter (ADS1220_50HZ_60HZ)
  2. → Kalman filter (KalmanFilter_1st_order or KalmanFilter_2nd_order)
  3. → Optional moving-average filter (movingAverageFilter.process())

Rudder Encoder Chain:

  1. Raw encoder → Moving-average filter (high-frequency smoothing)
  2. → Kalman filter (kalman_rudder) for offset estimation

The filtered force, position, and velocity values then feed into control-loop strategies (MPC, PID) that drive the servo actuator.

Configuration and Tuning Parameters

The signal filtering algorithms expose several runtime configuration options:

  • Kalman variance: Passed via constructor using loadcell->getVarianceEstimate() as implemented in the firmware initialization
  • Moving-average window: Set as constructor argument (e.g., 100 or 200 samples)
  • FIR command smoothing: Configured through the SimHub plugin UI and written to servo parameter pr_2_00+23

Summary

  • The DIY Sim-Racing FFB Pedal implements three signal filtering algorithms: Kalman filters (1st and 2nd order), moving-average filters, and digital FIR filters.
  • Kalman filters in ESP32/include/SignalFilter_*.h provide optimal state estimation for position, velocity, and acceleration using sensor variance calibration.
  • Moving-average filters in Common_Libs/MovingAverageFilter/ offer simple, configurable low-pass filtering for coarse noise reduction with minimal CPU overhead.
  • Digital FIR filters handle specific frequency-domain tasks: 50/60 Hz mains rejection in the ADC via LoadCell_ads1220.cpp and command-stream smoothing via Modbus parameter pr_2_00+23 in isv57communication.cpp.
  • The filters cascade in processing chains that preserve signal fidelity while minimizing latency critical for realistic force-feedback response.

Frequently Asked Questions

What is the difference between the 1st-order and 2nd-order Kalman filters in this firmware?

The 1st-order Kalman filter estimates position and velocity states, suitable for most pedal force and position applications. The 2nd-order variant adds acceleration estimation, providing smoother tracking for high-dynamic-range inputs. Both implementations reside in ESP32/include/SignalFilter_1st_order.h and SignalFilter_2nd_order.h, and accept variance estimates from the load-cell calibration routine via loadcell->getVarianceEstimate().

How do I configure the moving-average filter window size?

The window size is set at object instantiation in the constructor. For example, MovingAverageFilter forceAvg(100) creates a filter averaging the last 100 samples. The repository shows typical values of 100 for pedal forces and 200 for rudder signals, as seen in ESP32/include/Rudder.h (averagefilter_rudder(200)) and PedalFirmware/include/ABSOscillation.h.

When should I use the FIR filter versus the Kalman filter?

Use the FIR filter for specific frequency-domain problems like rejecting 50 Hz/60 Hz mains interference via adc.setFIRFilter(ADS1220_50HZ_60HZ) in LoadCell_ads1220.cpp, or for smoothing command streams via the pr_2_00+23 parameter. Use the Kalman filter when you need to estimate physical states (velocity, acceleration) from noisy measurements while following a predictive model. The filters often operate in series: FIR for raw ADC cleaning, followed by Kalman for state estimation.

Where is the command-smoothing FIR filter configured in the source code?

The positional command FIR smoothing is programmed in Firmware_for_V3/PedalFirmware/src/isv57communication.cpp at line 259, where the Modbus interface writes to servo parameter address pr_2_00 + 23. This corresponds to the "Position command FIR filter" setting (Pr2.23) documented in StepperParameterization/ServoParameterization.md and adjustable through the SimHub plugin interface.

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 →