目录
一、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 header void 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_H void 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 header void 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_H void 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); } }