1. STM32定时器基础解析
STM32F103C8T6这款经典的Cortex-M3内核MCU配备了4个定时器(TIM1-TIM4),其中TIM1是高级定时器,其余为通用定时器。这些定时器在嵌入式开发中扮演着至关重要的角色,从简单的延时功能到复杂的PWM波形生成、输入捕获等场景都有广泛应用。
定时器的本质是一个自动递增/递减的计数器,配合预分频器和自动重装载寄存器,可以实现精确的时间控制。理解定时器的工作原理,是掌握STM32开发的关键一步。在实际项目中,我经常使用定时器来实现:
- 精确延时控制
- 周期性任务调度
- 电机PWM驱动
- 外部信号测量
- 超声波测距等场景
2. 时基单元深度剖析
2.1 时钟源选择与分频机制
STM32定时器的时钟源通常来自APB总线,以TIM1为例,它挂载在APB2总线上。通过RCC配置,我们可以将APB2定时器时钟设置为72MHz,这是STM32F103系列的最高时钟频率。
时钟分频通过预分频寄存器PSC实现:
c复制计数频率 = APB2时钟 / (PSC + 1)
PSC的取值范围是0-65535,这意味着我们可以将72MHz的时钟分频到1.1kHz到72MHz之间的任何频率。
经验分享:在配置PSC时,我通常会先确定需要的计时精度。例如要实现1us的计时分辨率,就需要设置计数频率为1MHz(72MHz/72)。此时PSC应设置为71(因为72MHz/(71+1)=1MHz)。
2.2 核心寄存器详解
时基单元包含几个关键寄存器:
- CNT(计数器):16位寄存器,范围0-65535,可配置为递增或递减计数
- ARR(自动重装载寄存器):决定定时周期,当CNT达到ARR值时会产生溢出事件
- RCR(重复计数器):仅高级定时器具备,可延长定时周期
定时器溢出周期计算公式:
code复制溢出周期 = (ARR + 1) × (PSC + 1) / 时钟频率
2.3 高级定时器特性
TIM1作为高级定时器,具备一些特殊功能:
- 重复计数器(RCR):可以延长定时周期
- 互补PWM输出:支持带死区控制的互补PWM
- 刹车功能:紧急情况下快速关闭PWM输出
这些特性使TIM1特别适合电机控制等应用场景。
3. 自制精准延时函数实现
3.1 设计思路与原理
在嵌入式系统中,我们经常需要精确的延时控制。利用定时器中断可以实现高精度的延时函数,其核心原理是:
- 配置定时器产生周期性中断(如1ms一次)
- 在中断服务程序中递增一个全局时间变量
- 延时函数通过比较当前时间与目标时间实现阻塞等待
这种方法的优势是:
- 不占用CPU资源(相比软件延时)
- 精度高(取决于定时器配置)
- 可扩展性强(可轻松实现长延时)
3.2 CubeMX配置实战
以1ms定时中断为例,配置步骤如下:
- 时钟树配置:确保APB2定时器时钟为72MHz
- 定时器参数设置:
- Prescaler (PSC): 71 → 72MHz/(71+1)=1MHz
- Counter Mode: Up
- Counter Period (ARR): 999 → (999+1)×1us=1ms
- auto-reload preload: Enable
- NVIC设置:使能TIM1 update中断
3.3 代码实现详解
首先定义全局时间变量:
c复制static volatile uint32_t currentMiliSeconds = 0;
volatile关键字告诉编译器这个变量可能被意外修改(如中断服务程序),防止优化出错。
延时函数实现:
c复制static void MyDelay(uint32_t Delay) {
uint32_t expireTime = MyGetTick() + Delay;
while(expireTime > MyGetTick()); // 阻塞等待
}
中断回调函数:
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim == &htim1) {
currentMiliSeconds++; // 每1ms增加1
}
}
注意事项:中断服务程序应尽量简短,避免执行耗时操作。我在实际项目中曾因在中断中处理复杂逻辑导致系统响应异常,这点需要特别注意。
4. PWM输出与呼吸灯实现
4.1 PWM原理深度解析
PWM(脉冲宽度调制)通过调节占空比来控制平均电压,其核心参数:
- 周期:由ARR决定,周期=(ARR+1)/计数频率
- 占空比:CCR/(ARR+1),CCR为捕获比较寄存器值
在STM32中,PWM模式有两种:
- PWM模式1:CNT<CCR时输出有效电平
- PWM模式2:CNT≥CCR时输出有效电平
4.2 CubeMX配置要点
实现呼吸灯需要配置:
- 定时器基础配置(同延时函数)
- PWM通道设置:
- Mode: PWM mode 1
- Pulse: 初始占空比0
- Output compare preload: Enable
- CH Polarity: High
- GPIO配置:确认PWM输出引脚
4.3 呼吸灯算法实现
呼吸灯效果通过正弦波调制占空比实现:
c复制float t = HAL_GetTick() * 0.001f; // 获取当前时间(秒)
float duty = 0.5f * sin(2 * 3.14f * t) + 0.5f; // 生成0-1之间的正弦波
uint16_t arr = __HAL_TIM_GET_AUTORELOAD(&htim1);
uint16_t ccr = duty * (arr + 1); // 计算CCR值
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ccr); // 更新PWM占空比
调试技巧:在实际项目中,我发现直接使用正弦函数计算可能会造成LED亮度变化不够平滑。可以通过查表法预先计算好亮度曲线,或者使用更精细的浮点运算来提高效果。
5. 输入捕获与超声波测距
5.1 输入捕获原理
输入捕获功能可以精确测量外部信号的脉宽或频率,其工作流程:
- 配置捕获通道的触发边沿(上升/下降沿)
- 当检测到指定边沿时,当前CNT值自动存入CCR寄存器
- 通过比较两次捕获的CCR值差计算脉宽
5.2 超声波模块工作原理
HC-SR04超声波模块测距流程:
- 给Trig引脚10us以上高电平触发测距
- 模块自动发送8个40kHz超声波脉冲
- Echo引脚输出高电平,高电平持续时间与距离成正比
距离计算公式:
code复制距离(cm) = 高电平时间(us) × 340m/s ÷ 2 × 100
5.3 CubeMX配置技巧
-
定时器配置:
- 时钟源:内部时钟
- 通道1:输入捕获直接模式,上升沿触发
- 通道2:输入捕获间接模式,下降沿触发
- 预分频:71(1MHz计数频率)
- ARR:65535(最大计时范围)
-
GPIO配置:
- Trig引脚:普通GPIO输出
- Echo引脚:定时器输入捕获引脚
5.4 代码实现与优化
超声波测距核心代码:
c复制// 启动测量
__HAL_TIM_SET_COUNTER(&htim1, 0); // CNT清零
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_1); // 启动捕获
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_2);
// 发送10us触发脉冲
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET);
delay_us(20); // 实际略大于10us
HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET);
// 等待测量完成
while(!(__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC1) &&
__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_CC2)));
// 计算距离
uint16_t ccr1 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_1);
uint16_t ccr2 = __HAL_TIM_GET_COMPARE(&htim1, TIM_CHANNEL_2);
float pulseWidth = (ccr2 - ccr1) * 1e-6f; // 转换为秒
float distance = 340.0f * pulseWidth / 2.0f; // 计算距离
避坑指南:在实际使用中,我发现超声波模块对电源噪声非常敏感。建议在模块VCC引脚就近放置一个10μF电容,同时测量线不宜过长,否则可能导致测量结果不稳定。
6. 从模式控制器高级应用
6.1 从模式工作原理
从模式控制器允许定时器响应外部事件,主要工作模式:
- 复位模式:外部信号触发计数器复位
- 门控模式:外部信号电平控制计数使能
- 触发模式:外部信号启动定时器
- 外部时钟模式:外部信号作为计数时钟
6.2 典型应用场景
- 多定时器同步:使用一个定时器作为主设备,触发其他从定时器同步启动
- 外部事件计数:利用外部信号作为时钟源,实现脉冲计数
- 门控测量:精确测量外部信号的有效时间
6.3 配置实例
配置TIM2为外部时钟模式:
c复制// 在CubeMX中配置:
// Slave Mode = External Clock Mode 1
// Trigger Source = TI1FP1
// 代码中启动:
HAL_TIM_Base_Start(&htim2);
这种配置下,TIM2的计数器会在TI1引脚的每个上升沿递增,非常适合旋转编码器等脉冲计数应用。
7. 定时器应用中的常见问题与解决方案
7.1 中断响应延迟
现象:定时器中断响应不及时,实际周期比设定值长
原因:
- 中断优先级设置不当,被更高优先级中断阻塞
- 中断服务程序执行时间过长
解决方案:
- 合理配置NVIC优先级
- 优化中断服务程序,只做必要操作
- 使用DMA减轻CPU负担
7.2 PWM输出异常
现象:PWM波形不稳定或占空比不正确
原因:
- ARR预装载未使能,导致参数更新不同步
- 占空比计算错误
解决方案:
- 确保
TIMx_CR1.ARPE=1 - 检查CCR计算公式是否正确
- 使用示波器验证实际输出
7.3 输入捕获误差大
现象:脉宽测量结果波动大
原因:
- 输入信号噪声干扰
- 定时器时钟配置不合理
解决方案:
- 增加输入滤波器(TIMx_CCMRx.ICF)
- 提高计数频率(减小PSC)
- 多次测量取平均值
在实际项目开发中,我总结出几个调试定时器的有效方法:
- 使用逻辑分析仪捕获定时器相关引脚信号
- 利用STM32CubeMonitor实时监控寄存器值
- 在关键代码处添加调试输出
- 分模块验证,先确保基础功能正常再添加复杂逻辑
定时器是STM32最强大也最复杂的外设之一,掌握它的使用需要理论学习和实践相结合。建议从简单的延时功能开始,逐步尝试PWM输出、输入捕获等高级功能,最后再挑战从模式等复杂应用。每次调试遇到问题时,不妨回到基本原理,检查时钟配置、寄存器设置等基础环节,往往能发现问题的根源。
