0%

ESP32的定时器

说明

说起单片机或者嵌入式系统,定时器应该可以说是不可不提的内容,下面以具体的代码来介绍ESP32的定时器如何使用,一般而言在实现中断处理代码时,最好让ISR仅对中断进行响应,然后把实际的处理(可能包含时间较长的操作)交给主循环来做。按照惯例,先介绍一下环境吧:
硬件:TTGO T-Display ESP32带1.14LCD的小开发板 + 编码器
软件:VSCode + PlatformIO IDE(其实就是Arduino环境)

程序详解

关于定时器的初始化大概只有四五个函数,下面直接每一段单独放出来,有详细的注释。

timerBegin()

1
2
3
4
5
6
7
//	函数名称:timerBegin()
// 函数功能:Timer初始化,分别有三个参数
// 函数输入:1. 定时器编号(0到3,对应全部4个硬件定时器)
// 2. 预分频器数值(ESP32计数器基频为80M,80分频单位是微秒)
// 3. 计数器向上(true)或向下(false)计数的标志
// 函数返回:一个指向 hw_timer_t 结构类型的指针
timer = timerBegin(0, 80, true);

timerAttachInterrupt()

1
2
3
4
5
6
7
//	函数名称:timerAttachInterrupt()
// 函数功能:绑定定时器的中断处理函数,分别有三个参数
// 函数输入:1. 指向已初始化定时器的指针(本例子:timer)
// 2. 中断处理函数的地址
// 3. 表示中断触发类型是边沿(true)还是电平(false)的标志
// 函数返回:无
timerAttachInterrupt(timer, &onTimer, true);

timerAlarmWrite()

1
2
3
4
5
6
7
//	函数名称:timerAlarmWrite()
// 函数功能:指定触发定时器中断的计数器值,分别有三个参数
// 函数输入:1. 指向已初始化定时器的指针(本例子:timer)
// 2. 第二个参数是触发中断的计数器值(1000000 us -> 1s)
// 3. 定时器在产生中断时是否重新加载的标志
// 函数返回:无
timerAlarmWrite(timer, 1000000, true);

timerAlarmEnable()

1
timerAlarmEnable(timer);		//	使能定时器

然后是中断服务函数ISR:

1
2
3
4
5
6
//	函数名称:onTimer()
// 函数功能:中断服务的功能,它必须是一个返回void(空)且没有输入参数的函数,
// 为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR 属性
void IRAM_ATTR onTimer() {
Serial.println(interruptCounter++);
}

最后是主函数,啥都不做:

1
2
3
void loop() {

}

函数的结果:每秒输出一个自加的 interruptCounter,具体如下图所示:
4.5

结构优化

如前文所述,在ISR对中断做出响应之后,真正的定时器中断处理操作其实是在主循环中,更有效的处理方式是,使用一个信号量将主循环锁定,然后在ISR中将其解锁。具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <Arduino.h>

volatile int interruptCounter;
int totalInterruptCounter;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

// 函数名称:onTimer()
// 函数功能:中断服务的功能,它必须是一个返回void(空)且没有输入参数的函数,
// 为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR 属性
void IRAM_ATTR onTimer() {
// Serial.println(interruptCounter++);
portENTER_CRITICAL_ISR(&timerMux);
interruptCounter++;
portEXIT_CRITICAL_ISR(&timerMux);
}
void setup() {
Serial.begin(115200);


// 函数名称:timerBegin()
// 函数功能:Timer初始化,分别有三个参数
// 函数输入:1. 定时器编号(0到3,对应全部4个硬件定时器)
// 2. 预分频器数值(ESP32计数器基频为80M,80分频单位是微秒)
// 3. 计数器向上(true)或向下(false)计数的标志
// 函数返回:一个指向 hw_timer_t 结构类型的指针
timer = timerBegin(0, 80, true);

// 函数名称:timerAttachInterrupt()
// 函数功能:绑定定时器的中断处理函数,分别有三个参数
// 函数输入:1. 指向已初始化定时器的指针(本例子:timer)
// 2. 中断处理函数的地址
// 3. 表示中断触发类型是边沿(true)还是电平(false)的标志
// 函数返回:无
timerAttachInterrupt(timer, &onTimer, true);

// 函数名称:timerAlarmWrite()
// 函数功能:指定触发定时器中断的计数器值,分别有三个参数
// 函数输入:1. 指向已初始化定时器的指针(本例子:timer)
// 2. 第二个参数是触发中断的计数器值(1000000 us -> 1s)
// 3. 定时器在产生中断时是否重新加载的标志
// 函数返回:无
timerAlarmWrite(timer, 1000000, true);
timerAlarmEnable(timer); // 使能定时器
}
void loop() {
if (interruptCounter > 0) {
portENTER_CRITICAL(&timerMux);
interruptCounter--;
portEXIT_CRITICAL(&timerMux);
totalInterruptCounter++;
Serial.print("An interrupt as occurred. Total number: ");
Serial.println(totalInterruptCounter);
}
}

总结

关于定时器,我个人觉得使用环境真的是非常多,也希望大家能够多看看,下面贴大佬的链接,可以多多参考。

-------------本文结束感谢您的阅读-------------