STM32中功能最强大,结构最复杂的外设——定时器
第一部分,我们主要讲的时定时器的基本定时功能,也就是定一个时间,然后让定时器每个一段时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如要做时钟、秒表、或者使用一些程序算法的时候,都需要用到定时中断这个功能。
第二部分,我们主要讲的是定时器输出比较的功能,输出比较这个模块最常见的用途是产生PWM波形,用于驱动电机等设备,使用STM32输出的PWM波形来驱动舵机和直流电机的例子
第三部分,我们主要讲的是定时器输入捕获的功能,在这个部分,我们将会学习使用捕获这个模块来实现测量方波频率的例子。
第四部分,我们学习定时器的编码器接口,使用这个编码器接口,能更加方便的读出正交编码器输出波形,在编码电机测速中,应用非常广泛。
TIM(Timer)定时器
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断,换句话来说,定时器就是一个计数器。
16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
时基单元:(均是16位)
计数器就是用来执行计数定时的一个寄存器,每来一个时钟,计数器加1
预分配器,可以对计数器时钟进行分频,让这个计数器更加灵活
自动重装寄存器就是计数的目标值,就是我想要记多少个时钟申请中断
定时器类型
基本定时器
预分频器、计数器、自动重装载寄存器构成了最基本的计数计时电路,所以这一块电路就叫做时基单元
通向时基单元的计数基准频率是72M
预分频器:可以对72MHz的计数时钟进行预分频,比如寄存器写0,那就是不分频,或是1分频,这时候输出频率 = 输入频率 = 72MHz,如果预分频器写1,那就是2分频,输出频率=输入频率/2 = 36MHz,以次类推,实际分频系数 = 预分频器的值+1
计数器:计数时钟没来一个上升沿,计数器的值+1,计数器的值会不断的自增运行,当自增运行到目标值时,产生了中断,那就完成了定时的任务。
自动重装寄存器:自动重装寄存器也是16位,它存的就是我们写入的计数目标,在运行的过程中,计数值不断自增,自动重装讲的值时固定的目标,当计数值等于自动重载值时,也就是计时时间到了,他就会产生一个中断信号,并且清零计数器,计数器开始下一次的计数计时,同时产生中断信号,称为更新中断,更新中断 之后就会通往NVIC。在使用DAC的时候,可能会用DAC输出一段波形,定时器在这里设置了一个主模式,使用这个主模式,可以把这个定时器更新事件,映射到这个触发输出TRGO(Trigger Out)的位置,然后直接接到DAC的触发转换引脚上,仅需要把更新时间通过主模式映射到TRGO,然后TRGO就会直接去触发DAC
通用定时器
在通用计时器中,这个计数器的计数模式就不止向上计数这一种了,向上计数:计数器从0开始,向上自增,寄到重装值,清零同时申请中断,然后开始下一轮,一次循环,这是向上计数。
通用计时器中,时钟源不仅可以选择内部的72MHz时钟,还可以选择外部时钟,第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟。另外一个就是TRGI(Trigger In)用作触发输入来使用,触发输入可以触发定时器的从模式,在这里我们探讨触发作为外部时钟来使用的情况。
外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿,CH1引脚和CH引脚
编码器接口:可以读取正交编码器的输出波形
这个就是定时器的主模式输出,可以把内部的一些事件,映射到这个TRGO引脚上
输出比较电路:分别对应CH1~CH4的引脚,可以用于输出PWM波形,驱动电机
输入捕获电路:对应CH1~CH4的引脚,可以测量方波的频率等
捕获/比较寄存器:是输入捕获和输出比较电路共用的,因为输入捕获和输出捕获比较不能同时使用,所以这里的寄存器是公用的,引脚也是公用的
高级定时器
在申请中断的地方,增加了一个重复次数计时器,有了这计数器,就可以实现每隔几个计数周期,才发生一次事件更新和更新中断
DTG(Dead Time Generate)死区生成电路:
右边的输出引脚变为了两个互补输出,可以输出一对互补的PWM波,可以驱动三相无刷电机,
刹车输入:给电机驱动提供安全保障的
定时中断基本结构
预分频器时序
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
计数器有预装时钟
更加严谨,让值的变化和更新事件同步发生,防止在运行途中更改造成错误
RCC时钟树
初始化定时器步骤
第一步 RCC开启时钟
第二步 选择时基单元的时钟源
第三步 配置时基单元,包括预分配器,自动重装器,计数模式等等,这些参数用一个结构体就可以配置好了
第四步 配置输出中断控制,允许更新中断输出到NVIC
第五步 配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
第六步 运行控制,整个模块配置完成后,还要使能一下计数器,不然计数器不会运行,当是能计数器后,计数器就会开始技术了,当计数器更新时,触发中断,最后写定时器中断函数,这样中断函数每隔一段时间就能自动执行一次了
TIM函数
TIM_DeInit 恢复缺省配置
TIM_TimeBaseInit 时基单元初始化
TIM_TimeBaseStructInit 把结构体变量赋一个默认值
TIM_Cmd 用来使能计数器
TIM_ITConfig 使能中断输出信号的
TIM_InternalClockConfig 选择内部时钟
TIM_ITRxExternalClockConfig 选择ITRx其他定时器的时钟
TIM_TIxExternalClockConfig 选择TIx捕获通道的时钟
TIM_ETRClockMode1Config 选择ETR通过外部时钟模式1输入的时钟
TIM_ETRClockMode2Config 选择ETR通过外部时钟模式2输入的时钟
TIM_ETRConfig 用来配置ETR引脚的预分频器,极性,滤波器这些参数的
TIM_PrescalerConfig 用来单独写预分频值
TIM_CounterModeConfig 用来改变计数器的计数模式
TIM_ARRPreloadConfig 自动重装器预装功能配置
TIM_SetCounter 给计数器写入一个值
TIM_SetAutoreload 给自动重装器写入一个值
TIM_GetCounter 获取当前计数器的值
TIM_GetPrescaler 获取当前的预分频器的值
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使用APB1开启时钟函数
TIM_InternalClockConfig(TIM2); // 选择TIM2作为时基单元的时钟源
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitSturct; //配置结构体
TIM_TimeBaseInitSturct.TIM_ClockDivision = TIM_CKD_DIV1; //指定时钟分频
TIM_TimeBaseInitSturct.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式
TIM_TimeBaseInitSturct.TIM_Period = 10000 - 1; //ARR自动重装器
TIM_TimeBaseInitSturct.TIM_Prescaler = 7200 - 1; //PSC预分配频的值
TIM_TimeBaseInitSturct.TIM_RepetitionCounter = 0; //重复计数器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitSturct); // 配置时基单元
TIM_ClearFlag(TIM2, TIM_IT_Update); //手动清除更新中断标志位, 避免刚初始化完就进入中断的问题 (从0开始)
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断 更新中断到NVIC的通路
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// NVIC优先级分组
NVIC_InitTypeDef NVIC_InitStructrue;
NVIC_InitStructrue.NVIC_IRQChannel = TIM2_IRQn; // 定时器2在NVIC里的通道
NVIC_InitStructrue.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructrue.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级
NVIC_InitStructrue.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_Init(&NVIC_InitStructrue);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}