Menu
CoddyTech

当 millis() 回绕时

本课属于 Coddy Arduino 学习旅程的“无需 delay 的计时”单元。第 17 课,共 18 课。

unsigned long 的最大值是 4,294,967,295。大约 49.7 天后,millis() 会达到这个值,然后从 0 重新开始。这称为回绕。

较小的数据类型也会出现同样的情况。unsigned int 的最大值是 65535:

unsigned int a = 65530;
a = a + 10;   // 65540 超出可表示范围:a 现在是 4

减法也会以同样的方式回绕,这让它很安全:一个从 65530 开始、现在读数为 4 的时钟,已经计数了 10 步;将 4 减去 65530,并把结果存入 unsigned int,恰好得到 10。同样,即使发生回绕,millis() - start 始终表示从 start 开始经过的时间。

加法并不安全。在 now >= start + interval 中,总和可能超过最大值并回绕成一个较小的数,因此计时器会立刻结束。而当 now 已回绕、总和却还没有回绕时,计时器就永远不会结束。始终使用减法:

if (now - start >= interval) {
  // 完成
}
challenge icon

挑战

中等

没人能为了测试等 49 天,所以这段程序会向 isDone() 传入接近计时器回绕时刻的时间,并打印答案。它太早就说 "done",还会永远说 "waiting"。修改 isDone() 中的那一行,让它比较从 start 开始经过的时间和 interval。

按“运行”,读取四个答案,修改这一行,然后按“检查”。

亲手试试

const unsigned long interval = 2000;

// 从 start 开始的计时器到 now 时是否已结束?
bool isDone(unsigned long start, unsigned long now) {
  return now >= start + interval;   // 修复这一行
}

// 输出经过的时间以及 isDone() 的结果。
void test(unsigned long start, unsigned long now) {
  Serial.print(now - start);
  Serial.print(" ms: ");
  if (isDone(start, now)) {
    Serial.println("done");
  } else {
    Serial.println("waiting");
  }
}

void setup() {
  Serial.begin(9600);
  // millis() 到 4294967295 后会从 0 重新开始
  test(4294966000, 4294966500);   // 即将回绕
  test(4294966000, 4294967200);   // 仍未回绕
  test(4294966000, 900);          // 时钟已越过 0
  test(4294964000, 500);          // 一个更早启动的计时器
}

void loop() {
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

无需 delay 的计时中的所有课

自己练习: Arduino 在线编辑器