定时器,记录高精度晶振脉冲,可输出准确的时间间隔。
计数器,记录外设提供的随机性脉冲信号,用于反映脉冲的个数。
定时器本质上就是计数器,前者对时钟脉冲进行计数,后者对外来脉冲进行计数。
定时的三种方法:软件定时、不可编程的硬件定时、可编程的定时
软件定时:
- 利用delay函数
因为CPU执行每条指令需要花费一定的时间,增加循环重复次数即可达到延时目的
优点:不用增加硬件,可降低CPU利用率。
不可编程的硬件定时:
- 采用数字电路中的分频器将系统时钟进行适当的分频从而产生需要的定时信号。
- 采用单稳电路或简易定时电路(如555定时器)由外接RC电路控制定时时间。
缺点:定时范围不好通过程序控制,使用不便,且精度不高。
可编程的定时:
- 软硬结合,用可编程定时器芯片构成一个方便灵活的定时计数电路(Intel 8253)。
优点:定时值和定时范围都能通过程序控制,且拥有多种工作方式、可以输出多种控制信号。以MCU的时钟信号提供时间基准,所以精度也高。
STM32定时器种类
STM32总共有11个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2~5)和2个基本定时器(TIM6、TIM7),除此之外还有2个看门狗定时器(IWDG和WWDG)、1个系统滴答定时器(SysTick)
前三种定时器的特点如下:
高级定时器具有捕获/比较通道,也能够产生三对PWM互补输出,通用定时器只有捕获/比较通道,基本定时器都没有
一般用到最多的是高级定时器和通用定时器,而高级定时器一般也只用通用定时器的功能。
STM32通用定时器简介
- 由可编程预分频器(PSC)驱动的16位自动装载计数器(CNT)构成。
- 适用场合:测量输入信号的脉冲长度(输入捕获IC)或者产生输出波形(输出比较OC和 PWM)等。
- 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
- 每个通用定时器都是完全独立,不共享任何资源。
功能:
- 16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)。
- 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值。
- 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
输入捕获 IC
输出比较 OC
PWM 生成(边缘或中间对齐模式)
单脉冲模式输出 OPM
- 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
- 如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
更新(UPDate):计数器向上/向下溢出,计数器初始化(通过软件或者内部/外部触发)
触发事件(Trigger)(计数器启动、停止、初始化或者由内部/外部触发计数)
输入捕获 IC(Input Capture)
输出比较 (Output Capture)
支持针对定位的增量(正交)编码器和霍尔传感器电路
触发输入作为外部时钟或者按周期的电流管理
通用定时器框图
四种时钟源选择:
①内部时钟(CK_INT)
②外部时钟模式:外部触发输入(ETR)
③内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
④外部时钟模式:外部输入脚(TIx)
计数器模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
①向上计数模式:easy、②向下计数模式:easy
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数
时基单元
时基单元包括三个寄存器,分别是:计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)和自动装载寄存器(TIMx_ARR)。对这三个寄存器的介绍如下:
计数器寄存器(TIMx_CNT)
向上计数、向下计数或者中心对齐计数;
计数器寄存器(TIMx_CNT)
可将时钟频率按1到65535之间的任意值进行分频,可在运行时改变其设置值;
自动装载寄存器(TIMx_ARR)
如果TIMx_CR1寄存器中的ARPE位为0,ARR寄存器的内容将直接写入影子寄存器;
如果ARPE为1,ARR寄存器的那日同将在每次的更新时间UEV发生时,传送到影子寄存器;
如果TIM1_CR1中的UDIS位为0,当计数器产生溢出条件时,产生更新事件。
定时器中断应用
默认调用SystemInit函数情况下:
SYSCLK=72M、AHB时钟=72M、APB1时钟=36M
所以APB1的分频系数=AHB/APB1时钟=2
所以,通用定时器时钟CK_INT=2*36M=72M
Systick定时器
- 24位倒计数定时器,计数到0时将从RELOAD寄存器中自动重装载定时器初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就不会停,即使在睡眠模式也能工作。
- SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号为15)。
- SysTick可以设置中断优先级
SysTick_Config(SystemCoreClock/1000)的目的是确保SysTick定时器每次发生中断的时间间隔都是1ms(因为使用系统时钟的时钟频率为72M,SystemCoreClock/1000等于72K,也就是给重装载寄存器值赋72000,这样计72000正好是1ms)
注意:毫秒延时函数有最大上限,最大为1864(72MHz时钟频率)。用这种方式来延时的好处是不用中断比较简单,但是坏处就是最大延时时长有限(不到2秒),所以当需要长延时时间的时候要多写几个delay函数。
常用库函数:
/*定时器参数初始化*/
void TIM_TimeBaseInit(TIM_TypeDef* TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);
/*结构体内部成员*/
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
/*一般的声明方式*/
TIM_TimeBaseStructure.TIM_Period = 4999;
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/*定时器使能函数*/
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
/*定时器中断使能函数*/
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
/*状态标志位获取和清除*/
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
定时器中断实现步骤
① 能定时器时钟。 RCC_Configuration();//我喜欢放这
其中包含:RCC_APB1PeriphClockCmd();//去使能TIM的时钟
② 初始化定时器,配置ARR、PSC。 TIM3_Configuration();
其中包含:允许中断更新TIM_ITConfig();
使能定时器TIM_Cmd();
③开启定时器中断,配置NVIC。 NVIC_Configuration();
其中包含:NVIC分组
④ 编写中断服务函数。 TIMx_IRQHandler();
Tout(溢出时间)=(ARR)(PSC)/Tclk
//timer.c源文件
#include "timer.h"
#include "led.h"
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值
//psc:时钟预分频数
/**************通用定时器3中断初始化******************/
/*记得配置NVIC中断通道及优先级*/
void TIM3_Configuration(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = arr - 1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc - 1; //设置用来作为TIMx时钟频率除数的预分频值//这里主动减1以后参数填整数就行
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 ); //使能指定的TIM3中断,允许更新中断
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//sys.c源文件
#include "sys.h"
/*****************RCC配置HSE,主频为72MHz**************/
/*此配置为默认配置*/
void RCC_Configuration(void)
{
RCC_HSEConfig(RCC_HSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PCLK2Config(RCC_HCLK_Div1);
/**********配置RCC时钟,打开对应端口的时钟**********/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//用到哪些都要打开//原本可以不用配置,但这几句放在这,我觉得引用方便
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
}
/***************NVIC中断优先级配置程序********************/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/*调用时记得选择分组,要么在main里加,要么在这里加,配置一次即可*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
/**************用哪个加哪个,加在后面就行***********************/
/*这里用的是TIM3的中断*/
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
}
//main.c源文件
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "timer.h"
int main(void)
{
RCC_Configuration();
Delay_Configuration(); //延时函数初始化
LED_Configuration();
NVIC_Configuration();
TIM3_Configuration(5000,7200);// 72M/5000/7200 = 2HZ,0.5秒一周期
while(1);
}
//中断服务函数
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
LED1=!LED1;
}
}