在嵌入式开发中,定时器是最基础也最常用的外设之一。STM32F103C8T6作为经典的Cortex-M3内核单片机,内置了多达4个通用定时器(TIM2-TIM5),每个定时器都具备强大的功能。今天我们就来深入探讨如何配置TIM2实现精确的定时中断,并分享一些实际项目中积累的经验技巧。
STM32的定时器本质上是一个向上/向下计数的计数器,其核心部件包括:
定时器的工作流程可以类比为沙漏:预分频器控制沙子流动的速度(时钟分频),自动重装载值决定沙漏的容量(计数周期)。当沙子漏完(计数器达到ARR值)时,就会触发更新事件,产生中断。
定时器中断周期由以下公式决定:
code复制Tout = (ARR + 1) × (PSC + 1) / Fclk
其中:
以我们示例中的配置为例:
c复制RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
这一步开启了TIM2的时钟。在STM32中,任何外设使用前都必须先使能其时钟,这是初学者最容易忽略的一点。
注意:TIM2-TIM5属于APB1总线,最大时钟频率为36MHz。但实际定时器时钟会经过倍频器,最终得到72MHz的工作频率。
c复制TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
关键参数解析:
TIM_ClockDivision:用于配置滤波器时钟分频,不影响定时器基本功能,通常保持DIV1TIM_CounterMode:计数模式,常用向上计数(TIM_CounterMode_Up)TIM_Period:自动重装载值ARR,决定定时周期TIM_Prescaler:预分频值PSC,对输入时钟进行分频c复制TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
这里有两个关键点:
c复制void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// 用户代码区
GPIO_WriteBit(GPIOA, GPIO_Pin_0,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0)));
}
}
这个最简单的示例实现了LED闪烁功能。每次定时器中断触发时,PA0引脚的电平状态就会翻转一次。
在实际项目中,中断服务函数应该:
c复制volatile uint8_t timerFlag = 0;
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
timerFlag = 1; // 设置标志位
}
}
int main(void)
{
// 初始化代码...
while(1)
{
if(timerFlag)
{
timerFlag = 0;
// 执行实际任务
}
}
}
通过合理配置预分频器和自动重装载值,可以实现高精度的微秒级延时:
c复制void Delay_us(uint16_t us)
{
TIM2->PSC = 72 - 1; // 1MHz计数频率
TIM2->ARR = us - 1; // 设置自动重装载值
TIM2->CNT = 0; // 清零计数器
TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器
while(!(TIM2->SR & TIM_FLAG_Update));
TIM2->SR = ~TIM_FLAG_Update;
TIM2->CR1 &= ~TIM_CR1_CEN; // 关闭定时器
}
对于需要超长定时的情况,可以使用两个定时器级联:
在需要低功耗的场景下,可以:
可能原因及解决方法:
排查步骤:
优化建议:
对于性能敏感的应用,可以直接操作寄存器:
c复制TIM2->CR1 &= ~TIM_CR1_CEN; // 停止定时器
TIM2->PSC = 7199; // 预分频值
TIM2->ARR = 9999; // 自动重装载值
TIM2->CNT = 0; // 计数器清零
TIM2->CR1 |= TIM_CR1_CEN; // 启动定时器
对于需要精确时间控制的数据采集/输出:
多个定时器同步的方法:
在工业控制项目中,我们使用TIM2实现了多任务调度器。关键实现要点:
c复制void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
SystemTick++;
if((SystemTick % 10) == 0) // 每10ms
{
TaskScheduler();
}
}
}
另一个实际案例是使用TIM2捕获电机编码器信号。需要注意:
通过合理配置STM32的定时器,我们成功实现了±1us级别的高精度时间测量,满足了工业自动化设备的严格要求。