STM32 TIM 定时器深度剖析:结构、时基、中断与应用开发(超形象详解)
文章目录
- 定时器(TIM)
- 定时器种类与分布
- 定时器的基本结构
- 时基单元
- 时基单元基本结构
- 计数器计数方向
- 时基单元时钟来源计算
- 寄存器预加载机制
- 自制延时函数
- 获取单片机当前时间
- 实现延迟函数
- 初始化定时器3的时基单元
- 配置中断
- 编写中断响应函数
- 测试延迟函数
定时器(TIM)
定时器种类与分布
- 定时器种类:单片机里定时器分高级、通用、基本三种,功能上高级定时器最全,通用定时器是在高级定时器基础上阉割部分功能,基本定时器则是在通用定时器基础上再阉割部分功能。
- 分布情况:STM32F1 系列芯片最多有 14 个定时器,定时器 1 和 8 是高级定时器,定时器 6 和 7 是基本定时器,定时器 2 到 5 以及定时器 9 到 14 是通用定时器。实际使用芯片中有 1 个高级定时器(定时器 1)和 3 个通用定时器(定时器 2 - 4),学习编程一般以高级定时器 1 为例。
定时器的基本结构
时基单元,输出比较,输入捕获,从模式控制器四个部分。
时基单元
时基单元基本结构
- 时钟源:时钟信号主要有两个来源,通常认为来自上一章学习的时钟树,一个为从模式控制器。
- 预分频器:因输入时钟信号频率高,需用预分频器降频,其分频系数等于 PSC 的值加 1 。
- 计数器 CNT:对经过分频后的脉冲信号计数,每来一个脉冲,计数器的值增加 1。
- 自动重装寄存器 ARR:用于设置定时周期,定时周期的值等于 ARR 的值加 1。当计数器 CNT 的值增长到与 ARR 相等时,会发生溢出,然后从 0 开始重新计数。
- 重复计数器 RCR:设置重复计数的次数。没有 RCR 时,计数器 CNT 每溢出一次产生一个 update 事件;加入 RCR 后,计数器 CNT 需要溢出 RCR + 1 次才产生一个 update 事件。
和手表类似,手表有石英晶振提供时钟信号,还有其他电路装置进行降频,秒针相当于一个计数器,ARR为59,周期为60,RCR为0,每一个周期产生一个事件。
计数器计数方向
- 上计数(count up):从 0 开始,左边每来一个脉冲,计数器 CNT 的值增加 1,直到增长到和自动重装计算器 ARR 相等时发生溢出,然后从 0 开始重新计数。
- 下计数(count down):与上计数相反,从 ARR 开始计数,左边每来一个脉冲,计数器 CNT 的值减少 1,直到减少到 0 发生溢出,然后从 ARR 开始重新计数。
- 中心对齐(center line):首先从 0 开始上计数到 ARR,然后转变成下计数,从 ARR 开始又计数到 0,完成一个定时周期。下计数和中心对齐用得较少,一般用上计数。
时基单元时钟来源计算
-
分辨率和周期概念:类比手表,分辨率是计时的最小间隔,周期是转一圈所消耗的时间。对于定时器,计数器 CNT 每跳一下所消耗的时间是分辨率,从某点到计数器溢出是计数周期。
-
计算过程:以设置定时器 3 的 11 单元分辨率为 1 微秒,周期为 1 毫秒为例。先根据时钟树确定输入信号频率,当前配置下所有定时器时钟频率为 72 兆赫兹。要得到 1 微秒分辨率即 1 兆赫兹信号,设置预分频器 PSC 值为 71 ;要设置 1 毫秒周期,自动重装寄存器 AR 值为 999,重复计数器 RCR 值为 0。
TIM_CLK是系统自动配置的,如果APB2和APB1的分频系数为1,TIM_CLK = PCLK2,如果其他系数 TIM_CLK = 2 * PCLK2,右面同理。
寄存器预加载机制
预加载机制,如果阴影为黑色,表示默认为开启,灰色表示默认为关闭。
- 概念:向寄存器写值时,先写入影子寄存器,值不会立即生效,等计数器 CNT 溢出产生 update 事件时,影子寄存器的值才进入活动寄存器并生效,这种缓存机制叫寄存器预加载。
-
作用:以自动重装寄存器 AR 为例,在定时器运行中改变 AR 值,不使用预加载机制会导致定时器跑飞,加入预加载机制后,可避免该问题,使计数周期正常过渡 。
未使用预加载寄存器
使用了预加载寄存器
自制延时函数
使用定时器3和中断函数自制延时函数,因为使用的是while轮询等待所以还是会造成程序阻塞。
获取单片机当前时间
-
定时器配置:假设时基单元输入时钟频率 72 兆赫兹,设置 PSC 值为 71 对输入时钟 72 分频,得到 1 兆赫兹时钟频率,周期 1 微秒;设置定时周期为 1000(999 + 1),即 1 毫秒;设置重复计数器 RCR 值为 0,使 update 事件频率 1 毫秒一次,激活 update 标志位产生中断。
-
变量声明与作用:声明无符号 32 位整型变量 current tick ,赋初值 0,用于记录单片机当前时间(单位毫秒),变量前加 volatile 关键字(作用与中断有关,可自行百度学习)。
-
中断响应操作:中断每毫秒产生一次,在中断响应函数中让 current tick 值增加 1,从而记录单片机开机后的时间。
实现延迟函数
- 函数设计:定义延迟函数 APP delay,参数为要延迟的毫秒数。
- 实现思路:用 current tick 获取当前时间,加上要延迟的时间得到延迟结束时间 expire time ,通过 while 循环等待当前时间超过延迟结束时间,函数结束即实现延迟。
初始化定时器3的时基单元
- 第一步:使能时钟:定时器 3 在 APB1 总线上,调用 RCC_APB1PeriphClockCmd 开启其时钟。
- 第二步:配置参数:学习 TIM_TimeBaseInit 编程接口,声明结构体变量 TM_TimeBaseInitTypeDef 并赋值,设置预分频器 PSC 值 71、周期值 999、计数方向为上计数、重复计数器 RCR 值 0,调用该接口完成参数配置。
- 第三步:闭合开关:学习 TIM_Cmd 编程接口,用该接口闭合开关使定时器运行。
配置中断
- 使能 update 中断:学习 TIM_ITConfig 编程接口,通过该接口闭合 update 标志位产生中断的开关。
- 配置 NVIC 模块:
- 先在 main 方法开头配置中断优先级分组;
- 再声明结构体变量 NVIC_InitTypeDef 并赋值,设置定时器 3 中断相关参数,调用 NVIC_Init 完成 NVIC 模块配置。
编写中断响应函数
- 找到函数名:在启动文件的中断向量表中找到定时器 3 的中断响应函数 TIM3_IRQHandler ,复制到 main.c 文件进行实现。
- 函数内容:先通过 TM_GetFlagStatus 检查是否由 update 标志位触发中断,若是则用 TM_ClearFlag 清除标志位,再让 current tick 值加 1。
测试延迟函数
- 初始化板载 LED:写函数初始化板载 LED(PC13 引脚),开启 GPIOC 时钟,声明结构体变量并设置相关参数,初始化完成后关灯。
- 编写闪灯程序:在 main 方法的 while 循环中编写闪灯代码,调用 APP delay 延迟函数,实现不同闪烁效果(如亮 500 毫秒灭 500 毫秒、两次短闪一次长闪等),编译下载代码到单片机测试,验证延迟函数可用 。
完整main.c代码
#include "stm32f10x.h" // Device headervolatile uint32_t currentTick = 0;
void App_delay(uint32_t ms);
void App_TIM3_BaseInit(void);
void App_GpioInit(void);
int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);App_TIM3_BaseInit();App_GpioInit();while(1){GPIO_ResetBits(GPIOC, GPIO_Pin_13);App_delay(100);GPIO_SetBits(GPIOC, GPIO_Pin_13);App_delay(100);GPIO_ResetBits(GPIOC, GPIO_Pin_13);App_delay(500);GPIO_SetBits(GPIOC, GPIO_Pin_13);App_delay(500);}
}
//自制延时函数逻辑
void App_delay(uint32_t ms)
{uint32_t extime = ms + currentTick;while(extime > currentTick);
}
//定时器3初始化
void App_TIM3_BaseInit(void)
{//开启定时器3的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//配置时基单元参数TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStruct.TIM_Period = 999;TIM_TimeBaseInitStruct.TIM_Prescaler = 71;TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);//闭合时基单元开关TIM_Cmd(TIM3, ENABLE);//使能updateTIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//配置NVIC模块NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);}
void TIM3_IRQHandler(void)
{//先判断是不是要的中断if(TIM_GetFlagStatus(TIM3, TIM_FLAG_Update) == SET){//将中断标志位清除TIM_ClearFlag(TIM3, TIM_FLAG_Update);//操作currentTick++;}
}
void App_GpioInit(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStruct);
}