参考教程:ESP32 Timers & Timer Interrupt Tutorial (Arduino IDE) – DeepBlue
这个教程中有两个实验
1.通过定时器产生一个周期性的中断,来实现一个翻转信号控制一个LED的亮灭
2.通过定时器测量两个外部事件之间的时间。
ESP32 定时器
ESP32系列有4个硬件定时器,每个定时器是一个64位,向上/向下的计数器,并带有一个16位的分频器。ESP32C3只有2个定时器,每个定时器是54位。(对比STM32的定时器是16位的)同时ESP32的定时器在最后一个技术周期可以配置自动装载。
ESP32定时器功能描述
每个ESP32定时器都是使用APB时钟(一般是80MHz)作为基础时钟。这时钟可以被一个16位的分频器进行分频后作为 基本的tick 时间。这样我们就可以通过控制分频系数来实现不同的tick time。
16位的预分频器可以把APB_CLK除以2到65536的系数。如果设置为1或者2的话,系数位2.如果设置0的话,分频系数位65536;
ESP32 定时器告警生成
ESP32定时器可以触发警报事件,根据你的配置,决定定时器进行重载或者进行中断。当你保存在警报寄存器与当前寄存器的值相同的时候,就会触发警报。这个在接下来的项目中执行周期性的逻辑非常有用。
ESP32 定时器公式
我们将使用告警事件生成以及定时器的预分频器来实现所需的中断周期性。下面的定时器方程有 3 种特殊情况,分别是预分频器值 = 0、1 和 2 时。当 Prescaler=1or2 时,它将如下所示 [T外= 计时器滴答 x (2/APB_CLK)].当 Prescaler=0 时,它将如下所示 [T外= 计时器滴答 x (65536/APB_CLK)].否则,我们通常可以使用下面的等式。
ESP32 定时器示例 (Arduino)
假设我们希望不使用delay函数来阻塞CPU进而影响系统的时序性能的前提下,每1ms切换一次LED。根据上面的公式。APB默认为80MHz。所需的周期为1ms,因此我们可以设Prescaler=80;
根据这个公式,容易算出TimerTicks为1000;
下面是实现的代码:(乐鑫已经把这个代码的一些函数已经修改了)
#define LED 21
hw_timer_t *Timer0_Cfg = NULL;
void IRAM_ATTR Timer0_ISR()
{
digitalWrite(LED, !digitalRead(LED));
}
void setup()
{
pinMode(LED, OUTPUT);
Timer0_Cfg = timerBegin(0, 80, true);
timerAttachInterrupt(Timer0_Cfg, &Timer0_ISR, true);
timerAlarmWrite(Timer0_Cfg, 1000, true);
timerAlarmEnable(Timer0_Cfg);
}
void loop()
{
// Do Nothing!
}
这个是调整过的程序
#define LED 4
hw_timer_t *Timer0_Cfg = NULL;
void IRAM_ATTR Timer0_ISR()
{
digitalWrite(LED, !digitalRead(LED));
}
void setup()
{
pinMode(LED, OUTPUT);
Timer0_Cfg = timerBegin(1000000);
timerAttachInterrupt(Timer0_Cfg, &Timer0_ISR);
timerAlarm(Timer0_Cfg, 1000, true,0);
timerStart(Timer0_Cfg);
}
void loop()
{
// Do Nothing!
}
timerBegin()可以直接设置频率,这样就不需要自己去算分频系数了,这里写的1MHz。
timerAttachInterrupt()也只需要两个参数就可以了。
timerAlarmWrite()改为timerAlarm()前面三个参数好理解,第四个参数是重载的次数,比如写10,那么只进行10次重载,然后就结束了。
void timerAlarm(hw_timer_t * timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count);
-
timer
timer struct. -
alarm_value
alarm value to generate event. -
autoreload
enabled/disabled autorealod. -
reload_count
number of autoreloads (0 = unlimited). Has no effect if autorealod is disabled.
timerAlarmEnable()函数被改成了timerStart()
实际测试timerAlarm()第四个参数,修改成其他值使用没有影响还是能正常一直输出方波;
实际屏蔽测试ttimerStart()函数,也还是能输出方波。
实测输出方波频率为499.99Hz,还是非常准的。
可以在回调函数中把频率进行修改,这样就实现了扫频的功能,这里把计数修改成1000到10000;
1000对应的是1ms,对应周期为2ms,频率为500Hz;那么10000对应的是10ms,对应周期为20ms,对应频率为50Hz。测试可以实现效果
#define LED 4
hw_timer_t *Timer0_Cfg = NULL;
int fre=1000;//计数
void IRAM_ATTR Timer0_ISR()
{
digitalWrite(LED, !digitalRead(LED));
fre++;
if(fre == 10000){
fre=1000;
}
timerAlarm(Timer0_Cfg, fre, true,0);
}
void setup()
{
pinMode(LED, OUTPUT);
Timer0_Cfg = timerBegin(1000000);
timerAttachInterrupt(Timer0_Cfg, &Timer0_ISR);
timerAlarm(Timer0_Cfg, fre, true,0);
// timerStart(Timer0_Cfg);
}
void loop()
{
// Do Nothing!
}
为了效果更加明显,把频率提高,把1000缩短到100;频率从500Hz提高到5000Hz。
由于变化太快,所以这里修改每个频率进行10次的输出。
这是修改后的程序,非常舒服了。
#define LED 4
hw_timer_t *Timer0_Cfg = NULL;
int fre = 100; //计数
int cnt = 0;
void IRAM_ATTR Timer0_ISR() {
digitalWrite(LED, !digitalRead(LED));
cnt++;
if (cnt == 10) { //每个频率输出10次
cnt = 0;
fre++;
if (fre == 1000) {
fre = 1000;
}
}
timerAlarm(Timer0_Cfg, fre, true, 0);
}
void setup() {
pinMode(LED, OUTPUT);
Timer0_Cfg = timerBegin(1000000);
timerAttachInterrupt(Timer0_Cfg, &Timer0_ISR);
timerAlarm(Timer0_Cfg, fre, true, 0);
// timerStart(Timer0_Cfg);
}
void loop() {
// Do Nothing!
}