در این آموزش، نحوه استفاده از تابع آردوینو ()millis را به جای تاخیر یاد خواهیم گرفت. ما در مورد نحوه عملکرد تابع مبتنی بر تایمر millis و موارد استفاده آن بحث خواهیم کرد. و همچنین محدودیت های اساسی تابع millis و نحوه غلبه بر مشکل سرریز ()millis در پروژه های آردوینو را صحبت خواهیم کرد. بدون مقدمه، بیایید مستقیماً وارد موضوع شویم!

تابع millis آردوینو

()millis در آردوینو یک تابع مبتنی بر تایمر است که زمان سپری شده (بر حسب میلی ثانیه) از زمان روشن شدن برد آردوینو را به شما برمی گرداند. که می تواند برای ایجاد یک پایگاه زمانی برای رویدادهای مختلف در برنامه های شما (مانند چشمک زدن LED یا هر چیز دیگری) استفاده شود. این تابع بدون استفاده از تابع delay() می توان تاخیرهای مختلفی را ایجاد کرد.

;()millis

تابع آردوینو millis نوع داده long بدون علامت را برمی گرداند. این مدت زمانی است که از روشن شدن برد آردوینو تا فراخوانی تابع millis() سپری شده است.

;()currentTime = millis

نکته:

توجه داشته باشید که تابع آردوینو millis بر اساس یک وقفه تایمر سخت افزاری است. به هم ریختن تنظیمات تایمر سخت افزاری یا غیرفعال کردن وقفه ها به طور کلی می تواند رفتار تابع millis  را مختل کند.

تابع ()millis در مقابل ()delay

اگر تازه با آردوینو آشنا شده اید، استفاده از تابع ()delay برای درج تاخیر بازه زمانی برای جداسازی رویدادهای مختلف، همیشه راحت تر است. با این حال، اگر با بسیاری از رویدادها سر و کار دارید و سعی می کنید به یک رفتار زمانی خاص دست پیدا کنید، به سرعت آشفته می شود. از آنجایی که تابع ()delay در واقع CPU را مسدود می کند و به شدت بر روی پاسخگویی برنامه شما تأثیر می گذارد.

از طرف دیگر، تابع آردوینو ()millis می تواند برای دریافت و مقایسه جایگاه های زمانی برای دستیابی به نیازهای زمان بندی مختلف مطابق با نیاز برنامه شما استفاده شود. و هیچ تأثیر منفی بر رفتار یا پاسخگویی کلی سیستم ندارد همانطور که در نمونه های این آموزش پیش رو خواهیم دید.

مثال برای عملکرد تابع ()millis

در این پروژه، ما یک تاخیر زمانی با استفاده از تابع ()millis به جای تابع ()delay ایجاد می کنیم. این یک مثال چشمک زن LED است اما به جای آن از تابع millis استفاده می کند. ما هر 100 میلی‌ثانیه یک بار LED را تغییر می‌دهیم.

;unsigned long T1 = 0, T2 = 0

;uint8_t TimeInterval = 100 

()void setup

}

 ; pinMode(LED_BUILTIN, OUTPUT)

()void loop

}

