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

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32定时器详解与运用指南

    发表回复