STM32C562定时器输入捕获实现高精度频率测量
🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度
这次我们来看STM32C562的定时器输入捕获功能,重点是如何配置硬件来测量外部信号的频率。对于嵌入式开发来说,频率测量是个很常见的需求,无论是检测传感器输出、通信信号还是电机转速,都需要可靠的频率测量方案。
STM32C562的定时器模块提供了完整的输入捕获能力,可以直接测量脉冲宽度和频率,不需要额外的外部电路。相比软件轮询方式,硬件输入捕获更精确、更节省CPU资源,特别适合实时性要求高的应用场景。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 测量对象 | 外部数字信号频率、脉冲宽度 |
| 测量范围 | 取决于定时器时钟频率和分频系数 |
| 测量精度 | 硬件级精度,可达纳秒级别 |
| 资源占用 | 单个定时器通道,极低CPU开销 |
| 适用场景 | 传感器频率检测、编码器信号、通信时钟测量 |
| 开发环境 | Keil MDK、STM32CubeIDE、HAL库或LL库 |
2. 输入捕获原理与工作流程
输入捕获的核心原理是利用定时器的计数机制来精确记录信号边沿的时间戳。当配置为输入捕获模式时,定时器会持续运行计数,一旦检测到指定的边沿(上升沿或下降沿),就会立即将当前的计数值锁存到捕获寄存器中。
具体工作流程如下:
- 定时器以固定频率递增计数(例如1MHz,每个计数代表1微秒)
- 外部信号连接到定时器的输入捕获通道
- 当检测到预设的边沿事件时,当前计数值被保存到捕获寄存器
- 同时产生捕获完成中断,程序可以读取捕获值
- 通过计算连续两次捕获的差值,得到脉冲宽度或周期
对于频率测量,通常采用周期法:测量两个上升沿(或下降沿)之间的时间间隔,频率 = 1 / 周期。这种方法对于占空比变化的信号同样有效,因为频率只与周期有关。
3. 硬件环境准备
STM32C562开发板需要以下基本配置:
- 主控芯片:STM32C562系列微控制器
- 调试器:ST-Link V2或J-Link
- 电源:3.3V供电,确保稳定
- 信号源:用于测试的频率信号发生器或波形发生器
- 连接线:杜邦线连接信号源到定时器输入引脚
关键引脚配置:
- 定时器输入捕获通道对应的GPIO引脚(如TIMx_CHy)
- 该引脚必须配置为复用功能模式,连接到定时器外设
- 建议使引脚内部上拉或下拉,避免悬空状态
以TIM2_CH1为例,对应PA0引脚,需要配置为:
- GPIO模式:Alternate Function Push-Pull
- GPIO速度:High Speed
- 上拉/下拉:根据信号特性选择
4. 软件环境搭建
开发环境选择:
- Keil MDK:传统ARM开发首选,调试功能完善
- STM32CubeIDE:ST官方集成环境,包含HAL库和配置工具
- IAR Embedded Workbench:商业级IDE,编译效率高
库支持:
- HAL库:硬件抽象层,代码可移植性好
- LL库:底层库,执行效率更高,代码量更小
- 标准外设库:传统开发方式,直接寄存器操作
推荐使用STM32CubeMX进行图形化配置,可以自动生成初始化代码,减少手动配置错误。
安装必要的软件包:
- STM32C5系列设备支持包
- STM32CubeMX最新版本
- 对应的IDE和编译器
5. 定时器配置详解
5.1 时钟树配置
定时器的测量精度直接依赖于时钟源精度。STM32C562的定时器时钟通常来自APB总线,需要正确配置分频系数。
// 时钟配置示例 SystemClock_Config(); // 系统时钟配置 __HAL_RCC_TIM2_CLK_ENABLE(); // 使能TIM2时钟关键参数:
- 系统时钟频率:决定APB总线频率
- APB分频系数:影响定时器实际时钟
- 定时器预分频器:进一步分频得到计数频率
5.2 定时器基本参数
TIM_HandleTypeDef htim2; // 定时器基础配置 htim2.Instance = TIM2; htim2.Init.Prescaler = 71; // 预分频值,72分频 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数 htim2.Init.Period = 0xFFFF; // 自动重装载值,16位最大值 htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频 htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;计算计数频率:
- 如果系统时钟72MHz,预分频71,则计数频率 = 72MHz / (71+1) = 1MHz
- 每个计数周期 = 1微秒
- 最大测量周期 = 65535微秒(约65.5ms)
5.3 输入捕获通道配置
TIM_IC_InitTypeDef sConfigIC; // 输入捕获配置 sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; // 上升沿捕获 sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接输入 sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频 sConfigIC.ICFilter = 0x0; // 滤波器设置,0为无滤波 HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);滤波器配置说明:
- 滤波器值越大,抗干扰能力越强,但会引入延迟
- 对于干净信号,可以设置为0
- 对于噪声环境,建议设置适当滤波值
6. 中断配置与DMA设置
6.1 中断配置
输入捕获通常需要中断来处理捕获事件:
// 使能捕获/比较中断 __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_CC1); // 配置NVIC HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn);中断服务函数模板:
void TIM2_IRQHandler(void) { HAL_TIM_IRQHandler(&htim2); } // 捕获比较回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { // 处理捕获事件 static uint32_t first_value = 0; static uint32_t second_value = 0; static uint8_t capture_count = 0; if(capture_count == 0) { first_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); capture_count = 1; } else { second_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 计算周期和频率 uint32_t period = (second_value > first_value) ? (second_value - first_value) : (0xFFFF - first_value + second_value); float frequency = 1000000.0 / period; // 单位Hz capture_count = 0; } __HAL_TIM_SET_COUNTER(htim, 0); // 清空计数器 } }6.2 DMA配置(可选)
对于高频信号测量,可以使用DMA减少CPU中断开销:
// DMA配置示例 __HAL_LINKDMA(&htim2, hdma[TIM_DMA_ID_CC1], hdma_tim2_ch1); // 启动DMA传输 HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t*)capture_buffer, BUFFER_SIZE);7. 完整初始化流程
下面是完整的定时器输入捕获初始化函数:
void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; TIM_IC_InitTypeDef sConfigIC = {0}; // 定时器基础配置 htim2.Instance = TIM2; htim2.Init.Prescaler = 71; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 65535; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_Base_Init(&htim2); // 时钟源配置 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig); // 输入捕获配置 HAL_TIM_IC_Init(&htim2); sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; sConfigIC.ICFilter = 0; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); // 主模式配置 sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig); }GPIO配置:
void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); // PA0配置为TIM2_CH1 GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF1_TIM2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); }8. 频率测量算法实现
8.1 基本周期测量
最简单的频率测量方法是测量连续两个上升沿之间的时间:
typedef struct { uint32_t last_capture; uint32_t current_period; float current_frequency; uint8_t measurement_valid; } freq_measure_t; freq_measure_t freq_data = {0}; void UpdateFrequencyMeasurement(uint32_t capture_value) { if(freq_data.last_capture == 0) { freq_data.last_capture = capture_value; return; } uint32_t period = 0; if(capture_value > freq_data.last_capture) { period = capture_value - freq_data.last_capture; } else { // 处理计数器溢出 period = (0xFFFF - freq_data.last_capture) + capture_value; } freq_data.current_period = period; freq_data.current_frequency = 1000000.0 / period; // 1MHz计数频率 freq_data.measurement_valid = 1; freq_data.last_capture = capture_value; }8.2 多周期平均算法
为了提高测量精度,可以采用多周期平均:
#define SAMPLE_COUNT 10 typedef struct { uint32_t samples[SAMPLE_COUNT]; uint8_t sample_index; uint32_t total_period; float average_frequency; } avg_freq_measure_t; void UpdateAverageFrequency(uint32_t period) { static avg_freq_measure_t avg_data = {0}; // 减去最旧的样本,加上最新的样本 avg_data.total_period -= avg_data.samples[avg_data.sample_index]; avg_data.samples[avg_data.sample_index] = period; avg_data.total_period += period; // 更新索引 avg_data.sample_index = (avg_data.sample_index + 1) % SAMPLE_COUNT; // 计算平均频率 avg_data.average_frequency = 1000000.0 / (avg_data.total_period / SAMPLE_COUNT); }8.3 频率范围自适应
对于宽频率范围的测量,需要动态调整预分频器:
void AdaptivePrescalerAdjust(float measured_freq) { if(measured_freq > 50000) // 频率过高,需要增大分频 { __HAL_TIM_SET_PRESCALER(&htim2, 719); // 720分频,计数频率100kHz } else if(measured_freq < 100) // 频率过低,需要减小分频 { __HAL_TIM_SET_PRESCALER(&htim2, 71); // 72分频,计数频率1MHz } // 其他情况保持当前分频 }9. 实际测试与验证
9.1 测试信号准备
使用信号发生器产生测试信号:
- 方波信号,幅度0-3.3V
- 频率范围:100Hz - 50kHz
- 占空比:50%
连接方式:
- 信号发生器输出 → TIM2_CH1 (PA0)
- 共地连接确保参考电平一致
9.2 测试程序框架
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 启动定时器和输入捕获 HAL_TIM_Base_Start(&htim2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); while(1) { if(freq_data.measurement_valid) { // 通过串口输出频率值 printf("Frequency: %.2f Hz\r\n", freq_data.current_frequency); freq_data.measurement_valid = 0; // 可选:自适应调整预分频 AdaptivePrescalerAdjust(freq_data.current_frequency); } HAL_Delay(100); // 100ms更新一次显示 } }9.3 精度验证方法
- 基准对比:与高精度频率计对比测量结果
- 稳定性测试:连续测量稳定信号,观察读数波动
- 范围测试:在不同频率点验证测量准确性
- 温度测试:在不同环境温度下验证稳定性
典型精度指标:
- 1kHz信号:误差应小于0.1%
- 10kHz信号:误差应小于0.5%
- 50kHz信号:误差应小于1%
10. 性能优化技巧
10.1 减少中断开销
// 使用DMA减少CPU中断 #define CAPTURE_BUFFER_SIZE 100 uint32_t capture_buffer[CAPTURE_BUFFER_SIZE]; void StartCaptureDMA(void) { HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, capture_buffer, CAPTURE_BUFFER_SIZE); } // DMA传输完成回调 void HAL_TIM_IC_CaptureCallback(DMA_HandleTypeDef *hdma) { // 批量处理捕获数据,减少中断次数 ProcessCaptureBuffer(capture_buffer, CAPTURE_BUFFER_SIZE); }10.2 软件滤波算法
// 移动平均滤波 float MovingAverageFilter(float new_value) { static float buffer[5] = {0}; static uint8_t index = 0; static float sum = 0; sum -= buffer[index]; buffer[index] = new_value; sum += new_value; index = (index + 1) % 5; return sum / 5; } // 中值滤波 float MedianFilter(float new_value) { static float buffer[3] = {0}; static uint8_t index = 0; buffer[index] = new_value; index = (index + 1) % 3; // 排序取中值 float temp[3] = {buffer[0], buffer[1], buffer[2]}; // 简单排序算法 if(temp[0] > temp[1]) { float t = temp[0]; temp[0] = temp[1]; temp[1] = t; } if(temp[1] > temp[2]) { float t = temp[1]; temp[1] = temp[2]; temp[2] = t; } if(temp[0] > temp[1]) { float t = temp[0]; temp[0] = temp[1]; temp[1] = t; } return temp[1]; }11. 常见问题排查
11.1 无捕获信号
现象:始终无法触发捕获中断排查步骤:
- 检查GPIO配置是否正确(复用功能模式)
- 验证信号电平是否符合要求(0-3.3V)
- 检查定时器时钟是否使能
- 确认边沿极性设置是否正确
- 测试信号是否真正到达MCU引脚
解决方案:
// 调试用:检查定时器状态 void CheckTimerStatus(void) { if(__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_CC1)) { printf("Capture compare flag is set\n"); } if(__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_CC1)) { printf("Capture compare interrupt is enabled\n"); } }11.2 测量结果不稳定
现象:频率读数跳动较大可能原因:
- 信号质量差,存在噪声
- 滤波器配置不当
- 软件算法需要优化
- 电源噪声影响
解决方案:
- 增加硬件滤波电路(RC低通滤波)
- 调整输入捕获滤波器参数
- 采用软件滤波算法
- 改善电源质量
11.3 高频测量误差大
现象:高频段测量误差明显增大原因分析:
- 定时器计数频率不足
- 中断响应延迟影响
- 信号边沿不够陡峭
优化措施:
// 提高计数频率 void OptimizeForHighFrequency(void) { // 减小预分频值,提高计数频率 __HAL_TIM_SET_PRESCALER(&htim2, 0); // 无分频,直接使用系统时钟 // 使用更高优先级的中断 HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); }12. 扩展应用场景
12.1 脉冲宽度测量
通过上升沿和下降沿双捕获测量脉冲宽度:
void MeasurePulseWidth(void) { // 配置双沿捕获 TIM_IC_InitTypeDef sConfigIC; // 上升沿捕获 sConfigIC.ICPolarity = TIM_ICPOLARITY_RISING; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1); // 下降沿捕获 sConfigIC.ICPolarity = TIM_ICPOLARITY_FALLING; HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2); }12.2 多通道频率测量
同时测量多个信号的频率:
void MultiChannelFrequencyMeasure(void) { // 配置多个输入捕获通道 HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_3); // 在中断回调中区分通道 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1)) { // 通道1处理 } if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2)) { // 通道2处理 } } }12.3 频率变化检测
检测频率的突然变化:
void DetectFrequencyChange(float current_freq) { static float last_freq = 0; static uint32_t stable_count = 0; float change_ratio = fabs(current_freq - last_freq) / last_freq; if(change_ratio > 0.1) // 变化超过10% { printf("Frequency changed significantly: %.2f -> %.2f\n", last_freq, current_freq); stable_count = 0; } else { stable_count++; } last_freq = current_freq; }STM32C562的定时器输入捕获功能为频率测量提供了硬件级的解决方案,结合合理的软件算法,可以实现高精度、实时的频率检测。关键是要理解硬件工作原理,正确配置相关寄存器,并针对具体应用场景优化测量算法。
🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度
