【esp32 学习笔记】采用 millis() 函数的非阻塞循环的写法

在ESP32的循环中,为了避免使用 delay(100) 等阻塞循环,有以下写法

*/
#define TimeInterval1  2000   //定义时间间隔1  2s
#define TimeInterval2  4000   //定义时间间隔1  4s
unsigned long tick = 0;       //定义当前系统运行的时间值
void setup() {
            
      
  // put your setup code here, to run once:
Serial.begin(9600);           //初始化串口
}

void loop() {
            
      
  tick = millis() ;           //获取当前系统运行的时间值
  if(tick % TimeInterval1 == 0)   //2s判断一次成功
  {
            
      
    Serial.println("1stask");     //执行2s的代码部分,这里是串口打印
    delay(1);                     //延时1ms,如果没有,会在1ms内多次进入,
  }                               //原因是因为系统运行的很快,执行串口打印,不需要1ms
  if(tick % TimeInterval2 == 0)   //下面同理
  {
            
      
    Serial.println("2stask");
    delay(1);
    
  }

}

另外,除了采用%(除余)方法,也经常采用以下三段式的写法:

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 变成了一个很小的值

cpp
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),所以条件判断为真,代码正确执行!

 

 

 

扩展:Arduino采用定时器实现非阻塞

示例框架如下:

#include <TimerOne.h>  //这个库需要自己去下载,

void setup() {
            
      
  // put your setup code here, to run once:
  Serial.begin(9600); //开启串口,用于调试
  Timer1.initialize(1000000);   //设置中断时间,1000000us = 1s
  Timer1.attachInterrupt(Timer1Task);  //设置中断服务程序
}

void loop() {
            
      
  // put your main code here, to run repeatedly:

}
void Timer1Task()     //1s执行以下程序
{
            
      

Serial.println("1stask");   //串口打印1s任务

}

 

 

参考资料:

https://blog.csdn.net/weixin_67907028/article/details/139886573

posted @ 2025-11-07 01:28  FBshark  阅读(38)  评论(0)    收藏  举报