
STM32C5A3R 新手最容易上手的外部信号测量方式就是定时器输入捕获测量频率。它不需要额外挂高精度频率计只要把信号接入定时器引脚利用输入捕获记录边沿时刻再通过定时器计数差值换算出频率就能完成大多数 PWM 波、方波信号的测频需求。这篇内容适合刚接触 STM32C5A3R 定时器、想用输入捕获做实际测量的开发者。最值得关注的不是“怎么点几个配置项”而是理解捕获值怎么换算频率以及捕获不到、测不准时该从哪里排查。我会按实际落地顺序拆一轮先说输入捕获的原理和适用场景再讲硬件软件准备然后是 CubeMX 配置和 HAL 库代码最后给一批我在实测中遇到过的问题和排查优先级。你不需要一开始就追求多高精度先把单路信号测出来再考虑多通道、批量测量和占空比扩展。1. 定时器输入捕获到底能干什么适合解决什么问题1.1 输入捕获的本质是记录边沿时刻很多人会把这个功能和外部中断搞混。外部中断是“边沿触发后去执行一段代码”而输入捕获是“边沿触发瞬间由硬件自动把定时器当前计数值保存到捕获寄存器”。前者依赖软件响应速度后者几乎不需要软件介入精度更高也更适合测量连续脉冲。理解这一点很重要。因为测频率的本质就是“在一个完整的信号周期里定时器走了多少个计数节拍”。输入捕获做的工作就是把每个周期的边沿时刻记录下来然后让你算出两个边沿之间的差值。差值就是周期对应的计数值再结合定时器时钟频率周期和频率就都出来了。在 STM32C5A3R 上通用定时器基本都支持输入捕获。你不需要特意找高级定时器。输入捕获对脉冲宽度、信号占空比没有严格要求只要边沿足够干净、幅值满足 GPIO 电平要求就能捕获。1.2 测量频率的两种思路直接测周期和 PWM 输入测占空比第一种思路是直接测周期。配置某个通道为上升沿捕获第一次上升沿记录当前计数值第二次上升沿再记录一次。两次的差值除以计数时钟频率就是信号周期。频带范围由定时器位数、预分频、自动重载值共同决定。第二种思路是 PWM 输入模式。这种模式下同一个定时器的两个通道分别捕获一个周期内的上升沿和下降沿一路算周期一路算高电平时间两个值一除就是占空比。适合测量电机控制、PWM 灯光、传感器输出的占空比信息。这里我需要先说明一点如果你的目标只是测量普通方波频率直接用上升沿捕获最省事。如果你还关心占空比才需要把 PWM 输入模式放进计划里。不要一上来就同时开两个通道容易把配置复杂度拉高反而干扰第一次验证。适合用输入捕获测频率的场景主要有这几类测量电机编码器输出的脉冲频率测量传感器输出的方波信号比如流量计、风速计测量另一个 MCU 或模块输出的 PWM 频率测量无源晶振、RC 振荡器分频后的信号频率配合定时器输出 PWM 做自测验证捕获逻辑这类任务的共同特点是信号本身是周期性脉冲周期在微秒到毫秒级变化较多。如果信号周期特别长比如几秒一次输入捕获也能测但需要考虑定时器溢出扩展否则计数器回绕会算错。1.3 为什么用定时器而不是直接数脉冲还有一个常见疑问外部中断里每次边沿加一个计数变量比如每秒统计多少次不也能测频率吗确实可以。但外部中断在高频时容易忙不过来中断响应延迟也不稳定而且主循环里如果做串口打印、显示刷新计数窗口会抖动。定时器输入捕获的优势在于计时和计数全由硬件完成。捕获瞬间计数器值是硬件锁存的不会因为中断优先级、其他任务抢占而被推迟。你在回调函数里读到的就是触发瞬间的准确计数值。这样测出来的频率更稳定也更容易复现。不过这里也要说清楚输入捕获只负责“记录时间点”频率计算还是要软件完成。如果处理不当比如回调里做了大量浮点运算或长时间串口打印仍然可能错过下一个上升沿。所以要尽量保持回调函数轻量把计算和打印放到主循环或条件标志里。2. 动手前的硬件和软件条件先把环境理清楚2.1 硬件准备开发板、信号源、串口线、示波器基于标题这里围绕 STM32C5A3R 开发板这个载体做说明。你需要一块能正常烧录、串口能输出日志的 STM32C5A3R 开发板或最小系统板。如果板子上没有板载调试器就准备一个 ST-Link、DAP-Link 或 J-Link 类的调试器确保能烧录和在线调试。信号源方面手头有函数信号发生器最好可以输出 1Hz 到几十 kHz 的方波。没有信号发生器也不用卡住用另一块 STM32 开发板输出 PWM 信号或者用本芯片的另一个定时器 Channel 输出 PWM接到输入捕获引脚就能形成一个闭环自测。这个做法我在后面验证部分会详细说。串口线负责把测量结果打印出来。开发板一般会引出 USART 对应的 TX、RX 引脚也有板载 USB 转串口芯片的。准备一根 USB 转 TTL 串口线波特率固定到 115200 或 9600 即可。示波器属于强烈推荐但不是必须。如果你能观察输入引脚上的波形排查速度和准确度会高很多。没有示波器时可以用串口打印 GPIO 输入状态来粗测但精度有限无法确认抖动。2.2 软件准备STM32CubeMX、IDE、HAL 库软件方面我会用 STM32CubeMX 做初始化配置生成代码后再用 IDE 编写逻辑。这种流程对 STM32C5A3R 这类芯片也适用。你需要在 CubeMX 的芯片选择界面里搜索 STM32C5A3R如果能搜到就选中如果开发板是派生型号但芯片资源一致也可以搜索对应系列。IDE 方面主流选择是 STM32CubeIDE、Keil MDK 或者 IAR。这里不强制指定只要你的编译环境能正常编译 HAL 库工程即可。我通常用 STM32CubeIDE因为 CubeMX 生成的工程可以直接打开不需要再单独处理链接脚本和调试配置。HAL 库版本以你的 CubeMX 自动生成为准。不同版本的 HAL 库函数签名基本一致但个别宏定义和头文件路径会有差异。如果你在编译时提示找不到某个函数优先检查 HAL 库版本和工具链是否一致而不是先怀疑代码逻辑。2.3 时钟和引脚设计先确定信号从哪个引脚进来这一点非常关键。STM32C5A3R 的定时器输入捕获引脚通常和普通 GPIO 复用但并不是任意 GPIO 都能连接到某个定时器通道。严格来说引脚、定时器实例、通道、复用功能之间有一张固定的映射表。在 CubeMX 里配置时你不需要背这张表但你需要明确自己手上的信号源接到了哪个引脚上。建议先在原理图上确认开发板引出的引脚编号然后在 CubeMX 的 Pinout 视图里搜索芯片引脚就能看到可复用的定时器输入通道。我一般会按这个顺序选择引脚先确认信号源使用的是 3.3V 电平。再在 CubeMX 里查找哪个定时器通道可以映射到现有引脚。优先选择和串口、调试器不冲突的引脚。如果信号源输出的是 5V 电平必须加电平转换或分压电阻不能直接接到 3.3V 容忍引脚除非该引脚明确支持 5V 容忍。时钟树设计也不能忽略。定时器时钟来自 APB1 或 APB2 定时器时钟配置不当会直接影响测量分辨率。CubeMX 默认生成的时钟树通常已经使能了定时器时钟但你最好在 Clock Configuration 页面确认 APB 定时器时钟频率因为后续计算计数时钟时要用到这个值。原输入材料没有给出具体时钟频率这里不写死数字以你实际工程配置为准。3. STM32CubeMX 配置定时器输入捕获3.1 工程基本配置打开 STM32CubeMX新建工程并选择 STM32C5A3R 型号。在 System Core 的 SYS 里Debug 模式根据你的调试器选择 Serial Wire 或 JTAG。如果你用 STM32CubeIDE 烧录一般选 Serial Wire 即可。RCC 配置中如果板上有外部晶振选择 Crystal/Ceramic Resonator如果没有外部晶振就使用内部高速时钟。这个选择会直接影响定时器时钟精度。外部晶振通常精度更高测量出来的频率更接近真实值内部 RC 振荡器在不同温度下会存在一定偏差。然后配置 USART。选择你板子上连接 USB 转串口的那个 USART 实例比如 USART2模式选 Asynchronous波特率 115200。这个串口用来打印捕获结果是验证环节的主要输出通道。3.2 定时器时基配置以 TIM2 为例。CubeMX 中左侧找到 Timers - TIM2勾选 Activated。在 Parameter Settings 页面配置时钟源和参数。需要理解三个关键参数参数作用典型说明Prescaler (PSC)分频系数实际分频值为 PSC1决定计数时钟频率PSC 越大计数越慢Counter Period (ARR)自动重载值计数器计数到此值后回绕决定最大计数范围16 位定时器最大 65535Internal Clock Division定时器内部分频输入捕获一般保持不分频Auto-reload preload自动重载预装载建议 Enable避免运行时更新 ARR 导致异常选择 PSC 时要综合考虑你要测的频率范围。计数时钟越小单位计数值代表的时间越长对于高频信号你需要更小的 PSC让计数时钟足够快否则一个周期内只计了几个数分辨率很低。如果暂时不确定信号频率范围可以先保持一个比较保守的配置比如让计数时钟为 1MHz也就是 PSC 根据定时器输入时钟算出来。然后 ARR 设为 65535。先把功能跑通后面再根据实际信号调整。这里我再提醒一句ARR 是 16 位还是 32 位取决于具体定时器实例。STM32 的 TIM2 和 TIM5 通常是 32 位定时器其他很多通用定时器是 16 位。STM32C5A3R 的具体定时器资源需要以 CubeMX 实际显示为准不能凭老经验套用。3.3 捕获通道配置在 TIM2 的配置页里找到 Input Capture Channel 或 Channel1 选项。选择 Input Capture direct mode表示直接把引脚信号接到捕获通道。这样配置后CubeMX 会为对应通道分配一个 GPIO 引脚并把引脚复用为定时器输入功能。配置这一栏时有几个选项需要逐个确认Prescaler这里的预分频是针对输入信号的不是针对定时器时钟。比如设为 1表示每 2 个边沿才触发一次捕获。我建议先把捕获预分频设为 1即不额外分频保证捕获灵敏度最高。Filter输入滤波器可以滤除短毛刺。如果信号比较干净可以设为 0 或较小值。如果信号有抖动适当加大滤波器能提高稳定性但也会增加边沿识别延迟频率高时误差会变大。Polarity捕获极性。测频率时先选 Rising Edge。如果信号是低电平有效且需要测下降沿再改成 Falling Edge。IC Selection这里要确认是 Direct 还是 Indirect。Direct 表示直接使用对应通道Indirect 表示使用交叉映射。初学者直接用 Direct。有的人会误以为输入捕获的“分频”会降低计数时钟。完全不是一回事。捕获预分频只影响“多少个边沿捕获一次”不影响定时器计数速度。如果你做的是频率测量保持为不分频即可。3.4 中断和串口配置输入捕获只有在使能捕获中断后才能进入 HAL 库回调函数。在 CubeMX 的 NVIC Settings 中找到定时器全局中断把它 Enabled。中断优先级建议先设为中等比如抢占优先级 2子优先级 0。不要一开始就设成最高否则后续其他定时器或串口中断可能被频繁打断。串口也需要使能中断吗如果只用轮询发送 printf不一定要使能。但如果你计划更高效率地发送数据可以打开串口全局中断。默认场景下先关掉串口中断用 printf 轮询输出问题更少。生成工程时选择 Makefile 或对应的 IDE 工程类型。如果使用 STM32CubeIDE选择 STM32CubeIDE 生成项即可。代码生成完成后先编译一次确认环境没问题再开始填代码。4. 代码实现从启动捕获到频率计算4.1 全局变量与状态标记设计生成工程后主要代码需要写在用户代码区。先设计几个全局变量用来保存两次捕获值和计算状态。/* USER CODE BEGIN PV */ __IO uint16_t g_ic_val1 0; __IO uint16_t g_ic_val2 0; __IO uint16_t g_ic_diff 0; __IO uint8_t g_ic_flag 0; /* USER CODE END PV */这段代码的含义是第一次上升沿把计数值存到g_ic_val1第二次上升沿存到g_ic_val2然后计算差值g_ic_diff。g_ic_flag用来标记当前捕获状态。这里讲一下为什么用状态标记而不用简单一次性赋值因为频率测量必须凑齐两个边沿才能得到一个完整周期。如果没有状态标记第二次捕获会覆盖第一次的值计算就乱了。还有一种设计是用计数变量记录捕获次数。但我更推荐状态机方式代码更直观状态 0 表示等待第一次上升沿状态 1 表示等待第二次上升沿状态 2 表示已经拿到完整周期。4.2 捕获中断回调函数在stm32c5a3r_it.c或stm32xxxx_it.c中CubeMX 已经生成了定时器中断服务函数。你不需要修改它只需要在主源文件里重写HAL_TIM_IC_CaptureCallback。这是 HAL 库提供的弱函数用户重写后即会被调用。/* USER CODE BEGIN 4 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { if (htim-Channel HAL_TIM_ACTIVE_CHANNEL_1) { if (g_ic_flag 0) { g_ic_val1 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_ic_flag 1; } else if (g_ic_flag 1) { g_ic_val2 HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); g_ic_flag 2; } else { /* 如果已经计算完成这里可以根据需求继续滚动更新 */ } } } } /* USER CODE END 4 */这段回调里没有做频率计算因为回调里不应该出现浮点运算和串口打印。原因很实际输入捕获中断是高频事件如果在高频信号下每次中断都执行 printf 或除法CPU 会被大量占用还会因为串口阻塞导致下一个上升沿捕获异常。应该只保存原始计数值设置状态标志然后回到主循环里处理。HAL 库有一个封装细节值得注意HAL_TIM_ReadCapturedValue读取的寄存器是捕获/比较寄存器TIMx_CCRx。在输入捕获模式下这个寄存器保存的就是触发边沿时的 CNT 值。它不会自动清空下次捕获时会被硬件覆盖。所以要尽快读取并保存到变量中。4.3 频率计算计数差值怎么换算成 Hz拿到两次捕获值后在main函数的主循环中检查g_ic_flag。当g_ic_flag 2时计算频率。/* USER CODE BEGIN WHILE */ while (1) { if (g_ic_flag 2) { uint32_t arr __HAL_TIM_GET_AUTORELOAD(htim2); uint32_t psc htim2.Init.Prescaler; uint32_t timer_clock_hz HAL_RCC_GetPCLK1Freq() * 2; /* 这里需要根据时钟树确认 */ uint32_t count_clock_hz timer_clock_hz / (psc 1); if (g_ic_val2 g_ic_val1) { g_ic_diff g_ic_val2 - g_ic_val1; } else { g_ic_diff (g_ic_val2 arr 1) - g_ic_val1; } if (g_ic_diff ! 0) { float freq (float)count_clock_hz / (float)g_ic_diff; printf(IC1: %lu, IC2: %lu, Diff: %lu, Freq: %.2f Hz\r\n, (unsigned long)g_ic_val1, (unsigned long)g_ic_val2, (unsigned long)g_ic_diff, freq); } g_ic_flag 0; } } /* USER CODE END WHILE */这里最关键的是timer_clock_hz怎么取。不同 STM32 系列里定时器时钟未必等于 APB 外设时钟。常见情况是如果 APB 预分频不为 1定时器时钟是 APB 时钟的 2 倍。我在注释里写的是示意代码显然这不适用于所有情况。正确做法是在 CubeMX 的 Clock Configuration 页面里直接看 TIM2 的输入时钟频率然后在代码里写成那个值。不要照抄网上任意公式。频率换算公式是计数时钟频率 定时器时钟 / (PSC 1) 信号频率 计数时钟频率 / 捕获差值举例说明如果定时器时钟 64 MHzPSC 配置为 63那么计数时钟频率是 1 MHz也就是一个计数节拍代表 1 微秒。如果输入信号周期是 1000 个计数节拍那么信号频率就是 1 MHz / 1000 1000 Hz。4.4 计数器回绕问题的处理g_ic_val2小于g_ic_val1意味着发生了一次计数器回绕。计数器从某个值增长到 ARR 后自动回到 0再从 0 增长到g_ic_val2。实际差值应该是g_ic_val2 (ARR 1) - g_ic_val1。这段逻辑我在代码里写了。但如果你测量的信号周期较长甚至跨越多个溢出周期那么还需要维护一个溢出计数器。实现方式是使能定时器更新中断在HAL_TIM_PeriodElapsedCallback里对溢出次数加 1计算时把总周期换算成溢出次数 * (ARR 1) 当前差值。对于更高频率的信号计数器回绕的概率很低因为一个周期内捕获差值远小于 ARR。但对于低频信号比如 1Hz 信号如果计数时钟 1 MHz、ARR 65535一次捕获差值是 1000000早就超过 ARR 了必须溢出扩展才能处理。所以如果你要测低频信号先把 ARR 调到最大再考虑溢出计数扩展。5. 单任务验证和批量测量怎么判断结果是否正常5.1 没有信号源怎么先自测用定时器输出 PWM 做回环很多初学者最头疼的是手边没有信号发生器。其实这并不影响第一次验证。你可以直接使用 STM32 内部的另一个定时器产生 PWM然后通过杜邦线接到输入捕获引脚。甚至可以用同一个定时器的另一个通道输出 PWM再接回到捕获通道。这种方式可以快速验证捕获中断是否触发、频率计算是否合理。在 CubeMX 中增加一个定时器通道配置为 PWM Generation CHx设置一个固定频率比如 1 kHz。生成代码后调用 HAL 库 PWM 启动函数然后杜邦线连接 PWM 输出引脚到输入捕获引脚。这样串口就能打印出接近 1 kHz 的测量值。为什么建议这么做因为这样可以排除外部信号源本身的问题。如果你用函数信号发生器也测不出来就很难判断是板子问题还是信号源问题。用内部 PWM 回环自测至少能确认接线、GPIO 复用、捕获中断、频率公式都正常。后续再用外部信号源只需要额外排查输入电压和波形质量。以下是基本 PWM 启动代码段HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1);如果 PWM 通道输出的是 50% 占空比那么在输入捕获引脚上看到的就是一个标准方波上升沿周期就是 PWM 周期。测量结果和 PWM 配置频率的误差一般应该在 1% 以内如果超过这个范围优先检查时钟树配置。5.2 串口输出结果怎么看正常情况下串口会持续打印类似这样的内容IC1: 124, IC2: 1124, Diff: 1000, Freq: 1000.00 Hz IC1: 1124, IC2: 2124, Diff: 1000, Freq: 1000.00 HzDiff表示计数差值Freq表示换算后的频率。不同捕获周期内Diff可能因为信号抖动有几 count 的波动这是正常的。如果波动幅度超过 1%需要检查信号质量和测量分辨率。如果串口没有任何打印先检查是不是进入了if (g_ic_flag 2)分支。可以用调试器打断点或者加一个简单的计数变量在main循环里每进一次就递增通过串口打印出来。这样做可以快速判断是回调没触发、还是状态机没走完、还是主循环逻辑有问题。串口打印本身也有影响。如果频率很高比如 100 kHz一个周期只有 10 微秒打印过程会占用很多 CPU导致捕获中断被阻塞。这种情况不要实时打印可以每打印一次后加延时或者只在捕获到一定次数后打印一次平均值。批量测量的设计我会在下面展开。5.3 单通道验证通过后怎么扩展到多通道和批量测量单通道测通之后多通道扩展其实是在复制同一个模式。你可以在同一个定时器上配置多个捕获通道比如 TIM2_CH1 和 TIM2_CH2分别接到两路信号。回调函数中通过htim-Channel判断当前是哪一路捕获分别处理各自的g_ic_val1、g_ic_val2、g_ic_diff。多通道时变量命名要改成数组或结构体否则回调里容易把值覆盖掉。例如__IO uint16_t g_ic_val1[2]; __IO uint16_t g_ic_val2[2]; __IO uint16_t g_ic_diff[2]; __IO uint8_t g_ic_flag[2];在回调里用通道数组索引来区分。这里要注意同一个定时器的多个通道共享同一个计数器但捕获寄存器是独立的。因此不会出现通道间互相覆盖的问题。不过如果在同一定时器的不同通道上测的两路信号频率差特别大配置参数只能折中无法同时为高频和低频提供最优分辨率。对于真正的批量测量任务比如一个数据采集设备要轮询测量多个外部信号有三种常见扩展方式一个定时器的多个通道同时捕获不同信号适合通道数量少、全部是输入信号的场景。多个定时器各自捕获一路信号逻辑隔离性更好适合通道数量分散在不同定时器上的场景。单通道配合 DMA 循环采样适合需要连续记录很多个周期并做平均值的场景。DMA 方式最复杂但对高频信号和连续周期分析最有价值。它可以把捕获值直接搬运到内存数组不打断 CPU等到一定次数后再统一处理。如果只是测一个频率不建议上来就用 DMA。6. 参数边界和常见坑为什么测不准为什么没反应6.1 预分频和自动重载的取舍这是输入捕获配置中最值得反复调整的一对参数。预分频决定计数时钟频率自动重载决定计数器范围。两者合起来决定了可测频率的上下限和分辨率。先看上限。如果信号频率过高一个周期内的捕获差值很小比如只有几个 count那么测量分辨率就很差。比如计数时钟 1 MHz信号频率 500 kHz一个周期只有 2 个 count算出来频率要么是 500 kHz要么是 333 kHz量化误差非常大。要提高上限必须提高计数时钟频率也就是减小 PSC。再看下限。信号频率过低时一个周期内差值可能超过 ARR 范围计数器会发生回绕。如果没处理溢出算出来频率会偏高。要降低可测下限可以增大 ARR、使用 32 位定时器、降低计数时钟或者引入溢出计数。实际操作中我建议先估算信号频率大概范围再倒推计数时钟。以测量 10 kHz 信号为例如果希望每个周期至少测量到 1000 个 count那么计数时钟需要 10 MHz 以上。CubeMX 生成的定时器时钟如果是 64 MHzPSC 设置为 0 就可以得到 64 MHz 的计数时钟此时一个 10 kHz 周期有 6400 个 count分辨率已经不错。反过来如果你要测的信号只有 1 Hz那计数时钟太高就会导致差值远超 ARR。此时可以把 PSC 调大让计数时钟降到 1 kHz甚至更低这样 1 Hz 周期对应 1000 个 count精度也够。6.2 高频测不准和低频溢出分别怎么处理高频测不准的典型表现是测量值跳变幅度大、相邻两次频率结果差异明显。常见原因有三个捕获极性旁边的滤波器设置太大导致边沿识别延迟不一致。输入信号本身有抖动边沿不干净。代码里中断回调执行时间过长或者串口打印阻塞了主循环。处理顺序是先检查信号源波形再降低滤波器值然后精简中断回调最后才考虑提高计数时钟。如果你一开始就把 PSC 降到 0结果发现数字还是乱跳那问题大概率不在分频而在信号或回调效率。低频溢出的典型表现是测出来的频率异常高比如 1 Hz 信号测出几千 Hz。原因是捕获差值超过了 ARR 后回绕导致软件算出的差值偏小。处理方式是增加溢出中断记录计数器回绕次数在频率计算时把总周期统一折算。也可以直接选择 32 位定时器如果芯片上有减少溢出的概率。如果需要连续测量并展示稳定结果我建议做滑动平均。比如每采集到 5 个周期后把测量的周期值做平均再换算频率。这样虽然实时性稍微降低但输出更稳定也更适合观察频率变化趋势。6.3 排查优先级先看串口日志再改参数最后怀疑硬件针对输入捕获最常见的一类问题我整理了一条排查顺序先看串口有没有输出。如果完全没有输出优先怀疑捕获中断是否进了、状态机是否启动了。确认HAL_TIM_IC_Start_IT是否被调用。遗忘这个函数是很多人第一次捕获没反应的最大原因。确认引脚复用是否正确。CubeMX 生成的 GPIO 配置里Mode 必须显示为 AF复用功能不是普通的 Input。确认时钟树中定时器时钟是否为 0。有时候 CubeMX 生成工程后定时器时钟源未被正确使能计数器根本不动。确认输入信号电平是否符合 GPIO 电压范围。信号幅值太低捕获不会触发幅值超过芯片允许范围可能损坏 GPIO。确认滤波器参数。信号质量差时调大滤波器信号质量好时最好保持较小值。断开外部信号用内部 PWM 回环测试判断是输入问题还是配置问题。如果以上都正常再考虑定时器本身是否存在资源冲突比如同一个定时器被其他外设占用。我以前在调试时遇到一个典型问题串口一直打印频率为 0检查代码发现g_ic_flag一直为 0说明回调根本没进入。后来发现是 CubeMX 里只勾选了定时器全局中断但没在main函数里手动调用HAL_TIM_IC_Start_IT。这个函数不是 CubeMX 自动启动的必须自己加。这个问题很容易被忽略。另一个常见问题是捕获值偶尔异常偶尔又是对的。这种情况大概率与输入信号边沿抖动有关或者与中断优先级冲突有关。我之前调试时把串口中断优先级设为最高导致高频信号的捕获中断被串口输出抢占偶尔漏掉一次边沿计算结果就会突然翻倍。后来把定时器捕获中断优先级提高串口打印优先级降低问题就消失了。6.4 输入信号电平和引脚保护输入捕获引脚通常按 GPIO 输入功能工作不同芯片可能有不同的电压容忍范围。如果你要测量的信号来自高压驱动器、电机驱动板、AC 信号整流之后的脉冲先搞清楚输出电平是多少。很多电机驱动器的速度反馈引脚输出是 5V 或 12V 电平直接接入 3.3V 的 STM32 GPIO 很危险。处理方式有几种使用电平转换芯片。使用分压电阻比如 10k20k 把 5V 分到 3.3V 左右。使用 PNP/NPN 三极管或光耦做隔离并在输出端加上拉电阻。测普通数字信号时还要注意 GPIO 输入模式配置。CubeMX 会在你选择输入捕获后自动设置引脚模式一般不需要额外修改。但如果引脚被配置成了模拟输入捕获就不会工作。生成代码后可以在MX_GPIO_Init中检查对应引脚模式。还有一点输入捕获对信号质量有一定要求。如果外部信号从长线传输过来或者经过了继电器触点边沿可能很缓慢或带有大量毛刺。此时可以适当加大输入滤波器。但是不要调太大否则信号上升沿被滤波后捕获时刻会被推后频率测量结果会整体偏移。最后留一句我在做这类功能时最常用的话先把单路信号用默认配置跑通再根据测量结果反推是否需要调整 PSC、ARR、滤波器和中断优先级。不要一上来就追求高精度先把链路打通后面所有优化都会变得很顺。