1. TIM定时中断
1.1 TIM简介
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器(执行计数定时的一个寄存器,每来一个时钟,计数器加1)、预分频器(可以对计数器的时钟进行分频,使计数更灵活)、自动重装寄存器(计数的目标值,就是想要计多少个时钟申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
1.2 定时器类型
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
1.2.1 基本定时器
- 内部时钟的来源是RCC_TIMxCLK,频率值一般都是系统的主频72MHz,所以通向时基单元的计数基准频率就是72MHz
- 时基单元—预分频器(PSC):对72MHz的计数时钟进行预分频。
实际分频系数 = 预分频器的值 + 1。比如预分频器写0,就表示不分频,或者说是1分频,输出频率=输入频率=72MHz;如果预分频器写1,就表示2分频,输出频率=输出频率/2=36MHz;如果预分频器写2,就表示3分频... - 时基单元—计数器(CNT):对预分频后的计数时钟进行计数。计数时钟每来一个上升沿,计数器的值就加1。计数器是16位的,所以里面的值可以从0一直加到65535。计数器的值在计时过程中会不断地自增运行,当自增运行到目标值时,产生中断,那就完成了定时的任务
- 时基单元—自动重装寄存器(ARR):保存计数目标。16位
- 框图右下角向上的折线箭头UI,代表这里会产生中断信号。由计数值等于自动重装值产生的中断,称为更新中断。这个更新中断之后就会通往NVIC。
- 框图右下角向下的拆线箭头U,代表这里会产生一个事件。由计数值等于自动重装值产生的事件,称为更新事件。更新事件不会触发中断,但可以触发内部其他电路的工作。
- 主模式触发DAC:使用DAC时,可能会用DAC输出一段波形,这就需要每隔一段时间来触发一次DAC,使其输出下一个电压点。如果用正常思路来实现,就是先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC转换,然后DAC输出。这种思路没有问题,但是这样会使主程序处于频繁被中断的状态,影响主程序的运行和其他中断的响应,所以定时器就设计了一个主模式,使用这个主模式可以把这个定时器的更新事件映射到触发输出TRGO(Trigger Out)的位置,然后TRGO直接接到DAC的触发转换引脚上,这样定时器的更新就不需要再通过中断来触发DAC转换了。整个过程不需要软件的参与,实现了硬件的自动化,这就是主模式的作用。
- 计数模式为向上计数,也就是计数器从0开始,向上自增,计到重装值,清零同时申请中断。然后开始下一轮,依次循环。
1.2.2 通用定时器
通用定时器的计数模式:
- 支持的计数模式有向上计数模式、向下计数模式和中央对齐模式。
- 向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断。然后继续下一轮,依次循环。
- 中央对齐模式就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断。然后继续下一轮,依次循环。
通用定时器的时钟源:
- 时钟来源不仅有内部时钟(系统频率72MHz),还可以选择外部时钟。
- TIMx_ETR引脚上的外部时钟:可以在TIM2的ETR引脚,也就是PA0上接一个外部方波时钟。然后配置一下内部的极性选择、边沿检测和预分频器电路,再配置一下输入滤波电路,这两块电路可以对外部时钟进行一定的整形,因为是外部引脚的时钟,所以存在毛刺,这此电路就可以对输入的波形进行滤波。最后,滤波后的信号分两路。
上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟了。如果你想在ETR外部引脚提供时钟,或者想对ETR时钟进行计数,把这个定时器当计数器来用的话,那就可以配置这一路的电路,在STM32中,这一路也叫做“外部时钟模式2”。
下面一路也可以提供时钟,就是TRGI(Trigger In),它主要是用作触发输入来使用的,触发输入可以触发定时器的从模式。本小节讲的是触发输入作为外部时钟来使用的情况,暂且可以把TRGI当做外部时钟的输入来看。当这个TRGI当做外部时钟来使用的时候,这一路就叫做“外部时钟模式1”。
通过下面一路的外部时钟有,第一,ETR引脚信号,ETR引脚既可以通过上面一路进入作为时钟,又可以通过下面一路进入作为时钟,这两种情况对于时钟输入而言是等价的,只不过下面一路的输入会占用触发输入的通道;第二,ITR信号,这一部分的时钟信号来自其他定时器,从右边可以看出,主模式的输出TRGO可以通向其他定时器,通向其他定时器的时候,就接到了其他定时器的ITR引脚上来了。ITR0到ITR3分别来自其他4个定时器的TRGO输出,具体连接方式如下表。通过这一路可以实现定时器级联的功能。比如可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,选择ITR2,对应的就是TIM3的TRGO,然后再选择时钟为外部时钟模式1,这样TIM3的更新事件就可以驱动TIM2的时基单元,也就实现了定时器的级联。 - TI1F_ED:接连输入捕获单元的CH1引脚,也就是从CH1引脚的边沿获得时钟,后缀ED(Edge)是边沿的意思。通过这一路输入的时钟,上升沿和下降沿均有效。
- TI1FP1、TI1FP2:TI1FP1连接到了CH1引脚的时钟,TI1FP2连接到了CH2引脚的时钟。
- 总结一下,外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚。
编码器接口:可以读取正交编码器的输出波形。
输出比较电路:右下角,从“捕获/比较1寄存器”到“TIMx_CH4”的矩形区域。总共有四个通道,可以用于输出PWM波形,驱动电机。
输入捕获电路:左下角,从“TIMx_CH1”到“捕获/比较4寄存器”的矩形区域。总共有四个通道,可以用于测量输入方波的频率等。
捕获/比较寄存器:输入捕获和输出比较电路共用。因为输入捕获和输出比较不能同时使用,所以这里寄存器是共用的,引脚也是共用的。
1.2.3 高级定时器
支持的计数模式有向上计数模式、向下计数模式和中央对齐模式。
重复次数计数器:可以实现每隔几个计数周期,才发生一次更新事件和更新中断
DTG:死区生成电路,右边的输出引脚,由一个变为了两个互补的输出,可以输出一对互补的PWM波。这些电路主要是为了驱动三相无刷电机的。为了防止互补输出的PWM驱动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象,所以在前面加上了死区生成电路。在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。第四路输出没有DTG,因为三相电机只需要三路就可以了。
刹车输入:左下角,给电机驱动提供安全保障。如果外部引脚TIMx_BKIN产生了刹车信号,或者内部时钟失效产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生。
1.3 定时中断基本结构
1.4 时序图
1.4.1 预分频器时序
- CK_PSC:预分频器的输入时钟
- CNT_EN:计数器使能,高电平计数器正常运行,低电平计数器停止
- CK_CNT:计数器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
1.4.2 计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
1.4.2.1 计数器无预装时序
就是没有缓冲寄存器的情况。通过设置ARPE位,可以选择是否使用预装功能。
计数器正在计数,突然更改了自动重装寄存器,由FF改成了36,那计数值的目标就由FF变成了36,所以计数器寄存器计到36之后,就直接更新,开始下一轮计数。
1.4.2.2 计数器有预装时序
就是有缓冲寄存器的情况。
在计数的中途,突然把计数目标由F5改成了36,但影子寄存器还是F5,所以现在计数的目标还是计到F5产生更新事件,同时,要更改的36才被传递到影子寄存器,在下一个计数周期这个更改的36才有效。所以引入影子寄存器的目的是同步,就是让值的变化和更新事件同步发生,防止在运行途中更改造成错误。
如果不使用影子寄存器,F5改到36立刻生效,但此时计数器数值已经到了F1,已经超过36了,F1只能增加到FFFF,再回到0,再加到36,才能产生更新。
1.5 RCC时钟树
STM32用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统。
程序中主函数之间会执行SystemInit函数,这个函数就是用来配置这个时钟树的。
中间AHB(不含)左边的都是时钟的产生电路,AHB(含)右边的都是时钟的分配电路。
在时钟产生电路,有四个震荡源,从上到下分别是内部的8MHz高速RC振荡器、外部的4-16MHz高速石英晶体振荡器(也就是晶振,一般接8MHz)、外部的32.768KHz低速晶振(一般给RTC提供时钟)、内部的40KHz低速RC振荡器(可以给看门狗提供时钟)。上面两个高速晶振是用来提供系统时钟的,AHB、APB2、APB1的时钟均来源于这两个高速晶振,外部的石英振荡器比内部的RC振荡器更加稳定,所以一般用外部晶振。
CSS:时钟安全系统,负责切换时钟。它可以监测外部时钟的运行状态,一旦外部时钟失效,它就会自动把外部时钟切换回内部时钟,保证系统时钟的运行,防止程序卡死。
2. 定时器定时中断
2.1 接线图
2.2 代码
初始化定时器的步骤:
- 初始化时钟RCC
- 选择时基单元的时钟源(对于定时中断,选择内部时钟源)
- 配置时基单元(包括PSC、CNT、ARR)
- 配置输出中断控制,允许更新中断输出到NVIC
- 配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
- 运行控制
整个模块配置完成后,还需要使能一下计数器。当定时器使能后,计数器就会开始计数,当计数器更新时,触发中断。最后再写一个定时器的中断函数,这样中断函数每隔一段时间就能自动执行一次了。
Timer.c
#include "stm32f10x.h" // Device header
//extern uint16_t Num;
void Timer_Init(void)
{
// 1、初始化时钟RCC TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);// TIM2是APB1总线的外设
// 2、选择时基单元的时钟源
TIM_InternalClockConfig(TIM2);// 因为定时器上电后默认就是使用内部时钟,所以此行可省略
// 3、配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;// 指定时钟分频,设置的是输入滤波的采样频率,与时基单元关系不大。这里随便选一个
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;// 计数器模式,选择向上计数
// 例如定时1s,计数器溢出频率:CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)=72M/(PSC+1)/(ARR+1)
// 定时1s,也就是定时频率为1Hz。可以PSC给7200-1,ARR给10000-1
// 注意:PSC和ARR的取值都要在0~65535之间
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;// 自动重装器(ARR)的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;// 预分频器(PSC)的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;// 重复计数器的值(高级定时器才有,不是CNT),不需要用,直接给0
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);// 刚初始化完会生成一次更新事件,会使Num刚复位就显示1
// 手动清除一次更新事件,避免Num从1开始
TIM_ClearFlag(TIM2, TIM_FLAG_Update);// 手动清除更新中断标志位,避免刚初始化完就进中断
// 4、配置输出中断控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);// 使能中断,选择更新中断
// 5、配置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);
// 6、运行控制
TIM_Cmd(TIM2, ENABLE);// 启动定时器
}
/*
// 定时器2中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)// 检查中断标志位
{
// 主函数变量Num++。
// 可以在本文档开头用extern;也可以直接将中断函数复制到主函数后面,Timer.c的中断函数需要注释掉。
Num++;
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
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
// 每隔1s Num加1
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);
}
}
// 定时器2中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)// 检查中断标志位
{
// 主函数变量Num++。
// 可以在本文档开头用extern;也可以直接将中断函数复制到主函数后面,Timer.c的中断函数需要注释掉。
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);// 清除中断标志位
}
}
其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具,第5节)、Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)
3. 定时器外部时钟
3.1 接线图
3.2 代码
与上一例代码基本相同,仅修改了Timer.c中的初始化时钟RCC、时基单元时钟源、ARR和PSC的取值,增加了GPIO初始化。
不需要使用内部时钟,修改初始化时钟第二步(选择时基单元的时钟源),改为通过ETR引脚的外部时钟模式2配置。
TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter)
- 第一个参数是选择定时器,这里给TIM2。
- 第二个参数是外部触发预分频器,这里不需要分频。
- 第三个参数是外部触发的极性。TIM_ExtTRGPolarity_Inverted是反向,就是低电平或下降沿有效;TIM_ExtTRGPolarity_NonInverted是不反向,就是高电平或上升沿有效。
- 第四个参数是外部触发滤波器,参数值为0x00~0x0F之间的一个值。就是以采样频率f采样N个点,如果N个点都一样,才会有效输出,这个值就是决定f和N的,具体对应关系如下图。
(视频里写的0x00,但不滤波很容易造成在实际中挡一下光数跳好几次,所以这里改成了0x0f)
引脚要用到GPIO,所以配置时钟时还需要配置GPIO。
GPIO_Mode可以从“STM32F10xxx参考手册”中查询(8.1.11 外设的GPIO配置),推荐配置为浮空输入。但由于浮空输入电平会一直跳动,这里给上拉输入。浮空输入一般用于外部输入信号功率很小,内部如果有上拉电阻可能会影响到输入信号。
Timer.c
#include "stm32f10x.h" // Device header
//extern uint16_t Num;
void Timer_Init(void)
{
// 1、初始化时钟RCC TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);// TIM2是APB1总线的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置GPIO
// 初始化GPIO
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(GPIOB, &GPIO_InitStructure);
// 2、选择时基单元的时钟源
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0f);// 通过ETR引脚的外部时钟模式2配置
// 3、配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;// 指定时钟分频,设置的是输入滤波的采样频率,与时基单元关系不大。这里随便选一个
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;// 计数器模式,选择向上计数
// 计数器溢出频率:CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)=72M/(PSC+1)/(ARR+1)
// PSC给1-1,ARR给10-1,从0到9计数
// 注意:PSC和ARR的取值都要在0~65535之间
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;// 自动重装器(ARR)的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;// 预分频器(PSC)的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;// 重复计数器的值(高级定时器才有,不是CNT),不需要用,直接给0
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);// 刚初始化完会生成一次更新事件,会使Num刚复位就显示1
// 手动清除一次更新事件,避免Num从1开始
TIM_ClearFlag(TIM2, TIM_FLAG_Update);// 手动清除更新中断标志位,避免刚初始化完就进中断
// 4、配置输出中断控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);// 使能中断,选择更新中断
// 5、配置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);
// 6、运行控制
TIM_Cmd(TIM2, ENABLE);// 启动定时器
}
/*
// 定时器2中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)// 检查中断标志位
{
// 主函数变量Num++。
// 可以在本文档开头用extern;也可以直接将中断函数复制到主函数后面,Timer.c的中断函数需要注释掉。
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);// 清除中断标志位
}
}
*/
main.c
#include "stm32f10x.h" // Device
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
// 遮光片挡一下,CNT加1(没有预分频。如果有预分频了,就是遮挡几次,才能增加一次)
// 加到9后,自动清零,同时申请中断,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);
}
}
// 定时器2中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)// 检查中断标志位
{
// 主函数变量Num++。
// 可以在本文档开头用extern;也可以直接将中断函数复制到主函数后面,Timer.c的中断函数需要注释掉。
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);// 清除中断标志位
}
}
其他引用的头文件和c代码可在此处查阅:OLED.h(【江协STM32】4 OLED调试工具,第5节)、Delay.h(【江协STM32】3-2 LED闪烁&LED流水灯&蜂鸣器,第1.3节)
标签:TIM2,定时器,中断,NVIC,TIM,定时,时钟 From: https://blog.csdn.net/qq_19395823/article/details/144678449