آموزش کامل تابع millis در آردوینو ؛ خداحافظی با delay و کدنویسی همزمان

آموزش کامل تابع millis در آردوینو ؛ خداحافظی با delay و کدنویسی همزمان ، اگر تا به حال با آردوینو پروژهای ساخته باشید، به احتمال زیاد اولین کدی که نوشتهاید پروژه کلاسیک «بلینک» (Blink) یا همان چشمک زدن یک LED بوده است. در آن کد ساده، همهچیز با یک دستور آشنا پیش میرود: ()delay. اما خیلی زود، وقتی تصمیم میگیرید پروژه خود را کمی پیشرفتهتر کنید ، مثلاً همزمان با چشمک زدن LED، اطلاعات یک سنسور دما را بخوانید یا به فشردن یک دکمه پاسخ دهید ، با یک دیوار بزرگ مواجه میشوید! آردوینو دیگر به دکمهها پاسخ نمیدهد، خواندن سنسورها با تاخیر انجام میشود و همهچیز کند به نظر میرسد.
علت این اتفاق ساده است: تابع ()delay میکروکنترلر شما را کاملاً فلج میکند! وقتی مینویسید (1000)delay، آردوینو دقیقاً برای ۱۰۰۰ میلیثانیه دست از هر کاری میکشد و هیچ دستور دیگری را اجرا نمیکند.
در این مقاله جامع، میخواهیم یک بار برای همیشه پرونده تابع ()delay را ببندیم و یاد بگیریم چگونه با استفاده از تابع ()millis، برنامهنویسی غیرهمزمان (Asynchronous) را در آردوینو پیاده کنیم. با این روش، میکروکنترلر شما میتواند چندین کار مختلف را به صورت همزمان و بدون کوچکترین افت سرعتی مدیریت کند.
چرا باید تابع delay را همین امروز از کدهای خود حذف کنیم؟
برای درک بهتر مشکلی که ()delay ایجاد میکند، یک آشپز را در نظر بگیرید. فرض کنید این آشپز قرار است یک کیک بپزد و همزمان چند ساندویچ آماده کند.
اگر آشپز به سبک ()delay رفتار کند، کیک را داخل فر میگذارد و ۲۰ دقیقه تمام، جلوی در فر میایستد و به ساعت نگاه میکند! در این ۲۰ دقیقه، او هیچ کار دیگری انجام نمیدهد؛ ساندویچی آماده نمیکند، سفارشی نمیگیرد و عملاً معطل میماند.
اما یک آشپز حرفهای به سبک ()millis عمل میکند: کیک را در فر میگذارد، ساعت شروع را یادداشت میکند و بلافاصله مشغول آماده کردن ساندویچها میشود. هر چند دقیقه یکبار، نگاهی به ساعت میاندازد تا ببیند ۲۰ دقیقه تمام شده است یا نه.
دستور ()delay عملکردی دقیقاً مثل آشپز اول دارد. این دستور یک Blocking Code یا «کد مسدودکننده» است. وقتی پردازنده به این دستور میرسد، در یک حلقه بسته قفل میشود تا زمان مشخص شده بگذرد. در این مدت، پردازنده توانایی پردازش ورودیهای جدید مانند کلیدها، سنسورها یا ارتباطات سریال (UART/SPI/I2C) را ندارد.
تابع millis چیست و چگونه کار میکند؟
تابع ()millis یکی از توابع داخلی آردوینو است که از زمان روشن شدن یا ریست شدن برد، تعداد میلیثانیههای سپریشده را شمارش کرده و بازمیگرداند.
ویژگیهای کلیدی تابع ()millis:
- بدون توقف: فراخوانی این تابع، برخلاف
()delay، زمان اجرای کد را متوقف نمیکند. - نوع خروجی: مقدار بازگشتی این تابع از نوع
unsigned long(عدد صحیح مثبت ۳۲ بیتی) است. - محدوده شمارش: به دلیل ۳۲ بیتی بودن، این تایمر میتواند تا حدود ۴۹.۷ روز بدون وقفه شمارش کند تا اینکه مقدار آن سرریز (Overflow) شده و دوباره صفر شود.
| ویژگی | تابع delay | تابع millis |
| ماهیت اجرای کد | مسدودکننده (Blocking) | غیر مسدودکننده (Non-blocking) |
| واکنش به ورودیها | در طول زمان تاخیر کاملاً نابینا است | واکنش آنی به دکمهها و سنسورها |
| اجرای همزمان کارها | غیرممکن (یا بسیار پیچیده) | بسیار ساده و چندمنظوره |
| میزان مصرف پردازنده | ۱۰۰٪ پردازنده در حلقه قفل میشود | پردازنده آزادانه سایر محاسبات را انجام میدهد |
فرمول طلایی: مدیریت زمان با millis
برای اینکه با ()millis زمان را مدیریت کنید، فقط به یک الگوی ساده ۳ مرحلهای نیاز دارید:
- ثبت زمان فعلی: زمان حال را با
()millisمیخوانید. - محاسبه زمان سپریشده: زمان قبلی را از زمان حال کم میکنید.
- مقایسه با بازه مورد نظر: اگر حاصل تفریق بزرگتر یا مساوی با زمان انتظار شما (Interval) بود، دستور مورد نظر را اجرا کرده و زمان قبلی را بهروزرسانی میکنید.
C++
unsigned long previousMillis = 0; // ذخیره آخرین زمانی که کار انجام شد
const long interval = 1000; // بازه زمانی مورد نظر (میلیثانیه)
void setup() {
// تنظیمات اولیه
}
void loop() {
unsigned long currentMillis = millis(); // ۱. خواندن زمان فعلی
// ۲ و ۳. بررسی گذشت زمان
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // بهروزرسانی زمان قبلی
// دستوری که باید هر ۱ ثانیه یکبار اجرا شود
}
// سایر کدهای شما بدون هیچ توقفی در اینجا اجرا میشوند
}
پروژه عملی: چشمک زدن LED و خواندن سنسور به صورت همزمان
بیاورید این مفاهیم را در یک پروژه واقعی پیاده کنیم. میخواهیم یک LED با بازه ۱ ثانیه چشمک بزند و در همین حین، مقدار یک سنسور (یا وضعیت یک دکمه) بدون هیچ تاخیری روی سریال مانیتور چاپ شود.
C++
// تعریف پینها
const int ledPin = 13;
// متغیرهای زمانبندی
unsigned long previousMillisLED = 0;
const long ledInterval = 1000; // بازه ۱ ثانیهای برای LED
int ledState = LOW;
void setup() {
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
}
void loop() {
unsigned long currentMillis = millis();
// بخش ۱: تغییر وضعیت LED بدون متوقف کردن برنامه
if (currentMillis - previousMillisLED >= ledInterval) {
previousMillisLED = currentMillis;
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
// بخش ۲: خواندن سنسور و پاسخدهی آنی (بدون هیچ تاخیری)
int sensorValue = analogRead(A0);
Serial.print("Sensor Value: ");
Serial.println(sensorValue);
}
اگر این کد را اجرا کنید، میبینید که اطلاعات سنسور با سرعت بسیار بالا روی سریال مانیتور قرار میگیرد و LED نیز دقیقاً هر ۱ ثانیه یکبار تغییر وضعیت میدهد، بدون اینکه روی کار یکدیگر تاثیری بگذارند.
چالش سرریز (Overflow) در millis و نحوه حل آن
یکی از دغدغههای مهندسان الکترونیک هنگام کار با ()millis، مسئله سرریز شدن یا رسیدن شمارنده به سقف ۴۹.۷ روز است. پس از این زمان، متغیر ۳۲ بیتی پر شده و مقدار ()millis ناگهان صفر میشود.
اگر ریاضیات زمانبندی را اشتباه بنویسید، پروژه شما بعد از ۵۰ روز کار مداوم قفل خواهد شد!
نکته کلیدی برای جلوگیری از باگ سرریز:
همواره از تفریق برای محاسبه زمان استفاده کنید (
currentMillis - previousMillis >= interval).هرگز از جمع استفاده نکنید (
currentMillis >= previousMillis + interval).
به لطف ویژگیهای سرریز در محاسبات اعداد بدون علامت (unsigned integer arithmetic) در سیستمهای کامپیوتر، تفریق دو عدد unsigned long حتی پس از سرریز شدن شمارنده، حاصل کاملاً درستی ارائه میدهد.
چه زمانی مجاز هستیم از delay استفاده کنیم؟
آیا باید ()delay را برای همیشه کنار بگذاریم؟ نه لزوماً!
استفاده از ()delay در موارد زیر اشکالی ندارد و حتی کد را سادهتر میکند:
- در بخش
()setup: زمانی که مدار در حال راهاندازی است و نیاز دارید چند ثانیه برای پایدار شدن یک سنسور یا ماژول (مثل GSM یا Wi-Fi) صبر کنید. - پروژههای بسیار ساده: تستهای اولیه و سریع که هیچ ورودی همزمانی در آنها وجود ندارد.
- تپشهای بسیار کوتاه (Microsecond Delays): برای ایجاد پالسهای چند میکروثانیهای روی پایه ماژولهایی مثل التراسونیک (دستور
()delayMicroseconds).
سوالات متداول (FAQ)
خیر، فراخوانی ()millis بسیار سریع است و تنها یک متغیر سیستمی را میخواند. این روش صدها برابر کارآمدتر از قفل کردن پردازنده با ()delay است.
تابع ()micros دقیقاً مانند ()millis عمل میکند، با این تفاوت که زمان را بر حسب میکروثانیه (یکمیلیونیم ثانیه) برمیگرداند. دقت داشته باشید که ()micros پس از حدود ۷۰ دقیقه سرریز میشود.
کافی است برای هر کار یک متغیر previousMillis و یک interval مجزا تعریف کنید. به عنوان مثال previousMillisLED برای LED و previousMillisSensor برای سنسور.
خیر، تابع ()millis زمان را با دقت میلیثانیه برمیگرداند و به دلیل اورهد کدهای برنامه و ساختار تایمر داخلی، برای زمانبندیهای زیر چند میلیثانیه مناسب نیست. اگر به دقت بسیار بالا در حد میکروثانیه نیاز دارید، باید از تابع ()micros استفاده کنید یا برای کارهای فوقالعاده حساس (مانند تولید سیگنال PWM اختصاصی)، مستقیماً به سراغ وقفه تایمرها (Timer Interrupts) بروید.
تابع ()millis برای مدیریت زمانبندیهای عمومی (مثل چشمک زدن LED، خواندن دورهای سنسور یا ایجاد تاخیر بدون قفل شدن) استفاده میشود و کد در داخل حلقه ()loop چک میشود. اما وقفه (Interrupt) زمانی استفاده میشود که بخواهید رویدادی بیدرنگ و آنی (مثل فشار داده شدن یک دکمه اضطراری یا دریافت پالس از انکودر موتور) پردازش شود. در وقفه، اجرای برنامه اصلی بلافاصله متوقف شده، کد وقفه اجرا میشود و سپس برنامه به کار خود ادامه میدهد.
نتیجهگیری
گذار از تابع ساده اما محدودکننده ()delay به سمت تابع ()millis، نقطه عطفی در مسیر حرفهای شدن شما در برنامهنویسی میکروکنترلرها است. با استفاده از این روش غیرمسدودکننده (Non-blocking)، نه تنها مشکل هنگ کردن و عدم پاسخدهی مدار در برابر سنسورها و کلیدها برطرف میشود، بلکه زیربنای ساخت سیستمهای نهفته پیچیده و چندمنظوره نیز شکل میگیرد.
اکنون که الگوی سه مرحلهای مدیریت زمان با ()millis و نحوه جلوگیری از باگهای رایج مانند سرریز متغیرها را فرا گرفتهاید، وقت آن رسیده که پروژههای قبلی خود را بازبینی کنید و با حذف تاخیرهای سختافزاری، کدهایی روانتر، بهینهتر و حرفهایتر بنویسید.
اگر این مقاله آموزش کامل تابع millis در آردوینو ؛ خداحافظی با delay و کدنویسی همزمان برای شما مفید بود، آن را با دوستان خود به اشتراک بگذارید و برای مشاهده آموزشهای بیشتر به سایت ما سر بزنید.
دیدگاههای محترمانه: لطفاً نظرات خود را با رعایت احترام به دیگران و به صورت محترمانه ارسال کنید. از بهکار بردن زبان توهینآمیز، تهدیدآمیز یا نژادپرستانه خودداری کنید.
حفظ حریم خصوصی: از درج اطلاعات شخصی خود یا دیگران مانند شماره تماس، آدرس و هرگونه اطلاعات حساس خودداری کنید.
محتوای تبلیغاتی: ارسال دیدگاههای تبلیغاتی، لینکهای خارجی یا هر نوع محتوای تجاری که مرتبط با موضوع نباشد، ممنوع است.
موافقت با قوانین: با ارسال دیدگاه خود، شما تأیید میکنید که قوانین فوق را خوانده و با آنها موافقید. تیم ما حق دارد نظرات غیرمجاز را حذف کند.
پروژه های پیشنهادی
پروژه ریموت کنترل کدلرن حرفه ای
مشاهده پروژهپروژه امپلی فایر صوتی استریو با TDA2030
مشاهده پروژهپروژه قفل دیجیتال با ماژول RFID RC522
مشاهده پروژه