STM32定时器的定时器其分位为三大类:高级定时器、通用定时器、基本定时器。同时他们也有包含的关系,也就是高级定时器有的功能都包含了其他两个定时器,通用定时器的功能包含了基本定时器。基本定时器其的功能是最少的。但是普遍用的多的都是通用定时器。下面列举一下定时器的分类,高级定时器是TIM1和TIM8,通用定时器是TIM2、TIM3、TIM4、TIM5。基本定时器是TIM6和TIM7。一个共由8大个定时器。但是用的多的也是通用的四个。
然后看一下定时器的实现的时序逻辑图吧,参考手册的图太多了。有时候看的懵,所以就总结一下了。下面的这个图
上面的这个图就是一个定时器的配置的一些基本条件了。然后有红色标的就是内部时钟配置的要求。接下来就一一配置代码吧。
看到逻辑图大概可以看出就是首先要配置打开RCC定时器的时钟,可以在RCC.c文件可以看到这个时钟配置参数,这个就是可以APB1总线就可以配置TIM的时钟。
这里有一个注释可以看到 TIM_InternalClockConfig(TIM2);一个内部时钟配置开启啊,其实这个是一直打开的,可以不用配置,但是为了流程的完整性,就配置一下,这里配置的是通用定时器
void Timer2_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启定时器2时钟
TIM_InternalClockConfig(TIM2); //内部时钟,但是不配置也可以,因为是一直开启的
配置完时钟就定义定时器的结构体,把里面的结构以变量引出来。这一步其实就到了时基单元模式了,
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //初始化时基单元结构体
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分屏参数,选择哪一个没有很大的问题
TIM_TimeBaseStructure.TIM_CounterMode= TIM_CounterMode_Up; //时钟计数模式,分别由向上计数,向下计数,和三种中央对齐模式
TIM_TimeBaseStructure.TIM_Period=10000-1; //频率计数器的值,就是一个周期记多少个数重装载的值
TIM_TimeBaseStructure.TIM_Prescaler=7200-1; //预分频器的值
TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //这个是高级定时器,基本通用定时器不用开启
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //时基单元结构体写入初始化寄存器
然后这里有一个TIM_TimeBaseStructure.TIM_CounterMode这个是定时器的模式,这个模式分为三种,分别是向上计数、向下计数、和另外的有三种中央对齐的模式。
还有一个参数是高级定时器用的,这里不需要所以就设置为零。
另外两个参数就是定时器的核心了,这个关乎定时器的精确的。
TIM_TimeBaseStructure.TIM_Period=10000-1; 是频率计数器的值,就是一个周期记多少个数重装载的值,
TIM_TimeBaseStructure.TIM_Prescaler=7200-1; //预分频器的值,就是频率的快慢。
因为这个STM32板子是72Mhz的所以配置的话,如果1s要1hz,那么72000000=10000*7200。就是这个比例。这里为什么减一是因为它的一些内部结构问题,需要相差那么一点点。所以需要减1。
配置完时基单元,就要开启与NVIC的中断控制了,因为定时器记一定时间就要产生事件中断,不然记这个时间有什么用。
下面就是开启这个中断的通路。
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //开启更新中断到NVIC通路
TIM_ClearFlag(TIM2,TIM_IT_Update); //为了防止定时器开始从1开始,所以要手动清楚一下状态。
这里有一个比较重要的函数啊,就是清除标志位。为什么要这么做呢,因为像玩单片机这种东西,一旦需要计什么东西很多时候都是从0开始的。但是如果你不写这个标志位,他这个时基单元的函数后面有注释写到,就是当中断产生,它会立刻进入。也就是说,只要你配置好了,一通电它就已经是1开始了,所以不能从零开始。,所以要写一下这个标志位,让它计数从0开始
配置好上面的之后就到NVIC中断了
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断分组
NVIC_InitTypeDef NVIC_Initstrcture; //定义中断结构体
NVIC_Initstrcture.NVIC_IRQChannel=TIM2_IRQn; //中断端口
NVIC_Initstrcture.NVIC_IRQChannelCmd= ENABLE;
NVIC_Initstrcture.NVIC_IRQChannelPreemptionPriority=0; //中断优先级
NVIC_Initstrcture.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_Initstrcture);
TIM_Cmd(TIM2,ENABLE); //定时器开始工作
这里中断其实跟配置初始化一样的,但是不用开启时钟,因为中断的时钟一直打开的。然后,配置中断的结构体变量分别是中断端口、开启使能、开启中断优先级。最后还有一个
TIM_Cmd(TIM2,ENABLE); //定时器开始工作,这个就是定时器的运行控制。只有打开这个定时器才开启工作。
然后再找到定时器的中断通道。在startup_stm32f10x_hd.s文件里面找到相应的TIM定时器通道,这个通道函数名是规定的。然后判断这个标志位,如果记一个数那么就进入一次通道执行里面的参数。然后在清除,再记。
void TIM2_IRQHandler (void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET) //判断标志位
{
num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清楚标志位
}
}
下面是全部的代码,
#include "stm32f10x.h"
uint16_t num;
void Timer2_Init(void);
int main(void)
{
Timer2_Init(void);
while(1)
{
}
}
void Timer2_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启定时器2时钟
TIM_InternalClockConfig(TIM2); //内部时钟,但是不配置也可以,因为是一直开启的
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //初始化时基单元结构体
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分屏参数,选择哪一个没有很大的问题
TIM_TimeBaseStructure.TIM_CounterMode= TIM_CounterMode_Up; //时钟计数模式,分别由向上计数,向下计数,和三种中央对齐模式
TIM_TimeBaseStructure.TIM_Period=10000-1; //频率计数器的值,就是一个周期记多少个数重装载的值
TIM_TimeBaseStructure.TIM_Prescaler=7200-1; //预分频器的值
TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //这个是高级定时器,基本通用定时器不用开启
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //时基单元结构体写入初始化寄存器
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //开启更新中断到NVIC通路
TIM_ClearFlag(TIM2,TIM_IT_Update); //为了防止定时器开始从1开始,所以要手动清楚一下状态。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断分组
NVIC_InitTypeDef NVIC_Initstrcture; //定义中断结构体
NVIC_Initstrcture.NVIC_IRQChannel=TIM2_IRQn; //中断端口
NVIC_Initstrcture.NVIC_IRQChannelCmd= ENABLE;
NVIC_Initstrcture.NVIC_IRQChannelPreemptionPriority=0; //中断优先级
NVIC_Initstrcture.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_Initstrcture);
TIM_Cmd(TIM2,ENABLE); //定时器开始工作
}
void TIM2_IRQHandler (void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET) //判断标志位
{
num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清楚标志位
}
}
像计数的这种没有显示屏就很难知道这个计数标不标准,然后keil软件有一个仿真就可以显示这个,打开仿真新建参数。这个参数必须在main外面定义的是全局变量。然后添加参数。打开这个时间可以对着看一下准不准,仿真的时候一定记得要插着单片机板子
打开这个仿真,然后添加Watch窗口,添加变量,变量右键打开去掉显示十六进制,然后运行,再对着时间看看1s准不准。
上面的内部定时器的配置,接下来就是利用外部来触发定时器。
同样是这张逻辑图,但是这个外部定时器,就是利用GPIO口的复用TIM定时器功能来触发,可以看一下流程图
这次就利用模块触发,这里用到是对射式红外,就是类似于电梯那个,人走到了,就会走得快的那个。
下面看一下首先第一步,找到可以用TIM2的GPIO口的这个功能
可以看这个引脚分布图,PA0~PA3都可以用这个TIM2的这个定时器功能
接下来就简单了。同样是上面的代码修改一下就可以了,根据流程图,先初始化GPIO端口,这里初始化PA0的端口,这个端口模式可以参考手册的外部GPIO的模式
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开引脚时钟
GPIO_InitTypeDef GPIO_Initstructure; //初始化GPIO属于的TIM的端口
GPIO_Initstructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
GPIO_Initstructure.GPIO_Pin=GPIO_Pin_0; //在GPIOA端口0~3才有TIM2外部定时器复用功能
GPIO_Initstructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure); //写入寄存器的A组
这里参考手册,建议的浮空模式,但是这里没有用为什么呢,浮空模式在什么时候用呢,就是如果外部输入信号功率很小内部的这个上拉电阻可能会影响到这个输入信号,这时就可以用这个浮空输入,防止影响外部输入的电平。
上面初始化GPIO口之后就可以打开外部定时器的模式了,这样上面的 TIM_InternalClockConfig(TIM2); 这个内部时钟打开模式的代码就可以注释掉了,因为需要用到的是外部定时器触发。然后配置外部定时器的代码,
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00); //外部时钟模式2,预分频器, 外部外部触发极性,外部触发滤波器,
这里第一个参数是第几个定时器,第二个参数是,预分频模式,这里不需要用到,所以就关了,然后第三个参数是,外部触发的极性,可以看到,
这里参数说明就是,低电平触发下降沿或者高电平上升沿触发的意思,可以选其中一个的,这里选的是高电平的上升沿
第四个参数就是外部触发的滤波器,意思就是在F频率里面采样N个点,如果采样N个点的频率一样才有效输出。手册里面有说明,这里暂时不用。
配置完这些就差不多了,接下来就是读取这个TIM端口的触发值了。利用这个函数就是获取端口的值的意思
然后可以定义一个函数,然后返回TIM2的值
uint16_t TIM_counter(void)
{
return TIM_GetCounter(TIM2);
}
接下来完整代码,就是这样的。
#include "stm32f10x.h"
uint16_t num=0,num1=0;
void Timer2_OutInit(void);
uint16_t TIM_counter(void);
int main(void)
{
Timer2_OutInit(void);
while(1)
{
num1=TIM_counter();
}
}
void Timer2_OutInit(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启定时器2时钟 像打开时钟的代码必须要在其他初始化代码的前面,否者会无效
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开引脚时钟
GPIO_InitTypeDef GPIO_Initstructure; //初始化GPIO属于的TIM的端口
GPIO_Initstructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
GPIO_Initstructure.GPIO_Pin=GPIO_Pin_0; //在GPIOA端口0~3才有TIM2外部定时器复用功能
GPIO_Initstructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure); //写入寄存器的A组
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00); //外部时钟模式2,预分频器, 外部外部触发极性,外部触发滤波器,
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //初始化时基单元结构体
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分屏参数,选择哪一个没有很大的问题
TIM_TimeBaseStructure.TIM_CounterMode= TIM_CounterMode_Up; //时钟计数模式,分别由向上计数,向下计数,和三种中央对齐模式
TIM_TimeBaseStructure.TIM_Period=10-1; //频率计数器的值,就是一个周期记多少个数重装载的值
TIM_TimeBaseStructure.TIM_Prescaler=1-1; //预分频器的值
TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //这个是高级定时器,基本通用定时器不用开启
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //时基单元结构体写入初始化寄存器
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE ); //开启更新中断到NVIC通路
TIM_ClearFlag(TIM2,TIM_IT_Update); //为了防止定时器开始从1开始,所以要手动清楚一下状态。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断分组
NVIC_InitTypeDef NVIC_Initstrcture; //定义中断结构体
NVIC_Initstrcture.NVIC_IRQChannel=TIM2_IRQn; //中断端口
NVIC_Initstrcture.NVIC_IRQChannelCmd= ENABLE;
NVIC_Initstrcture.NVIC_IRQChannelPreemptionPriority=2; //中断优先级
NVIC_Initstrcture.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_Initstrcture);
TIM_Cmd(TIM2,ENABLE); //定时器开始工作
}
uint16_t TIM_counter(void)
{
return TIM_GetCounter(TIM2);
}
这里注意
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启定时器2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //打开引脚时钟
像这些打开时钟的这种代码,一定要放在函数的头顶上面,不然的话初始化代码是无效的,在执行任何IO口都是要先打开时钟才可以使用。
然后这个分频的的两个值发现变小了
TIM_TimeBaseStructure.TIM_Period=10-1; //频率计数器的值,就是一个周期记多少个数重装载的值
TIM_TimeBaseStructure.TIM_Prescaler=1-1; //预分频器的值
因为外部的定时器触发都是触发一次执行一次的,所以这里分频的值为1就好了、然后触发9次就进入定时器通道。这个就是重装再值得意思。
如果PSC分频值给大了,就要触发好几次才会加一个值。然后到9就进入定时器通到。
这样可以利用仿真添加外部变量来获取这个外部触发得值,测试得时候就是,触发一次对射式红外num1的值就加1,这个就是PSC分频值得意思,不分频就是触发一次算一次,分频就是触发好几次才算一次。然后可以看到num1触发9次后num就加1,接着下次触发num1就清零,然后接着在0~9。这样循环。记得仿真得时候插上STM设备下载一次后设备一直插着才可以仿真。