STM32--TIM定时器(1)
STM32的TIM(定时器)是一种非常常用的外设,用于实现各种定时和计数功能。它是基于时钟信号进行计数,并在计数值达到设定值时触发中断,执行相应的操作。
TIM简介
STM32的TIM(定时器)是一种非常常用的外设,用于实现各种定时和计数功能。它是基于时钟信号进行计数,并在计数值达到设定值时触发中断,执行相应的操作。
定时器类型
一般来说,STM32中有三类定时器:
在我们这款STM32F03C9T6有4种定时器资源:TIM1,TIM2,TIM3,TIM4;
对于定时器,类型越高级,拥有的功能越多,且向下兼容;
我们将以通用定时器进行讲解。
通用定时器
通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。
它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。
使用定时器预分频器和RCC时钟控制器预分频器,脉冲信号长度和波形周期可以在几个微妙到几个毫秒间调整。
每个定时器都是完全独立的,没有互相共享任何资源。它们可以同步操作。
这是通用定时器的总框图,我将会分为几部分进行讲解。
这是定时器最基本的结构,通过RCC内部时钟产生的脉冲频率通向预分频器,频率分频后到计数器,当达到自动重装载寄存器的值,将会发出信号,或者触发中断。
RCC的TIMxCLK会产生一个72MHz的脉冲频率;
这一部分称为时基单元,是TIM计时器最主要的计数计时结构。
PSC预分频器会将72MHZ进行分频,可以按1到65536之间的任意值分频;将输入频率除以预分频器值就得到分频结果;
通过频率会在计数器中计数,通过传输的频率的上升沿,计数器将加一,
计数器取值范围为0到65535;所以计数的快慢由输入频率决定;
对于通用计数器来说,计数器是有多种模式进行计数的。
向上计数模式:计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
向下计数模式:计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。
中央对齐模式:计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器
溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。
自动重装载寄存器相当就是给计数器一个周期值,当计数器达到这个值就会产生中断,并清零计数器;计数器溢出中断后,会产生更新中断,传到NVIC中,最后传到CPU,那么定时器就能产生中断了。也会产生更新事件,它会触发内部其他电路的工作。
这部分,是定时器时钟频率的来源,在通用定时器中,不止有内部时钟,还有外部时钟。
第一个外部时钟TIMx_ETR,如果在引脚上默认有该功能,就能直接使用,作为外部时钟的连接引脚;
传输进来的方波信号会通过极性选择,边沿检测,滤波等进行整形,处理掉一些毛刺;滤波后的信号兵分两路,第一路是走到ETRF,通过触发控制器走到复位使能,这种走法称为“外部时钟模式2”。(TRIGO是映射功能,能够从主模式触发DAC)。第二路是TRGI,主要用作触发使用的,可以走到从模式;当然也可以走复位,使能那里,那么这样的外部时钟称为“外部时钟模式1”。
第二个的来源就是ITR,TRIGO可以通向其他定时器,其他定时器就是通过ITR引脚来连接的。
这是内部定时器连接的方式。可以允许4种定时器进行连接到定时器上,但是只允许一个定时器连接着一个定时器。
第三个一个是TIIF_ED,这里连接着输入捕获单元的CH1,ED为Edge,边沿的意思,触发方式上升沿和下降沿都有效。
最后一个是TI1FP1和TI2FP2
后续将会讲解。
下边的,左半部分为输入捕获电路,右半部分为输出比较部分,每部分都有4个通道可以进入,且输入和输出共用一个寄存器,意味着不能边输入边输出,具体功能将会后续讲解。
预分频器时序
这是一个预分频器从1变到2的时序图。
CK_PSC是时钟频率,一般都为72MHZ;
CNT_EN是计时器使能,只有在使能高电平状态下,才能运行。
CK_CNT是计时器时钟,它既是预分频器的时钟输出,也是计数器的时钟输入;当使能为高电平时,CNT开始运行,前半段频率跟时钟一样,后半段预分频器从1变到2,CNT让定时器上升沿减半,即一个周期有效,一个周期无效(保持低电平)。
在计时器时钟的驱动下,计时器寄存器也不断增加,当达到FC时(与自动重转载寄存器的值一样)将会从0开始;
更新事件UEV,当计数器寄存器到FC时,更新事件将会触发。
下面三个时序将一起看,这是预分频控制寄存器的缓冲机制,我们写入的值会到预分频控制寄存器上,当在计数器未归零之前写入时,为了保持完整性,将会在更新事件后才会进行分频。所以到预分频缓冲器上才是所读的正确结果,而预分频计数器会在1时保持定时器时钟为低电平,为0时保持原先状态。
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
PSC相对我们输入者来说,就是0开始的,当对于PSC来说,是从1开始的。就像一块蛋糕,不切时它就是1份完整的,切一刀时,就会被分成两份。
计数器时序
大体来说与预分频器一致,当计数器寄存器满时,将会使计数器溢出,更新事件发生,更新中断标志。
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
定时中断基本结构
对于我们来说,由于有库函数的提供,不需要管哪些寄存器,我们需要了解一些代码逻辑结构。
通过外部引脚GPIO就可连接外部时钟,然后选择时钟模式,接着对时基单元初始化,接上NVIC即可。
TIM内部中断工程
连接方式:
该工程将会实现走秒的例子。
Timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
void Timer_Init();
#endif
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init()
{
//开启APB1外设开关
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//配置TIM2为内部时钟
TIM_InternalClockConfig(TIM2);
//时钟结构体初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //划分TIM2
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计时器模式,上升沿计时
TIM_TimeBaseInitStructure.TIM_Period=10000-1; //自动加载寄存器周期值
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1; //预分频值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //指定重复计时器的值,这里不用到
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//清除标志位
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//启用TIM2中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//配置优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC初始化
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);
}
对于内部时钟,没有外部引脚的使用,记住TIM所在总线是APB1,先开启外设开关,接着配置TIM2的内部时钟,然后对时基单元结构体成员进行初始化,对于预分频器值,通过公式可知需要-1才能达到我们想要的数字,重复计时器是高级计时器的操作,这里不需要用到。
在初始化完将会生成一个更新事件,立即重新加载预分频和计时器的计算。在更新一个事件后,同时也会产生中断标志,为了让计时时从0开始,就采用了清除标志的函数。
最后记得启用TIM2的外设,否则无效。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Count;
int main()
{
OLED_Init();
Timer_Init();
while(1)
{
OLED_ShowNum(1,1,Count,4);
}
}
//中断函数
void TIM2_IRQHandler()
{
//表示已经触发中断了
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
Count++;
//中断挂起位,中断结束后需要将中断位挂起,让下一个能进入中断
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
TIM外部中断工程
接线方式:
通过对射式红外传感器的电平变化作为CNT的触发条件,然后通过10次的电平变化,让计时器溢出进1;
Timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
void Timer_Init();
#endif
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init()
{
//开启APB1外设开关
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);
//配置TIM2为外部时钟模式2
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
//时钟结构体初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //表示不分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //计时器模式
TIM_TimeBaseInitStructure.TIM_Period=10-1; //自动加载寄存器周期值
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1; //预分频值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //指定重复计时器的值,这里不用到
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//启用TIM2中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//配置优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//NVIC初始化
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);
}
外部时钟模式2:
TIM_ExtTRGPrescaler:外部触发预分频器
TIM_ExtTRGPolarity_NonInverted:触发极性为上升沿或高电平;
ExtTRGFilter:最后一个参数,表示滤波频率高低,可选范围0x00 and 0x0F;一般来说,滤波频率越高,毛刺与不规则信号处理的越干净。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Count;
int main()
{
OLED_Init();
Timer_Init();
while(1)
{
OLED_ShowNum(1,1,Count,4);
OLED_ShowNum(2,1,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
Count++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)