Hi everyone,
While calibrating my TSDZ8 torque sensor, I noticed a significant physical limit (sensor saturation) at higher loads:
- 0 to 50 kg: Very linear behavior, moving 183 ADC steps (from ADC 209 up to 392).
- 50 to 80 kg: The sensor flattens out completely, moving only 24 ADC steps (from ADC 392 to 416).
If we calibrate the motor strictly linearly based on the 50 kg point, the firmware drastically underestimates human power during hard sprints. On the other hand, if we calibrate linearly up to 80 kg, the everyday resolution under 50 kg is completely ruined.
Important Note:
Just a quick heads-up: I am not a programmer myself and have absolutely no coding experience. However, I wanted to find a solution for this issue, so I worked this out with the help of an AI assistant to translate my sensor curves into code. There might be syntax errors or logical bugs in it, so please treat this as a concept proposal from a passionate rider/tester rather than ready-to-merge code!
The Solution: A Dynamic Piecewise Linear Interpolation
Instead of changing the display code or adding new communication protocols, we can implement a dual-slope logic purely inside the motor controller using the variables
already sent by the display.
By utilizing ui16_adc_pedal_torque_offset_set (which holds the calibrated weight ADC, e.g., 392) and ui16_adc_pedal_torque_range, the motor can dynamically map the curve into two distinct sectors:
- Sector 1 (Under 50 kg): Uses the normal, honest linear slope (mapping 0–50 kg to 0–100 steps in the internal 160-step software window).
- Sector 2 (Over 50 kg): Automatically increases the slope (in my case, by 4.57x) for the remaining steps to perfectly compensate for the sensor saturation up to 80 kg.
Proposed Code Change for ebike_app.c
Inside get_pedal_torque(void), we can replace the original linear mapping block with this fully dynamic fallback-safe version:
C
// ===================================================================================
// START: FULLY DYNAMIC & VARIABLE PIECEWISE LINEAR TORQUE CALIBRATION (KNEE-POINT)
// ===================================================================================
if (ui16_adc_pedal_torque > ui16_adc_pedal_torque_offset) {
uint16_t raw_delta = ui16_adc_pedal_torque - ui16_adc_pedal_torque_offset;
// Dynamically fetch the knee-point from the display calibration settings.
// ui16_adc_pedal_torque_offset_set holds the calibrated intermediary ADC value (e.g. 392)
uint16_t adc_knee_delta = 0;
if (ui16_adc_pedal_torque_offset_set > ui16_adc_pedal_torque_offset) {
adc_knee_delta = ui16_adc_pedal_torque_offset_set - ui16_adc_pedal_torque_offset;
}
// Safety Fallback: If no valid weight ADC is set, revert to original linear mapping
if (adc_knee_delta == 0 || adc_knee_delta >= ui16_adc_pedal_torque_range) {
ui16_adc_pedal_torque_delta_160 = ((uint32_t)raw_delta * ADC_TORQUE_SENSOR_RANGE_TARGET) / ui16_adc_pedal_torque_range;
}
else {
// SECTOR 1: Linear main range (From offset up to calibrated weight, e.g. 50 kg)
if (raw_delta <= adc_knee_delta) {
// Map the clean linear range to exactly 100 steps out of the 160 target window
ui16_adc_pedal_torque_delta_160 = (uint32_t)(raw_delta * 100) / adc_knee_delta;
}
// SECTOR 2: Saturation range (From calibrated weight up to max weight)
else {
uint16_t extra_delta = raw_delta - adc_knee_delta;
uint16_t total_range_delta = ui16_adc_pedal_torque_range;
uint16_t remaining_adc_range = total_range_delta - adc_knee_delta;
if (remaining_adc_range > 0) {
// Scale the flat saturation curve with a steeper slope dynamically
// to capture the remaining 60 steps up to the 160 target limit.
uint16_t extra_remapped = (uint32_t)(extra_delta * 60) / remaining_adc_range;
ui16_adc_pedal_torque_delta_160 = 100 + extra_remapped;
} else {
ui16_adc_pedal_torque_delta_160 = ADC_TORQUE_SENSOR_RANGE_TARGET;
}
}
}
// Hard ceiling safety check
if (ui16_adc_pedal_torque_delta_160 > ADC_TORQUE_SENSOR_RANGE_TARGET) {
ui16_adc_pedal_torque_delta_160 = ADC_TORQUE_SENSOR_RANGE_TARGET;
}
}
// ===================================================================================
// END: FULLY DYNAMIC & VARIABLE PIECEWISE LINEAR TORQUE CALIBRATION
// ===================================================================================
Why this benefits the community:
- Zero Display Code Changes: No need to update the 860C UI or the serial communication array.
- 100% User-Adjustable: Other riders can use a different knee-point (e.g., 30 kg) simply by entering their specific data into the existing display calibration fields.
- Backwards Compatible: If someone prefers a simple linear setup or skips the advanced calibration, the logic automatically defaults to the original linear formula.
What do you guys think about adding this to the official OSF branch? Would love to hear your thoughts and feedback!