文章基于适用于STM32F4系列,作者使用STM32F401CCU6开发板。
本文章基于此系列和开发板展开讨论。

定时器

什么是定时器

定时器其实就是一种计数器,它可以计算边沿(或电平)的次数,如果这个电平变化的频率确定,则可以根据这个计数值来区分时间。

定时器分类

分为高级定时器,通用定时器,基本定时器
这三个分别是向下包含的,也就是说高级定时器功能包含通用定时器和基本定时器,通用定时器功能包含基本定时器

高级定时器比通用定时器多了关于电机驱动输出功能,需要使用时再细说

基本定时器的功能和数量较少,一般用于DAC(数模转换)和DMA(直接存储器访问)且只能使用内部时钟

一般情况我们使用通用定时器即可,本文重点介绍通用定时器

STM32F4定时器

类型编号分辨率计数方向DMA请求挂载总线
高级TIM1,(TIM8)16增 / 减APB2
通用TIM2,TIM532增 / 减APB1
通用TIM3,TIM416增 / 减APB1
通用TIM9,TIM10,TIM116APB2
通用(TIM12,TIM13,TIM14)16APB1
基本(TIM6,TIM7)16APB1

加括号的是作者使用的单片机没有的定时器

因为系统时钟的频率较高,使用定时器时需要的频率可能较低,因此可以预先分频 预分频的值为1-65535

通用定时器

基础介绍

时钟源

  1. 内部时钟
  2. 外部时钟1
  3. 外部时钟2
  4. 其他定时器的溢出

复位时默认为内部时钟,一般情况不需要使用内部时钟即可。

需要修改请参考 定时器时钟源选择

使用内部时钟时,默认时所有的定时器的输入时钟均为系统时钟,详情见
时钟系统介绍

计数模式

向上计数模式

从0开始计时,到自动加载值ARR(后文初始化时会介绍),归零,这时产生一次溢出

黑色箭头处产生溢出
在这里插入图片描述

向下计数模式

从自动加载值ARR(后文初始化时会介绍)开始计时,到0,回到ARR,这时产生一次溢出

黑色箭头处产生溢出
在这里插入图片描述

中央对齐模式

从0到自动加载值ARR-1(后文初始化时会介绍),此时产生一次溢出,后从自动加载值ARR(后文初始化时会介绍)开始计时,到1,此时产生一次溢出

黑色箭头处可以产生比较事件
在这里插入图片描述

初始化

本文使用内部时钟

初始化思路

使用内部时钟的初始化流程

  1. 打开定时器时钟(注意要打开对应的时钟)
  2. 配置定时器初始化结构体
  3. 初始化定时器

1.打开定时器时钟

使用这两个函数之一打开时钟

void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)

注意:不同的定时器挂载的总线不同

TIM2,TIM3,TIM4,TIM5,TIM12,TIM13,TIM14挂载在APB1

TIM9,TIM10,TIM11挂载在APB2

APB1总线下的名称

RCC_APB1Periph_TIM2
RCC_APB1Periph_TIM3
RCC_APB1Periph_TIM4
RCC_APB1Periph_TIM5
RCC_APB1Periph_TIM12
RCC_APB1Periph_TIM13
RCC_APB1Periph_TIM14

APB2总线下的名称

RCC_APB2Periph_TIM9
RCC_APB2Periph_TIM10
RCC_APB2Periph_TIM11

例子

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

打开TIM2的时钟

2. 配置定时器初始化结构体

typedef struct
{
  uint16_t TIM_Prescaler;						//预分频
  uint16_t TIM_CounterMode; 					//计数模式
  uint32_t TIM_Period; 							//定时器周期
  uint16_t TIM_ClockDivision; 					//定时器时钟分频因子
  uint8_t TIM_RepetitionCounter;				//重载周期(高级定时器特有)
} TIM_TimeBaseInitTypeDef; 

TIM_Prescaler

预分频器,设置这个可以选定定时器的时钟频率,将总线时钟分频后输入 其数值为1-65535
定时器时钟频率= 系统频率/(TIM_Prescaler+1)

