概述
ESP32 内置 4 个 64-bit 通用定时器。每个定时器包含一个 16-bit 预分频器和一个 64-bit 可自动重新加载向上/向下计数器。
• 16-bit 时钟预分频器,分频系数为 2-65536
• 64-bit 时基计数器
• 可配置的向上/向下时基计数器:增加或减少
• 暂停和恢复时基计数器
• 报警时自动重新加载
• 当报警值溢出/低于保护值时报警
• 软件控制的即时重新加载
• 电平触发中断和边沿触发中断
名词扫盲
16-bit 预分频器:分频就是把系统工作频率分频后当做定时器的工作频率,例如系统时钟为12MHz,12分频后定时器的dao工作时钟为1MHz。
按照ESP32的输入时钟频率为80MHZ,换句话说也就是1/80us=0.0125us就会计数加一,如何我们设置分频系数为80,则1us就会计数加一。分频系数范围是0-65536
64-bit 时基计数器:这个更简单,就是累加计数器,按照输出时钟,每过一个’波‘就加一。它的计数范围是0-0xFFFF FFFF FFFF FFFF,非常大大大大大的数。
Arduino层编程
在Arduino编程时因为无需考虑寄存器的设置,我们只需记住该外设的配置思路即可~
定时器的配置思路:
- 选择定时器(两组四个)
- 配置合适分频系数
- 绑定中断函数
- 配置报警计数器保护值
- 开启报警
其中我们还可以随时停止定时器、停止报警、重启、重设等等……
1、开启定时器
hw_timer_t * timerBegin(uint8_t timer, uint16_t divider, bool countUp);
timer(选择定时器):0-3 divider(分频系数):0-65536 countUp:是否为向上计数
代码中可以看出,程序执行了该语句后,定时器立即按照默认状态开始了工作。
hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp){
if(num > 3){
return NULL;
}
hw_timer_t * timer = &hw_timer[num];
if(timer->group) {
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_TIMERGROUP1_CLK_EN);
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_TIMERGROUP1_RST);
TIMERG1.int_ena.val &= ~BIT(timer->timer);
} else {
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_TIMERGROUP_CLK_EN);
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_TIMERGROUP_RST);
TIMERG0.int_ena.val &= ~BIT(timer->timer);
}
timer->dev->config.enable = 0;
timerSetDivider(timer, divider);
timerSetCountUp(timer, countUp);
timerSetAutoReload(timer, false);
timerAttachInterrupt(timer, NULL, false);
timerWrite(timer, 0);
timer->dev->config.enable = 1;
addApbChangeCallback(timer, _on_apb_change);
return timer;
}
2、停止定时器
直接调用,即用即停,效果显著。
void timerEnd(hw_timer_t *timer){
timer->dev->config.enable = 0;
timerAttachInterrupt(timer, NULL, false);
removeApbChangeCallback(timer, _on_apb_change);
}
3、设置定时器(细化)
void timerSetConfig(hw_timer_t *timer, uint32_t config);
uint32_t timerGetConfig(hw_timer_t *timer);
提供了可以更细化的配置内容,这里不做详解。
4、开启中断
void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge);
void timerDetachInterrupt(hw_timer_t *timer);
绑定中断函数
5、配置报警计数器保护值
void timerAlarmWrite(hw_timer_t *timer, uint64_t interruptAt, bool autoreload);
timer:目标定时器 interruptAt:报警保护值 autoreload:是否开启自动重载
配置好上面后,使能报警:
bool timerAlarmEnabled(hw_timer_t *timer);
6、杂七杂八
懒得逐一介绍~
void timerStart(hw_timer_t *timer);
void timerStop(hw_timer_t *timer);
void timerRestart(hw_timer_t *timer);
void timerWrite(hw_timer_t *timer, uint64_t val);
void timerSetDivider(hw_timer_t *timer, uint16_t divider);
void timerSetCountUp(hw_timer_t *timer, bool countUp);
void timerSetAutoReload(hw_timer_t *timer, bool autoreload);
bool timerStarted(hw_timer_t *timer);
uint64_t timerRead(hw_timer_t *timer);
uint64_t timerReadMicros(hw_timer_t *timer);
double timerReadSeconds(hw_timer_t *timer);
uint16_t timerGetDivider(hw_timer_t *timer);
bool timerGetCountUp(hw_timer_t *timer);
bool timerGetAutoReload(hw_timer_t *timer);
uint64_t timerAlarmRead(hw_timer_t *timer);
uint64_t timerAlarmReadMicros(hw_timer_t *timer);
double timerAlarmReadSeconds(hw_timer_t *timer);
例子(模拟看门狗)
当GPIO接地超过3s,系统判断程序跑飞,强制重启。
**#include <Arduino.h>
const int button = 0; // 按键用于触发延时
const int wdtTimeout = 3000; // 看门狗时间(ms)
hw_timer_t *timer = NULL;
void IRAM_ATTR resetModule() { // 中断函数
Serial.println("reboot\n");
esp_restart();
}
void setup() {
Serial.begin(9600);
Serial.println();
Serial.println("running setup");
pinMode(button, INPUT_PULLUP);
timer = timerBegin(0, 80, true); // 选择timer0,分频系数为80,向上计数
timerAttachInterrupt(timer, &resetModule, true); // 绑定中断函数
timerAlarmWrite(timer, wdtTimeout * 1000, false); // 设置报警保护函数
timerAlarmEnable(timer); // 使能报警器
}
void loop() {
Serial.println("running main loop");
timerWrite(timer, 0); // 重置定时器,喂狗 (feed watchdog)
long loopTime = millis();
// 当按键被按着超过了3秒,看门狗重启程序
while (!digitalRead(button)) {
Serial.println("button pressed");
delay(500);
}
delay(1000);
loopTime = millis() - loopTime;
Serial.print("loop time is = ");
Serial.println(loopTime);
}
评论区