一、定时器:

        ESP32 内置4 个64-bit 通用定时器。每个定时器包含一个16-bit 预分频器和一个64-bit 可自动重新加载向上/向下计数器。

        ESP32 的定时器分为2 组,每组2 个。TIMGn_Tx 的n 代表组别,x 代表定时器编号。

定时器特性:

  • 16-bit 时钟预分频器,分频系数为2-65536
  • 64-bit 时基计数器
  • 可配置的向上/向下时基计数器:增加或减少
  • 暂停和恢复时基计数器
  • 报警时自动重新加载
  • 当报警值溢出/低于保护值时报警
  • 软件控制的即时重新加载
  • 电平触发中断和边沿触发中断
  • 1.  16-bit 预分频器

             每个定时器都以APB 时钟(缩写APB_CLK,频率通常为80 MHz)作为基础时钟。而预分频器的作用就是对APB时钟进行分频,产生时基计数器时钟(TB_clk)。TB_clk 每过一个周期,时基计数器会向上数一或者向下数一。在使用寄存器TIMGn_Tx_DIVIDER 配置分频器除数前,必须关闭定时器(清零TIMGn_Tx_DIVIDER)。定时器使能时配置预分频器会导致不可预知的结果。

     2. 寄存器

  • TIMGn_Tx_EN 置1 后,定时器x 时基计数器使能。(读/写) 
  •          TIMGn_Tx_EN 置1 或清零可以使能或关闭计数。

  • TIMGn_Tx_INCREASE 置1 后,定时器x 的时基计数器会在每个时钟周期后增加。清零后,定时器x 时基计数器会在每个时钟周期后减少。(读/写)
  •          TIMGn_Tx_INCREASE 置1 或清零可以将64-bit 时基计数器分别配置为向上计数或向下计数。同时,64-bit 时基计数器支持自动重新加载和软件即时重新加载,计数器达到软件设定值时会触发报警事件。

  • TIMGn_Tx_AUTORELOAD 置1 后,定时器x 报警时自动重新加载使能。(读/写)
  • TIMGn_Tx_DIVIDER 计时器x 时钟(Tx_clk) 的预分频器值。(读/写)
  • TIMGn_Tx_EDGE_INT_EN 置1 后,报警会产生一个边沿触发中断。(读/写)
  • TIMGn_Tx_LEVEL_INT_EN 置1 后, 报警会产生一个电平触发中断。(读/写)
  •        电平触发是在高或低电平保持的时间内触发,而边沿触发是由高到低或由低到高这一瞬间触发。

  • TIMGn_Tx_ALARM_EN 置1 后, 报警使能。(读/写)
  •        TIMGn_TxLO_REG 在TIMGn_TxUPDATE_REG 上写值后,定时器x 时基计数器的低32 位可以被读取。(只读)

             TIMGn_TxHI_REG 在TIMGn_TxUPDATE_REG 上写值后,定时器x 时基计数器的高32 位可以被读取。(只读)

             TIMGn_TxUPDATE_REG 写任何值触发定时器x 时基计数器值更新(定时器x 当前值会被存储到以上寄存器)。(只写)

            以上寄存器只涉及到配置寄存器以及基本的64-bit时基寄存器,还有一些其他的如报警、重新加载等寄存器可以参考官方技术手册。

    二、软件程序编写:

    time.h文件内容:

    #ifndef COMPONENTS_TIME_INCLUDE_TIME_H_
    #define COMPONENTS_TIME_INCLUDE_TIME_H_
    
    #include "driver/timer.h"
    #include <stdio.h>
    
    void Time_Init();//初始化定时器函数
    void IRAM_ATTR timer_group0_isr(void *para); //定时器中断函数
    
    #endif /* COMPONENTS_TIME_INCLUDE_TIME_H_ */

    time.c文件内容:

    #include "time.h"
    #include "led.h"
    
    int led_flag = 0;
    
    void Time_Init(){
    	/**
    	 * 设置定时器初始化参数
    	 */
    	timer_config_t config ={
            .divider = 8, //分频系数
    		.counter_dir = TIMER_COUNT_UP, //计数方式为向上计数
    		.counter_en = TIMER_PAUSE, //调用timer_init函数以后不启动计数,调用timer_start时才开始计数
    		.alarm_en = TIMER_ALARM_EN, //到达计数值启动报警(计数值溢出,进入中断)
            .auto_reload = 1, //自动重新装载预装值
    	};
        /**
         * 初始化定时器
         *    TIMER_GROUP_0(定时器分组0)
         *    TIMER_0(0号定时器)
         */
    	timer_init(TIMER_GROUP_0,TIMER_0,&config);
    
    	/*设置定时器预装值*/
    	timer_set_counter_value(TIMER_GROUP_0,TIMER_0,0x00000000ULL);
    
    	/**
    	 * 设置报警阈值
    	 *    1000[定时1000ms] (TIMER_BASE_CLK[定时器时钟/8[分频系数]/1000[延时为ms级别,因此除以1000])
    	 */
    	timer_set_alarm_value(TIMER_GROUP_0,TIMER_0,3000*(TIMER_BASE_CLK/8/1000)); //TIMER_BASE_CLK 为80M
    	//定时器中断使能
    	timer_enable_intr(TIMER_GROUP_0,TIMER_0);
    	/**
    	 * 注册定时器中断函数
    	 */
    	timer_isr_register(TIMER_GROUP_0,TIMER_0,
    			timer_group0_isr,  //定时器中断回调函数
    			(void*)TIMER_0,    //传递给定时器回调函数的参数
    			ESP_INTR_FLAG_IRAM, //把中断放到 IRAM 中
    			NULL //调用成功以后返回中断函数的地址,一般用不到
    			);
    	/*启动定时器*/
    	timer_start(TIMER_GROUP_0,TIMER_0); //开始计数
    }
    
    /**
     * 定时器中断函数
     */
    void IRAM_ATTR timer_group0_isr(void *para){
    	//获取定时器分组0中的哪一个定时器产生了中断
    	    uint32_t timer_intr = timer_group_get_intr_status_in_isr(TIMER_GROUP_0); //获取中断状态
    	    if (timer_intr & TIMER_INTR_T0) {//定时器0分组的0号定时器产生中断
    	        /*清除中断状态*/
    	        timer_group_clr_intr_status_in_isr(TIMER_GROUP_0, TIMER_0);
    	        /*重新使能定时器中断*/
    	        timer_group_enable_alarm_in_isr(TIMER_GROUP_0, TIMER_0);
    	    }
            /*led交替闪烁,时间为定时器时间2s*/
            if(led_flag==0){
            	led_flag =1;
            	led_on();
            }else{
            	led_flag=0;
            	led_off();
            }
    }
    

    定时器的初始化如上述代码Time_Init()中所示。主要涉及的API函数有:

  • timer_init() 定时器初始化函数;
  • timer_config_t config 定时器配置结构体,用于配置分频器,计数模式,计数器使能,报警使能,自动重载等。
  • timer_set_alarm_value() 设置警报值(中断),定时器一旦超过该值,会立即触发中断。
  • timer_enable_intr() 使能定时器中断
  • timer_isr_register 注册定时器中断函数
  • timer_start() 启动定时器
  • main函数:

    #include "interrupt.h"
    #include "driver/timer.h"
    #include "time.h"
    #include "led.h"
    void app_main(void)
    {
    //	/**
    //	 * 初始化GPIO
    //	 */
    	led_init();
    //	inter_init();
    	Time_Init();
        while(1) {
            //必须加延时,任务不能没有延时,否则导致任务无法切换.
            vTaskDelay(1000 / portTICK_RATE_MS);
        }
    }

            实验现象,在定时器的驱动下,LED每隔2秒,电平翻转一次。 

    软件定时器:

            除了上述硬件定时器之外,还有软件定时器。软件定时器是在硬件定时器的基础之上实现的。软件定时器内部运行着一个1us的硬件定时器,而软件定时器的回调函数都放在了这个1us定时器的中断函数里面。

    time2.h文件内容:

    #ifndef COMPONENTS_TIME2_INCLUDE_TIME2_H_
    #define COMPONENTS_TIME2_INCLUDE_TIME2_H_
    
    #include <stdio.h>
    #include "driver/timer.h"
    #include "esp_timer.h"
    #include "led.h"
    
    void Time2_Init(); //定时器初始化
    void esp_timer_cb(void *arg); //定时器中断函数
    
    #endif /* COMPONENTS_TIME2_INCLUDE_TIME2_H_ */

    time2.c文件内容:

     

    #include "time2.h"
    
    int led_flag = 0;
    esp_timer_handle_t esp_timer_handle = 0; //定时器句柄
    
    /**
     * 初始化软件定时器
     */
    void Time2_Init(){
    	//定时器结构体初始化
    	esp_timer_create_args_t fw_timer = {
    			.callback = &esp_timer_cb, //定时器回调函数
    			.arg = NULL, //传递给回调函数的参数
    			.name = "esp_timer", //定时器名称
    	};
    
    	/**
    	 * 创建定时器
    	 *     返回值为定时器句柄,用于后续对定时器进行其他操作。
    	 */
    	esp_err_t err = esp_timer_create(&fw_timer,&esp_timer_handle);
    	//启动定时器 以循环方式启动定时器
    	err = esp_timer_start_periodic(esp_timer_handle,1000*1000); //us级定时,1000*1000=1s
    	//单次启动
    	//err = esp_timer_start_once(esp_timer_handle, 1000 * 1000);
    	if(err==ESP_OK){
    		printf("ok!\r\n");
    	}
    }
    
    
    /**
     * 定时器回调函数
     */
    void esp_timer_cb(void *arg){
        /*led交替闪烁,时间为定时器时间2s*/
        if(led_flag==0){
        	led_flag =1;
        	led_on();
        }else{
        	led_flag=0;
        	led_off();
        }
    }

            上述软件定时器相当于对开始的硬件定时器进行了1us定时封装,省去了繁琐的配置过程,更易于开发过程。采用这种方法配置定时器用到的API函数有:

    创建定时器函数:esp_timer_create(); 

    函数原型

    esp_err_t esp_timer_create

             const esp_timer_create_args_t* create_args,

             esp_timer_handle_t* out_handle

    参数

    create_args: 定时器结构体

    typedef struct {
            esp_timer_cb_t callback; //定时器时间到回调
            void* arg; //要传入回调的参数
            esp_timer_dispatch_t dispatch_method; //从任务或 ISR 调用回调
            const char* name; //定时器名称,esp_timer_dump 函数使用
    } esp_timer_create_args_t;
    out_handle:定时器句柄

    函数功能 创建定时器
    返回值 ESP_OK:成功
    ESP_ERR_INVALID_ARG : 参数错误
    ESP_ERR_INVALID_STATE:定时器已经运行

    启动单次定时器函数:esp_timer_start_once();

    启动周期定时器函数:esp_timer_start_periodic();

    函数原型 esp_err_t esp_timer_start_periodic(
            esp_timer_handle_t timer,
            uint64_t period
    )
    参数

    timer: 定时器句柄

    period: 定时器定时周期,单位us,1000us=1ms

    函数功能 启动周期定时器。
    返回值 ESP_OK:成功
    ESP_ERR_INVALID_ARG : 参数错误
    ESP_ERR_INVALID_STATE:定时器已经运行

            除了上述两个定时器API函数之外, 还有一些其他的定时器函数,可以通过定时器句柄,实现对定时器的控制,如:

  • esp_timer_stop(esp_timer_handle_t timer);    停止定时器
  • esp_timer_delete(esp_timer_handle_t timer);  删除定时器
  • int64_t esp_timer_get_time()  获取定时器时间,返回自调用 esp 计时器 init 以来的微秒数(通常在应用程序启动的早期发生)
  • 三、结束

            本文主要介绍了ESP32通用定时器的使用,主要内容就是定时器的配置, 需要熟悉几个定时器配置使用到的API函数及结构体。

    物联沃分享整理
    物联沃-IOTWORD物联网 » ESP32定时器学习

    发表评论