目录
一、TIM(Timer)定时器简介
二、定时器类型
2.1基本定时器结构
2.2通用定时器结构
2.3高级定时器结构
三、定时中断基本结构
四、时序图分析
4.1 预分频器时序
4.2 计数器时序
4.3 计数器无预装时序(无影子寄存器)
4.4 计数器有预装时序(有影子寄存器)
五、RCC时钟树
六、开发步骤
七、定时器函数
八、实验
8.1定时器定时中断
8.2定时器外部时钟
一、TIM(Timer)定时器简介
①定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
②16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时计数器:执行计数定时的一个寄存器,每来一个时钟,计数器加1
预分频器:对计数器的时钟进行分频,让这个计数更加灵活
自动重装寄存器:计数的目标值,计多少个时钟来申请一次中断
③不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
④根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
二、定时器类型
2.1基本定时器结构
主模式触发DAC功能:把定时器的更新事件映射到触发输出TRGO(Trigger Out)的位置,TRGO直接接到DAC的触发转换引脚上,这样就不需要通过中断来触发DAC转换了。实现了硬件的自动化。
通用定时器和高级定时器除了向上计数模式,还有向下计数模式和中央对齐模式
2.2通用定时器结构
2.3高级定时器结构
三、定时中断基本结构
四、时序图分析
4.1 预分频器时序
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
4.2 计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)
计数器时间:(PSC + 1)(ARR + 1)/CK_PSC
4.3 计数器无预装时序(无影子寄存器)
4.4 计数器有预装时序(有影子寄存器)
五、RCC时钟树
作用:产生和配置时钟,将配置好的时钟发送到各个外设系统
开发技巧:
在SystemInit函数中,首先启动内部8MHz时钟为系统时钟运行,然后再启动外部时钟,进入PLL锁相环进行倍频,8MHz倍频9倍,得到72MHz,锁相环输出稳定之后,选择锁相环输出为系统时钟,这样就把系统时钟由8MHz变成了72MHz。
实际问题:
如果外部晶振出现问题,程序时钟慢了大概10倍,定时器定时1s,结果过了大概10s才进中断。是因为现在是以内部时钟8MHz运行的
六、开发步骤
①RCC打开时钟
②选择时基单元的时钟源
③结构体配置时基单元(预分频器,自动重装器,计数模式)
④配置输出中断控制,允许更新中断输出到NVIC
⑤配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
⑥运行控制,使能计数器
⑦写定时器中断函数
七、定时器函数
=====================================================================
=================================基本函数=============================
void TIM_DeInit(TIM_TypeDef* TIMx);
//定时器恢复缺省配置
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//时基单元初始化
//第一个参数:某个定时器,第二个参数:结构体
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
//把结构体变量赋默认值
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
//使能计数器
//第一个参数:TIMx选择定时器,第二个参数:使能或失能
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
//使能中断输出信号(中断输出控制)
//第一个参数:TIMx选择定时器,第二个参数:哪个中断输出,第三个参数:使能或失能
=====================================================================
=========================配置时钟输入的函数=============================
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
//选择内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//选择ITRx其他定时器时钟
//第一个参数:选择配置哪个定时器,第二个参数:选择要接入哪个定时器
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,uint16_t TIM_ICPolarity, uint16_t ICFilter);//选择TIx捕获通道的时钟
//第一个参数:TIMx,第二个参数:TIMx具体哪个引脚,第三、四个参数:输入极性和滤波器
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//ETR通过外部时钟模式1输入时钟
//第一个参数:TIMx,第二个参数:外部触发预分频器,第三、四个参数:输入极性和滤波器
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);//ETR通过外部时钟模式2输入时钟
//第一个参数:TIMx,第二个参数:外部触发预分频器,第三、四个参数:输入极性和滤波器
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);//单独配置ETR引脚的预分频器、极性、滤波器
//第一个参数:TIMx,第二个参数:外部触发预分频器,第三、四个参数:输入极性和滤波器
=====================================================================
===================更改关键参数函数(预分频值,自动重装载值)==============
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
//修改预分频值
//第一个参数:TIMx,第二个参数:预分频值,第三个参数:写入模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);//修改计数器的计数模式
//第一个参数:TIMx,第二个参数:新的计数器模式
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//自动重装器预装功能配置
//第一个参数:TIMx,第二个参数:预装功能使能或失能
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
//给计数器值
//第一个参数:TIMx,第二个参数:计数器值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);//给自动重装器写入值
//第一个参数:TIMx,第二个参数:自动重装值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
//获取计数器值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);//获取预分频值
=====================================================================
============================获取和清除标志位函数========================
//主函数
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);
八、实验
8.1定时器定时中断
实验现象:1s计数加一
代码实现:
Timer.c
#include "stm32f10x.h" // Device headervoid Timer_Init(void) {/*一、RCC开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);/*二、选择时基单元的时钟源*/TIM_InternalClockConfig(TIM2);//选择内部时钟/*三、配置时基单元(预分频器,自动重装器,计数模式)*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//采样频率(内部时钟+时钟分频)//不分频TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//计数器模式TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR自动重装器值TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器值(高级定时器才有)TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);/*避免刚初始化就进入中断(复位就是1,而不是0)*/TIM_ClearFlag(TIM2,TIM_FLAG_Update);/*四、配置输出中断控制,允许更新中断输出到NVIC*/TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启更新中断到NVIC的通路/*五、配置NVIC,在NVIC打开定时器中断的通道,分配优先级*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//中断通道NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);/*六、运行控制,使能计数器*/TIM_Cmd(TIM2,ENABLE); }/*中断函数模板 void TIM2_IRQHandler(void) {if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2,TIM_IT_Update);} } */
Timer.h
#ifndef __TIMER_H #define __TIMER_Hvoid Timer_Init(void);#endif
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Timer.h"uint16_t Num;int main(void) {OLED_Init();Timer_Init();OLED_ShowString(1,1,"Num:");OLED_ShowString(2,1,"CNT:");while (1){OLED_ShowNum(1,5,Num,5);OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);//观察CNT计数器值的变化情况} }void TIM2_IRQHandler(void) {if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){Num ++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update);} }
8.2定时器外部时钟
方法:定时器指定的外部引脚输入一个方波信号,来提供定时器计数的时钟
实验现象:利用对射式红外传感器来手动模拟一个外部时钟,用挡光片,依次遮挡、移开来模拟一个方波,定时器计数值(CNT)逐次加一,当CNT到9后,产生一次中断,Num加一,CNT清零重新计数
代码实现:
Timer.c
#include "stm32f10x.h" // Device headervoid Timer_Init(void) {/*一、RCC开启时钟 + GPIOA的初始化*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);/*二、选择时基单元的时钟源*/TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);//通过ETR的外部时钟模式2,不分频,不反向(高电平/上升沿有效),外部触发滤波器/*三、配置时基单元(预分频器,自动重装器,计数模式)*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//ARR自动重装器值TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//PSC预分频器值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器值TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);/*避免刚初始化就进入中断(复位就是1,而不是0)*/TIM_ClearFlag(TIM2,TIM_FLAG_Update);/*四、配置输出中断控制,允许更新中断输出到NVIC*/TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);/*五、配置NVIC,在NVIC打开定时器中断的通道,分配优先级*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);/*六、运行控制,使能计数器*/TIM_Cmd(TIM2,ENABLE); }/*查看CNT的值*/ uint16_t Timer_GetCounter(void) {return TIM_GetCounter(TIM2); }/*中断函数模板 void TIM2_IRQHandler(void) {if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2,TIM_IT_Update);} } */
Timer.h
#ifndef __TIMER_H #define __TIMER_Hvoid Timer_Init(void); uint16_t Timer_GetCounter(void);#endif
main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Timer.h"uint16_t Num;int main(void) {OLED_Init();Timer_Init();OLED_ShowString(1,1,"Num:");OLED_ShowString(2,1,"CNT:");while (1){OLED_ShowNum(1,5,Num,5);OLED_ShowNum(2,5,Timer_GetCounter(),5);//观察CNT计数器值的变化情况} }void TIM2_IRQHandler(void) {if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET){Num ++;TIM_ClearITPendingBit(TIM2,TIM_IT_Update);} }