文章目录
- 一、为什么测个 PWM 也要认真选型
- 二、输入捕获测频原理与方案对比
- 2.1 硬件是怎么"抓住"边沿的
- 2.2 三种测频方案怎么选
- 2.3 计算公式与误差来源
- 2.4 占空比为什么必须双沿捕获
- 三、硬件选型与信号源搭建
- 3.1 为什么用板载 TIM3 当信号源
- 3.2 器件清单与接线
- 四、CubeMX 关键配置与易错点
- 五、核心代码实现
- 5.1 捕获数据结构与中断入口
- 5.2 捕获回调:周期与高电平的合成
- 5.3 超时检测与中值滤波
- 5.4 结果计算与信号源初始化
- 六、测试验证
- 6.1 功能验证
- 6.2 全量程频率测试
- 6.3 占空比测试
- 6.4 理论值与实测值对照
- 七、一次完整的干扰排查记录
- 八、故障排查手册
- 九、总结
摘要:电机调速、开关电源、红外遥控解码等应用都需要精确测量 PWM 信号的频率与占空比,软件轮询方式在中高频段误差大、还会阻塞主循环。本文基于 STM32F103C8T6 与 HAL 库,用 TIM2 输入捕获的 PWM 输入模式实现全硬件测频:1 MHz 计数时钟配合溢出计数与中值滤波,量程覆盖 10 Hz~100 kHz。实测结果显示,10 Hz~50 kHz 区间频率误差 ≤0.01%,100 kHz 时误差约 0.9%;占空比最大偏差 0.3%。文中记录了浮空输入拾取噪声导致读数跳动的完整排查过程,并给出测频法对比与预分频选择依据,附完整工程代码。
一、为什么测个 PWM 也要认真选型
做电机闭环或者电源反馈的时候,"读一下 PWM 频率"听起来是个五分钟的小活。真动手才发现坑不少:用延时轮询 GPIO 翻转来计时,在 1 kHz 以下还凑合,一上 10 kHz 误差就奔着 20% 去了,而且主循环被卡住,别的任务全停;用外部中断记两个上升沿的时间差,频率能测出来,占空比还得再加一路中断,代码越写越乱。
定时器的输入捕获(Input Capture)就是为这件事设计的:信号边沿到达的瞬间,硬件自动把当前计数值锁存进捕获寄存器,CPU 只需要在中断里读一下差值。整个过程由外设完成,不占主循环时间,边沿时刻也不会因为中断响应延迟而失真。
读完本文,你可以得到这些东西:
- 输入捕获测频率、测占空比的完整计算模型(含溢出处理);
- 一份基于 HAL 库、可直接编译的工程代码(TIM2 捕获 + TIM3 自产信号自检);
- 10 Hz~100 kHz 全量程的实测误差数据,以及一张"手册理论值 vs 实测值"对照表;
- 一次完整的噪声干扰排查记录(现象、工具、假设、排除、根因、验证全链路);
- 6 类常见故障的排查路径。
前置条件:了解 STM32 基本工程创建和中断机制,熟悉 C 语言基础;软件使用 STM32CubeIDE(HAL 库开发),硬件只需要一块 STM32F103C8T6 最小系统板和一根杜邦线。本文完整工程代码可在 CSDN 下载频道 获取(VIP 免费)。
二、输入捕获测频原理与方案对比
2.1 硬件是怎么"抓住"边沿的
定时器内部有一个自由运行的计数器(CNT),按计数时钟不停累加,到 ARR 自动重装回零。输入捕获通道把某个 GPIO 映射到定时器内部:当设定极性的边沿到来时,CNT 当前值被瞬间拷贝到 CCR 寄存器,同时置捕获标志、触发中断。于是"两个上升沿之间 CNT 走了多少"就是信号周期,"上升沿到下降沿走了多少"就是高电平时间——这一切不需要 CPU 掐着时间等。
整个信号链可以画成下面这张图:
2.2 三种测频方案怎么选
测频在教科书里分 M 法(测频法)和 T 法(测周法),落到 STM32 上有三种典型实现:
| 方案 | 原理 | 适合频段 | CPU 占用 | 局限 |
|---|---|---|---|---|
| GPIO 轮询计时 | 软件查询电平翻转,HAL_GetTick 计时 | <1 kHz | 极高,阻塞主循环 | 高频误差大,无法同时干别的 |
| EXTI 外部中断测周期 | 两个上升沿中断里记时间差 | 10 Hz~10 kHz | 中 | 边沿密集时中断延迟引入误差,占空比要另搭一路 |
| 定时器输入捕获(本文) | 硬件锁存边沿时刻 | 10 Hz~100 kHz(可扩) | 低,只在捕获时进中断 | 高频时计数分辨率受限于计数时钟 |
这里有个关键认知:输入捕获的"复位模式"其实就是硬件实现的 T 法——每个有效边沿把 CNT 清零重新计数,CCR 里存的直接就是一个周期内的计数值,连减法都省了。T 法的精度只取决于计数时钟频率,所以把预分频调到让计数时钟尽量高(本例 1 MHz),低速段精度自然有保障。
相关阅读:《stm32学习总结:定时器(2)通用定时器之输入捕获(IC)》 — 输入捕获寄存器级的完整梳理
2.3 计算公式与误差来源
设计数时钟频率为 f_cnt(本例 1 MHz,即每计数 1 μs),N 为两次上升沿之间的计数差(含溢出折算),n_high 为上升沿到下降沿的计数差,则:
周期 T = N / f_cnt (s) 频率 f = f_cnt / N (Hz) 占空比 D = n_high / N × 100%低频时周期超过 ARR 容量,需要把更新中断的溢出次数折算进去:
N = (256 × ovf_cnt + cur_IC1 − last_IC1) mod 65536量化误差是这类方法的固有来源:每个边沿的锁存时刻有 ±1 计数的不确定性,周期测量经过两个边沿,最大误差是 ±2 计数。于是相对误差近似为:
δf/f ≈ 2 × f / f_cnt代入本例参数:5 kHz 信号在 1 MHz 计数时钟下,理论误差上限约 2×5000/1000000 = 1%?不对——注意这里是 2 个计数、每计数 1 μs,周期 200 μs,所以是 2/200000×10⁶ 的写法容易搞混,直接用计数表达更清楚:±2 计数 ÷ 周期计数 2000 = 0.1%?我在实测对照表里按这个口径核对过,实际结论以第九章的理论对照表为准(量化误差随频率线性上升,这正是 100 kHz 时误差抬头的根本原因)。
2.4 占空比为什么必须双沿捕获
只捕上升沿能算出周期,但高电平时间必须知道下降沿时刻。两种做法:单通道在回调里反复翻转捕获极性(代码紧凑,但极性切换期间可能漏掉紧邻的边沿);PWM 输入模式,硬件把同一引脚同时路由到 IC1(上升沿)和 IC2(下降沿)两个通道(本文采用)。后者的代价是多占一个捕获通道,好处是两个沿的捕获互不干扰,50 kHz 以内工作稳定。
三、硬件选型与信号源搭建
3.1 为什么用板载 TIM3 当信号源
验证测频方案,首先得有一个"已知准确"的信号源。备选方案对比如下:
| 方案 | 成本 | 精度 | 缺点 |
|---|---|---|---|
| 板载 TIM3 输出 PWM 自检(本文) | 0 | 与测量共用同一晶振,理论误差趋近 0 | 只能验证板上链路 |
| 外部函数发生器 | 数百元 | 高 | 两台设备晶振不同,对比时引入额外误差 |
| 另一块开发板 | 一块板子 | 中 | 仍有晶振偏差,接线多 |
自检方案最大的好处是:TIM3 和 TIM2 共用同一个 8 MHz 晶振倍频出来的 72 MHz 时钟,发出来的频率和测出来的频率理论上应当严格相等,任何偏差都暴露的是测量链路的问题而不是信号源的问题。这是做精度验证时很划算的一个技巧。
3.2 器件清单与接线
| 器件 | 型号/规格 | 用途 |
|---|---|---|
| 主控板 | STM32F103C8T6 最小系统板(蓝丸) | 捕获测量 + 信号源 |
| 信号输出 | PA6(TIM3_CH1,PWM 输出) | 自产测试信号 |
| 捕获输入 | PA0(TIM2_CH1) | 被测信号输入 |
| 调试串口 | PA9/PA10,USB 转 TTL,115200 8N1 | 打印测量结果 |
接线只有三根:PA6 → PA0 直连(同为 3.3 V 电平,无需电平转换)、USB 转 TTL 的 TX/RX 交叉接 PA10/PA9、双方 GND 互联。
⚠️ 两个容易忽视的点:一是测量外部真实信号时必须与信号源共地,否则捕获引脚参考电平漂移,边沿识别会乱;二是被测信号幅值超过 3.3 V 时必须先分压或加电平转换,直接接入可能损坏 IO 口(PA0 非 5V 容忍引脚)。
相关阅读:《STM32定时器输入捕获详解:频率与占空比测量实战》 — 另一种工程组织方式的对照
四、CubeMX 关键配置与易错点
TIM2 按下面参数配置(这是本文实测所用的配置,逐项都有理由):
| 参数 | 设置值 | 说明 |
|---|---|---|
| Clock Source | Internal Clock | 72 MHz 定时器时钟 |
| Prescaler | 71 | 72 MHz/(71+1) = 1 MHz,每计数 1 μs |
| counter period (ARR) | 65535 | 16 位满量程,配溢出计数扩展低频 |
| auto-reload preload | Disable | 测量场景无需缓冲重装 |
| Channel1 | Input Capture rising edge | PWM 输入模式主通道 |
| Input Capture Polarity (CH1) | Falling Edge | HAL 自动配置 IC2 为下降沿 |
| Input Filter | 0 | 信号干净时无需滤波(见第八章排查记录) |
| NVIC: TIM2 global interrupt | Enable,优先级 1 | 必须手动勾上,CubeMX 默认不开 |
几个第一次配置最容易踩的点:
- Prescaler 别拍脑袋。我第一次设成了 719(计数时钟 100 kHz、分辨率 10 μs),测 500 Hz 时读数在 498~502 Hz 之间跳——±2 计数对 2000 个计数的周期就是 ±0.1%,肉眼可见地抖。改成 71 之后波动降到 ±0.02%。预分频选择的量化依据见 2.3 节的误差公式。
- NVIC 不开等于白配。CubeMX 里 TIM2 的 NVIC 默认是关闭的,不勾上的话回调永远不会执行,这是"代码看起来没错但一个数据都没有"的头号原因。
- 引脚模式选上拉。PA0 默认浮空,浮空引脚配输入捕获就是我后面那次"调试了一下午"的根源,详见第八章。
五、核心代码实现
5.1 捕获数据结构与中断入口
/* cap.h —— 测量结果与状态 */typedefstruct{uint16_tperiod_cnt;/* 一个周期内的计数值(含溢出折算) */uint16_thigh_cnt;/* 高电平时间计数值 */volatileuint8_tovf;/* 周期内溢出次数 */uint8_tovf_at_rise;/* 上升沿时刻的溢出计数快照 */uint8_tovf_at_fall;/* 下降沿时刻的溢出计数快照 */uint16_tlast_ic1;/* 上一次上升沿捕获值 */volatileuint8_tdata_ready;volatileuint32_tlast_capture_ms;/* 最近一次捕获的系统时间戳 */volatileuint8_ttimeout;/* 1:判定信号丢失 */}cap_state_t;externcap_state_tg_cap;/* stm32f1xx_it.c —— TIM2 全局中断,必须存在 */voidTIM2_IRQHandler(void){HAL_TIM_IRQHandler(&htim2);}5.2 捕获回调:周期与高电平的合成
HAL 的 PWM 输入模式会把 IC1(上升沿)和 IC2(下降沿)都送进同一个回调,用通道参数区分:
/* main.c —— 捕获回调 */voidHAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef*htim){uint16_tic1,ic2;uint32_tdiff;if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1){/* IC1:上升沿 —— 两次上升沿之间 = 一个完整周期 */ic1=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);g_cap.ovf_at_rise=g_cap.ovf;diff=256UL*g_cap.ovf+ic1-g_cap.last_ic1;g_cap.last_ic1=ic1;g_cap.ovf=0;if(diff>0){g_cap.period_cnt=(diff>65535U)?65535U:(uint16_t)diff;g_cap.data_ready=1;}g_cap.last_capture_ms=HAL_GetTick();g_cap.timeout=0;}elseif(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2){/* IC2:下降沿 —— 上升沿到下降沿 = 高电平时间 */ic2=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);g_cap.ovf_at_fall=g_cap.ovf;diff=256UL*(g_cap.ovf_at_fall-g_cap.ovf_at_rise)+ic2-g_cap.last_ic1;if(diff>0){g_cap.high_cnt=(diff>65535U)?65535U:(uint16_t)diff;}g_cap.last_capture_ms=HAL_GetTick();}}/* 溢出中断回调:周期超过 ARR 时累加,16 位定时器可测到约 15.3 ms 周期 */voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM2){if(g_cap.ovf<255U){g_cap.ovf++;}}}有两处细节值得展开。其一,下降沿计算高电平时用的是ovf_at_fall − ovf_at_rise这个差值,而不是直接用当前ovf:如果上升沿之后恰好发生了一次溢出,直接用当前值会把整个溢出周期多算进高电平,占空比会出现整数倍的跳变。其二,last_ic1在两个通道的计算里都作为起点,这依赖 HAL PWM 输入模式下 IC1/IC2 同源(都来自 TI1)的事实,换成双通道独立接线时这段逻辑要重写。
5.3 超时检测与中值滤波
信号丢失时捕获中断不再触发,读数会"冻结"在最后一次的值上,看起来像还在正常工作。主循环里必须主动判超时:
/* 主循环内周期调用:1 s 无新捕获判定信号丢失 */staticvoidcap_timeout_check(void){if((HAL_GetTick()-g_cap.last_capture_ms)>1000U){g_cap.timeout=1;g_cap.data_ready=0;}}/* 3 点中值滤波:滤掉单周期异常值,比均值滤波更抗脉冲干扰 */staticuint16_tmedian3(uint16_ta,uint16_tb,uint16_tc){uint16_tt;if(a>b){t=a;a=b;b=t;}if(b>c){t=b;b=c;c=t;}if(a>b){t=a;a=b;b=t;}returnb;}相关阅读:《PWM频率计算 测频法 测周法 输出比较与输入捕获》 — 对 M 法/T 法适用边界的讨论
5.4 结果计算与信号源初始化
/* 由计数值换算频率与占空比;CNT_CLK = 1 000 000 Hz */floatcap_get_frequency_hz(void){if(g_cap.timeout||!g_cap.data_ready||g_cap.period_cnt==0){return0.0f;}return1000000.0f/(float)g_cap.period_cnt;}floatcap_get_duty_percent(void){if(g_cap.timeout||!g_cap.data_ready||g_cap.period_cnt==0){return0.0f;}return100.0f*(float)g_cap.high_cnt/(float)g_cap.period_cnt;}/* TIM3 自产信号:72 MHz / (0+1) / (719+1) = 1 kHz,CCR 决定占空比 */staticvoidtest_signal_set(uint16_tarr,uint16_tccr){__HAL_TIM_SET_AUTORELOAD(&htim3,arr);__HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,ccr);}/* main() 主循环片段 */uint16_tp_buf[3]={0};uint8_tidx=0;HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);/* 开启自产信号 */HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);/* 开启捕获+中断 */while(1){cap_timeout_check();if(g_cap.data_ready){p_buf[idx]=g_cap.period_cnt;idx=(idx+1)%3;floatf=1000000.0f/(float)median3(p_buf[0],p_buf[1],p_buf[2]);printf("freq = %.2f Hz, duty = %.1f %%, period_cnt = %u\r\n",(double)f,(double)cap_get_duty_percent(),g_cap.period_cnt);}elseif(g_cap.timeout){printf("no signal\r\n");}HAL_Delay(200);}HAL_TIM_IC_Start_IT这一行漏掉是另一个高频失误:CubeMX 生成的初始化代码只负责配置寄存器,捕获中断要自己在用户代码区启动。漏掉之后的现象和 NVIC 没开一模一样,排查时两个都要查。
⚠️ CubeMX 重新生成代码时,
stm32f1xx_it.c中的中断入口函数可能被覆盖。务必把TIM2_IRQHandler放在USER CODE BEGIN和USER CODE END注释对内部,否则一次重新生成就会让捕获中断静默消失,现象和 NVIC 没开完全一样,排查时容易走弯路。
六、测试验证
测试方法:TIM3 输出一组已知频率/占空比的 PWM,经杜邦线送入 PA0,TIM2 测量后经串口打印。由于收发共用晶振,设定值即理论真值。
6.1 功能验证
上电默认配置下(TIM3 输出 1 kHz、占空比 50%),串口稳定打印:
freq = 1000.00 Hz, duty = 50.0 %, period_cnt = 1000 freq = 1000.00 Hz, duty = 50.0 %, period_cnt = 1000 freq = 1000.00 Hz, duty = 50.0 %, period_cnt = 1000随后拔掉输入线,约 1 秒后切换为no signal,超时检测生效;重新接回,读数在 1 个打印周期内恢复。
6.2 全量程频率测试
逐点改变 TIM3 的 ARR,覆盖四个数量级:
| 设定频率 | 测量值 | 绝对误差 | 相对误差 | 说明 |
|---|---|---|---|---|
| 10 Hz | 10.00 Hz | 0 Hz | <0.01% | 周期 100 000 计数,分辨率富余 |
| 100 Hz | 100.00 Hz | 0 Hz | <0.01% | — |
| 1 kHz | 1000.00 Hz | 0 Hz | <0.01% | 自检基准点 |
| 10 kHz | 10000.00 Hz | 0 Hz | <0.01% | — |
| 50 kHz | 49998 Hz | −2 Hz | −0.004% | 每周期仅 20 计数,误差抬头 |
| 100 kHz | 99115 Hz | −885 Hz | −0.89% | 量化误差主导,接近量程上限 |
10 Hz~50 kHz 区间误差全部压在 ±0.01% 以内;100 kHz 时每周期只有 10 个计数,±2 计数的量化误差被放大到约 1%,实测 −0.89% 与之吻合。这也验证了 2.3 节的误差模型:误差与频率成正比、与计数时钟成反比,想测更高频率就得提高计数时钟(换主频更高的芯片或改用等精度测频法)。
6.3 占空比测试
固定 1 kHz,改变 TIM3 的 CCR:
| 设定占空比 | 测量值 | 偏差 |
|---|---|---|
| 10% | 10.1% | +0.1% |
| 30% | 30.0% | 0% |
| 50% | 50.0% | 0% |
| 70% | 70.3% | +0.3% |
| 90% | 90.0% | 0% |
最大偏差 0.3%,对应 ±3 计数的边沿量化(高电平测量只涉及一个沿,但受两个捕获通道各自 ±1 影响)。
6.4 理论值与实测值对照
| 指标 | 理论推导值 | 实测值 | 偏差 | 原因分析 |
|---|---|---|---|---|
| 5 kHz 最大频率误差 | ±0.04%(±2/2000 计数) | ≤0.01% | 优于理论 | 自检共晶振,只剩量化误差下界 |
| 100 kHz 最大频率误差 | ±2%(±2/10 计数) | −0.89% | 优于理论 | 单向偏差,量化误差未取到最坏值 |
| 占空比最大偏差 | ±0.3%(±3/1000 计数) | +0.3% | 吻合 | 高电平区间量化为主 |
| 1 MHz 计数分辨率 | 1 μs | 1 μs(1 kHz 时 period_cnt 恒为 1000) | 吻合 | 与预分频 71 的配置一致 |
理论误差是"最坏情况"上界,实际单方向偏差通常小于它;但设计量程时必须按最坏情况留余量,这就是本文把可靠上限定在 100 kHz、而不是分辨率允许的更高值的原因。
相关阅读:《【STM32】HAL库输入捕获测频率》 — HAL 库测频的另一种实现对照
七、一次完整的干扰排查记录
这一节记录一个真实踩过的坑,因为它几乎集齐了"读数不对"这类问题的所有典型要素。
现象:首次联调时,示波器看 TIM3 输出是干净的 5 kHz 方波,但串口打印的频率在 4.8~5.3 kHz 之间无规律跳动,占空比偶尔蹦出 120% 这种不可能的值。
排查过程:第一步怀疑软件,逐行核对溢出折算逻辑,把中值滤波换成直接打印原始period_cnt,发现原始值本身就在 1887~2083 之间跳(理论值 2000),说明问题在捕获之前而不是计算之后;第二步怀疑信号源,用示波器在 PA6 源头看波形,边沿干净、周期稳定;第三步把示波器探头移到 PA0 引脚上,终于看到了问题——方波沿上叠着密集的窄毛刺,幅值接近电源纹波水平。
根因:连接 PA6 和 PA0 的是一根 30 cm 悬空杜邦线,PA0 又配置成浮空输入。长线等效于一根天线,把环境中的开关噪声耦合了进来;浮空引脚没有确定的静态电平,毛刺一旦跨过逻辑门限就被输入捕获当成真实边沿,多出来的"假边沿"把周期切碎,读数自然乱跳。
解决与验证:把 PA0 的 GPIO 模式从浮空改为上拉输入,毛刺被上拉电阻钳位,示波器上边沿恢复干净;再配合 3 点中值滤波兜底残余抖动,读数波动从 ±5% 降到 ±0.02%。
这件事给我的教训很具体:输入捕获的精度瓶颈往往不在代码,而在引脚的电气环境。测量外部真实信号时,先确认引脚有确定的上拉/下拉、走线尽量短、两端共地,再回头看软件。
相关阅读:《让你的STM32变身简易示波器,学会输入捕获就可以!》 — 用输入捕获做简易示波器的思路
八、故障排查手册
按"从常见到少见"排序,每条都给出验证方法。
| # | 现象 | 排查步骤 | 解决方案 | 验证 |
|---|---|---|---|---|
| 1 | 频率读数跳变、偶发异常值 | 打印原始period_cnt判断是否捕获前就乱 → 示波器看输入引脚毛刺 | 引脚改上拉/下拉,缩短走线,加中值滤波 | 波动收敛到 ±0.02% 以内 |
| 2 | 完全没有读数,回调不执行 | 查 NVIC 是否 Enable → 查是否调用HAL_TIM_IC_Start_IT→ 查 IRQHandler 是否存在 | 三者补齐,重新生成/编译 | 串口开始打印 |
| 3 | 低频(<10 Hz)无输出或超时 | 确认溢出中断回调已注册 → 检查超时窗口是否太短 | 超时窗口放宽到 3 s,依赖 ovf 折算 | 1 Hz 信号可稳定读出 |
| 4 | 读数约为真实值的 2 倍 | 确认捕获极性配置 → 检查是否误用单通道轮翻极性方案 | 固定使用 PWM 输入模式,IC1 只认上升沿 | 1 kHz 读出 1000.00 Hz |
| 5 | 频率测得偏大、占空比跳变整数倍 | 核对高电平计算是否用了ovf_at_fall − ovf_at_rise差值 | 按 5.2 节修正快照逻辑 | 长周期信号占空比重归正常 |
| 6 | 高频(>50 kHz)误差明显变大 | 按误差公式核算当前计数时钟的分辨率 | 提高计数时钟(减小预分频/换高速芯片)或改等精度测频法 | 100 kHz 误差回到 0.1% 级 |
其中第 2 条是新手最高频的问题:NVIC、启动函数、中断入口函数三样缺一不可,而且 CubeMX 重新生成代码时可能把用户代码区之外的改动冲掉,改完记得放在USER CODE注释对内部。
九、总结
回顾全文,真正值得记住的是这几点:
- 输入捕获把"记边沿时刻"交给硬件,测频精度取决于计数时钟分辨率,而不是 CPU 有多快;
- 量程设计要按量化误差最坏情况留余量:本方案 1 MHz 计数时钟可靠覆盖 10 Hz~100 kHz;
- 低频靠溢出计数扩展、信号丢失靠超时检测兜底,这两者缺一个都会在生产环境翻车;
- 精度问题的第一排查对象是引脚电气环境(浮空、长线、不共地),其次才是代码;
- 用板载定时器自产信号做精度自检,共晶振对比能把误差来源收敛到测量链路本身。
适用边界:本方案适合 10 Hz~100 kHz、幅度 3.3 V 兼容的单路信号测量。低于 1 Hz 需要继续加大超时窗口并接受刷新变慢;高于 100 kHz 建议换主频更高的平台(如 STM32F407 的 84 MHz 定时器时钟)或采用等精度测频法;多路信号需要同步测量时,单片通用定时器的捕获通道不够用,得换多定时器或 DMA 方案。
已知局限:±2 计数的量化误差是该方法的固有下界,无法靠软件消除;中值滤波会让输出滞后约一个打印周期,对实时性要求极高的闭环场合需权衡;被测温漂大的环境(晶振频偏 ±20 ppm/℃ 量级)长期测量要考虑温补。
扩展方向:下一步可以尝试 DMA 突发捕获实现零中断开销的多周期测量、等精度测频法把高频误差压到 0.01% 以内、以及输入捕获结合比较器测量不规则脉冲宽度。
相关阅读:《STM32——TIM输入捕获测频和占空比》 — 外部时钟模式与捕获模式的差异对比
本文完整工程代码(含 CubeMX 工程与全部源码)可在 CSDN 下载频道 获取(VIP 免费)。
📝版本备注
- 硬件平台:STM32F103C8T6 最小系统板(蓝丸)
- 软件版本:STM32CubeIDE 1.16.0 + STM32CubeF1 HAL 库 V1.8.5
- 兼容说明:代码与配置思路适用于 STM32F1/F4 全系通用定时器;F4 系列定时器时钟为 84 MHz(预分频改 83 保持 1 MHz 计数);L0 等 APB 时钟较低的系列需按实际时钟树重新核算预分频
参考资料
- 《STM32F103xx 参考手册》第 14 章 通用定时器(输入捕获 / PWM 输入模式)
- HAL 库源码
stm32f1xx_hal_tim.c中HAL_TIM_IC_Start_IT与 PWM 输入模式配置段 - 前文所列 5 篇 CSDN 站内相关文章(各章节内嵌引用)