ساخت پروژه
0 محصولات نمایش سبد خرید

هیچ محصولی در سبد خرید نیست.

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

آموزش کامل تابع 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 زمان را مدیریت کنید، فقط به یک الگوی ساده ۳ مرحله‌ای نیاز دارید:

  1. ثبت زمان فعلی: زمان حال را با ()millis می‌خوانید.
  2. محاسبه زمان سپری‌شده: زمان قبلی را از زمان حال کم می‌کنید.
  3. مقایسه با بازه مورد نظر: اگر حاصل تفریق بزرگ‌تر یا مساوی با زمان انتظار شما (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 حتی پس از سرریز شدن شمارنده، حاصل کاملاً درستی ارائه می‌دهد.

مقاله پیشنهادی  آموزش ارتباط USB در میکروکنترلر STM32 از تنظیمات تا پیاده‌سازی

چه زمانی مجاز هستیم از delay استفاده کنیم؟

آیا باید ()delay را برای همیشه کنار بگذاریم؟ نه لزوماً!

استفاده از ()delay در موارد زیر اشکالی ندارد و حتی کد را ساده‌تر می‌کند:

  • در بخش ()setup: زمانی که مدار در حال راه‌اندازی است و نیاز دارید چند ثانیه برای پایدار شدن یک سنسور یا ماژول (مثل GSM یا Wi-Fi) صبر کنید.
  • پروژه‌های بسیار ساده: تست‌های اولیه و سریع که هیچ ورودی هم‌زمانی در آن‌ها وجود ندارد.
  • تپش‌های بسیار کوتاه (Microsecond Delays): برای ایجاد پالس‌های چند میکروثانیه‌ای روی پایه ماژول‌هایی مثل التراسونیک (دستور ()delayMicroseconds).

سوالات متداول (FAQ)

1. آیا تابع millis باعث کند شدن میکروکنترلر می‌شود؟

خیر، فراخوانی ()millis بسیار سریع است و تنها یک متغیر سیستمی را می‌خواند. این روش صدها برابر کارآمدتر از قفل کردن پردازنده با ()delay است.

2. فرق millis با micros چیست؟

تابع ()micros دقیقاً مانند ()millis عمل می‌کند، با این تفاوت که زمان را بر حسب میکروثانیه (یک‌میلیونیم ثانیه) برمی‌گرداند. دقت داشته باشید که ()micros پس از حدود ۷۰ دقیقه سرریز می‌شود.

3. چطور چند کار با زمان‌بندی‌های متفاوت را هم‌زمان اجرا کنیم؟

کافی است برای هر کار یک متغیر previousMillis و یک interval مجزا تعریف کنید. به عنوان مثال previousMillisLED برای LED و previousMillisSensor برای سنسور.

4. آیا می‌توان با millis بازه‌های زمانی بسیار دقیق (مثلاً در حد میکروثانیه) ایجاد کرد؟

خیر، تابع ()millis زمان را با دقت میلی‌ثانیه برمی‌گرداند و به دلیل اورهد کدهای برنامه و ساختار تایمر داخلی، برای زمان‌بندی‌های زیر چند میلی‌ثانیه مناسب نیست. اگر به دقت بسیار بالا در حد میکروثانیه نیاز دارید، باید از تابع ()micros استفاده کنید یا برای کارهای فوق‌العاده حساس (مانند تولید سیگنال PWM اختصاصی)، مستقیماً به سراغ وقفه تایمرها (Timer Interrupts) بروید.

5. فرق تابع millis با وقفه (Interrupt) چیست و کدام‌یک بهتر است؟

تابع ()millis برای مدیریت زمان‌بندی‌های عمومی (مثل چشمک زدن LED، خواندن دوره‌ای سنسور یا ایجاد تاخیر بدون قفل شدن) استفاده می‌شود و کد در داخل حلقه ()loop چک می‌شود. اما وقفه (Interrupt) زمانی استفاده می‌شود که بخواهید رویدادی بی‌درنگ و آنی (مثل فشار داده شدن یک دکمه اضطراری یا دریافت پالس از انکودر موتور) پردازش شود. در وقفه، اجرای برنامه اصلی بلافاصله متوقف شده، کد وقفه اجرا می‌شود و سپس برنامه به کار خود ادامه می‌دهد.

نتیجه‌گیری

گذار از تابع ساده اما محدودکننده ()delay به سمت تابع ()millis، نقطه عطفی در مسیر حرفه‌ای شدن شما در برنامه‌نویسی میکروکنترلرها است. با استفاده از این روش غیرمسدودکننده (Non-blocking)، نه تنها مشکل هنگ کردن و عدم پاسخ‌دهی مدار در برابر سنسورها و کلیدها برطرف می‌شود، بلکه زیربنای ساخت سیستم‌های نهفته پیچیده و چندمنظوره نیز شکل می‌گیرد.

اکنون که الگوی سه مرحله‌ای مدیریت زمان با ()millis و نحوه جلوگیری از باگ‌های رایج مانند سرریز متغیرها را فرا گرفته‌اید، وقت آن رسیده که پروژه‌های قبلی خود را بازبینی کنید و با حذف تاخیرهای سخت‌افزاری، کدهایی روان‌تر، بهینه‌تر و حرفه‌ای‌تر بنویسید.

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

امتیاز دادن به مطلب
0
دیدگاه‌های نوشته

  1. دیدگاه‌های محترمانه: لطفاً نظرات خود را با رعایت احترام به دیگران و به صورت محترمانه ارسال کنید. از به‌کار بردن زبان توهین‌آمیز، تهدیدآمیز یا نژادپرستانه خودداری کنید.

  2. حفظ حریم خصوصی: از درج اطلاعات شخصی خود یا دیگران مانند شماره تماس، آدرس و هرگونه اطلاعات حساس خودداری کنید.

  3. محتوای تبلیغاتی: ارسال دیدگاه‌های تبلیغاتی، لینک‌های خارجی یا هر نوع محتوای تجاری که مرتبط با موضوع نباشد، ممنوع است.

  4. موافقت با قوانین: با ارسال دیدگاه خود، شما تأیید می‌کنید که قوانین فوق را خوانده و با آن‌ها موافقید. تیم ما حق دارد نظرات غیرمجاز را حذف کند.


*
*