在嵌入式开发中,精确测量信号周期是常见需求。无论是工业控制中的传感器信号采集,还是消费电子中的用户交互检测,都需要可靠的时间测量手段。STM32系列单片机凭借其丰富的外设资源,成为这类应用的理想选择。其中,高级定时器TIM1的输入捕获功能尤为强大,能够应对各种复杂场景。
本文将深入探讨如何利用STM32CubeMX配置TIM1定时器,实现按键信号的周期测量。不同于理想方波,真实按键信号往往存在抖动、不规则边沿等问题,这对测量算法提出了更高要求。我们将重点解决两个核心问题:如何准确捕获非理想信号,以及如何处理周期超过定时器最大计数值的情况。
STM32F1系列通常配备1个高级定时器(TIM1)和多个通用定时器。虽然基本功能相似,但高级定时器在以下方面具有明显优势:
对于周期测量应用,TIM1的多中断独立管理特性尤为实用。我们可以将更新中断(溢出中断)和捕获中断分开处理,使代码结构更清晰,响应更及时。
TIM1与通用定时器中断对比:
| 特性 | TIM1(高级定时器) | TIM2~4(通用定时器) |
|---|---|---|
| 中断类型 | 4种独立中断 | 1个全局中断 |
| 中断优先级 | 可分别配置 | 统一配置 |
| 捕获通道关联 | 每个通道独立中断 | 共享中断 |
| 适用场景 | 复杂时序测量 | 简单定时/捕获 |
正确配置CubeMX是成功实现输入捕获的基础。以下是关键配置步骤及原理说明:
TIM1挂载在APB2总线上,默认时钟频率为64MHz(基于STM32F103C8T6)。我们需要合理设置预分频器,使定时器计数器达到合适的计数速度:
c复制// 预分频器设置为63,实际分频系数为64
// 定时器时钟 = 64MHz / 64 = 1MHz → 每计数1次=1μs
htim1.Init.Prescaler = 63;
提示:预分频器值=实际分频系数-1。例如需要64分频,则填入63。
c复制htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
htim1.Init.Period = 0xFFFF; // 最大重装载值(65535)
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 不分频
htim1.Init.RepetitionCounter = 0; // 不重复计数
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 使能自动重装载
以通道1为例,配置为下降沿捕获:
c复制sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频
sConfigIC.ICFilter = 0; // 不滤波
对于按键信号,适当增加滤波可以消除抖动影响。滤波参数范围0~15,数值越大滤波效果越强,但会引入额外延迟。
TIM1的中断配置比通用定时器更复杂,需要分别使能:
在NVIC中,建议将捕获中断优先级设置为高于更新中断,确保边沿事件得到及时响应。
当信号周期超过定时器最大计数值(65535)时,简单的差值计算将失效。我们需要通过软件记录溢出次数,结合当前计数值计算真实周期。
周期计算公式:
code复制实际周期 = 溢出次数 × 65536 + 当前计数值
这类似于时钟的12小时制:
c复制volatile uint8_t g_capture_flag = 0; // 捕获完成标志
volatile uint8_t g_first_edge = 0; // 首次边沿标志
volatile uint16_t g_capture_val = 0; // 捕获值
volatile uint16_t g_overflow_count = 0; // 溢出计数
注意:这些变量在中断服务程序中被修改,必须使用volatile关键字防止编译器优化。
c复制void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM1) {
if(!g_capture_flag) {
if(g_first_edge) {
// 第二次捕获,记录数值并完成测量
g_capture_val = __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1);
g_capture_flag = 1;
g_first_edge = 0;
} else {
// 第一次捕获,重置计数器
__HAL_TIM_SET_COUNTER(htim, 0);
g_overflow_count = 0;
g_first_edge = 1;
}
}
}
}
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM1) {
if(g_first_edge && !g_capture_flag) {
if(g_overflow_count < 0xFFFF) {
g_overflow_count++;
} else {
// 溢出计数达到最大值,强制结束测量
g_capture_flag = 1;
g_capture_val = 0xFFFF;
}
}
}
}
机械按键通常会产生5-20ms的抖动。我们可以在硬件和软件两个层面进行处理:
硬件消抖:
软件消抖:
c复制// 在捕获回调中添加时间验证
if(__HAL_TIM_GET_COUNTER(htim) - last_capture_time > DEBOUNCE_THRESHOLD) {
// 处理有效边沿
last_capture_time = __HAL_TIM_GET_COUNTER(htim);
}
完善的测量程序应处理以下异常:
c复制if(HAL_GetTick() - last_event_time > TIMEOUT_MS) {
// 重置测量状态
g_capture_flag = 0;
g_first_edge = 0;
}
c复制int main(void) {
HAL_Init();
SystemClock_Config();
MX_TIM1_Init();
// 启动捕获和溢出中断
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim1);
while(1) {
if(g_capture_flag) {
uint32_t period = g_overflow_count * 65536UL + g_capture_val;
printf("Measured period: %lu us\n", period);
// 重置状态,准备下一次测量
g_capture_flag = 0;
HAL_Delay(100);
}
}
}
c复制printf("Capture: ovf=%u, val=%u\n", g_overflow_count, g_capture_val);
register类型在实际项目中,我发现TIM1的输入捕获功能非常可靠,但需要注意中断优先级配置。曾经遇到因中断冲突导致的测量不准问题,将捕获中断设为最高优先级后解决。另一个实用技巧是在测量开始时强制清除计数器,这能消除累计误差。