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

> Explore Kalman, moving average, and digital FIR filters for your DIY Sim-Racing FFB Pedal. Improve sensor data processing for a smoother force-feedback experience.

- Repository: [chrgri/diy-sim-racing-ffb-pedal](https://github.com/chrgri/diy-sim-racing-ffb-pedal)
- Tags: deep-dive
- Published: 2026-02-27

---

**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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/include/SignalFilter_1st_order.h) and [`ESP32/include/SignalFilter_2nd_order.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/include/SignalFilter_2nd_order.h), with corresponding implementations in [`ESP32/src/SignalFilter_1st_order.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/src/SignalFilter_1st_order.cpp) and [`ESP32/src/SignalFilter_2nd_order.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/SimHubPlugin/UIFunction/GeneralSetting_KF.xaml.cs) exposes a selector for choosing between the 1st and 2nd order variants.

### Usage Example

```cpp
// 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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Common_Libs/MovingAverageFilter/src/MovingAverageFilter.h). The firmware instantiates these filters in multiple locations:

- [`ESP32/include/Rudder.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/include/Rudder.h) declares `MovingAverageFilter averagefilter_rudder(200)` for rudder signal conditioning
- [`PedalFirmware/include/ABSOscillation.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/PedalFirmware/include/ABSOscillation.h) employs moving averages for pedal force smoothing

### Configuration

The window size is determined at instantiation:

```cpp
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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/src/LoadCell_ads1220.cpp), the initialization routine configures:

```cpp
#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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/StepperParameterization/ServoParameterization.md), is programmed via Modbus in [`Firmware_for_V3/PedalFirmware/src/isv57communication.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/src/isv57communication.cpp) at line 259:

```cpp
// 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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/LoadCell_ads1220.cpp) and command-stream smoothing via Modbus parameter `pr_2_00+23` in [`isv57communication.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/include/SignalFilter_1st_order.h) and [`SignalFilter_2nd_order.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/include/Rudder.h) (`averagefilter_rudder(200)`) and [`PedalFirmware/include/ABSOscillation.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/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`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/StepperParameterization/ServoParameterization.md) and adjustable through the SimHub plugin interface.