在嵌入式系统开发中,定时器是最基础也最重要的外设之一。以STM32F1系列为例,其内置多个通用定时器,每个定时器本质上都是一个自动运行的计数器。理解定时器的工作原理,特别是分频器(PSC)和自动重装载寄存器(ARR)的配合使用,是掌握定时器编程的关键。
定时器的核心功能可以概括为:在特定时间间隔产生中断或触发事件。这个"特定时间间隔"就是通过PSC和ARR共同决定的。想象你有一个滴水的水龙头,PSC决定了每滴水的间隔时间,ARR则决定了多少滴水后触发一次事件通知。
提示:STM32的定时器有多种工作模式,本文主要讨论最基本的向上计数模式,这也是最常用的定时器配置方式。
STM32F1系列的主频通常配置为72MHz,这意味着CPU每秒钟可以执行7200万次基本操作。如果直接让定时器对这个时钟进行计数,计数器会以惊人的速度累加:
这在大多数实际应用中显然太短了。比如我们需要1秒的定时,或者产生1kHz的PWM波,直接计数根本无法满足需求。这就是预分频器(Prescaler, PSC)存在的意义。
预分频器是一个16位的分频计数器,其分频系数 = PSC寄存器值 + 1。这个"+1"的设计经常让初学者困惑,需要特别注意:
实际分频后的计数频率计算公式为:
code复制f_cnt = f_ck / (PSC + 1)
其中f_ck是定时器的输入时钟频率。
以72MHz时钟和PSC=7199为例:
code复制f_cnt = 72,000,000 / 7200 = 10,000 Hz
这意味着计数器每0.1ms(1/10,000)才会加1。
注意:PSC寄存器的修改通常需要等到下一次更新事件才会生效,这是为了避免在计数过程中改变分频系数导致计时错误。
选择合适的分频系数需要考虑以下因素:
所需定时精度:分频系数越大,定时分辨率越低。例如PSC=7199时,最小定时单位是0.1ms。
最大定时周期:16位计数器最大计数值65535,配合分频系数决定了最长定时时间。
PWM分辨率:在PWM应用中,分频系数影响PWM占空比的调节精度。
一个实用的经验法则是:在满足最大定时需求的前提下,尽可能选择较小的分频系数以获得更好的定时精度。
自动重装载寄存器(Auto-Reload Register, ARR)是定时器的另一个关键配置项,它决定了计数器的溢出值。在向上计数模式下:
通过设置ARR,我们可以灵活控制定时器的溢出周期,而不必受限于计数器的最大值65535。
完整的定时周期计算公式为:
code复制T = (ARR + 1) * (PSC + 1) / f_ck
其中:
举例说明:
定时周期计算:
code复制T = (999 + 1) * (7199 + 1) / 72,000,000
= 1000 * 7200 / 72,000,000
= 0.1秒 (100ms)
在实际应用中,我们经常需要动态调整定时周期。STM32的ARR寄存器支持两种更新方式:
立即更新:直接写入ARR,新值立即生效。这可能导致当前计数周期不完整。
影子寄存器更新:写入ARR后,新值在下一次更新事件(UEV)时才生效。这是推荐的方式,可以确保定时周期的完整性。
配置方法(以标准外设库为例):
c复制TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseStructure.TIM_Period = 999; // ARR值
TIM_TimeBaseStructure.TIM_Prescaler = 7199; // PSC值
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 启用ARR影子寄存器
TIM_ARRPreloadConfig(TIM2, ENABLE);
更新事件是定时器最重要的硬件事件之一,它在以下情况下产生:
更新事件可以触发三种行为:
配置定时器中断的基本步骤:
c复制TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
c复制NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
c复制void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
// 处理定时器中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
在定时器应用中,我们有两种方式响应定时事件:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 中断 | 灵活,可执行复杂逻辑 | CPU开销大,响应时间不确定 | 需要复杂处理的任务 |
| DMA | 不占用CPU,时间精确 | 只能执行数据传输 | 数据采集、PWM控制等 |
在需要精确时间控制且处理简单的场景(如ADC定时触发),DMA是更好的选择。
利用定时器可以实现微秒级和毫秒级的精确延时。以下是一个实现1ms延时的例子:
c复制void TIM_Delay_Init(void) {
TIM_TimeBaseInitTypeDef TIM_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InitStructure.TIM_Period = 999; // ARR
TIM_InitStructure.TIM_Prescaler = 71; // 72MHz/(71+1)=1MHz -> 1us
TIM_InitStructure.TIM_ClockDivision = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
}
void delay_us(uint16_t us) {
TIM_SetCounter(TIM2, 0);
TIM_Cmd(TIM2, ENABLE);
while(TIM_GetCounter(TIM2) < us);
TIM_Cmd(TIM2, DISABLE);
}
void delay_ms(uint16_t ms) {
while(ms--) {
delay_us(1000);
}
}
PWM是定时器的另一个重要应用。配置步骤:
c复制TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_Period = 999; // ARR,决定PWM频率
TIM_InitStructure.TIM_Prescaler = 71; // 72MHz/72=1MHz
TIM_InitStructure.TIM_ClockDivision = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
c复制TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // 占空比=500/1000=50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
c复制TIM_Cmd(TIM2, ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
定时器不工作:
定时精度不符合预期:
中断不触发:
PWM无输出或波形异常:
调试技巧:使用示波器观察定时器相关引脚信号是最直接的调试方法。对于复杂的定时器应用,可以先用简单的定时中断或PWM输出验证基本功能正常后再添加复杂逻辑。