; () T2 = millis

  if( (T2-T1) >= TimeInterval )

  }

   ; digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN))

   ;() T1 = millis

  {

{

در مثال بالا یک متغیر TimeInterval  ایجاد می کنیم تا فاصله زمانی تاخیر مورد نظر را برای رویداد چشمک زن LED نگه دارد. سپس در تابع ()loop، تابع millis() را فراخوانی می کنیم تا یک نشان زمانی بگیرد که نشان دهنده زمان فعلی برد آردوینو از زمان روشن شدن آن است. و ما آن را در متغیر T2 ذخیره می کنیم. سپس، تفاوت بین T1 (زمان صفر) و T2 (نشان زمانی فعلی) را بررسی می کنیم و می بینیم که آیا مساوی یا بزرگتر از فاصله زمانی تاخیر مورد نظر (TimeInterval) است.

اگر فاصله زمانی 100 میلی‌ثانیه سپری شده باشد، شرط if برآورده می‌شود و پین خروجی LED را تغییر می‌دهیم و نشان زمانی فعلی را در متغیر T1 ذخیره می‌کنیم. و این کار را دایما تکرار می کنیم.

با این روش بدون استفاده از تابع delay() که وقت CPU را هدر می دهد می توان LED را در بازه های زمانی مشخص خاموش و روشن کرد.

خروجی تغییرات LED به صورت روی اسکپ قابل مشاهد است:

مثال چندمنظوره تابع millis

این پروژه کمی پیچیده تر از مثال قبلی است. ما از تابع آردوینو millis() برای دستیابی به چندوظیفگی و اجرای وظایف مختلف در دوره های مختلف استفاده خواهیم کرد.

ما در این پروژه 3 وظیفه خواهیم داشت که هر کدام دوره تناوب خاص خود را دارند و منطق خاصی برای اجرا دارند (عملکرد Task handler.

وظیفه 1: هر 70 میلی ثانیه اجرا می شود و LED داخلی (پین 13) را تغییر می دهد.

وظیفه 2: هر 25 میلی‌ثانیه اجرا می‌شود، یک دکمه ورودی (پین4) را می‌خواند، آن را باز می‌گرداند و خروجی LED متصل به پین 5 را در حالی که دکمه فشار داده می‌شود روشن می‌کند.

وظیفه 3: هر 200 میلی ثانیه اجرا می شود و وضعیت دکمه را از طریق پورت سریال به رایانه شخصی می فرستد.

در این مثال فقط می خواهیم نشان دهیم که چگونه می توان با استفاده از تابع millis در آردوینو چند وظیفه ای را انجام داد.

*/

 * LAB Name: Arduino millis() Multitasking Example

 * Author: Khaled Magdy

 * For More Info Visit: www.DeepBlueMbedded.com

/*

#define BTN_PIN  4

define LED_PIN  5#

 

;unsigned long Task1_T1 = 0, Task2_T1 = 0, Task3_T1 = 0, T2 = 0

;uint8_t btnC_State = 1, btnP_State = 1, btnState = 1

;uint8_t Task1_Periodicity = 70

;uint8_t Task2_Periodicity = 25

;uint8_t Task3_Periodicity = 200 

()void setup

}

;  pinMode(LED_BUILTIN, OUTPUT)

;  pinMode(LED_PIN, OUTPUT)

 ; pinMode(BTN_PIN, INPUT_PULLUP)

;  Serial.begin(9600)

()void loop

}

 ;() T2 = millis

  if( (T2 - Task1_T1) >= Task1_Periodicity )

 }

   ;() Task1_Handler

    ;()Task1_T1 = millis

  {

  if( (T2 - Task2_T1) >= Task2_Periodicity )

  }

 ;  () Task2_Handler

  ; () Task2_T1 = millis

{

  if( (T2 - Task3_T1) >= Task3_Periodicity )

 }

  ;() Task3_Handler

   ; ()Task3_T1 = millis

 {

void Task1_Handler(void)

}

  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle LED State

{

void Task2_Handler(void)

}

; btnC_State = digitalRead(BTN_PIN)

  if(btnC_State == 0 && btnP_State == 0)

  }

    ;digitalWrite(LED_PIN, 1)

 {

  else

 }

  ;  digitalWrite(LED_PIN, 0)

    ;btnP_State = btnC_State

  {

{

void Task3_Handler(void)

}

  ;Serial.print("Button State is: ")

 ; Serial.println(btnState)

{

وظیفه 1 هر 70 میلی ثانیه به صورت زیر در مثال بالا ال ای دی را خاموش و روشن می کند:

void Task1_Handler(void)

}

  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // Toggle LED State

{

وظیفه 2: وضعیت پین دکمه ورودی را می خواند و آن را در متغیر وضعیت دکمه فعلی (btnC_State) ذخیره می کند و بررسی می کند که آیا از آخرین بار یکسان بوده است (btnP_State) (منطق debouncing). اگر وضعیت دکمه LOW تأیید شود، LED خروجی به سمت HIGH هدایت می شود. در غیر این صورت، خروجی LED را LOW می کنیم.

void Task2_Handler(void)

}

  ;btnC_State = digitalRead(BTN_PIN)

  if(btnC_State == 0 && btnP_State == 0)

}

   ; digitalWrite(LED_PIN, 1)

  {

  else

  }

  ;  digitalWrite(LED_PIN, 0)

   ; btnP_State = btnC_State

 {

{

وظیفه 3: وضعیت دیجیتال دکمه (صفر یا یک بودن آن) را در پورت سریال چاپ کنید (هر 200 میلی ثانیه).

void Task3_Handler(void)

}

  ;Serial.print("Button State is: ")

  ;Serial.println(btnState)

{

مشکل سرریز تابع millis

زمانی که متغیر شمارنده داخلی آردوینو mills() به حداکثر حد خود ( 232-1 که 4,294,967,295) می رسد، سرریز می شود و به صفر برمی گردد و دوباره شروع به شمارش می کند.

این یک بار در هر 4,294,967,295 میلی‌ثانیه (49.71 روز) اتفاق می‌افتد و بسیاری از پروژه‌های شما برای این مدت طولانی اجرا نمی‌شوند. اما پس از 49.71 روز، شمارنده سرریز می شود و سیستم حداقل یک عمل را قبل از اینکه خود را اصلاح کند و به حالت عادی بازگردد، از دست می دهد.

رفتار در زمان سرریز از پروژه ای به پروژه دیگر بسته به هدفی که از تابع millis() برای آن استفاده می کنید متفاوت است. در اینجا مثال اصلی فاصله زمانی تاخیر () millis است که ما در نظر خواهیم گرفت.

;()T2 = millis

if( (T2-T1) >= TimeInterval )

}

   ;digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN))

  ; ()T1 = millis

{

همیشه T2 (نشان زمانی فعلی) بزرگتر از T1 (نشان زمانی قبلی) خواهد بود. و برنامه شما به آرامی کار می کند و LED را در هر TimeInterval همانطور که برنامه ریزی کرده اید تغییر می دهد. فرض کنید 100 میلی‌ثانیه است

با این حال، در زمان سرریز، T1 چیزی شبیه به (4,294,967,280) خواهد داشت و T2 از حد (4,294,967,295) عبور کرده و به صفر برگشته و اکنون دارای مقدار 84 است.

در این حالت، T2 دیگر بزرگتر از T1 نیست، و انجام تفریق (T2-T1) انتظار می رود یک عدد منفی بزرگ را به همراه داشته باشد. با این حال، از آنجایی که ما از اعداد بدون علامت برای هر دو متغیر استفاده می کنیم و نتیجه نیز یک عدد بدون علامت است، تفریق تفاوت مطلق بین (T2 و ،1) را نشان می دهد.

بنابراین، به هیچ وجه نیازی به نگرانی در مورد این موضوع نیست زیرا حتی در صورت وقوع، رفتار منطقی یا زمان‌بندی سیستم شما را مختل نمی‌کند. و شما می توانید این وضعیت سرریز را به تنهایی و بدون انتظار 50 روز با استفاده از مثال کد زیر تست کنید. که سناریوی سرریز را دوباره ایجاد می کند و نتیجه را که می ترسیم خراب شود را چاپ می کند.

unsigned long T1 = 4294967280; // Previous TimeStamp

unsigned long T2 = 84;         // Current TimeStamp

unsigned long T = T2 - T1;     // Time Elapsed

 

}()void setup 

 ; Serial.begin(9600)

  ;Serial.println(T)

{

}()void loop 

 ; Serial.println(T)

 ; delay(100)

{

برای حفظ امنیت، همچنین می‌توانیم نوع داده برای نتیجه تفریق را اعمال کنیم تا مطمئن شویم که اگر نسخه جدیدتر کامپایلر منتشر شده باشد یا چیز دیگری، خراب نمی‌شود. و در اینجا نحوه انجام آن آورده شده است.

If( (unsigned long)(T2-T1) >= TimeInterval )

بازنشانی تابع millis

نیازی به تنظیم مجدد برای شمارنده تابع آردوینو millis() نیست. من به صورت آنلاین خوانده‌ام که شخصی در تلاش است تایمر سخت‌افزاری را برای millis() بازنشانی کند تا از مشکل سرریز (rollover) millis  جلوگیری کند.

همانطور که در بخش قبل بیان کردیم، حتی زمانی که این اتفاق بیفتد، سرریز ()millis  زمان بندی یا رفتار منطقی سیستم شما را مختل نمی کند. و شما می توانید به صراحت نتیجه تفریق را برای محافظت از کد خود در برابر این مشکل ارسال کنید.

با این حال، بازنشانی سخت‌افزاری به تایمر سخت‌افزاری که تابع millis استفاده می‌کند، بسیاری از عملکردهای هسته داخلی آردوینو را خراب می‌کند. در ابتدای لیست عملکردهایی که مختل می شوند PWM است که به تایمر سخت افزاری نیز وابسته است.

تنظیم مجدد تایمر باعث ایجاد اشکالات ناخواسته در کانال های خروجی PWM می شود که با زمان سخت افزاری مشابهی مطابقت دارند. و به طور کلی راه حلی برای سرریز تایمر نیست.

نکاتی در مورد تابع millis

اینها نکات مهمی هستند که باید در مورد تابع آردوینو millis بدانید تا بتوانید در پروژه های خود به طور موثرتری از آن استفاده کنید.

حداکثر مقدار آردوینو millis

حداکثر مقدار برای تابع millis معادل عدد 4,294,967,295 است. که معادل 49.71 روز قبل از اینکه متغیر millis به سرریز برسد و به صفر برگردد و دوباره شروع به شمارش کند، است.

Millis به ثانیه

می توانید تابع millis را خوانده و آن را در قالب (ساعت: دقیقه: ثانیه) به زمان تبدیل کنید. در اینجا نحوه خواندن میلی ثانیه و تجزیه اطلاعات زمان مورد نظر آمده است.

;()TimeInMs = millis

;Seconds = TimeInMs / 1000

;Minutes = Seconds / 60

;Hours = Minutes / 60

دقت millis

تابع آردوینو millis  بر اساس یک وقفه تایمر سخت افزاری است که قبلاً در این آموزش گفته شد. و دقت در حدود 1 میلی ثانیه است زیرا حداقل واحد زمانی است که این تابع می تواند برگرداند. قطعاً مقداری زمان در فراخوانی توابع و سوئیچینگ زمینه تلف می‌شود که به چند میکروثانیه اضافه می‌شود که به هر حال احساس نمی‌کنیم زیرا این تابع فقط اعداد صحیح را برمی‌گرداند نه کسری از میلی‌ثانیه.

Millis در Interrupt

هنگام استفاده از تابع millis، لطفاً توجه داشته باشید که به وقفه تایمر بستگی دارد. اگر به هر دلیلی وقفه‌ها را غیرفعال کرده باشید، تا زمانی که دوباره آن را دوباره فعال نکنید، موقتاً کار نمی‌کند.

همچنین می توانید از تابع millis() در کنترل کننده های ISR (روتین سرویس وقفه) استفاده کنید. اما توجه داشته باشید که تا زمانی که شما هنوز در زمینه ISR هستید، خواندن مجدد خود را به روز نمی کند. اگر می خواهید تابع millis() را نظرسنجی کنید و منتظر بمانید تا مدتی در یک کنترل کننده ISR بگذرد، سیستم برای همیشه در آنجا گیر می کند. زیرا خروجی تابع millis() تا زمانی که در زمینه کنترل کننده ISR هستید تغییر نخواهد کرد.

علاوه بر این، استفاده از هر نوع تاخیر در کنترل کننده ISR یک عمل نامطلوب است که همیشه باید به هر قیمتی از آن اجتناب کنید. این کار در دراز مدت مشکلات زیادی را در سیستم شما ایجاد می کند. سعی کنید از روش های معرفی شده در این آموزش استفاده کنید تا از استفاده از هر نوع تاخیر "مسدود کردن CPU" خودداری کنید.