Arduino 為何會因 delay() 停止回應,以及如何改用 millis()
Why delay() Freezes Your Arduino and How to Use millis() Instead
作者示範以 millis() 取代 Arduino 程式中的 delay(),避免控制 LED 和伺服馬達時阻塞程式,令裝置數秒內無法接收 AI 智能體的狀態訊息。改用 millis() 後,程式可在 loop() 中持續檢查時間,讓新狀態於幾毫秒內生效,LED 閃爍和伺服馬達移動亦可同時進行。文章另提醒,Arduino Uno 開啟序列埠時會重啟;較大型伺服馬達應使用獨立 5V 電源並共地。
For my AI agent project, I wanted an Arduino to show what the AI was doing: a blue LED pulses while it researches, a yellow LED pulses while it debates, and a servo sweeps back and forth while it’s busy. The Python program sends one letter over USB whenever the state changes. The first version you’d naturally write uses delay(), and it has a frustrating bug: the Arduino ignores new messages for seconds at a time. Here’s why, and how millis() fixes it.
The Problem: The Arduino Stops Listening
Here’s the obvious way to blink an LED and sweep a servo:
void loop() {
if (Serial.available() > 0) {
state = Serial.read();
}
if (state == 'B') {
digitalWrite(BLUE_LED, HIGH);
delay(500);
digitalWrite(BLUE_LED, LOW);
delay(500);
for (int angle = 0; angle <= 180; angle++) {
servo.write(angle);
delay(15);
}
}
}
It looks right, but one pass through loop() now takes almost four seconds: one second for the LED and about 2.7 seconds for the sweep. During all that time the Arduino only checks for new messages once. When the AI finishes and Python sends W, the board keeps blinking blue for a few more seconds, and the LED and servo can’t move at the same time either.
Why delay() Causes It
delay(500) doesn’t mean “wait here while doing other things”. It means “do nothing at all for 500 milliseconds”. The Arduino can’t read serial, update the servo, or blink another LED while it’s inside delay(). Every delay you add makes the whole board slower to react.
The Fix: Check the Clock with millis()
millis() returns how many milliseconds have passed since the board started. Instead of stopping to wait, each part of the program remembers when it last did something, and on every pass through loop() it checks: “has enough time passed yet?” If not, it moves on straight away. loop() then runs thousands of times a second, so everything stays responsive.
#include <Servo.h>
const int BLUE_LED = 12;
const int YELLOW_LED = 13;
const int SERVO_PIN = 8;
Servo servo;
char state = '0';
unsigned long lastLedToggle = 0;
unsigned long lastServoStep = 0;
bool pulseOn = false;
int angle = 90;
int step = 2;
// Blink one LED without stopping the program
void pulse(int pin, unsigned long intervalMs) {
unsigned long now = millis();
if (now - lastLedToggle >= intervalMs) {
pulseOn = !pulseOn;
lastLedToggle = now;
}
digitalWrite(pin, pulseOn ? HIGH : LOW);
}
// Move the servo a little, only when it's time to
void sweep(unsigned long intervalMs) {
unsigned long now = millis();
if (now - lastServoStep >= intervalMs) {
lastServoStep = now;
angle += step;
if (angle >= 180 || angle <= 0) {
step = -step; // turn around at the ends
}
servo.write(angle);
}
}
void setup() {
Serial.begin(9600);
pinMode(BLUE_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
servo.attach(SERVO_PIN);
servo.write(90);
}
void loop() {
if (Serial.available() > 0) {
state = Serial.read();
digitalWrite(BLUE_LED, LOW);
digitalWrite(YELLOW_LED, LOW);
}
if (state == 'B') { // researching
pulse(BLUE_LED, 500);
sweep(15);
} else if (state == 'Y') { // debating: faster pulse
pulse(YELLOW_LED, 200);
sweep(15);
} else { // '0' idle or 'W' done
servo.write(90);
}
}
Now a new letter takes effect within a few milliseconds, and the LED pulses while the servo sweeps. Writing now - lastLedToggle >= intervalMs (instead of comparing against a future time) also keeps working when millis() wraps back to zero after about 49 days.
Two More Gotchas from This Project
The board resets when Python connects. Opening the serial port restarts an Arduino Uno, so the first letters you send get lost. Wait about two seconds after
serial.Serial(...)before sending anything:time.sleep(2).Install pyserial, not serial.
pip install serialinstalls a different package. If Python saysmodule 'serial' has no attribute 'Serial', runpip uninstall serial pyserialand thenpip install pyserial.
A small SG90 servo is fine on the Uno’s 5V pin, but a bigger servo can pull so much power that the board resets mid-sweep. Give it its own 5V supply and connect the grounds together.
See It in the Full Project
The Python side, the wiring steps and how the AI agents decide which letter to send are all in Architecting Autonomous AI Agents with Hardware Integration. If you’re new to agents, start with Creating Agents: A Complete Guide.
What are you building with your Arduino? Tell me in the comments, and I might write a tutorial for it.
Originally published on my blog. I post new C, Python and AI-agent guides there regularly.
來源:DEV Community · dev.to