在嵌入式系统开发中,定时器就像是一个精准的"心跳"发生器,为各种时序控制任务提供基准。STM32系列微控制器内置的定时器外设功能强大且灵活,是控制LED闪烁、电机转速、信号采集等场景的核心组件。以我多年使用STM32的经验来看,熟练掌握定时器的使用是嵌入式工程师的必修课。
STM32F407系列芯片内部集成了多达14个定时器,根据功能差异可分为三类:
基本定时器(TIM6/TIM7)是最简单的定时器类型,仅具备基础的定时功能。它们就像是一个简单的闹钟,只能完成最基本的计时任务。在实际项目中,我常用它们来实现系统延时或触发DAC转换。
通用定时器(TIM2-TIM5, TIM9-TIM14)则像是瑞士军刀,功能全面且实用。除了基本定时功能外,它们还支持:
高级定时器(TIM1/TIM8)是定时器中的"旗舰型号",在通用定时器功能基础上增加了互补输出、死区控制等专业特性,特别适合电机驱动等需要精确时序控制的应用场景。
经验分享:在项目选型时,我通常会先评估定时器需求。如果只是简单定时,基本定时器就够用;需要PWM控制时选择通用定时器;只有涉及电机驱动等复杂场景才会动用高级定时器。合理分配定时器资源可以避免后期外设冲突。
理解STM32定时器的第一步是掌握其时钟来源。就像机械钟表需要发条提供动力一样,定时器需要时钟信号才能工作。STM32的时钟系统相当复杂,但我们可以抓住几个关键点:
时钟源:定时器的时钟来自APB总线
时钟倍频机制:这是STM32时钟系统中一个容易让人困惑的特性。当APB分频系数>1时,定时器时钟会获得2倍频:
调试技巧:我曾经在一个项目中遇到定时器计时不准的问题,最后发现是APB分频配置错误。建议在初始化定时器前,先用RCC_GetClocksFreq()函数验证时钟频率是否符合预期。
定时器的精准控制依赖于三个关键寄存器:
预分频器(PSC):16位寄存器(0-65535),用于对定时器时钟进行分频
自动重载寄存器(ARR):决定定时器的计数周期
计数器(CNT):实时记录当前计数值,可读写
定时时间的计算公式为:
code复制定时时间 = (PSC + 1) × (ARR + 1) / 定时器时钟频率
实例计算:我们需要TIM3产生100ms的定时中断,定时器时钟为84MHz。
计算过程:
STM32定时器支持多种计数模式,适应不同应用场景:
递增计数(最常用):
递减计数:
中心对齐模式:
注意事项:不是所有定时器都支持全部计数模式。例如TIM9-TIM14仅支持递增模式,在选型时需要注意这个限制。
配置定时器中断需要遵循以下步骤,我以TIM6实现1秒定时为例:
c复制// TIM6初始化函数
void TIM6_Init(void) {
// 1. 开启TIM6时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
// 2. 配置时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8399; // 分频至10kHz
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 9999; // 1秒定时
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStruct);
// 3. 配置NVIC
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM6_DAC_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 4. 使能中断
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
// 5. 启动定时器
TIM_Cmd(TIM6, ENABLE);
}
// 中断服务函数
void TIM6_DAC_IRQHandler(void) {
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) {
GPIO_ToggleBits(GPIOF, GPIO_Pin_9); // 翻转LED
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 清除中断标志
}
}
在实际项目中,定时中断可能会遇到各种问题,以下是我总结的一些经验:
中断不触发:
定时时间不准确:
中断响应延迟:
调试技巧:我习惯在中断服务函数开始处设置一个GPIO引脚为高电平,结束时设为低电平,用示波器观察可以准确测量中断响应时间和执行时间。
PWM(脉冲宽度调制)是定时器最重要的应用之一。简单来说,PWM就是通过调节脉冲的占空比来控制平均电压的技术。关键参数包括:
STM32的PWM有两种模式:
舵机(如SG90)的控制需要精确的PWM信号:
以下是使用TIM9控制舵机的完整代码:
c复制void TIM9_PWM_Init(void) {
// 1. 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
// 2. 配置GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOE, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_TIM9);
// 3. 配置时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 168-1; // 1MHz时钟
TIM_TimeBaseInitStruct.TIM_Period = 20000-1; // 20ms周期
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM9, &TIM_TimeBaseInitStruct);
// 4. 配置PWM
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 1500; // 初始位置90度
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM9, &TIM_OCInitStruct);
// 5. 使能预装载
TIM_OC2PreloadConfig(TIM9, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM9, ENABLE);
// 6. 启动定时器
TIM_Cmd(TIM9, ENABLE);
}
注意事项:舵机的控制信号对时序要求严格,PWM频率必须稳定在50Hz左右。在实际应用中,我建议:
- 使用硬件PWM而非软件模拟
- 避免频繁改变舵机角度
- 为舵机提供独立电源,避免电流不足导致抖动
呼吸灯效果是通过不断改变PWM占空比实现的。以下是使用TIM14实现呼吸灯的代码:
c复制void TIM14_PWM_Init(void) {
// 初始化代码与前面类似...
// 配置为1kHz频率:PSC=83, ARR=999
}
int main(void) {
uint16_t ccr_val = 0;
uint8_t dir = 1; // 方向标志
TIM14_PWM_Init();
while(1) {
if(dir) {
if(++ccr_val >= 999) dir = 0;
} else {
if(--ccr_val == 0) dir = 1;
}
TIM_SetCompare1(TIM14, ccr_val);
delay_ms(5); // 控制渐变速度
}
}
呼吸灯优化技巧:
输入捕获是定时器的另一重要功能,用于测量脉冲宽度或频率。基本工作原理:
典型应用包括:
对于需要超长定时的场景,可以将多个定时器级联使用。例如:
为了减轻CPU负担,可以使用DMA来处理定时器相关数据:
这种组合可以构建高效的数据采集或控制系统。
当硬件定时器资源不足时,可以使用软件模拟PWM:
优点:
缺点:
示例代码:
c复制while(1) {
GPIO_SetBits(GPIOF, GPIO_Pin_9); // 高电平
delay_us(500); // 0.5ms
GPIO_ResetBits(GPIOF, GPIO_Pin_9); // 低电平
delay_us(19500); // 19.5ms
}
经验之谈:在最近的一个项目中,我需要控制12个LED,但只有8个硬件PWM通道。最终我对4个次要LED使用软件PWM,关键LED使用硬件PWM,既满足了需求又节省了资源。
在项目初期,我会制定定时器使用计划:
当多个定时器中断同时使用时,合理的优先级设置很重要:
PWM无输出:
定时器计数不准确:
中断频繁触发:
经过多个项目的实践,我发现STM32定时器虽然功能复杂,但只要掌握了核心原理和配置方法,就能充分发挥其强大功能。建议初学者从基本定时功能开始,逐步探索PWM、输入捕获等高级功能,最终实现灵活运用。