Skip to content

Title: p_pid_ang_div = 0 in FOC config causes permanent ISR hang (division by zero → infinite loop in utils_norm_angl #930

Description

@Kenaiwolf

Summary

Setting mc_configuration.p_pid_ang_div to 0 (either via a custom firmware default in motor/mcconf_default.h, or live through VESC Tool while the motor is configured as MOTOR_TYPE_FOC) causes a division-by-zero that produces +Inf, which is then fed into utils_norm_angle(). Because that function's loop condition can never become false for a non-finite value, execution hangs permanently inside the FOC ADC interrupt handler. Since this happens inside an ISR that runs at the current-sample rate, the whole MCU effectively freezes — no fault is raised, no reset occurs, and the device stops enumerating over USB/CAN entirely.

Where it happens

mcpwm_foc_adc_int_handler() divides by the field unconditionally when its value falls outside the 0.98–1.02 "treat as 1" pass-through band: mcpwm_foc.c:3881-3884

if (conf_now->p_pid_ang_div > 0.98 && conf_now->p_pid_ang_div < 1.02) {
motor_now->m_pos_pid_now = angle_now;
} else {
const float ang_div_inv = 1.0 / conf_now->p_pid_ang_div;

Full context showing the result feeding into utils_norm_angle(): mcpwm_foc.c:3877-3898

A second, independent division by the same field exists in foc_math.c: foc_math.c:421-422

if (conf_now->p_pid_gain_dec_angle > 0.1) {
float min_error = conf_now->p_pid_gain_dec_angle / conf_now->p_pid_ang_div;

The infinite loop itself — utils_norm_angle() never terminates for +Inf/NaN input: utils_math.h:151-160

static inline void utils_norm_angle(float *angle) {
while (*angle < 0.0) { *angle += 360.0; }
while (*angle > 360.0) { *angle -= 360.0; }
}

No validation exists anywhere in the incoming-config path. The wire deserializer copies the raw float with no bounds check: confgenerator.c:509

And commands_apply_mcconf_hw_limits(), which is the function responsible for sanitizing/clamping risky config fields before they're applied (it already clamps ~a dozen adjacent fields such as l_current_max_scale, foc_overmod_factor, foc_f_zv, l_max_vin, l_max_erpm, etc.), has no such guard for p_pid_ang_div: commands.c:1889-1978

Reproduction

Build/flash firmware with motor_type = MOTOR_TYPE_FOC and p_pid_ang_div = 0.0 (e.g., via a modified motor/mcconf_default.h).
Power on the board. The FOC ADC interrupt handler hits the division-by-zero on the very first current sample and hangs forever — the board never finishes boot, never enumerates over USB.
Alternatively, with a normally-booted board already running MOTOR_TYPE_FOC, send a config write from VESC Tool with p_pid_ang_div set to 0. commands_apply_mcconf_hw_limits() does not reject it, and mc_interface_set_configuration() applies it live — the very next ADC interrupt hangs the device identically, with no reflash needed to trigger it.

Confirmed via GDB/OpenOCD over SWD: the target was found halted permanently inside the hang, with backtrace:

#0 utils_norm_angle (angle=0x200180c4 <m_motor_1+892>) at util/utils_math.h:158
#1 mcpwm_foc_adc_int_handler (...) at motor/mcpwm_foc.c:3891
#2 Vector130 () at .../stm32_dma.c:351

Expected behavior

p_pid_ang_div should never be allowed to reach 0 (or any value close enough to 0 to overflow a float reciprocal) once it reaches these division sites — regardless of whether it originates from a compiled-in default, an EEPROM-stored config, or a live VESC Tool write.

Suggested fix

Add a guard for p_pid_ang_div in commands_apply_mcconf_hw_limits(), consistent with how neighboring fields are already clamped there, e.g. utils_truncate_number_abs(&mcconf->p_pid_ang_div, <minimum_nonzero>) or explicitly forcing it to 1.0 if within some epsilon of 0.
Defensively, utils_norm_angle() could early-return on non-finite input (!isfinite(*angle)), which would prevent this entire class of hang regardless of which caller supplies a bad value in the future.

Stock default for reference

The upstream default for this field is 1.0 (safe/inert value): mcconf_default.h:182-184

#ifndef MCCONF_P_PID_ANG_DIV
#define MCCONF_P_PID_ANG_DIV 1.0 // Divide angle by this value
#endif

The bug only surfaces when this default is overridden (in source or at runtime) with 0, but since nothing in the validation path prevents that, it should be treated as a firmware robustness bug rather than "user error."

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions