1. 定时器基础概念与STM32定时器体系
在嵌入式系统开发中,定时器(Timer)是最基础也最重要的外设之一。作为STM32开发者,理解定时器的工作原理和配置方法,是掌握单片机实时控制能力的关键。STM32F42xxx系列控制器提供了丰富的定时器资源,包括:
- 2个高级控制定时器(TIM1和TIM8)
- 10个通用定时器(TIM2-TIM5, TIM9-TIM14)
- 2个基本定时器(TIM6和TIM7)
- 2个看门狗定时器(独立看门狗和窗口看门狗)
这些定时器彼此完全独立,不共享任何资源,可以同时使用。不同类别的定时器功能差异明显:
高级控制定时器具备最完整的功能集,支持:
- 互补输出的PWM生成
- 死区时间控制
- 编码器接口
- 霍尔传感器接口
通用定时器功能适中,适合大多数常规应用:
- 输入捕获(测量脉冲宽度)
- 输出比较(PWM输出)
- 单脉冲模式
基本定时器功能最为简单,主要用于:
- 基础定时功能(时基生成)
- DAC转换触发
实际项目中,基本定时器常被用作系统"心跳"或为DAC提供精确的触发时钟。虽然功能简单,但理解其工作原理是掌握更复杂定时器的基础。
2. 基本定时器TIM6/TIM7深度解析
STM32F42xxx的两个基本定时器TIM6和TIM7虽然功能相同,但资源完全独立。这意味着我们可以同时使用它们来完成不同的定时任务,比如:
- TIM6用于系统时基
- TIM7用于触发DAC转换
2.1 基本定时器功能框图
基本定时器的核心组件包括:
-
时钟源:基本定时器只能使用内部时钟(CK_INT),无法像高级定时器那样选择外部时钟源。时钟频率通常为APB1总线时钟的2倍(当RCC_DCKCFGR寄存器的TIMPRE位为0时)。
-
控制器:负责定时器的使能、复位等基本控制,以及DAC触发信号的生成。
-
计数器:16位向上计数器(TIMx_CNT),从0开始递增,达到自动重装载值(TIMx_ARR)时产生更新事件。
-
预分频器:16位可编程预分频器(TIMx_PSC),用于降低输入时钟频率。
-
自动重装载寄存器:16位寄存器(TIMx_ARR),决定计数器的溢出周期。
2.2 时钟配置实战
假设系统APB1时钟为42MHz,要使TIM6获得84MHz的时钟:
c复制RCC->DCKCFGR &= ~RCC_DCKCFGR_TIMPRE; // 确保TIMPRE位为0
此时定时器时钟TIMxCLK = APB1时钟 × 2 = 84MHz。这是STM32定时器能支持的最高时钟频率。
注意:如果APB1预分频系数不为1(即APB1时钟不是等于AHB时钟),定时器时钟会额外×2。这是STM32时钟树设计的一个特点,目的是保持定时器时钟与总线时钟的比例关系。
2.3 定时周期计算原理
定时器的实际工作频率由预分频器(TIMx_PSC)和自动重装载寄存器(TIMx_ARR)共同决定:
定时周期公式:
[ T_{定时周期} = \frac{(TIMx_PSC + 1) \times (TIMx_ARR + 1)}{TIMxCLK} ]
其中:
- TIMx_PSC:预分频值(0-65535)
- TIMx_ARR:自动重装载值(0-65535)
- TIMxCLK:定时器输入时钟频率
实例:要实现1秒定时,系统时钟84MHz:
- 先确定ARR值:选择10000-1 = 9999
- 计算所需预分频值:
[ TIMx_PSC = \frac{TIMxCLK \times T_{定时周期}}{TIMx_ARR + 1} - 1 = \frac{84MHz \times 1s}{10000} - 1 = 8399 ]
配置代码:
c复制TIM6->PSC = 8399; // 预分频值
TIM6->ARR = 9999; // 自动重装载值
这样配置后,计数器每100us计数一次(84MHz/8400=10kHz),计数10000次后溢出,正好1秒。
3. 定时器初始化结构体详解
STM32 HAL库使用TIM_TimeBaseInitTypeDef结构体来配置定时器基本参数:
c复制typedef struct {
uint16_t TIM_Prescaler; // 预分频器
uint16_t TIM_CounterMode; // 计数模式
uint32_t TIM_Period; // 定时器周期(ARR值)
uint16_t TIM_ClockDivision; // 时钟分频
uint8_t TIM_RepetitionCounter; // 重复计数器(高级定时器专用)
} TIM_TimeBaseInitTypeDef;
3.1 参数详细说明
-
TIM_Prescaler:预分频器值
- 范围:0-65535
- 实际分频系数 = TIM_Prescaler + 1
- 例如:设置8399得到8400分频(84MHz→10kHz)
-
TIM_CounterMode:计数模式
- 基本定时器只能向上计数(TIM_CounterMode_Up)
- 高级/通用定时器支持向下计数和中心对齐模式
-
TIM_Period:定时周期(ARR值)
- 范围:0-65535
- 实际计数次数 = TIM_Period + 1
- 与PSC共同决定定时周期
-
TIM_ClockDivision:时钟分频
- 用于数字滤波器,基本定时器无需设置
- 可选:TIM_CKD_DIV1/2/4
-
TIM_RepetitionCounter:重复计数器
- 仅高级定时器有效
- 用于控制PWM脉冲数量
3.2 初始化代码示例
c复制TIM_HandleTypeDef htim6;
void TIM6_Init(void) {
htim6.Instance = TIM6;
htim6.Init.Prescaler = 8399; // 预分频值
htim6.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
htim6.Init.Period = 9999; // 自动重装载值
htim6.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟不分频
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; // 自动重装载预装载使能
if (HAL_TIM_Base_Init(&htim6) != HAL_OK) {
Error_Handler();
}
// 启动定时器
HAL_TIM_Base_Start(&htim6);
}
4. 定时器应用实战与调试技巧
4.1 基本定时器典型应用
- 精确延时:
c复制void delay_us(uint16_t us) {
__HAL_TIM_SET_COUNTER(&htim6, 0);
while(__HAL_TIM_GET_COUNTER(&htim6) < us);
}
- DAC触发:
c复制// 配置TIM6触发DAC
hdac.Instance = DAC;
hdac.Init.Trigger = DAC_TRIGGER_T6_TRGO;
HAL_DAC_Init(&hdac);
- 系统时基:
c复制// 配置1ms时基中断
HAL_TIM_Base_Start_IT(&htim6);
4.2 调试常见问题与解决
-
定时不准:
- 检查时钟树配置,确认APB1和定时器时钟频率
- 验证PSC和ARR计算是否正确
- 注意ARR和PSC的实际分频系数是配置值+1
-
无法进入中断:
- 确认NVIC中断已使能
- 检查TIMx_DIER寄存器中的中断使能位
- 确保全局中断已开启(__enable_irq())
-
DAC不触发:
- 验证TIMx_CR2中的TRGO设置
- 检查DAC触发源配置
- 确保定时器已启动
4.3 性能优化技巧
-
减少中断延迟:
- 在NVIC中设置合适的抢占优先级
- 中断服务函数尽量简短
- 使用DMA减轻CPU负担
-
提高定时精度:
- 使用最高可用定时器时钟
- 选择适当的PSC和ARR组合
- 考虑使用定时器的重复计数器功能
-
低功耗设计:
- 在不需要时关闭定时器时钟
- 使用定时器唤醒代替轮询
- 选择低功耗运行模式
5. 进阶应用:定时器与DAC协同工作
基本定时器与DAC的协同工作是STM32的一个典型应用场景。下面展示如何配置TIM6定时触发DAC输出:
c复制// TIM6配置
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim6.Instance = TIM6;
htim6.Init.Prescaler = 83; // 84MHz/84 = 1MHz
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 999; // 1MHz/1000 = 1kHz
HAL_TIM_Base_Init(&htim6);
// 配置TIM6触发输出
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig);
// DAC配置
hdac.Instance = DAC;
hdac.Init.Trigger = DAC_TRIGGER_T6_TRGO;
HAL_DAC_Init(&hdac);
// 启动定时器和DAC
HAL_TIM_Base_Start(&htim6);
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
这种配置下,DAC会以1kHz的频率被定时触发,非常适合生成波形信号。通过调整TIM6的ARR和PSC值,可以精确控制DAC更新速率。
在实际项目中,我曾用这种方案实现了一个音频信号发生器。关键是要注意:
- 定时器频率要满足奈奎斯特采样定理
- DAC输出后需要适当的模拟滤波
- 考虑使用DMA来传输波形数据,减轻CPU负担