TIM_CounterMode

这里计数模式有三种,上文介绍过
其取值为

TIM_CounterMode_Up								//向上计数
TIM_CounterMode_Down							//向下计数
TIM_CounterMode_CenterAligned1					//中央对齐模式1
TIM_CounterMode_CenterAligned2					//中央对齐模式2
TIM_CounterMode_CenterAligned3					//中央对齐模式3

向上和向下计数产生溢出事件,

中央对齐模式1在向下计数时产生比较事件

中央对齐模式2在向上计数时产生比较事件

中央对齐模式3在向上和向下计数时产生比较事件

中央对齐模式可以使用向上和向下计数来代替

TIM_Period

定时器周期,定时器随输入的时钟计数,一共计数 TIM_Period+1
TIM_Period也就是能达到的最大值,即上文介绍计数模式时纵坐标的最大值(最小值为0)

16位范围为0-0xFFFF
32位范围为0-0xFFFFFFFF

TIM_ClockDivision

定时器时钟分频因子ClockDivision是决定数字滤波器采样频率的参数。

没怎么使用过,一般设置为 不分割(TIM_CKD_DIV1)即可

TIM_RepetitionCounter

高级定时器特有,通用定时器设置为0就行,

定时器的时间

使用内部时钟,默认时钟设置,向上或向下计数模式

溢出时间 = [ ( TIM_Period + 1 ) * ( TIM_Prescaler + 1 ) / ( SystemCoreClock ) ] ( s )

间隔时间= [ 两次计数值的差 * ( TIM_Prescaler + 1 ) ] / ( SystemCoreClock ) ] ( s )

3. 初始化定时器

使用这个函数初始化定时器

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)

第一个输入是定时器号,取值可以是

TIM1 到 TIM14

第二个输入是定时器初始化结构体的地址,请使用取址符 ( & )

例子

TIM_TimeBaseInitTypeDef TIM_Init_Struct;						//声明定时器初始化结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);				//打开时钟
TIM_Init_Struct.TIM_ClockDivision=TIM_CKD_DIV1;				//滤波器不分频
TIM_Init_Struct.TIM_CounterMode=TIM_CounterMode_Up;			//向上计数模式

//每次中断触发时间=[(TIM_Period+1)*(TIM_Prescaler+1)/(SystemCoreClock)] (s)
//这里是5ms
TIM_Init_Struct.TIM_Period=839;
TIM_Init_Struct.TIM_Prescaler=499;

TIM_Init_Struct.TIM_RepetitionCounter=0;						//高级定时器特有,这里写0就行
TIM_TimeBaseInit(TIM3,&TIM_Init_Struct);					//调用函数初始化

定时器中断

思路

  1. 初始化定时器
  2. 配置NVIC
  3. 编写中断服务函数
  4. 打开定时器,使能中断

2. 配置NVIC

关于NVIC的内容在之前说过,详情 见中断和NVIC
与通用定时器相关的中断名称如下

有些通用定时器中断和高级定时器中断放在一个中断函数中了

TIM1_BRK_TIM9_IRQn;					
TIM1_UP_TIM10_IRQn;
TIM1_TRG_COM_TIM11_IRQn;
TIM2_IRQn;
TIM3_IRQn;
TIM4_IRQn;
TIM5_IRQn;

(TIM8_BRK_TIM12_IRQn);
(TIM8_UP_TIM13_IRQn);
(TIM8_TRG_COM_TIM14_IRQn);

加括号的为作者使用的单片机没有的定时器

3. 编写中断服务函数

与通用定时器有关中断服务函数名如下

void TIM1_BRK_TIM9_IRQnHandler(void);
void TIM1_UP_TIM10_IRQHandler(void);
void TIM1_TRG_COM_TIM11_IRQHandler(void);
void TIM2_IRQHandler(void);
void TIM3_IRQHandler(void);
void TIM4_IRQHandler(void);
void TIM5_IRQHandler(void);

