(九)定时器
1.分类
- 基本定时器:TIM6、TIM7
- 通用定时器:TIM2-5、TIM9-TIM14
- 高级定时器:TIM1、TIM8
1.1.通用定时器
-
STM32F4的通用定时器包含一个 16 位或 32 位自动重载计数器(CNT),该计数器由可编程预分频器(PSC)驱动。
-
通用定时器具有如下功能:
-
16位/32位向上、向下、向上/向下自动装载计数器(TIMx_CNT)
注:仅TIM2和TIM5是32位(在设置period参数时应选用u32类型的);TIM9~TIM4只支持向上(递增)计数
-
16位可编程(可实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间的任意数值
-
可使用外部信号(TIMx_ETR,外部控制管脚,开发板原理图可查)控制定时器,且可实现多个定时器互连(可以用1个定时器控制另外一个定时器)的同步电路(很少使用)
-
支持针对定位的增量(正交)编码器和霍尔传感器电路(TIM9-TIM14不支持)
-
触发输入作为外部时钟或者按周期的电流管理( TIM9-TIM14不支持)
-
发生如下事件时产生中断/DMA请求(TIM9-TIM14不支持DMA):
- 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
-
4个独立通道(TIMx_CH1-4,TIM9-TIM14最多2个通道),用来作为:
- 输入捕获
- 输出比较
- PWM生成(边缘对齐或中间对齐,其中,TIM9~TIM14不支持中间对齐)
- 单脉冲模式输出
-
-
STM32F4的每个通用定时器都是完全独立的,没有互相共享的任何资源。
1.2.通用定时器结构图
stm32f4xx.h文件中可以找到各种总线时钟的定义。
2.通用定时器配置步骤
-
使能定时器时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);//使能TIM4时钟
-
初始化定时器参数,包含自动重装值,分频系数,计数方式等,将stm32f4xx_time.c文件添加到工程组中、stm32f4xx_time.h添加文件路径
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); typedef struct { uint16_t TIM_Prescaler; //定时器预分频器 uint16_t TIM_CounterMode; //计数模式:向上计数、向下计数、中心对齐 uint32_t TIM_Period; //定时器周期:此处是uint32_t表示32位(仅对应TIM2和TIM5),其余是uint_16即16位 uint16_t TIM_ClockDivision; //时钟分频 uint8_t TIM_RepetitionCounter; //重复计数器 } TIM_TimeBaseInitTypeDef;
-
设置定时器中断类型,并使能
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
-
设置定时器中断优先级,使能定时器中断通道
NVIC_Init();//初始化库函数
-
开启定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
-
编写定时器中断服务函数
TIM4_IRQHandler//中断服务函数名 ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);//获取中断状态标志,参数分别是定时器名和中断类型 if(TIM_GetITStatus(TIM4,TIM_IT_Update)) { ...//执行TIM4更新中断内控制 } void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);//清除中断状态标志
3.编程操作
注:可以先将外部中断的程序移除(exti.c和exti.h)
-
新建文件夹在APP内,并新建文件time.c和time.h将两个文件加入工程组、添加路径
-
编写time.h程序如下:
#ifndef _time_H #define _time_H #include "system.h" #endif
-
编写time.c程序如下:
#include "time.h" void TIM4_Init(u16 pre,u16 psc)//初始化函数:第一个u16类型参数的原因是TIM4定时器是16位的 { NVIC_InitTypeDef NVIC_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE); TIM_TimeBaseInitStructure.TIM_Period=pre; TIM_TimeBaseInitStructure.TIM_Prescaler=psc;//通过配置该项,将时钟输入的时钟源经过预分频成为的时钟 TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_1;//配置该项一般在滤波器使用时,在此时是不用的则设置为1分频 TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //TIM_TimeBaseInitStructure.TIM_RepetitionCounter通用定时器不用配置此项,此项用于高级定时器 TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure); TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//第二个参数指的是更新中断 TIM_ClearITPendingBit(TIM4,TIM_IT_Update);//在程序伊始无法判断定时中断标志位情况时,先将其清除 NVIC_InitStructure.NVIC_IQRChannel=TIM4_IQRn;//配置中断源,此处要进行修改成定时器4 NVIC_InitStructure.NVIC_IQRChannelPreemptionPriority=2;//抢占优先级 NVIC_InitStructure.NVIC_IQRChannelSubPriority=3;//响应优先级 NVIC_InitStructure.NVIC_IQRChannelCmd=ENABLE;//使能中断 NVIC_Init(&NVIC_InitStructure);//初始化结构体 TIM_Cmd(TIM4,ENABLE); } void TIM4_IRQHandler(void)//中断服务函数 { if(TIM_GetITStatus(TIM4,TIM_IT_Update)) { led2=!led2; } TIM_ClearITPendingBit(TIM4,TIM_IT_Update); }
-
完成函数的声明:
#ifndef _time_H #define _time_H #include "system.h" void TIM4_Init(u16 pre,u16 psc); void TIM4_IRQHandler(void); #endif
-
根据需要完成主函数的编写:
#include "system.h" #include "zs_pro_standard.h" #include "SysTick.h" #include "time.h" int main() { u8 i;//不知名原因,此行内容不能放到下一行后面 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//进行优先级位的分组 IpO_Init(); TIM4_Init(5000-1,8400-1);//定时500ms //计数器从5000开始计数,计数至0时刚好为5001次,因此要减去一次,8400同理减一 //84MHz在预分频系数为8400状态下的频率变为10KHz,即计数一次为0.1ms while(1) { i++; if(i%20==0) { led1=!led1; } delay_ms(10); } }
4.时间轮询
-
以一定时间间隔轮番查询是否到达指定时间并对任务进行变更
-
步骤如下:
- 在单片机初始化任务完成以后,再初始化基频定时器;
- 设置用于计数的参数,用于记录进入中断服务函数的次数,达到指定次数时将相应任务的标志位更改;
- main函数中使用 switch 或 if 对标志位进行判断,实现任务的内容或数量的变更。
-
优点:
-
能够实现伪多线程
-
定时相对准确
-
相较于使用 delay 函数实现的任务切换,能够及时进行任务变更
假设 GPIOA9 管脚连接光电开关
-
使用 delay 函数时:
当光电开关检测到物体、引起管脚电平变化时,应当及时触发下一步的任务变更。但是由于使用了 delay 函数,系统停滞在其内部的 while 循环中无法跳出,任务变更这一事件就无法得到及时触发。
-
使用时间轮询时:
当光电开关检测到物体、引起管脚电平变化时,在基频定时器的中断服务函数中会将下一步任务的标志位更改。当跳出中断后,main函数就会立即对任务是否变更进行判断。
-
-
-
值得注意的是:
- 基频的大小能否满足响应速度的需要
- 基频太小时,任务切换快,但是由于频繁开启中断会导致处理速度变慢
- 基频太大时,任务且缓慢,如果有电平变化时间小于基频对应的时间,可能会检测不到
- 中断服务函数中不宜使用大量计算或处理程序,避免中断占用时间过长
- 基频的大小能否满足响应速度的需要