在ESP32的循环中,为了避免使用 delay(100) 等阻塞循环,经常采用以下三段式的写法:
void loop() {audio.loop(); // 必须频繁调用unsigned long currentMillis = millis();// 使用millis()检查是否到了执行时间,而不是delay()if (currentMillis - previousMillis >= interval) {previousMillis = currentMillis;doSomething(); // 执行你的其他任务}
}
三段分别是:
- 求当前时间
- 求上次执行时间和当前时间的间隔(是否超过设定间隔(时间周期))
- 如果满足,更新上次执行时间为当前时间,并执行任务
- 回到开头,如此循环....
于是就有了疑问:unsigned long currentMillis = millis();这里不会溢出吗?溢出了怎么办?
简短不看的结论
对于绝大多数ESP32应用:
-
其最大值为49.7天,这样的连续运行已经非常长了
-
大多数物联网设备会定期重启(OTA更新、看门狗复位、电源波动等)
-
即使设备真的连续运行了49.7天,我们的代码也能正确处理回绕
简短回答: 会越界,但完全不需要担心,因为C/C++的无符号长整型算术特性让它能够完美地处理这个"越界"问题。
详细解释
1. millis() 返回什么?
millis() 返回一个 unsigned long 类型的值,表示从ESP32启动开始经过的毫秒数。
-
unsigned long的范围是 0 到 4,294,967,295 (即 2³² - 1) -
当达到最大值后,它会自动回绕到0,就像汽车里程表从99999回到00000一样
-
这个回绕周期大约是 49.7天 (4,294,967,295 ÷ 1000 ÷ 60 ÷ 60 ÷ 24 ≈ 49.7天)
2. 为什么我们的代码不怕回绕?
关键在于我们使用的是无符号整数的减法运算,这种运算在发生回绕时仍然能给出正确的时间间隔。
让我们通过一个例子来理解:
假设场景:
-
previousMillis在接近最大值时被保存 -
随后发生了回绕,
currentMillis变成了一个很小的值
unsigned long previousMillis = 4294967290; // 非常接近最大值
unsigned long interval = 1000; // 1秒void loop() {unsigned long currentMillis = millis();// 关键检查:当回绕发生时会发生什么?if (currentMillis - previousMillis >= interval) {// 这个条件会在回绕时正确触发吗?}
}
计算过程:
当 currentMillis 回绕到 10(假设)时:
currentMillis - previousMillis
= 10 - 4294967290
由于是无符号整数,这个减法会产生一个非常大的正数(实际上是算术上的负数,但被解释为正数):
10 - 4294967290 = (一个很大的正数,具体是 10 + (4294967296 - 4294967290) = 16)
实际上,由于无符号整数的模运算特性:
10 - 4294967290 = 16 (因为 4294967290 + 16 = 4294967306,模 4294967296 = 10)
这个结果(16)显然大于我们的间隔(1000),所以条件判断为真,代码正确执行!