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."
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
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
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;
static inline void utils_norm_angle(float *angle) {
while (*angle < 0.0) { *angle += 360.0; }
while (*angle > 360.0) { *angle -= 360.0; }
}
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
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
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."