定时器是STM32中一个非常强大的外设,功能强大,用途很广。STM32F103系列提供了8个定时器:2个基本定时器(TIM6,7),4个通用定时器(TIM2-5),2个高级定时器(TIM1和TIM8)。
本章节介绍2个基本定时器(TIM6,TIM7)的用法
基本定时器介绍
基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
这2个定时器是互相独立的,不共享任何资源。
这个2个基本定时器只能向上计数(区别系统滴答定时器只能向下计数),由于没有外部IO,所以只能计时,因此不能对外部脉冲进行计数。
功能:定时中断,主模式,触发DAC(数模转换)。
时钟来源:
只有内部时钟一种时钟来源。一般为72MHz(注意通用定时器2~5和基本定时器6,7)
注意预分频最大可设置为72MHz,并不是一定为72MHz,同时可以看到(如果APB1预分频系数=1则频率不变,否则*2)该表明除了72MHz外,都要*2才是2~7定时器的最终时钟频率
基本定时器功能框图:
自动重装载寄存器包含两个寄存器(从图上的影子也可以看出):
预加载寄存器和影子寄存器,数据的写入是先到预加载寄存器然后再更新到影子寄存器,目的是更改计数值时可以选择立即生效还是等待前一个重装载值出现更新事件时再重生效新的计数值,达到缓冲效果(寄存器CR1的ARPE位决定更新时机(是否预加载)),由此可以看出影子寄存器才是真正的和系统对接工作的,但是读者不必多想就只需要考虑自动重装载寄存器和是否预加载即可!!!!!
PSC预分频寄存器
是16位寄存器,按1~65536之间的任意值分频,作用是当时钟来源的频率太高而定时器内部计数器不适用时就会分频
注意它也有两个寄存器,但是影子寄存器没有控制位可以设置,所以只能靠更新事件将预分频值给到影子寄存器,即当我们在代码层上直接更改PSC值时不会立即起作用,因此我们就要考虑以上电就手动产生更新事件来解决问题
以该时序图为例隔37(计数:0~36,自动重装载值:36,则加到37时溢出到0)个计数周期残剩一次中断:
多久产生一次更新事件,即计数器多久产生一次溢出:
计数器的时钟频率是:来自内部时钟频率和PCB预分频系数
分频值:预分频系数+1(因为如果开始为0值表示1分频,即不分频,所以1表示2分频,2表示3分频......因此我们的分频可选范围为1~65536)
例如:假设:内部时钟频率:72MHz,预分频系数:7199,则内部时钟频率(72MHz)/(预分频系数+1)=计数器时钟频率为10000Hz,即100us计数一次(计数器的周期)
计数器累加多少次产生一次更新事件?自动重装载值+1
例如:以上面的时序图例子为例自动重装载值:36,则加到37时溢出到0
最终定时事件=内部时钟频率/(预分频系数+1)*(自动重装载值+1);
用HAL库的方式(含代码实现):
SYS一般默认的就是SysTikc,SYS配置的核心是系统的基本时钟来源,即系统底层的心跳
我们要在Timer中配TIM6:
最终定时事件=内部时钟频率/(预分频系数+1)*(自动重装载值+1);
Prescaler(PSC - 16 bits value):预分频器数值,16位
Counter Mode:计数模式,前文已经强调基本定时器只有向上计数功能,所以只有UP
Counter Period(AutoReload Register -16 bits value):自动重装载值
auto-reload preload:自动重装载预加载寄存器是否马上生效还是缓冲生效(具体看自动重装载寄存器包含两个寄存器的介绍)
Trigger Output(trgo) Parameters:触发输出参数,跟连接的外设相关,因为这里是基础介绍所以我不管它,读者可以按需求配置
在NVIC中配置中断 ,我们可以看到System tick timer的优先级最低,TIM6的优先级数值位0最高,如果读者有其他需求可以执行更改优先级(数值越小优先级越大)
以上就是TIM6的相关配置了,关于引脚配置和时钟树配置博主就不展开详细说明了,在博主的STM32系列中有详细说明
配置完后就可以生成KEIL5文件了,这里我用VSCode打开
代码实现:
可以看到我们用CubeMX初始化后生成的工程文件中配置TIM6的参数一致,我们要改数值直接在代码中该即可
它和USART,I2C,SystemTick等片内外设都是有定义好了的句柄变量TIM_HandleTypeDef htim6;我们可以直接用
注意这里的代码中没有打开TIM6定时器,所以我们在初始化函数下添加一个打开定时器的函数(我们可以写在主函数内,或者在初始化函数中都行,看你自己):
HAL_TIM_Base_Start_IT(&htim6);//我们选用一个中断使能的开始
我们可以在stm32f1xx_it.c中找到中断服务函数,我们可以把处理的逻辑写在中断服务函数中,也可以通过重写中断回调函数实现业务(注意我们不用手动清除溢出中断标志位,因为CubeMX已经帮我们生成了函数去清除了)
通过重写中断回调函数实现业务 ,这里博主就在main.c中重写了
//mian.c
//周期结束回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
if(hotim->Instance==TIM6){
//相关业务实现
//例如翻转灯
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_4);
}
}
用寄存器的方式(含代码实现):通过实现1s灯翻转1次为例子进行配置
注意用寄存器开发方式中所有的中断标志位都需要手动清除,而用HAL库方式则CubeMX生成的KEIL5工程代码内部配有自动清除,不用我们手动写了
void TIM6_Init(void)//且记要在主函数中初始化
{
//开启定时器
RCC->APB1ENR |=RCC_APB1ENR_TIM6EN;//(内部时钟频率一般为72MHz)
//定时器配置(预分频,自动重装载值)最终定时事件=内部时钟频率/(预分频系数+1)*(自动重装载值+1);
TIM6->PSC |=7200-1;//100us
TIM6->ARR |=10000 -1;//100us*10000=1s,ARR寄存器是16值范围为0~65535;
//注意PSC预分频寄存器也有两个寄存器,但是影子寄存器没有控制位可以设置,所以只能靠更新事件将预分频值给到影子寄存器,即当我们在代码层上直接更改PSC值时不会立即起作用,因此我们就要考虑一上电就手动产生更新事件将数值刷新进入影子寄存器
//但是要注意要在开启定时器中断和定时器之前就要做这样的操作
TIM6->EGR |=TIM6_EGR_UG;
//开启定时器中断(计数器溢出中断会触发一次中断回调函数)
TIM6->DIER |=TIM_DIER_UIE;
//自动重装载预装载使能,我在这里置1即配置寄存器具有缓冲(当更改计数值时不会立即更新而是等到上一次计数溢出后才更新)
TIM6->CR1 |=TIM_CR1_ARPB;
//NVIC配置
//配4位的抢占优先级
NVIC_SetPriorityGrouping(3);
//配TIM6的中断优先级为2
NVIC_SetPriority(TIM6_IRQn,2);
//使能TIM6中断
NVIC_EnableIRQ(TIM6_IRQn);
//开启定时器
TIM6->CR1 |=TIM_CR1_CEN;
}
//中断服务程序
void TIM6_IRQHandler(void){
//初始状态进行中断手动清零
TIM6->SR &=~TIM_SR_UIF;
//灯翻转
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_4);
}