STM32定时器详解与运用指南
一、定时器基本定时功能
1.定时器本质及应用方面
定时器本身就是计数器!定时器本身就是计数器!定时器本身就是计数器!但更厉害的是,它可以通过各种不同的计数姿势的巧妙组合,实现普通的定时功能。
2.定时器的分类
在STM32的F10系列芯片中,有八个定时器,这是专业介绍
那我们到底该怎么选择用哪个类型的定时器呢?
只需要定时 → 基本定时器
要PWM/测脉冲 → 通用定时器
做电机驱动 → 高级定时器
这是一个大概,具体情况具体分析
3.如何实现定时?
芯片内部传送一个72MHz的方波信号,给计时器。由于72MHz是指一秒传输72000000个方波(一个高电平,一个低电平)而我们的计数器是16bit,1bit就是一位,只能写0和1;16bit=2的16次方=65536.远远小于72000000,所能计时的秒数很低。为了满足我们更高的计时需要,我们在计时器前加一个预分频器,起到一个分频的作用,当将预分频器设为1时,72MHz经过它的处理,会变成一秒输出36000000,给计时器。但预分频器也是有上限的,不能无限分频,它也是16bit(65536)。当两个器都是取值为65536时,我们可以计时59秒多。我们这样子就可以解决计时秒数低的问题。
但我们如何得到一个具体的时间,这里就要引出自动重装载寄存器,假设令自动重装载寄存器值为3,那么当计数器从0数到3时,自动重装载寄存器检测到计数器的值与它相同时,就会产生一个中断标志位,所以我们可以使用中断服务函数,让芯片去执行别的功能。而计数器又会从0开始数数,如此的进行重复。
举个例子:传过来一个72MHz的信号,我们一般将预分频器设为7200-1(因为从0开始数数,分频7200次就要将它赋值为7200-1),这样计数器就一秒中数10000次(72000000/7200=10000)。如果我们想实现1秒的计时,此时只需要让自动重装载寄存器的值为10000就可以达到此效果。
如图,图片来源哔哩哔哩上的keysking博主
二、定时器的外部时钟
输入滤波会过滤一些微小的抖动。
边沿检测器会根据我们的配置,检测输入信号的边沿并输出一个脉冲。有三个不同的配置,上升沿,下升沿,双边沿。
整体逻辑:通用或者高级定时器有四个输入通道,其中通道1和通道2在经过输入滤波和边沿检测后,分别输出了TI1FP1和TI2FP2(可以选择上升沿,下升沿,双边沿)进入触发器,另外通道1还输出了一个只能是双边沿触发产物的TI1_ED,然后还有个外部触发器输入ETR,它在一通操作后,可以通过触发器进入从模式控制器,也可以直接进入触发控制器。
三、配置代码实现定时器使得灯每秒亮一次灭一次
编程思路
系统内部原理:看上面这个图画红线的地方,它需要走到AHB系统总线,然后再走到APB1的TIM2; 同理小灯的GPIOA引脚在APB2上,AHB总线下面时时钟控制(RCC),我们同样也需要使能对应APB1和APB2上的时钟,也要配置。
1.使能时钟 定时器时钟(RCC)
2.配置定时器结构体
3.开启定时器中断,配置中断结构体
4.写中断服务函数,并清除中断标志位
进入代码部分
我在每行代码旁都写了相关的注释说明,请大家往下观看
led.c文件
#include "stm32f10x.h" //包含led.h文件
#include "main.c"
//初始化LED灯的函数——定义(在led.c文件),声明(在led.h文件),调用(在main.c文件)
void Chion_Led_Init(void)
{
//初始化LED引脚GPIOA1
GPIO_InitTypeDef led_initstruct; //定义GPIO的结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE ); //初始化时钟APB2的GPIOA
led_initstruct.GPIO_Mode = GPIO_Mode_Out_PP; //配置GPIO1的引脚
led_initstruct.GPIO_Pin = GPIO_Pin_1 ; //配置引脚的输出速度为2MHZ
led_initstruct.GPIO_Speed = GPIO_Speed_2MHz; //配置引脚的模式为推挽输出
GPIO_Init(GPIOA, &led_initstructt); //初始化GPIO的结构体
}
这里我们在选择LED引脚模式的时候,通常使用推挽输出,因为推挽输出的特性就是推挽输出能够同时驱动高电平和低电平,这样我们能够很直观的看出小灯是否亮灭,不用担心输出不稳导致的问题。
tim.c文件
#include "tim.h"
#include "stm32f10x.h"
void tim_config(void)
{
TIM_TimeBaseInitTypeDef TIMinitStructure;//定义定时器结构体
NVIC_InitTypeDef NVICinitStructure;//定义中断结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE );//初始化时钟
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1 );//选了优先级第一组
TIMinitStructure.TIM_ClockDivision =TIM_CKD_DIV1;//表示不分频,直接使用APB1总线时钟作为
定时器的时钟源,可以实现更高的时间精度
TIMinitStructure.TIM_CounterMode =TIM_CounterMode_Up;//选择向上计数模式,如果没有特殊需
求,那么向上计数模式是一个默认且合理的选择
TIMinitStructure.TIM_Period = 10000-1;//配置自动重装载寄存器,达到一万进入中断
TIMinitStructure.TIM_Prescaler = 7200-1;//在本例中系统时钟为72MHz,通过预分频器可以降低到
10kHz.算式为72000000/7200 =10000。为啥降到10000,是因为计数器最大只能数到65535,取一个中
间值。
TIM_TimeBaseInit(TIM2,&TIMinitStructure);//初始化定时器结构体
TIM_ITConfig( TIM2,TIM_IT_Update, ENABLE );//打开定时器中断
TIM_Cmd( TIM2, ENABLE);//为了计算更精确,再引入一个时钟,TIM2是根据我们自己选择的
NVICinitStructure.NVIC_IRQChannel =TIM2_IRQn;//中断向量,每数一秒,进入中断
NVICinitStructure.NVIC_IRQChannelPreemptionPriority =1;//在优先级第一组里,随便选一个范
围内的数字,这个是抢占优先级
NVICinitStructure.NVIC_IRQChannelSubPriority =1;//响应优先级
NVICinitStructure.NVIC_IRQChannelCmd =ENABLE;//让中断开始工作
NVIC_Init( &NVICinitStructure);//初始化中断结构体
}
因为定时器文件里包含定时器和中断,有点复杂,我们需要有个顺序,防止漏了什么代码没敲。
在这我写代码的顺序是这样的先定义定时器结构体,配置定时器结构,再初始化定时器,最后把相应的时钟使能;
再到中断,先定义中断结构体,配置中断向量,这里要注意的是当我们配置优先级时,一定不要忘了写这个代码在上面NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1 )这个是告诉电脑你选了什么组,要记得写,然后就是配置抢占优先级,响应优先级,由于我这里就用了一个中断,所以数字只要是在组的范围内都可以选,最后就是要让中断使能,并且初始化就好啦!
小灯为什么是1s灯亮,1s灯灭呢?
因为我在配置定时器,计数器开始工作,数到一万时,和自动重装载寄存器(赋值为一万)相同时,产生中断标志位,就进入中断函数(main.c中的void TIM2_IRQHandler(void)),实现灯1s闪烁。
原理:一般来说,都传输72MHz方波信号,也就是1秒传过来一个72MHz的信号,我们一般将预分频器设为7200-1(因为从0开始数数,分频7200次就要将它赋值为7200-1),这样计数器就一秒中数10000次(72000000/7200=10000)。
如果我们想实现1秒的计时,此时只需要让自动重装载寄存器的值为10000就可以达到此效果。
main.c文件
#include "stm32f10x.h"
#include "main.h"
#include "led.h"
#include "tim.h"
int main()
{
led_config();
tim_config();
GPIO_SetBits(GPIOA, GPIO_Pin_1);//让小灯熄灭
while (1)
{
}
}
void TIM2_IRQHandler(void)//中断函数
{
static uint16_t temp;//静态变量,所以第一次为0
if(TIM_GetITStatus(TIM2, TIM_IT_Update) !=RESET)//如果发生中断,产生中断标志位
{
if(temp++ %2 ==1)//这里表示0除以2,但经历完这个语句,temp变为1
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);//灯亮
}
else
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);//灯灭
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update );//清除中断标志位
}
这里我首先让小灯熄灭,我后续更好判断,然后我们就要写一个中断函数了,需要用到一个静态变量,这个变量的好处就是可以从0开始数数,与定时器原理相同,不会有歧义。初学的小伙伴记下这个函数就好啦!因为我们是想实现小灯1s亮,1s灭,所以只要我们将这个函数配置为除以2,根据余数是0,让灯灭;余数是1,让灯亮就好了,最后一定要清除中断标志位!
作者:most diligent