STM32的HAL库开发—TIMER(定时器) — 基本定时器
一、定时器概述
1.1 软件定时原理
使用纯软件(CPU死等)的方式实现定时(延时)功能。就是delay_us()函数进行延迟,延迟时间是不精准的,同时CPU还在被占用,浪费资源。
1.2 定时器定时定时原理
使用精准的时基,通过硬件的方式,实现定时功能,定时器核心就是计数器。
STM32系统时钟线CLK经过预分频器,得到定时器TIM_CLK工作时钟,每来一个时钟信号,计数器CNT就变化1,这个计数器可以递增,也可以递减,当计数器CNT溢出的时候就代表时间到了,时间到了之后可以产生中断也可以产生事件,同时自动重装载值就会重新装在到计数器里边。
1.3 STM32定时器分类
1.4 STM32定时器特性表
计时器位数为16,就是计数器的值可以是0-65535之间。
计时器模式:
预分频系数:必须为整数,就是将系统时钟信号进行分频,分频就是除一个系数。
产生DMA请求:当计时器溢出时,就是时间到了,可以产生DMA请求。
捕获/比较通道:基本定时器没有,只有通用和高级定时器有。
互补输出:只有高级定时有。
1.5 STM32基本、通用、高级定时器功能整体的区别
基本定时器只能用作定时功能,,没有输入输出通道,通用定时器具有基本定时器的功能,同时还可以输入捕获/输出比较。高级定时器具备通用定时器的左右功能,同时还有互补输出等功能。
二、基本定时器
2.1 基本定时器简介
基本定时器时TIM6和TIM7,有一个16位的递增计数器,不能递减,计数值为0~65535。
有一个16位预分频器,分频系数为1~65536。
可触发DAC,当计数器溢出的时候,可进行一次数模转化。
在更新事件(计数器溢出)时,可产生中断/DMA请求,这个月由用户自己设置。
2.2 基本定时器框图
1、时钟源来自内部时钟
2、时钟信号经过控制器来到预分频器,经过预分频器PSC分频,得到基本定时器的工作时钟CK_CNT,每来一个时钟,计数器的值就加一。
3、当计数器CNT的值与自动重装载寄存器ARR的影子寄存器的值相同时,达到溢出条件,发生溢出。
图中可以看到,自动重装载寄存器和预分频器都有影子,这俩都有影子寄存器。影子寄存器是实际
起作用的寄存器,不可直接访问。例如不可以直接访问自动重装载寄存器的影子寄存器,但是可以访问自动重装载寄存器,这个寄存器起到缓冲的作用。
而写入ARR寄存器的值不能马上起作用,必须转移到影子寄存器里边才能起作用。转移到影子寄存器里需要有条件,就是事件。PSC寄存器也是一样的,必须转移到影子寄存器里边才能起作用。
这个是图解, U事件就是Update事件,当计数器CNT溢出的时候,除了产生更新事件,还可以产生中断和DMA输出。默认情况下U事件是默认产生的,可以配置成不产生。而中断和DMA输出默认是不产生的,可以配置成产生。
除了计数器溢出的时候产生更新事件,设置UG位也可以软件产生更新事件。
产生U更新事件后,预装载寄存器的值会加载到对应的影子寄存器里边,包括自动重装载寄存器和PSC预分频器。这里边可以设置ARR寄存器有没有缓冲功能,设置APRE位,置1就是有缓冲功能,写入ARR寄存器不会立即转移到影子寄存器里边。置0会就是无缓冲作用,会立即转移到影子寄存器里边。
控制部分:
可以在控制寄存器设置复位、使能、计数等。触发控制器就是触发DAC产生一次DAC转换,TRGO信号在计数器溢出的时候产生。
时钟源:
TIM1和TIM8是挂在在APB2上面的,TIM2-TIM7是挂载在APB1上面的,其中APB2最大频率是72MHz,APB1最大稳定频率为36MHz。但是实际定时器最大时钟频率不是这么确定的,看下面时钟树。
程序里边有一个HAL初始化配置,将APB1预分频系数配置成2,那么满足前面时钟数里边说的的”如果APB1预分频系数=1则频率不变,否则频率x2“,分频系数不是1,所以乘2得到72MHz时钟,前提是PCLK1时钟是36MHz。
同理,程序里边设置的APB2预分频系数为1,那个满足时钟树里边说的"如果APB2预分频系数=1则频率不变,否则频率x2",分频系数为1,所以TIM1和TIM8时钟频率为72MHz,前提是PCLK2时钟为72MHz。
2.3 STM32定时器计数模式及溢出条件
递增计数模式:溢出条件为计数器的值与ARR影子寄存器值相同。 发生溢出之后计数器从0开始重新计数。
递减计数模式:溢出条件为CNT值为0,发生溢出之后计时器从ARR重新开始递减。
中心对齐模式:溢出条件有两个,一个是CNT值为ARR-1,另一个是CNT的值为1。计时器从0开始递增,递增到ARR-1发生溢出。然后从ARR开始递减,递减到1发生溢出。就是递增的时候在ARR-1发生溢出,递减的时候在1发生溢出。
递增和递减的就不说了,很简单 这里讲一下中心对齐模式的时序图。
其中CK_PSC是定时器的系统时钟信号。
CNT_EN是计时器使能,开启之后计数器才开始工作。
CK_CNT是计数器时钟,由CK_PSC经过预分频器得到的时钟,由于PSC为0,需要加1,也就是1分频,就是不分频。如果PSC的值为1,就是二分频,每来两个CK_PSC,定时器时钟CK_CNT来一个,计数器的值变化一个。
最开始计数器从04开始递减,递减到01的时候,满足下溢条件,产生更新事件,同时更新中断标志置1。然后递减到0,开始递增,递增到ARR-1了,满足上溢条件,再次产生更新事件,同时更新中断标志置1,如果程序里边开中断了,那么会进入中断服务函数,需要手动讲这个更新中断标志清0;
2.4 定时器中断实验相关寄存器
2.4.1 TIM6 和TIM7 控制寄存器 1(TIMx_CR1)
位 7 ARPE:置1则ARR寄存器有缓冲功能,需要等更新事件来了,才能将ARR寄存器的值写入到ARR影子寄存器里边。置0,则没有缓冲功能,不需要更新事件,立即写入影子寄存器。
例子:让一个LED亮1s,再灭2s
亮1s,就是使用定时器设置ARR寄存器的值,例如ARR寄存器设置100,为1s,那么当CNT计数器的值等于100的时候,发生溢出,此时在中断函数里边将LED灭,同时需要修改ARR寄存器的值,因为灯需要灭2s,如果没有缓冲功能,那么修改ARR寄存器的值时间被计入进去了,时间不准确。如果开启了ARR缓冲功能,那么在ARR到达100之前,就可以写入ARR寄存器的值,等到更新时间发生时,就可以自动写入到影子寄存器里边,去掉了无缓冲写入ARR寄存器的时间,定时时间更加精准,无误差。如果是亮1s,再灭1s,那么就不需要更改ARR寄存器的值了,就没有这个说法了。
位 0 CEN:计数器使能位 ,置1则开启计数器,置0关闭计数器。默认是关闭的。
用于设置ARR寄存器是否具有缓冲,使能/关闭计数器。
2.4.2 TIM6 和TIM7 DMA/中断使能寄存器(TIMX_DIER)
位8 UDE:置0 关闭更新DMA请求 ,置1开启更新DMA请求。
位0 UIE:置0关闭更新中断,置1开启更新中断。
2.4.3 TIM6 和TIM7 状态寄存器(TIMX_SR)
位0 UIF: 更新中断标志位,当计数器发生溢出时,这位会被硬件置1,如果使用更新中断,需要再中断函数里边使用软件将这一位清除。
用于判断是否发生了更新中断,由硬件置1,软件清零。
2.4.4 TIM6 和TIM7 计数器(TIMx_CNT)
16位计数器,存储计数器的值,范围为0-65535。可读可写,在计数器运行当中,也可以对它进行读写操作。
2.4.5 TIM6 和TIM7 预分频器(TIMx_PSC)
设置定时器时钟分频系数的,实际计算分频的时候,需要将这个寄存器的值加1。在每一次更新事件时,PSC的数值被传送到实际的预分频寄存器中。
2.4.5 TIM6 和TIM7 自动重装载寄存器(TIMX_ARR)
设置自动重装载值得,ARR的数值将传送到实际的自动重装载寄存器中。
2.5 定时器溢出时间计算方法
这个公式很简单,可以自己推一下,主要就是先计算计数器计一个数需要的时间,在去乘总共计多少个数。这里的(PSC + 1)表示分频系数加一才是真正的分频数,比如PSC寄存器设置的为0,那么他是1分频,不可能是0分频。(ARR + 1)表示计数总个数,如果设置为0,那么它计一个数就溢出了,不可能是计0个数。
2.6 定时器中断实验配置步骤
1、HAL_TIM Base_Init()函数,配置定时器基础工作参数。
2、HAL_TIM_Base_MspInit()函数,定时器基础MSP初始化,配置NVIC、CLOCK等。
3、HAL_TIM_Base_Start_IT()函数,使能更新中断并启动计数器。
4、HAL_NVIC_SetPriority()、 HAL_NVIC_EnablelRQ()函数设置优先级,使能中断。
5、TIMx_IRQHandler()>HAL_TIM_IRQHandler()函数,编写中断服务函数。
6、HAL_TIM_PeriodElapsedCallback()函数,编写定时器更新中断回调函数。
2.7 定时器实验中断
实验:使用定时器6,实现500ms定时器更新中断,在中断里翻转LED0。
定时器产生中断有两种方式,一种是软件设置UG位,一种是计数器溢出时,产生中断。
思路:定时500ms带入前面的公式,TIM6是挂载在APB1总线上面的,APB1的时钟为36MHz,APB1的分频系数为2,不为1,所以TIM6的时钟为36MHz乘2,就是72MHz。剩下ARR和PSC两个未知数,任取一个计算出另一个,一般是取PSC,计算出ARR的值。例如设置PSC的值为7199,那么计算出ARR的值为4999。
btim.h头文件
#ifndef __BTIM_H
#define __BTIM_H
#include "stm32f1xx.h"
void TIM_Init(uint16_t arr ,uint16_t psc);
#endif
btim.c源程序
#include "./BSP/TIMER/btim.h"
#include "./BSP/LED/led.h"
TIM_HandleTypeDef htim;
void TIM_Init(uint16_t arr ,uint16_t psc)
{
htim.Instance = TIM6;
//设置开启ARR寄存器缓冲功能 这里设置固定500ms 如果定时时间不同 需要修改ARR的值 需要开启
htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
//基本定时器只有向上计数模式 这个不设置也可以
htim.Init.CounterMode =TIM_COUNTERMODE_UP;
//设置自动重装载值
htim.Init.Period = arr;
//分频系数
htim.Init.Prescaler = psc;
HAL_TIM_Base_Init(&htim);
HAL_TIM_Base_Start_IT(&htim);
}
//这个函数由HAL_TIM_Base_Init自动调用
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
//这个函数是所有定时器公用的 所以需要判断 如果使用了多个定时器
if(htim->Instance == TIM6)
{
//开启定时器6时钟
__HAL_RCC_TIM6_CLK_ENABLE();
//设置定时器6优先级
HAL_NVIC_SetPriority(TIM6_IRQn, 2, 2);
//开启定时器中断
HAL_NVIC_EnableIRQ(TIM6_IRQn);
}
}
//定时器6中断处理函数
void TIM6_IRQHandler(void)
{
//定时器6HAL库中断公共处理函数
HAL_TIM_IRQHandler(&htim);
}
//定时器TIM Update event回调函数 HAL_TIM_IRQHandler里边自动调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//这个函数是所有定时器公用的 所以需要判断 如果使用了多个定时器
if(htim->Instance == TIM6)
{
LED0_TOGGLE();
}
}
main.c主函数
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/btim.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_Init(); /* LED初始化 */
TIM_Init(5000 - 1,7199); //初始化定时器6 ARR设置成4999 ,PSC设置成 7199
while(1)
{
}
}
现象: 正电平时间500ms
作者:猿~~~