系列文章目录
第一章 STM32学习笔记之新建标准库
第二章 STM32学习笔记之GPIO及点灯实例
第N章 STM32学习笔记之OLED屏幕
第N章 STM32学习笔记之EXTI外部中断
第N章 STM32学习笔记之对射式红外传感器计次
第N章 STM32学习笔记之TIM定时中断
目录
前言:
笔记:跟着B站教学视频做的学习笔记
1. TIM定时中断
1.1. 简介
TIM(Timer)定时器
- 可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
1.2. 定时器类型
根据定时器的类型,分为了高级定时器、通用定时器和基本定时器
- 编号:因为同一个芯片有很多个定时器,所以TIM后面会跟一个数字,知道编号和定时器类型对应关系即可
- 总线:通用定时器和基本定时器连接的是APB1总线,在开启RCC时钟时要注意下
1.2.1. 基本定时器
基本定时器可以完成定时中断和主模式触发DAC的功能,但只能选择内部时钟,即系统频率72MHz
- 预分频器:
- 对内部时钟进行预分频,例如写0,就是不分频,或者说是1分频,这时候输出频率=输入频率=72MHz,如果写1,那就是2分频,这时候输出频率=输入频率/2=36MHz...以此类推
- 预分频器的值和实际的分频系数相差了1,即实际分频系统=预分频系数的值+1
- 这预分频器是16位的,所以最大值可以写65535,即65536分频
- 计数器:对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就+1,这个计数器也是16位的,所以里面的值可以从0一直加到65535,如果再加的话,计数器就会回到0重新开始
- 重载寄存器:也是16位的,它是计数的目标值,当计数值=重装值,就会产生一个更新中断和更新事件,CPU响应更新中断,就完成了定时中断的任务了,计数到了这个值后就重新开始
- TRGO:定时器触发中断后,控制器就把更新事件映射到TRGO这个引脚上,用于触发DAC
1.2.2. 通用定时器
通用定时器拥有基本定时器全部功能,还能外接时钟,即它的时钟源可以选择内部的72MHz时钟,还可以选择外部时钟,外部时钟常用的是TIMx_ETR
- TIMx_ETR引脚上的外部时钟,这个ETR(External)引脚的位置,可以参考引脚定义表,也就是可以在TIM2的ETR引脚,即PA0上接一个外部方波时钟,
1.2.3. 高级定时器
1.3. 定时中断基本结构
即选择时钟后,经过分频,计数器计数,到了重装载值时触发中断,中断信号经过中断输出控制到NVIC申请中断
1.4. 时序图
1.4.1. 预分频器时序
计数器计数频率:CK_CNT = CK_PSC /(PSC+1)
- CK_PSC是源时钟,开启预分频使能后,如果PSC是0,计数器的频率就是源时钟频率,1就是原始中频率的一半
1.4.2. 计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC /(PSC+1) / (ARR + 1)
- UIF为1时就会去申请中断,中断响应后需要手动清零,不然就会一个申请中断
1.4.3. 计数器无预装时序
无预装时,当重装载值发生改变,计数寄存器会直接更新,如果计数到了就直接变,如果计数已经超过了,它会默认先计数到FFFF再重新回到0重新开始
1.4.4. 计数器有预装时序
有预装时,会多了一个影子寄存器,当自动加载寄存器发生改变时,它先等计数器计数到位了,影子寄存器才发生改变,计数器重新开始计数,而不是立即改变
2. 定时器内部时钟配置
根据定时器基本结构图,得出配置定时器外部时钟主要有以下步骤:
本次实操是基于新建好标准库工程之后
2.1. 模块化软件开发
新增Timer.c和Timer.h文件到System组里,具体操作见《STM32模块化软件开》链接
2.2. 初始化函数
2.2.1. RCC时钟开启
根据STM32的系统结构得出TIM定时器是挂在APB1总线上的,开启时钟后,定时器的基准时钟和整个外设的工作时钟都会同时开启,
- 调用该函数开启RCC时钟
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}
2.2.2. 选择时钟源
对于定时中断,选择内部的时钟源,
- 在tim.h里找到"TIM_InternalClockConfig()"函数,即选择为内部时钟,时基单元是TIM2
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
}
2.2.3. 配置时基单元
- 在tim.h里找到"TIM_TimeBaseInit()"来初始化时基单元,第一个参数是TIMx,第二个参数是一个结构体
- 第一个参数写TIM2,第二个参数点去定义里查找下
- 复制以下,根据它来定义一个结构体,把这个结构体的地址放在初始化函数里即可
- 把结构体成员都列出来进行初始化
- TIM_ClockDivision:去它的定义里查找后,分频选择DIV1,也就是不分频
- TIM_CounterMode:计数模式选择向上计数
- TIM_Period:周期(ARR自动重装值):
根据公式CK_PSC /(PSC+1) / (ARR + 1),为了配1秒的中断(1Hz),所以ARR选择为(10000-1),即72000000 /(PSC+1) / (10000 - 1 + 1)
- TIM_Prescaler:PSC:
根据公式CK_PSC /(PSC+1) / (ARR + 1),为了配1秒的中断(1Hz),所以PSC选择为(7200-1),即72000000 /(7200 - 1+1) / (10000 - 1 + 1)= 1Hz
- TIM_RepetitionCounter:高级定时器才有的,这里用不到,写0
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct) ;
}
2.2.4. 使能中断
使能中断,就是允许中断更新中断输出到NVIC中
- 在tim.h里找到"TIM_ITConfig()"来使能中断
- 第一个参数选择"TIM2",第二个参数跳转到定义里去看,选择更新中断
- 第三个参数选择"ENABLE"
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct) ;
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
}
2.2.5. 配置NVIC
在NVIC中打开定时器中断的通道,并分配一个优先级,详细步骤见《STM32学习笔记之EXTI应用实例》链接
因为NVIC是内核外设,它的库函数被ST发配到misc.h(杂项)文件里,在这里找它的库函数
- NVIC_PriorityGroupConfig:指定下中断的分组,这里选择两位抢占两位响应
- 定义一个NVIC结构体
- NVIC_IRQChannel:因为库函数所以兼容所以的F1系列芯片,但是不同的芯片中断通道列表是不一样的,所以有很多条件编译,这里选择MD(中等密度)里的TIM2_IRQn
- NVIC_IRQChannelCmd:指定中断通道是使能还是失能,选择“ENABLE”
- NVIC_IRQChannelPreemptionPriority:抢占优先级,这里只有一个中断源,比较随意,设置为1即可
- NVIC_IRQChannelSubPriority:响应优先级,这里只有一个中断源,比较随意,设置为1即可
- 调用NVIC初始化函数初始化结构体
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct) ;
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
}
2.2.6. 启动定时器
- 在tim.h里找到"TIM_Cmd()"来启动定时器
- 第一个参数选择"TIM2",第二个参数给"ENABLE"
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct) ;
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2,ENABLE);
}
2.3. 中断函数编写
在STM32中,中断通道的名字都是固定的,每个中断通道都对应一个中断函数,可以在启动文件里查看,里面以“ IRQHandler ”结尾的字符串就是中断函数的名字
- 找到"TIM2_IRQHandler",编写中断函数,记住要清除标志位
- 先对TIM2的更新事件中断标志位进行判断,响应后清除标志位
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
2.4. 声明初始化函数
在.h文件里声明刚刚编写的传感器初始化函数,这样在别的文件里就能调用该函数了,中断函数不用声明
2.5. main函数处理
声明红外传感器.h文件和调用初始化函数
通过OLED屏显示时间:功能函数如下
3. 定时器外部时钟配置
外部时钟采用红外传感器计数来模拟时钟,因为使用的是TIM2_CH1_ETR这个外部时钟,对应的引脚是PA0,所以要将红外传感器的AO口接到PA0引脚上
3.1. 区别
3.1.1. 时钟源
把2.2.2里的时钟源"TIM_InternalClockConfig函数"换成TIM_ETRClockMode2Config函数"
- 第一个参数给TIM2,第二个参数进入定义看一下
- 第三个参数去定义里看,根据需要来,这里选择不反向
- 第四个参数是外部触发滤波器:即以一个采样频率f采样N个点,如果N个点都一样,才会有效输出,这个值对应的f和N的关系如下,这里就选择不要滤波器了,即0x00
3.1.2. 添加GPIO模块
开启时钟并且初始化GPIO
3.1.3. 时基单元
将周期改成10,分频改成1,即触发一次加一次,加到10后触发中断
3.1.4. 功能函数实现
计数函数:
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
主函数:
END
总结
本章节记录了STM的定时器相关操作,无论是内部时钟或者外部时钟都有样例讲解