# Cubic Spline Force Curve Interpolation in DIY Sim Racing FFB Pedals: Implementation Guide

> Implement cubic spline force curve interpolation for DIY Sim Racing FFB pedals. Learn how to pre-compute coefficients, locate segments, and evaluate polynomials for smooth force feedback.

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

---

**The cubic spline force curve interpolation in the DIY Sim Racing FFB Pedal project uses a three-stage process: pre-computing spline coefficients via matrix solving, locating the active segment for a given pedal position, and evaluating the cubic Hermite polynomial to generate smooth force feedback.**

The `chrgri/diy-sim-racing-ffb-pedal` repository implements real-time cubic spline force curve interpolation to transform user-defined control points into continuous, smooth force feedback. This algorithm runs on embedded ESP32 hardware to convert pedal travel position into motor torque commands, ensuring natural pedal feel without discontinuities.

## How Cubic Spline Interpolation Works in the FFB Pedal

The implementation divides the interpolation into three distinct stages executed across different compilation units.

### Stage 1: Pre-computing Spline Coefficients

Before real-time operation begins, the system converts raw control-point tables into cubic-spline coefficients. In [`Common_Libs/CubicInterpolatorFloat/src/CubicInterpolatorFloat.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Common_Libs/CubicInterpolatorFloat/src/CubicInterpolatorFloat.cpp), the `CubicInterpolatorFloat::Interpolate1D` method builds a distance array and solves a tridiagonal matrix system to produce the **a** and **b** coefficients for each segment.

These coefficients represent the second-derivative-related terms required for cubic Hermite interpolation and are stored in a `Result` struct for later access.

### Stage 2: Locating the Active Spline Segment

During each control loop, the firmware must determine which piece-wise segment contains the current pedal position. The `EvalForceCubicSpline` function in [`Firmware_for_V3/PedalFirmware/src/ForceCurve.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/src/ForceCurve.cpp) performs a linear scan over the pre-computed `calc_st->travel[]` array.

The code calculates `splineSegment_fl32` as a floating-point index representing the exact position within the segment, where the integer portion (`splineSegment_u8`) selects the coefficient pair and the fractional portion represents the interpolation parameter **t**.

### Stage 3: Evaluating the Cubic Polynomial

With the segment identified and the local parameter **t** calculated, the system evaluates the cubic Hermite polynomial:

```cpp
y = (1-t)*y_i + t*y_{i+1} + t*(1-t)*(a_i*(1-t) + b_i*t)

```

This calculation appears at line 69 of `EvalForceCubicSpline` in [`ForceCurve.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ForceCurve.cpp). The result is then remapped from the 0-100% spline output to the user-defined physical force range using `Force_Min` and `Force_Range` parameters before being sent to the motor driver.

## Code Implementation Details

### Generating Coefficients in [`CubicInterpolatorFloat.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/CubicInterpolatorFloat.cpp)

The matrix solver implementation handles the boundary conditions and tridiagonal system solution required for natural cubic splines. The `FitMatrix` function (lines 28-80) processes the input arrays to ensure the spline is parameterized by cumulative Euclidean distance, creating smooth transitions between control points regardless of their spacing.

### Real-time Evaluation in [`ForceCurve.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ForceCurve.cpp)

The three evaluation functions share identical segment-location logic but differ in their final application:

- **`EvalForceCubicSpline`**: Returns interpolated force values for motor control
- **`EvalJoystickCubicSpline`**: Maps pedal position to joystick output values (line 95)
- **`EvalForceGradientCubicSpline`**: Differentiates the Hermite expression to obtain the force gradient (slope) for feed-forward control (lines 71-88)

### Gradient Calculation for Feed-forward Control

The gradient evaluation computes the derivative of the cubic polynomial with respect to the interpolation parameter. This slope information enables predictive motor control, allowing the system to anticipate force changes before they occur rather than reacting to position errors.

## Practical Code Examples

Generating the spline coefficients during pedal initialization:

```cpp
// In DiyActivePedal_types.cpp – after loading the travel/force tables
float travel_x[MAX_POINTS];
float force_y[MAX_POINTS];
for (uint8_t i = 0; i < cfg.payLoadPedalConfig_.quantityOfControl; ++i) {
    travel_x[i] = cfg.travel[i];      // percent of pedal travel
    force_y[i]  = cfg.force[i];       // raw force percentage
}
// Compute cubic spline coefficients a[] and b[]
_cubic.Interpolate1D(travel_x, force_y,
    cfg.payLoadPedalConfig_.quantityOfControl - 1,
    cfg.payLoadPedalConfig_.quantityOfControl - 1);

```

