مشکل delay
delay(1000) ساده است ولی در آن یک ثانیه، کد اصلی هیچ کار دیگری انجام نمیدهد. برای Blink ساده اشکالی ندارد؛ اما ربات باید همزمان دکمه اضطراری، سنسور و موتور را بررسی کند.
راه بهتر برای بسیاری از کارهای دورهای استفاده از millis() است:
unsigned long previousBlink = 0;
const unsigned long interval = 500;
bool ledState = false;
void loop() {
unsigned long now = millis();
if (now - previousBlink >= interval) {
previousBlink = now;
ledState = !ledState;
digitalWrite(LED_BUILTIN, ledState);
}
readEmergencyButton();
readSensors();
}
برنامه منتظر نمیماند؛ هر دور loop سریع بررسی میکند آیا زمان کار دورهای رسیده یا نه.
چرا تفاضل زمان؟
millis() بعد از مدت طولانی سرریز (wrap around) میکند. الگوی now - previous >= interval با نوع unsigned در بسیاری از کاربردهای Arduino این سرریز را درست تحمل میکند؛ مقایسههای ساده مثل now > previous + interval میتوانند در مرز سرریز مشکلساز شوند.
چند تایمر نرمافزاری
برای Blink، خواندن سنسور و ارسال Log میتوانی سه previous... مستقل داشته باشی. هر کار interval خودش را دارد:
LED: هر 500ms
Sensor: هر 50ms
Telemetry:هر 1000ms
این هنوز سیستمعامل چندوظیفهای نیست؛ همه کارها در همان loop اجرا میشوند. اگر یکی از توابع خودش ۳ ثانیه Block کند، بقیه هم دیر میشوند.
محدودیت مهم: کارهای طولانی هنوز Block میکنند
استفاده از millis() فقط وقتی مفید است که خود کارهایی که در loop() صدا میزنی کوتاه باشند. اگر تابعی داخل خودش چند ثانیه منتظر بماند، فایل بزرگی بخواند یا یک حلقه طولانی بدون خروج اجرا کند، بقیه زمانبندیها باز هم دیر میشوند. برای پروژههای ساده تلاش کن هر دور loop() سریع تمام شود و وضعیت کارهای طولانی را در چند مرحله نگه داری. این طرز فکر بعدا مقدمه خوبی برای State Machine است؛ یعنی به جای «صبر کن تا کار تمام شود»، وضعیت فعلی را نگه میداریم و در دور بعد ادامه میدهیم.
تمرین
برنامهای طراحی کن که LED هر ۵۰۰ms تغییر کند ولی سنسور هر ۱۰۰ms خوانده شود. روی کاغذ Timeline دو ثانیهای بکش و زمان اجرای هر کار را علامت بزن. سپس کد را با دو متغیر previous مستقل پیاده کن و از Serial برای ثبت زمان نمونه سنسور استفاده کن.