Skip to content

Arduino - 时间

管理时间是嵌入式编程最基本的一个方面。Arduino 提供了简单的函数来暂停执行,但要创建响应式和复杂的项目,你必须学会以非阻塞方式处理时间。

delay() 函数通常是初学者学习的第一个时间函数。它会暂停程序指定数量的毫秒。

函数签名:

void delay(unsigned long ms); // 暂停 'ms' 毫秒
void delayMicroseconds(unsigned int us); // 暂停 'us' 微秒

delay() 的问题: 当你的程序处于 delay() 调用中时,它会阻塞。它无法读取传感器、检查按钮按下,或通过串口进行通信。这使得它不适用于任何需要同时处理多项任务的项目。

millis() 函数:非阻塞代码的关键

Section titled “millis() 函数:非阻塞代码的关键”

millis() 函数是 Arduino 上多任务处理的基石。它返回自 Arduino 开发板开始运行当前程序以来经过的毫秒数。

函数签名:

unsigned long millis(); // 返回自启动以来的毫秒数

核心思想: 不是暂停程序,而是记录事件发生的时间。然后,在你的主 loop() 函数中,你持续检查是否已过去足够的时间来触发下一个事件。这就像看钟表来判断是否该做某事,而不是启动秒表并一直盯着它。

这是一个典型示例,演示了 millis() 的强大功能。它在闪烁 LED 的同时,让 loop() 函数可以自由地执行其他任务。

// LED 引脚和闪烁间隔的常量
const int ledPin = LED_BUILTIN; // 使用内置 LED
const long interval = 1000; // 闪烁的间隔(1000 毫秒 = 1 秒)
// 存储状态的变量
int ledState = LOW; // ledState 用于设置 LED
// 将存储 LED 上次更新的时间
unsigned long previousMillis = 0;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
// 获取当前时间
unsigned long currentMillis = millis();
// 检查是否是时候闪烁 LED 了
if (currentMillis - previousMillis >= interval) {
// 保存上次闪烁 LED 的时间
previousMillis = currentMillis;
// 如果 LED 熄灭,则打开它,反之亦然
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// 用新的 ledState 设置 LED
digitalWrite(ledPin, ledState);
}
// --- 你可以在这里添加其他代码 ---
// 例如,读取传感器或检查按钮。
// 这段代码将持续运行,不会被 LED 闪烁阻塞。
}

对于需要更高时间分辨率的任务(例如控制舵机或读取某些传感器),可以使用 micros() 函数。它的工作方式与 millis() 完全相同,但返回自程序启动以来的微秒数。

函数签名:

unsigned long micros(); // 返回自启动以来的微秒数

millis() 函数使用 unsigned long 类型,它可以存储高达 4,294,967,295 的值。这意味着 millis() 将在大约 49.7 天后溢出(重置为零)。micros() 函数溢出速度快得多,大约 70 分钟后就会溢出。

幸运的是,“不使用 delay() 闪烁 LED”示例中使用的减法技术(currentMillis - previousMillis)旨在正确处理这种回绕,这得益于无符号整数算术的特性。只要你使用这种模式,对于短于 49 天的间隔,你无需担心回绕问题。

  • 避免使用 delay():对于任何需要执行多个简单、顺序任务的项目,请避免使用 delay()。尽早采用 millis() 模式。
  • 使用 unsigned long:始终将 millis() 或 micros() 的结果存储在 unsigned long 变量中,以避免溢出问题和错误。
  • 管理多个定时器:你可以通过为每个独立定时事件使用单独的 previousMillis 和 interval 变量来管理它们。
  • 选择正确的工具:对于大多数人类尺度的时间(秒、分钟),请使用 millis()。仅当你在快速硬件交互中需要非常高的精度时才使用 micros()。