Evaluating the force at a specific pedal position during the control loop:

```cpp
float pedalPos = currentPedalPosition();            // 0‑1 range
float force   = forceCurve.EvalForceCubicSpline(
                    &dap_config_pedalUpdateTask_st,
                    &dap_calculationVariables_st,
                    pedalPos);
setServoForce(force);  // feed the result to the motor driver

```

Getting the gradient for feed-forward control:

```cpp
float grad = forceCurve.EvalForceGradientCubicSpline(
                 &dap_config_pedalUpdateTask_st,
                 &dap_calculationVariables_st,
                 pedalPos,
                 false);          // false ⇒ return gradient in physical units

```

## Key Source Files and Architecture

| File | Purpose |
|------|---------|
| [`Common_Libs/CubicInterpolatorFloat/src/CubicInterpolatorFloat.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Common_Libs/CubicInterpolatorFloat/src/CubicInterpolatorFloat.cpp) | Implements `Cubic::Interpolate1D`, the matrix solve that creates the a/b coefficient tables. |
| [`Firmware_for_V3/PedalFirmware/src/ForceCurve.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/src/ForceCurve.cpp) | Contains the three evaluation functions (`EvalForceCubicSpline`, `EvalForceGradientCubicSpline`, `EvalJoystickCubicSpline`) that drive the pedal’s feedback. |
| [`Firmware_for_V3/PedalFirmware/src/DiyActivePedal_types.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/src/DiyActivePedal_types.cpp) | Shows where the spline is built (`_cubic.Interpolate1D`) and how it is stored in `calc_st` for later use. |
| [`ESP32/src/ForceCurve.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/src/ForceCurve.cpp) & [`ESP32/src/DiyActivePedal_types.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ESP32/src/DiyActivePedal_types.cpp) | Mirrors the same implementation for the ESP32‑based build. |
| [`Common_Libs/CubicInterpolatorFloat/src/CubicInterpolatorFloat.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Common_Libs/CubicInterpolatorFloat/src/CubicInterpolatorFloat.h) | Declaration of the `Cubic` class and result container. |

## Summary

- The cubic spline force curve interpolation in `chrgri/diy-sim-racing-ffb-pedal` uses a three-stage pipeline: coefficient pre-computation, segment location, and polynomial evaluation.
- Coefficients are generated in [`CubicInterpolatorFloat.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/CubicInterpolatorFloat.cpp) using a tridiagonal matrix solver that processes user-defined control points.
- Real-time evaluation occurs in [`ForceCurve.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ForceCurve.cpp) through the `EvalForceCubicSpline` function, which implements the cubic Hermite formula for smooth interpolation.
- The system supports gradient evaluation via `EvalForceGradientCubicSpline` for predictive feed-forward motor control.
- All calculations run on ESP32 hardware, providing sub-millisecond interpolation for high-fidelity force feedback.

## Frequently Asked Questions

### How does the pedal handle discontinuous force curves between control points?

The firmware uses **cubic Hermite spline interpolation** rather than linear interpolation, ensuring that both the force values and their first derivatives remain continuous across segment boundaries. The `a` and `b` coefficients computed in `CubicInterpolatorFloat::Interpolate1D` specifically enforce these continuity constraints through the tridiagonal matrix solution.

### What is the computational cost of the spline evaluation on the ESP32?

The evaluation uses a **linear search** to locate the active segment followed by constant-time polynomial evaluation. Because the number of control points is small (typically fewer than 20), the linear scan in `EvalForceCubicSpline` completes in microseconds, leaving sufficient CPU headroom for the 1-2 kHz control loops typical of force feedback applications.

### Can the force curve be updated dynamically while the pedal is operating?

Yes. The `Interpolate1D` function can be called at any time to regenerate the `a` and `b` coefficient arrays from new control-point data. The [`DiyActivePedal_types.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/DiyActivePedal_types.cpp) initialization code demonstrates this pattern, and the resulting coefficients are stored in `calc_st` where the real-time evaluation functions access them without blocking.

### Why does the implementation use cubic Hermite splines instead of natural cubic splines?

The code uses **cubic Hermite formulation** (the formula with `a` and `b` coefficients) because it allows direct control over the tangent vectors at control points through the matrix solver in `FitMatrix`. This provides more stable behavior for force feedback curves where sudden changes in stiffness (the derivative) must be handled predictably, whereas natural cubic splines might introduce unwanted oscillations in the second derivative that could cause motor instability.