尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32输入捕获测频实战:定时器原理与CubeMX配置

STM32输入捕获测频实战:定时器原理与CubeMX配置 之前在调试一块基于 STM32 的采集板时遇到一个很实际的需求传感器输出的是一路脉冲信号频率从几十 Hz 到几十 kHz 变化单片机需要实时测量出频率并上报。最开始想用外部计数器方案后来发现直接利用定时器的输入捕获功能就能解决而且精度完全够用。本文基于 STM32C5A3R从输入捕获的原理讲起完整演示如何通过 STM32CubeMX 配置定时器输入捕获并使用 HAL 库完成频率测量。如果你是刚接触定时器捕获功能的新手或者想把“测频”这个功能快速落地到项目中这篇文章应该能帮你少走不少弯路。1. 背景与核心概念1.1 为什么需要测量频率频率测量是嵌入式开发里非常常见的需求。比如无刷电机调速时需要读取霍尔传感器的频率来计算转速流量计输出脉冲信号时需要通过脉冲频率换算流量红外避障模块、超声波模块、PPM 遥控接收机等很多传感器也都是以脉冲频率作为输出形式。对于这类场景单片机的定时器输入捕获是最直接、成本最低的解决方案。不需要额外增加专用测频芯片只需要一个定时器通道和一个 GPIO 引脚就能实现从几 Hz 到几百 kHz 甚至更高范围的频率测量。1.2 输入捕获的工作原理输入捕获Input Capture是定时器的一种工作模式。它的核心能力是当外部信号出现指定的跳变沿上升沿或下降沿时定时器硬件会立刻把当前计数器的值锁存到捕获/比较寄存器 CCRx 中同时可以触发捕获中断。整个过程由硬件自动完成CPU 不需要参与捕获动作。这一点非常关键输入捕获是硬件保存计数值不是 CPU 在中断里去读。所以即使 CPU 正在处理其他中断或者主循环很忙只要捕获中断的响应没有慢到丢失下一次捕获测量值本身依然是准确的。这保证了频率测量的时间精度很高。1.3 频率测量常用方法对比在嵌入式系统中测量频率主要有三种思路测量方法基本原理适合频率范围精度特点典型实现测频法M 法在固定闸门时间内记录脉冲个数f N / T高频信号频率越高精度越好低频时误差大外部计数模式或定时器门控测周法T 法测量一个信号周期对应的定时器计数次数f F_cnt / N中低频信号频率越低精度越好高频时计数太少误差大输入捕获上升沿等精度测量同时测量频率和周期并用同步闸门处理宽频率范围综合最优但实现复杂两个计数器配合本文重点讲解测周法也就是通过定时器输入捕获来实现。这种方式实现简单代码量小适合绝大多数传感器信号测量场景。2. 环境准备与开发工具2.1 硬件环境STM32C5A3R 开发板一块。信号源可以使用另一块单片机开发板输出 PWM也可以使用信号发生器。USB 转串口模块用于把测量结果打印到电脑上观察。杜邦线若干。STM32C5 系列是 ST 新一代基于 Cortex-M33 内核的 MCU 系列在定时器资源和 HAL 库接口上保持了良好的通用性。具体的引脚分配和定时器资源建议以你手中的芯片数据手册和 CubeMX 生成的代码为准。2.2 软件环境STM32CubeMX用于图形化配置芯片引脚、时钟、定时器和中断。STM32CubeIDE 或其他支持 HAL 库的 IDE。串口调试助手。版本说明STM32CubeMX 的版本以及 HAL 库版本不同界面细节可能略有差异但配置思路和 HAL 库 API 基本一致。本文以常见配置环境为例重点演示配置思路。2.3 测量方案概览本文的测量方案是使用 TIM1 的通道 1 作为输入捕获通道。外部脉冲信号接到对应的捕获引脚。定时器以固定计数频率运行每次捕获到上升沿时硬件自动保存当前计数值。在捕获中断中读取捕获值计算相邻两次上升沿之间的计数差值。用定时器计数频率除以计数差值得到信号频率。3. 输入捕获测量频率的核心原理3.1 测周法频率计算的完整过程先来看一个最简单的例子。假设定时器的计数时钟是 1MHz也就是每个计数周期为 1us。如果外部信号是 1kHz 的方波周期就是 1ms那么定时器在相邻两个上升沿之间会计数 1000 次。第一次捕获上升沿时的计数值是 X第二次捕获上升沿时的计数值是 Y则周期计数值 D Y - X 信号周期 T D / F_cnt 信号频率 f F_cnt / D带入数字就是f 1,000,000 / 1000 1000 Hz这就是输入捕获测频的核心算式。3.2 计数器溢出问题上面的例子信号频率比较高两次捕获之间计数器没有发生溢出计算很简单。但如果信号频率很低比如要测量 1Hz 的信号一个周期是 1 秒在 1MHz 计数时钟下需要计数 1,000,000 次。而 STM32 的通用定时器大部分是 16 位计数器最大计数值是 65535。也就是说计数器从 0 计到 65535 之后会溢出回 0重新开始计数。一个 1Hz 的信号周期内计数器会溢出 15 次左右。如果只读捕获值不处理溢出计算结果就会完全错误。所以测低频信号时必须配合定时器的更新中断Update Interrupt每溢出一次就让一个溢出计数变量加一。这样两次捕获之间的总计数增量就是总计数增量 (当前捕获值 - 上次捕获值) 溢出次数 × (ARR 1)然后在捕获中断中读取并清零溢出计数避免溢出计数累积到下一次周期。3.3 测量范围与精度分析测量范围和精度主要由定时器计数时钟频率、定时器位宽和信号频率共同决定。以计数时钟 1MHz、16 位定时器 ARR65535 为例信号频率每周期计数次数是否溢出理论精度100 Hz10000否±0.01% 量级1 kHz1000否±0.1% 量级100 kHz10否误差较大1 Hz1000000是约 15 次仍然可测高频信号的问题是每个周期内的计数次数太少输入捕获的 ±1 个计数误差占比重变大。比如 100kHz 信号每个周期只有 10 个计数如果误差 1 个计数频率误差就是 10%。低频信号的问题可以通过溢出计数解决计数总量很大所以相对精度很高。如果需要测量很高的频率建议改用测频法M 法即用外部信号本身作为计数时钟在固定时间内统计脉冲个数。4. STM32CubeMX 配置定时器输入捕获4.1 新建工程与时钟配置打开 STM32CubeMX选择 STM32C5A3R 芯片新建工程。进入 Clock Configuration 页面根据开发板实际情况配置时钟源和系统主频。这里需要注意一个重点定时器挂在 APB 总线上定时器时钟频率不一定等于系统主频。在 CubeMX 的时钟树页面中可以直接看到 APB1 Timer Clocks 和 APB2 Timer Clocks 的频率数值。以本文示例为例假设 APB2 定时器时钟为 100MHz后续配置参数都基于这个数值。如果你的实际配置不同需要同步调整预分频参数。4.2 TIM1 输入捕获参数配置在 Pinout Configuration 页面找到 Timers选择 TIM1Clock Source 选择 Internal Clock。Channel1 选择 Input Capture direct mode。配置参数如下Prescaler预分频设置为 99。Counter Mode选择 Up 向上计数模式。Counter Period自动重装载值 ARR设置为 65535。Auto-reload preloadEnable。Input Capture Channel 参数PolarityRising Edge上升沿捕获IC SelectionDirect TIPrescaler1不分频Filter0不滤波这里解释一下Prescaler 设置为 99实际分频系数是 PSC1等于 100。如果定时器输入时钟是 100MHz那么计数时钟就是 100MHz / 100 1MHz每个计数周期为 1us。这个数值方便计算频率但这只是示例实际工程中要根据被测信号的频率范围调整。Counter Period 设置为 65535 是因为 TIM1 是 16 位定时器最大自动重装载值就是 65535。4.3 NVIC 中断配置在 NVIC Settings 页面中需要使能两个中断TIM1 capture compare interrupt用于捕获中断读取捕获值。TIM1 update interrupt用于计数器溢出中断统计溢出次数。分别勾选 Enabled并可以设置优先级。通常捕获中断优先级应高于更新中断这样在捕获发生时能优先处理捕获值避免丢失边沿信息。4.4 生成工程代码配置完成后点击 Generate Code选择目标 IDE 工具链生成初始化工程。CubeMX 会自动生成 GPIO 复用配置、时钟初始化、定时器初始化和中断服务函数的框架代码。到这里CubeMX 的配置工作就完成了。接下来要做的就是在生成的 HAL 库工程中编写业务代码实现频率计算逻辑。5. HAL 库输入捕获代码实现5.1 频率测量数据结构设计为了方便管理测量状态建议定义一个结构体来保存频率测量的中间变量/* 文件路径Core/Inc/freq_measure.h */ #ifndef FREQ_MEASURE_H #define FREQ_MEASURE_H #include main.h typedef struct { volatile uint32_t overflow_cnt; /* 定时器溢出次数 */ volatile uint32_t last_ccr; /* 上一次捕获值 */ volatile uint32_t period_counts; /* 一个信号周期对应的计数次数 */ volatile uint32_t freq_hz; /* 测量得到的频率(Hz) */ volatile uint8_t first_capture; /* 是否第一次捕获标志 */ } freq_measure_t; extern freq_measure_t g_freq; #endif在 main.c 中定义这个全局变量/* 文件路径Core/Src/main.c */ freq_measure_t g_freq;5.2 启动输入捕获与更新中断在 main 函数中完成定时器初始化之后启动输入捕获int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); /* 启动 TIM1 输入捕获并使能捕获中断 */ HAL_TIM_IC_Start_IT(htim1, TIM_CHANNEL_1); /* 额外使能更新中断用于统计计数器溢出次数 */ __HAL_TIM_ENABLE_IT(htim1, TIM_IT_UPDATE); while (1) { /* 主循环中读取频率值即可 */ if (g_freq.freq_hz 0) { printf(Freq %lu Hz\r\n, (unsigned long)g_freq.freq_hz); } HAL_Delay(500); } }注意HAL_TIM_IC_Start_IT只会使能捕获比较中断更新中断需要单独使能。5.3 捕获中断回调实现在 HAL 库中定时器捕获中断最终会回调到HAL_TIM_IC_CaptureCallback函数。我们需要在这个函数中实现频率计算的逻辑/* 文件路径Core/Src/stm32c5xx_it.c 或用户回调文件 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { uint32_t current_ccr HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t overflow_cnt 0; /* 在读取溢出计数前关闭更新中断防止读取过程中溢出计数被修改 */ __HAL_TIM_DISABLE_IT(htim, TIM_IT_UPDATE); overflow_cnt g_freq.overflow_cnt; g_freq.overflow_cnt 0; __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE); if (g_freq.first_capture 0) { /* 第一次捕获只记录基准值 */ g_freq.first_capture 1; } else { /* 两次捕获之间的总计数增量 */ g_freq.period_counts (current_ccr - g_freq.last_ccr) overflow_cnt * (htim-Init.Period 1); /* 频率 定时器计数时钟 / 周期计数值 */ /* 注意这里的 1000000 要和 CubeMX 中配置的计数时钟保持一致 */ if (g_freq.period_counts 0) { g_freq.freq_hz 1000000UL / g_freq.period_counts; } } /* 记录本次捕获值作为下一次计算的基准 */ g_freq.last_ccr current_ccr; } }这里有一个关键点需要解释current_ccr - g_freq.last_ccr使用的是无符号整数减法。即使两次捕获之间计数器发生过回绕也就是当前捕获值小于上次捕获值无符号减法的结果依然能正确表示两者之间的差值。因为计数器的回绕本质上是模运算而无符号减法和模运算的规则是一致的。5.4 更新中断回调实现定时器的更新中断最终会回调到HAL_TIM_PeriodElapsedCallback。在这个函数中只需要做一件事让溢出计数器加一。void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { g_freq.overflow_cnt; } }这段代码体现了测周法的核心思想利用硬件中断把 16 位定时器的计数能力扩展到任意长度。即使信号频率低到 0.1Hz只要溢出计数变量足够大依然可以准确测量。5.5 完整代码整理为了方便理解整个逻辑把核心代码整理到一起。注意这只是一个汇总展示实际项目中这些代码分布在不同的源文件中/* CubeMX 生成的定时器初始化 */ void MX_TIM1_Init(void) { TIM_IC_InitTypeDef sConfigIC {0}; htim1.Instance TIM1; htim1.Init.Prescaler 99; /* 100MHz / 100 1MHz 计数时钟 */ htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.Period 65535; htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter 0; htim1.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_IC_Init(htim1); sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV1; sConfigIC.ICFilter 0; HAL_TIM_IC_ConfigChannel(htim1, sConfigIC, TIM_CHANNEL_1); } /* 频率计算回调 */ void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { uint32_t current_ccr HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); uint32_t overflow_cnt 0; __HAL_TIM_DISABLE_IT(htim, TIM_IT_UPDATE); overflow_cnt g_freq.overflow_cnt; g_freq.overflow_cnt 0; __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE); if (g_freq.first_capture) { g_freq.first_capture 0; } else { g_freq.period_counts (current_ccr - g_freq.last_ccr) overflow_cnt * (htim-Init.Period 1); if (g_freq.period_counts 0) { g_freq.freq_hz 1000000UL / g_freq.period_counts; } } g_freq.last_ccr current_ccr; } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { g_freq.overflow_cnt; } }6. 运行验证与误差分析6.1 输入信号连接将信号源的输出引脚连接到 TIM1 的捕获输入引脚。具体引脚号需要查看你使用的 STM32C5A3R 芯片的引脚功能表或者在 CubeMX 中直接观察 TIM1_CH1 被分配到哪个引脚。需要注意的是被测信号的电压范围必须在芯片 GPIO 的允许输入电压范围内。如果信号来自 5V 传感器需要做电平转换或串联限流电阻不能直接接到 MCU 引脚。6.2 验证结果与计算过程假设信号源输出 1kHz 方波计数时钟为 1MHz即每个计数周期为 1us。理想情况下相邻两次上升沿之间的计数差值应该是 1000那么f 1,000,000 / 1000 1000 Hz但实际输出可能会看到 999Hz 或 1001Hz这是因为信号源本身存在频率误差再加上 ±1 个计数周期的量化误差在 1000Hz 信号上表现为千分之一的偏差这是完全正常的。再看一个低频例子。假设信号源输出 10Hz 方波一个周期为 100ms在 1MHz 计数时钟下对应 100,000 个计数。TIM1 是 16 位定时器ARR65535所以一个周期内计数器会溢出一次。捕获中断中的计算过程为溢出次数 1 计数差值 (当前捕获值 - 上次捕获值) 1 × 65536最终计算出的周期计数值约为 100,000频率约为 10Hz。这说明溢出处理逻辑在低频场景下是必要的。6.3 误差来源分析输入捕获测频的误差主要来自三个方面。第一个是量化误差。定时器计数器每个计数周期加一捕获时刻不可能刚好落在整数计数边界上所以存在 ±1 个计数周期的固有误差。信号频率越高每个周期的计数次数越少这个误差占比越大。第二个是时钟源误差。定时器计数时钟来自 HSE 或 HSI如果晶振本身存在频率偏差例如 20ppm 的晶振那么测量结果也会有同等量级的相对误差。第三个是信号边沿抖动。如果输入信号本身存在抖动或噪声捕获到的上升沿位置会有微小波动导致测量结果不稳定。针对这些误差实际工程中通常采用多次测量取平均、软件滤波、使用更高精度的计数时钟等手段来优化。7. 常见问题与排查思路7.1 捕获不到信号如果程序运行后频率值始终为 0说明捕获中断没有触发。优先检查以下几项问题现象常见原因解决思路始终读不到频率捕获引脚接错对照 CubeMX 的引脚分配确认信号接到正确引脚始终读不到频率信号电平不在 GPIO 输入范围用示波器或万用表测量引脚电压始终读不到频率中断未使能检查 NVIC 配置和HAL_TIM_IC_Start_IT是否调用始终读不到频率通道配置错误确认使用的是 TIM1_CH1而不是其他通道7.2 频率值跳变不稳定频率值忽高忽低通常是输入信号边沿抖动造成的。可以在 CubeMX 的 Input Capture Filter 参数中配置数字滤波这个参数本质上是一个数字信号滤波器的采样次数能够滤除高频毛刺。另外如果信号上升沿比较缓慢也可能导致捕获时刻不稳定。建议在输入引脚上并联一个 100nF 的小电容或者使用施密特触发器对信号进行整形。7.3 溢出计数不更新低频信号测量结果仍然偏大或偏小很可能是因为更新中断没有正确工作。检查一下是否在启动代码中调用了__HAL_TIM_ENABLE_IT(htim1, TIM_IT_UPDATE)。如果只启动了捕获中断而没有使能更新中断HAL_TIM_PeriodElapsedCallback就不会被调用溢出计数始终为 0。另外要注意HAL_TIM_PeriodElapsedCallback是一个通用回调函数项目中可能被多个定时器共用必须在函数内部通过htim-Instance判断是哪个定时器产生的更新事件。7.4 高频信号丢失如果被测信号频率很高比如几百 kHz捕获中断触发频率就会很高。如果中断处理函数执行时间过长或者还有其他高优先级中断频繁抢占就可能导致捕获覆盖。解决思路是缩短捕获中断回调中的处理逻辑不要在回调中做串口打印、复杂计算等耗时操作。提高捕获中断的优先级。如果需要测量非常高频率的信号改为使用 M 法外部计数方式更合适。8. 最佳实践与工程建议8.1 输入信号处理输入到 MCU 捕获引脚的信号必须保证电平兼容和边沿干净。对于传感器输出的脉冲信号建议在硬件上做一级 RC 滤波时间常数根据信号频率选择避免高频噪声干扰触发边沿检测。如果信号是集电极开路输出外部必须接上拉电阻。如果信号是差分输出则需要使用转换芯片转换为单端信号。不要直接在引脚上输入超出芯片供电电压的信号。8.2 定时器参数选择策略预分频值 PSC 和自动重装载值 ARR 的选择原则是让被测信号在一个周期内的计数值尽量大同时避免计数器溢出过于频繁。举个例子如果被测信号范围是 1Hz 到 10kHz计数时钟可以选择 1MHz这样 10kHz 信号每周期有 100 个计数1Hz 信号有 1,000,000 个计数。16 位定时器 ARR 设置为 65535低频时配合溢出计数处理。如果被测信号范围是 100Hz 到 100kHz可以适当提高计数时钟到 10MHz让高频段的计数分辨率更高。但要注意计数时钟越高低频时溢出越频繁溢出计数丢失的风险越大。如果被测信号频率上限很高超过定时器中断处理能力就应该改用测频法而不是继续调大计数时钟。8.3 消除测量抖动在实际项目中直接输出每次捕获计算得到的频率值显示往往会在一定范围内跳动。可以通过两个方式改善。一个是在主循环中做滑动平均取最近 N 次测量结果的均值作为最终输出能明显平滑波动。另一个是使用多周期测量法也就是不是每次捕获上升沿都计算一次频率而是每累计 N 个上升沿之后才计算一次总时间的平均值。这样等效于延长了测量闸门能抑制单周期的量化误差。8.4 扩展PWM 输入模式与 DMA 方式STM32 的定时器除了单通道输入捕获之外还支持 PWM 输入模式。开启 PWM 输入模式后定时器可以同时测量输入信号的周期和占空比并且只需要占用两个捕获通道。这对于需要同时检测频率和占空比的场景非常实用比如遥控器信号解码、电机 PWM 反馈检测等。另外如果系统中已经有 DMA 控制器空闲可以考虑使用 DMA 方式搬运捕获值到内存数组中再周期性计算频率。这种方式可以大大降低中断频率适合高频信号测量。9. 总结与下一步学习方向本文从输入捕获的基本原理出发详细演示了在 STM32C5A3R 上通过定时器输入捕获测量信号频率的完整流程。包括 CubeMX 图形化配置、HAL 库代码实现、溢出处理逻辑、频率计算公式以及常见问题排查思路。掌握输入捕获之后你可以继续学习这几个方向。第一个是 PWM 输入模式。STM32 定时器可以同时测量周期和占空比适合遥控器信号解码和电机控制反馈。第二个是 DMA 配合定时器捕获。用 DMA 把多次捕获值自动搬运到内存可以降低中断频率提高高频测频能力。第三个是等精度测频。当被测信号频率跨度很大时可以用两个定时器配合实现从 Hz 到 MHz 的宽范围等精度测量。如果你在实际配置中遇到问题建议先回到 CubeMX 确认定时器时钟频率、通道映射和中断配置再检查回调函数中的计算逻辑。把参数打印出来对照本文的算式逐步核对问题基本都能定位。如果这篇文章对你有帮助可以收藏备用。后续我也会继续更新 STM32C5A3R 开发系列包括 PWM 输出、编码器接口、DMA 传输等内容。
返回列表