stm32 学习 定时器定时中断(最详细的Timer定时器介绍)_stm32定时中断的预分频器
TIM定时器
TIM概念
预分频器:可以对计数器的时钟进行分频,让计数更加灵活
自动重装寄存器:设定触发,让计数达到一定数值的时候,进行中断的申请
总结:三部分统称为时基单元,并且都是16位的寄存器,那么最大数值2的16次方,也就是所有数值最大设置,就能实现59.65秒的定时 72MHz / 65536 / 65536 得到中断频率,取倒数就是最大定时时间
最大定时 = (最大计数器)*(最大自动重装数)/72/Mhz
定时器类型
基本定时器
主从模式触发DAC 的功能
DAC即数模转换器(Digital to Analog Converter)
当需要用DAC 输出一段波形的时候(例如PWM),那么就需要每隔一段事件来触发一次DAC,让他输出下一个电压点,如果用正常的更新中断来手动触发DAC 转换的话,就会极大占用资源,影响主程序的运行和其他中断的相应,而在使用主模式下,可以将触发事件映射到触发输出TROG(Trigger Out)的位置然后TRGO就直接接到DAC 这样定时器的更新就不需要经过NVIC 中断响应来触发DAC转换了,过程不需要软件的参与,实现了硬件的自动化。
注:D是数字两 A是模拟量
通用定时器
向上计数: 计数器从0开始向上自增,达到设定值,清零同时申请中断,依次循环
通用/高级定时器除此还支持向下计数以及中央对齐计数模式;
情况1
情况2
情况3
总结,外部时钟模式1 下,输入可以是ETR引脚。其他定时器,CH1 引脚的边沿,CH1 和CH2 引脚,一般情况下,使用ETR 为外部时钟输入
输入捕获电路/输出比较电路 (之后会详细介绍)
输出比较电路有四个通道分别对应CH1 到 CH4的引脚,可以用于输出PWM 波形
输入捕获电路也有四个通道,可以用于输入方波的频率测量等
中间的是输入捕获跟输出比较电路寄存器,两个电路共用四根引脚,不能同时进行
高级定时器
相比于通用计时器,这里首先在申请中断的地方 增加了一个重复次数计数器,可以实现每隔几个计数周期,才发生一次更新事件和更新中断,这就相当于对于输出的更新信号,又做了一次分频,这里可以实现级联计时器的效果。
DTG (Dead Time Generate)死区生成电路 防止桥臂直通现象
BRK 刹车输入功能 如果外部BKIN产生刹车信号,或者内部时钟信号产生问题,那么就会自动切断电机的驱动,防止意外的发生
定时器中断基本结构
运行控制:控制寄存器位的,比如向上计数,向下计数,启动停止等等
中断输出控制:中断输出允许位,如果需要中断,就允许
预分频器时序
CK_PSC 时钟源; CNT_EN 计数器使能; CK_CNT 计数器时钟,他即是预分频器的时钟输出,也是计数器的时钟输入
缓冲寄存器(影子寄存器):在计数过程中,如果突然改变分频数值,只有等到更新事件后,改变后的分频数值才会真正生效
计数器计数频率 = 时钟源频率/(分频器数值+1);
计数器时序
计数器溢出频率=时钟频率/(设定定时阈值+1) | =定时器时钟源 / (分频系数+1)/(自动重转器系数+1)
注: CK_CNT 跟 CK_PSC 虽然同为时钟,但是CK_PSC 为时钟源,SK_CNT 为预分配器的输出,也为CNT 计数器的输入,是经过处理后的时钟,
定义:计数器溢出频率是指一段时间内,有多少个计数周期,与其呈反比,计数周期越短,溢出频率(触发频率)也就越大 溢出时间为溢出频率的倒数,例如溢出频率为10MHz 表示1秒内,会发生10次的溢出,那么每次就为1/10
计数器预装时序
让数值的更改与更新事件同步发生,防止运行途中出现错误,通过第ARPE进行设置就可以开启/关闭
RCC时钟树
主要分为时钟的产生电路与时钟的分配电路两大部分
时钟产生电路
在时钟的产生部分,有四个震荡源,高速晶振提供系统时钟AHB APB2 APB1 都是来源这两个高速晶振,外部晶振比RC 内部振荡器更稳定准确
内部的8MHz高速RC振荡器
外部的4-16MHz石英晶体振荡器,也就是晶振,一般都是接8MHz
外部的32.768Khz低速晶振,一般是给RTC提供时钟的 RTC(Real-Time Clock)即实时时钟
内部的40KHz低速RC振荡器,这个可以给看门狗提供时钟
CSS (Clock Security System) 时钟安全系统
一旦外部时钟失效,就会让内部RC振荡器作为时钟
时钟分配电路
所有定时器时钟频率都为72MHz
定时器定时中断(代码函数部分)
硬件连接
根据定时中断基本结构进行配置
相关配置函数列表
//时钟源选择void TIM_InternalClockConfig(TIM_TypeDef* TIMx);void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,//时基单元配置void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//中断输出控制void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//运行控制void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//设置NVICvoid NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
用于中途单独更改设定数值的函数:
// 配置定时器的预分频器void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);// 配置定时器的计数模式void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);// 配置定时器自动重装载寄存器预装载使能void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);// 设置定时器的计数器值void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);// 设置定时器的自动重装载值void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);// 获取定时器的当前计数器值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);
初始化函数部分
#include \"stm32f10x.h\" // Device header// 定时器初始化函数void Timer_Init(void){ // 使能 TIM2 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 配置 TIM2 使用内部时钟源 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; TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; // 高级定时器才有的重复计数器,这里设置为 0 TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); // 使能 TIM2 更新中断 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); 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); // 使能 TIM2 TIM_Cmd(TIM2, ENABLE);}// TIM2 中断服务函数void TIM2_IRQHandler(void){ // 判断是否为 TIM2 更新中断 if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { // 清除 TIM2 更新中断标志位 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); }}
主函数main
#include \"stm32f10x.h\" // Device header#include \"OLED.h\"#include \"Timer.h\"uint16_t Num;// 主函数int main(void){ // 初始化 OLED 显示屏 OLED_Init(); // 在 OLED 显示屏第一行第一列显示字符串\"Num:\" OLED_ShowString(1, 1, \"Num:\"); // 初始化定时器 Timer_Init(); while (1) { OLED_ShowSignedNum(1,5,Num,5); // 无限循环,等待定时器中断 }}// TIM2 中断服务函数void TIM2_IRQHandler(void){ // 判断 TIM2 是否产生更新中断 if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { // 变量 Num 自增 Num++; // 清除 TIM2 的更新中断标志位 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); }}
代码效果,实现Num变量每秒自增+1
但是这里有一个现象不知道大家发现没有,就是stm32每次复位,Num的数值都会直接变成1 ,按理来说,应该以Num 的初始值0开始的,这就表明,在初始化的时候,程序就进入了一次中断,要解决其实也很简单,这题就出现在了这里
这个的意思是生成一个更新事件以立即重新加载预分频器和重复计数器的值,要知道,我们的预分屏器是有缓冲寄存器的,我们写的值只有在更新事件的时候,才会有效,这里为了让数值在初始化的时候生效就生成了一个更新事件,缺点就是更新事件与更新中断会同时发生,导致以上电就会从1 开始计时,想要解决其实也很简单
现在就可以正常显示啦!还可以在主循环里添加查看计数器数值的代码,可以发现,从0到重装值10000后,才会发生一次更新中断