STM32F103系列的单片机一共有11个定时器:
2个高级定时器
4个通用定时器
2个基本定时器
2个看门狗定时器
1个系统滴答定时器
除去看门狗定时器和滴答定时器,其他8个定时器列表:
其中,
TIM1和TIM8是高级定时器
TIM2 - TIM5是通用定时器
TIM6和TIM7是基本定时器
这8个定时器都是16位的,它们计数的类型除了基本定时器TIM6和TIM7,都支持向上、向下、向上/向下3种计数模式。
计数器的3种计数模式:
向上计数模式:从0开始,计到arr预设值,产生溢出事件,返回重新计时;
向下计数模式:从arr预设值开始,计到0,产生溢出事件,返回重新计时;
中央对齐模式:从0开始向上计数,计到arr产生溢出事件,然后向下计数,计数到1以后,又产生溢出,然后再从0开始向上计数(这种计数方式也叫向上/向下计数)。
基本定时器(TIM6和TIM7)主要功能:
只有最基本的定时功能。基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
通用定时器(TIM2 - TIM5)主要功能:
除了基本的定时器的功能外,还可以测量输入信号的脉冲长度( 输入捕获) 或者产生输出波形( 输出比较和PWM)。
高级定时器(TIM1和TIM8)主要功能:
高级定时器不但具有基本、通用定时器的所有的功能,还具有控制交直流电动机的所有功能。比如它可以输出6路互补带死区的信号,刹车功能等等。
通用定时器的时钟来源:
a. 内部时钟(CK_INT)
b. 外部时钟模式1:外部输入脚(TIx)
c. 外部时钟模式2:外部触发输入(ETR)
d. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器
自动装载寄存器arr值得计算:
Tout = ((arr + 1)*(psc + 1))/Tclk;
Tclk:TIMx的输入时钟频率(单位:MHz)
Tout:TIMx的溢出时间(单位:us)
eg: 计时1秒,输入时钟频率为72MHz,psc预分频的值为35999,那么,((arr + 1)*(psc + 1))/72M = ((arr + 1)*(35999 + 1))/72M = 1s,就可以求解出自动预装载寄存器arr = 1999。
定时器每次计数到预设值后,会产生一次中断更新事件,进入中断服务函数。因此,每个定时器有一个或者多个对应的中断处理函数的入口。
对于STM32F103C8T6,需要注意的是,这款芯片没有基本定时器TIM6和TIM7。它的中断服务函数名如下:
不知小伙伴们还记不记得上一篇的精准延时?没错啦,精准延时那里就用到了系统滴答定时器。
ARM Cortex-M3内核中有一个Systick定时器,它是一个24位(0~(2^24-1))的倒计数定时器,这个脉冲计数值保存在当前计数值寄存器STK_VAL(Systick current value register)中,当计数到0时,它就会从Load寄存器中自动重装定时初值,只要不把CTRL寄存器中的ENABLE清0,它就永不停。
Systick定时器只能向下计数,每接收到一个时钟脉冲,STK_VAL的值就会向下减1,当减到0时,硬件会自动把重装载寄存器STK_LOAD(Systick reload value register)中保存的数据加载到STK_VAL,重新开始向下计数。如果STK_VAL的值被减至0时,会触发异常产生中断。所以,小伙伴们不要因为好玩,在程序里写一句 delay_ms(0) 或 delay_us(0)。这样会触发异常产生中断,到时你就找不着北啦~
既然提到了精准延时,那这次小R给小伙伴们分享更加准确的LED灯闪烁时间,我们使用通用定时器中断来实现LED灯闪烁:
1. 新建两个文件,tim.c 和 tim.h
2. 在头文件 tim.h 添加下面代码:
3. 把 tim.c 添加到工程中
4. 在 tim.c 中添加以下代码:
#include “tim.h”
void TIMx_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //声明TIMx定时器结构体
NVIC_InitTypeDef NVIC_InitStructure; //声明中断优先级结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先级分组
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能APB1总线上的TIMx时钟
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件活动的自动装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许TIMx中断更新
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //配置外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢先优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置子优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //初始化中断优先级
TIM_Cmd(TIM3,ENABLE); //使能TIMx
}
void TIM3_IRQHandler(void) //TIMx中断服务函数
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) //检查TIMx更新中断
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIMx中断标志位
GPIOC->ODR ^= 0X0001<<13; //PC.13高低电平交替翻转
}
}
定时器中断配置步骤:
1. NVIC优先级组配置
2. 使能定时器时钟
3. 初始化定时器
4. 设置定时器允许更新中断
5. 设置定时器中断优先级
6. 使能定时器
7. 编写中断服务函数
5. 实现定时器更新中断LED灯闪烁功能
需要注意的是:在中断函数中需要检验一下标志位,因为定时器的所有事件共用一个中断。但只使用更新中断不用检验也是没有问题滴。
欢迎关注微信公众号『OpenSSR』