From a51286396fd480656b56c728c2f702e18c02aac7 Mon Sep 17 00:00:00 2001 From: 94xhn <87560781+94xhn@users.noreply.github.com> Date: Sat, 11 Jul 2026 23:45:23 +0800 Subject: [PATCH] fix(HardwareTimer): guard HERTZ_FORMAT get* against register==0 getCount(HERTZ_FORMAT) and getCaptureCompare(..., HERTZ_COMPARE_FORMAT) both compute getTimerClkFreq() / (register * Prescalerfactor), dividing directly by the live CNT (counter) or CCR (capture/compare) hardware register with no zero-guard. Both registers legitimately read 0 with no caller error involved: - CNT is 0 right at/after every timer reset or overflow, simply from the timer running (getOverflow()/setOverflow() already special-case this exact situation via the ARR+1 "period" offset; getCount() and getCaptureCompare() do not). - CCR is 0 on reset before the first setCaptureCompare() call, and in Input Capture mode holds whatever was last captured, which is 0 before any edge has been captured or if an edge lands exactly at CNT == 0. Hitting either case triggers an integer divide by zero: undefined behavior in C/C++, verified to crash (SIGFPE-equivalent) on a desktop toolchain. On Cortex-M without DIV_0_TRP configured (the default), UDIV silently returns 0, so on-target this instead silently reports "0 Hz" with no indication anything went wrong. Add an explicit zero-check to both HERTZ_FORMAT branches, returning 0 (no meaningful frequency available) instead of dividing, matching the existing "do not underflow" zero-guard style already used by setOverflow() in this same file. No public API/signature changes. Signed-off-by: 94xhn <87560781+94xhn@users.noreply.github.com> --- libraries/SrcWrapper/src/HardwareTimer.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libraries/SrcWrapper/src/HardwareTimer.cpp b/libraries/SrcWrapper/src/HardwareTimer.cpp index 5c88223e21..4f832a1ea0 100644 --- a/libraries/SrcWrapper/src/HardwareTimer.cpp +++ b/libraries/SrcWrapper/src/HardwareTimer.cpp @@ -729,7 +729,10 @@ uint32_t HardwareTimer::getCount(TimerFormat_t format) return_value = (uint32_t)((CNT_RegisterValue * Prescalerfactor * 1000000.0) / getTimerClkFreq()); break; case HERTZ_FORMAT: - return_value = (uint32_t)(getTimerClkFreq() / (CNT_RegisterValue * Prescalerfactor)); + // CNT_RegisterValue is 0 right at/after every timer reset or overflow, + // which is a normal, reachable state (not a caller error): avoid a + // divide by zero and report 0 (no meaningful frequency available). + return_value = (CNT_RegisterValue == 0) ? 0 : (uint32_t)(getTimerClkFreq() / (CNT_RegisterValue * Prescalerfactor)); break; case TICK_FORMAT: default : @@ -1186,7 +1189,12 @@ uint32_t HardwareTimer::getCaptureCompare(uint32_t channel, TimerCompareFormat_ return_value = (uint32_t)((CCR_RegisterValue * Prescalerfactor * 1000000.0) / getTimerClkFreq()); break; case HERTZ_COMPARE_FORMAT: - return_value = (uint32_t)(getTimerClkFreq() / (CCR_RegisterValue * Prescalerfactor)); + // CCR_RegisterValue is 0 on reset (before the first setCaptureCompare() + // call) and, in Input Capture mode, whenever the last captured edge + // landed exactly at CNT == 0 or before any edge has been captured yet. + // That is a normal, reachable state (not a caller error): avoid a + // divide by zero and report 0 (no meaningful frequency available). + return_value = (CCR_RegisterValue == 0) ? 0 : (uint32_t)(getTimerClkFreq() / (CCR_RegisterValue * Prescalerfactor)); break; case PERCENT_COMPARE_FORMAT: return_value = (CCR_RegisterValue * 100) / LL_TIM_GetAutoReload(_timerObj.instance);