1. 项目概述
作为一名嵌入式开发初学者,掌握STM32定时器的使用是进阶路上的重要里程碑。本实验基于正点原子STM32F407探索者开发板,系统性地探索了STM32定时器的原理与应用。通过6天的实践(Day8-Day12),我从基础定时器入手,逐步深入到通用定时器的各种功能模式,包括定时中断、PWM输出和输入捕获等核心功能。
定时器作为STM32的重要外设,其应用场景极为广泛:
- 精确时间基准生成
- 电机控制与PWM调制
- 信号频率与脉宽测量
- 硬件触发与事件同步
- 系统看门狗保护
实验过程中,我不仅理解了寄存器级的配置原理,还掌握了HAL库的封装使用方法,为后续项目开发打下了坚实基础。
2. 定时器基础原理
2.1 软件定时与硬件定时的本质区别
在嵌入式系统中,定时功能可以通过两种方式实现:
软件定时(阻塞式延时)
c复制void delay_ms(uint32_t ms) {
uint32_t i;
while(ms--) {
for(i=0; i<1000; i++); // CPU空循环
}
}
特点:
- 实现简单,无需硬件支持
- 占用CPU资源(死等期间CPU不能执行其他任务)
- 定时精度低(受中断影响大)
- 无法实现精确的周期性触发
硬件定时器
c复制void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM6) {
// 定时中断回调函数
LED_TOGGLE();
}
}
特点:
- 独立硬件计数器,不占用CPU资源
- 纳秒级定时精度
- 支持多种工作模式(定时、PWM、输入捕获等)
- 可触发中断或DMA请求
2.2 STM32F4定时器分类与特性
STM32F407系列包含多达17个定时器,分为5大类:
| 类型 | 型号 | 数量 | 核心特性 | 典型应用 |
|---|---|---|---|---|
| 高级控制定时器 | TIM1, TIM8 | 2 | 32/16位计数,6路PWM,死区控制 | 电机控制,大功率驱动 |
| 通用定时器 | TIM2-TIM5 | 4 | 32位计数,4路通道,编码器接口 | 精密定时,多通道PWM |
| 通用定时器 | TIM9-TIM14 | 6 | 16位计数,2/1路通道 | 基础定时,简单PWM |
| 基本定时器 | TIM6, TIM7 | 2 | 16位计数,无GPIO,仅定时/DAC触发 | 时基生成,DAC触发 |
| 看门狗定时器 | IWDG, WWDG | 2 | 独立看门狗,系统复位保护 | 系统可靠性保障 |
3. 基本定时器实验
3.1 TIM6/TIM7核心架构
基本定时器是STM32定时器家族中最简单的成员,其核心组成如下:
-
时钟源
- 仅支持内部时钟CK_INT(来自APB1总线)
- 当APB1预分频系数≥2时,定时器时钟=APB1时钟×2
- 本实验系统中:APB1=42MHz → TIM6时钟=84MHz
-
时基单元
- 16位预分频器(PSC):1~65536分频
- 16位计数器(CNT):0~65535
- 自动重装载寄存器(ARR):设定计数上限
-
工作流程
- 时钟信号经过PSC分频得到计数时钟CK_CNT
- CNT每个CK_CNT周期加1
- 当CNT=ARR时产生更新事件(UEV)
- UEV可触发中断或DMA请求
3.2 定时器中断实验实现
3.2.1 硬件设计
使用TIM6实现500ms定时中断,控制LED1(Green)翻转:
- LED0(Red):主循环200ms软件延时控制
- LED1(Green):TIM6 500ms中断控制
3.2.2 关键寄存器配置
-
控制寄存器1(TIMx_CR1)
- ARPE位:ARR缓冲使能
- CEN位:计数器使能
-
中断使能寄存器(TIMx_DIER)
- UIE位:更新中断使能
-
状态寄存器(TIMx_SR)
- UIF位:更新中断标志(需手动清除)
-
预分频器(TIMx_PSC)
- 写入实际分频系数-1(如83表示84分频)
-
自动重装载寄存器(TIMx_ARR)
- 设定计数周期(如4999表示5000个计数周期)
3.2.3 定时时间计算
计算公式:
[ T_{out} = \frac{(ARR+1) \times (PSC+1)}{TIMxCLK} ]
本实验参数:
- TIMxCLK = 84MHz
- PSC = 8399 → 分频后时钟=84MHz/8400=10kHz
- ARR = 4999 → 计数周期=5000
- Tout = 5000 × 0.1ms = 500ms
3.2.4 代码实现
btim.h 头文件定义
c复制#define BTIM_TIMX_INT TIM6
#define BTIM_TIMX_INT_IRQn TIM6_DAC_IRQn
#define BTIM_TIMX_INT_IRQHandler TIM6_DAC_IRQHandler
#define BTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0)
void btim_timx_int_init(uint16_t arr, uint16_t psc);
btim.c 初始化函数
c复制TIM_HandleTypeDef g_timx_handler;
void btim_timx_int_init(uint16_t arr, uint16_t psc) {
g_timx_handler.Instance = BTIM_TIMX_INT;
g_timx_handler.Init.Prescaler = psc;
g_timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP;
g_timx_handler.Init.Period = arr;
HAL_TIM_Base_Init(&g_timx_handler);
HAL_TIM_Base_Start_IT(&g_timx_handler);
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) {
if(htim->Instance == BTIM_TIMX_INT) {
BTIM_TIMX_INT_CLK_ENABLE();
HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn);
}
}
中断服务函数
c复制void BTIM_TIMX_INT_IRQHandler(void) {
HAL_TIM_IRQHandler(&g_timx_handler);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == BTIM_TIMX_INT) {
LED1_TOGGLE(); // 500ms翻转LED1
}
}
3.2.5 实验现象与问题排查
预期现象:
- LED0(Red):400ms周期闪烁(亮200ms,灭200ms)
- LED1(Green):1s周期闪烁(亮500ms,灭500ms)
常见问题:
-
定时器时钟频率错误
- 现象:定时时间与计算值不符
- 检查:APB1预分频配置,确认TIMxCLK是否正确
-
中断未触发
- 检查:NVIC中断使能、DIER寄存器UIE位、CR1寄存器CEN位
-
中断频繁进入
- 原因:未清除SR寄存器UIF位
- 解决:确保在中断中清除标志位
4. 通用定时器进阶应用
4.1 通用定时器架构解析
相比基本定时器,通用定时器(TIM2-TIM5)增加了丰富的外设接口和功能模块:
-
时钟源选择
- 内部时钟(CK_INT)
- 外部时钟模式1(TI1/TI2)
- 外部时钟模式2(ETR)
- 内部触发输入(ITRx)
-
核心功能单元
- 输入捕获通道(测量脉冲宽度/频率)
- 输出比较单元(生成PWM波形)
- 编码器接口(正交解码)
- 主从模式(定时器级联)
-
中断与DMA支持
- 更新/捕获/比较/触发中断
- 多种DMA请求生成
4.2 PWM输出实验
4.2.1 PWM生成原理
PWM(Pulse Width Modulation)通过调节占空比实现对平均电压的控制:
-
频率:由ARR和PSC决定
[ f_{PWM} = \frac{TIMxCLK}{(PSC+1) \times (ARR+1)} ] -
占空比:由CCRx决定
[ Duty = \frac{CCRx}{ARR+1} \times 100% ]
4.2.2 PWM模式详解
STM32支持多种PWM输出模式:
| 模式 | 计数器方向 | CNT<CCR时 | CNT≥CCR时 |
|---|---|---|---|
| PWM1 | 递增 | 有效电平 | 无效电平 |
| PWM1 | 递减 | 无效电平 | 有效电平 |
| PWM2 | 递增 | 无效电平 | 有效电平 |
| PWM2 | 递减 | 有效电平 | 无效电平 |
有效电平由CCER寄存器的CCxP位决定:
- CCxP=0:高电平有效
- CCxP=1:低电平有效
4.2.3 呼吸灯实现
硬件连接:
- TIM14_CH1 → PF9 (LED0-Red)
代码实现:
gtim.h 配置
c复制#define GTIM_TIMX_PWM TIM14
#define GTIM_TIMX_PWM_CHY TIM_CHANNEL_1
#define GTIM_TIMX_PWM_CHY_CCRX TIM14->CCR1
#define GTIM_TIMX_PWM_CHY_CLK_ENABLE() do{ __[HAL](https://taotoken.net/?utm_source=hardware)_RCC_TIM14_CLK_ENABLE(); }while(0)
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc);
gtim.c 初始化
c复制TIM_HandleTypeDef g_timx_pwm_chy_handle;
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc) {
TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};
g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM;
g_timx_pwm_chy_handle.Init.Prescaler = psc;
g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
g_timx_pwm_chy_handle.Init.Period = arr;
HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);
timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;
timx_oc_pwm_chy.Pulse = arr / 2; // 初始50%占空比
timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;
HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY);
HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY);
}
主循环控制
c复制int main(void) {
// 系统初始化
gtim_timx_pwm_chy_init(500-1, 84-1); // 2kHz PWM
while(1) {
// 呼吸灯效果
static uint16_t pwmVal = 0;
static uint8_t dir = 1;
delay_ms(10);
dir ? pwmVal++ : pwmVal--;
if(pwmVal > 300) dir = 0;
if(pwmVal == 0) dir = 1;
__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, pwmVal);
}
}
4.3 输入捕获实验
4.3.1 脉宽测量原理
输入捕获通过记录信号边沿时刻的计数器值来测量脉宽:
- 上升沿捕获:记录CCR1
- 下降沿捕获:记录CCR2
- 计算高电平时间:
[ T_{high} = \frac{(N \times (ARR+1) + (CCR2 - CCR1)) \times (PSC+1)}{TIMxCLK} ]
其中N为溢出次数
4.3.2 按键脉宽测量实现
硬件连接:
- WK_UP按键 → PA0 → TIM5_CH1
代码实现:
gtim.h 配置
c复制#define GTIM_TIMX_CAP TIM5
#define GTIM_TIMX_CAP_CHY TIM_CHANNEL_1
#define GTIM_TIMX_CAP_CHY_CCRX TIM5->CCR1
void gtim_timx_cap_chy_init(uint32_t arr, uint16_t psc);
gtim.c 初始化
c复制TIM_HandleTypeDef g_timx_cap_chy_handle;
void gtim_timx_cap_chy_init(uint32_t arr, uint16_t psc) {
TIM_IC_InitTypeDef timx_ic_cap_chy = {0};
g_timx_cap_chy_handle.Instance = GTIM_TIMX_CAP;
g_timx_cap_chy_handle.Init.Prescaler = psc;
g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
g_timx_cap_chy_handle.Init.Period = arr;
HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING;
timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;
timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1;
timx_ic_cap_chy.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, GTIM_TIMX_CAP_CHY);
__HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);
HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY);
}
中断处理
c复制void TIM5_IRQHandler(void) {
static uint32_t captureCount = 0;
static uint8_t captureState = 0;
if(__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle, TIM_FLAG_UPDATE)) {
__HAL_TIM_CLEAR_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);
captureCount++;
}
if(__HAL_TIM_GET_FLAG(&g_timx_cap_chy_handle, TIM_FLAG_CC1)) {
__HAL_TIM_CLEAR_IT(&g_timx_cap_chy_handle, TIM_IT_CC1);
if(captureState == 0) {
// 上升沿捕获
captureCount = 0;
__HAL_TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_FALLING);
captureState = 1;
} else {
// 下降沿捕获
uint32_t pulseWidth = captureCount * 65536 + HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY);
printf("Pulse Width: %lu us\r\n", pulseWidth);
__HAL_TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_RISING);
captureState = 0;
}
}
}
5. 经验总结与进阶技巧
5.1 定时器使用中的常见问题
-
时钟配置错误
- 现象:定时时间不准确
- 解决:检查RCC时钟树配置,确认APB分频系数
-
中断优先级冲突
- 现象:定时中断不响应或被其他中断阻塞
- 解决:合理配置NVIC优先级分组和抢占/子优先级
-
PWM输出异常
- 检查:GPIO复用功能、PWM模式与极性设置
- 技巧:使用逻辑分析仪观察实际波形
-
输入捕获误差
- 优化:适当配置输入滤波(ICFilter)
- 注意:长脉宽测量需处理计数器溢出
5.2 性能优化建议
-
减少中断处理时间
- 在中断中仅设置标志位,主循环处理实际任务
- 使用DMA传输替代中断
-
精确时间控制技巧
- 使用定时器的单脉冲模式(OPM)
- 利用从模式实现硬件级同步
-
PWM高级应用
- 互补输出与死区插入(电机驱动)
- 刹车功能(紧急停止)
-
低功耗设计
- 在不需要时关闭定时器时钟
- 使用LPTIM(低功耗定时器)
5.3 扩展学习方向
-
定时器级联
- 主从模式实现32位定时
- 硬件同步多个定时器
-
编码器接口
- 正交解码应用(电机位置检测)
- 霍尔传感器接口
-
高级控制定时器
- 六步PWM(无刷电机控制)
- 中心对齐模式
-
与DMA的配合
- 定时器触发DMA传输
- PWM波形存储器播放
通过本系列实验,我系统掌握了STM32定时器的原理与应用,从寄存器级理解到HAL库封装,建立了完整的知识体系。定时器作为STM32最强大的外设之一,其灵活性和功能性为嵌入式系统设计提供了无限可能。后续计划深入研究高级定时器在电机控制领域的应用,并探索定时器与DMA的协同工作模式。
