ESP32学习5:定时器
一、定时器:ESP32 内置4 个64-bit 通用定时器。每个定时器包含一个16-bit 预分频器和一个64-bit 可自动重新加载向上/向下计数器。ESP32 的定时器分为2 组,每组2 个。TIMGn_Tx 的n 代表组别,x 代表定时器编号。定时器特性:16-bit 时钟预分频器,分频系数为2-6553664-bit 时基计数器可配置的向上/向下时基计数器:增加或减少暂停和恢复时基计数器报
一、定时器:
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_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函数及结构体。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)