{void TIM8_BRK_TIM12_IRQnHandler(void);
void TIM8_UP_TIM13_IRQnHandler(void);
void TIM8_TRG_COM_TIM14_IRQnHandler(void);}

加大括号的为作者使用单片机的没有的定时器

4. 打开定时器,使能中断

使用这个函数设置中断使能

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)

第一个输入是选择哪个定时器
第二个输入是选择那种中断
第三个输入是选择使能或失能

定时器的中断和外部中断不同,有不同种类的中断,可以是溢出,可以是捕获等等

一般常用的就是溢出

TIM_IT_Update

使用这个函数打开定时器

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)

第一个输入是选择哪个定时器
第二个输入是选择使能或失能

例子

初始化和NVIC配置

TIM_TimeBaseInitTypeDef TIM_Init_Struct;						//声明定时器初始化结构体
NVIC_InitTypeDef NVIC_Init_Struct;								//声明NVIC初始化结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);				//打开时钟
TIM_Init_Struct.TIM_ClockDivision=TIM_CKD_DIV1;				//滤波器不分频
TIM_Init_Struct.TIM_CounterMode=TIM_CounterMode_Up;			//向上计数模式

//每次中断触发时间=[(TIM_Period+1)*(TIM_Prescaler+1)/(SystemCoreClock)] (s)
//这里是5ms
TIM_Init_Struct.TIM_Period=839;
TIM_Init_Struct.TIM_Prescaler=499;
TIM_Init_Struct.TIM_RepetitionCounter=0;					//高级定时器特有,这里写0就行
TIM_TimeBaseInit(TIM3,&TIM_Init_Struct);					//调用函数初始
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);					//启用溢出中断


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2
NVIC_Init_Struct.NVIC_IRQChannel=TIM3_IRQn;					//中断名称
NVIC_Init_Struct.NVIC_IRQChannelCmd=ENABLE;					//使能
NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority=1;		//主优先级1
NVIC_Init_Struct.NVIC_IRQChannelSubPriority=1;				//副优先级1
NVIC_Init(&NVIC_Init_Struct);								//初始化NVIC
TIM_Cmd(TIM3,ENABLE);										//打开定时器

中断服务函数

void TIM3_IRQHandler(void)
{
	if( TIM_GetITStatus(TIM3,TIM_IT_Update)  != RESET)
	{
		/*需要执行代码*/
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);		//将中断标志清除
	}
}

与定时器有关的常用函数

中断标志相关

TIM_GetITStatus

原型

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT)
名称描述
输入1定时器编号
输入2标志类型
输出设置或未设置

输出

RESET				//没有触发
SET					//已经触发

功能描述:判断选定定时器的选定中断标志的状态

TIM_ClearITPendingBit

原型

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
名称描述
输入1定时器编号
输入2标志类型
输出

功能描述:将选定定时器的选定中断标志清除(设置为RESET)

计数值相关

TIM_GetCounter

原型

uint32_t TIM_GetCounter(TIM_TypeDef* TIMx)
名称描述
输入定时器的编号
输出当前计数值

功能描述:获取选定的定时器当前的计数值

TIM_SetCounter

原型

void TIM_SetCounter(TIM_TypeDef* TIMx, uint32_t Counter)
名称描述
输入1定时器编号
输入2要设置的计数值
输出

功能描述:设置选定的定时器的计数值

TIM_CounterModeConfig

原型

void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode)
名称描述
输入1定时器编号
输入2计数模式
输出

功能描述:改变定时器的计数模式

TIM_Cmd

原型

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
名称描述
输入1定时器编号
输入2使能或失能
输出

输入2

ENABLE				//打开
DISABLE				//关闭

功能描述:打开或关闭选定的定时器

总结

定时器的用法较多,STM官方给了许许多多好用的功能,比如输入捕获,输出比较等等,这些可以使用外部中断和基本的定时器设置实现。
可以根据需要选择不同用法。
个人不习惯使用捕获和比较,他们的输入输出通道相对单一,可移植性不好。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