Cubic Spline Force Curve Interpolation in DIY Sim Racing FFB Pedals: Implementation Guide
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, 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 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:
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. 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
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
The three evaluation functions share identical segment-location logic but differ in their final application:
EvalForceCubicSpline: Returns interpolated force values for motor controlEvalJoystickCubicSpline: 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:
// 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:
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:
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 |
Implements Cubic::Interpolate1D, the matrix solve that creates the a/b coefficient tables. |
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 |
Shows where the spline is built (_cubic.Interpolate1D) and how it is stored in calc_st for later use. |
ESP32/src/ForceCurve.cpp & ESP32/src/DiyActivePedal_types.cpp |
Mirrors the same implementation for the ESP32‑based build. |
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-pedaluses a three-stage pipeline: coefficient pre-computation, segment location, and polynomial evaluation. - Coefficients are generated in
CubicInterpolatorFloat.cppusing a tridiagonal matrix solver that processes user-defined control points. - Real-time evaluation occurs in
ForceCurve.cppthrough theEvalForceCubicSplinefunction, which implements the cubic Hermite formula for smooth interpolation. - The system supports gradient evaluation via
EvalForceGradientCubicSplinefor 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →