|
:: استاذ و مشرف قسم الالكترونيات ::
تاريخ التسجيل: May 2007
المشاركات: 6,894
|
|
نشاط [ F.Abdelaziz ]
قوة السمعة:331
|
|
23-02-2020, 08:15 PM
المشاركة 1
|
|
الاردوينو والمهام المتعددة Multitasking وكيفية استخدام الدالة millis()
قاد تعدد المهام أجهزة الكمبيوتر إلى ثورة حيث يمكن تشغيل برنامج واحد أو أكثر في وقت واحد مما يزيد من الكفاءة والمرونة والقدرة على التكيف والإنتاجية. في الأنظمة المضمّنة embedded ، يمكن لميكروكنترولر أيضًا التعامل مع تعدد المهام وتنفيذ مهمتين أو أكثر في وقت واحد دون إيقاف التعليمات الحالية.
في هذا التدريب سوف نتعلم كيف يؤدي Arduino تعدد المهام. بشكل عام ، يتم استخدام الدالة delay() في Arduino للقيام بمهمة دورية مثل وميض ليد LED Blinking ولكن هذه الدالة توقف البرنامج لبعض الوقت المحدد ولا تسمح بإجراء عمليات أخرى. لذلك يوضح هذا التدريب كيف يمكننا تجنب استخدام الدالة delay() واستبدالها بالدالة millis() لأداء أكثر من مهمة في وقت واحد وجعل Arduino متحكم متعدد المهام. قبل الخوض في التفاصيل ، لنبدأ بتفهم تعدد المهام.
ماهو "تعدد المهام" Multitasking ؟
تعدد المهام يعني ببساطة تنفيذ أكثر من مهمة أو برنامج في وقت واحد في نفس الوقت. تقريبا جميع أنظمة التشغيل لها ميزة تعدد المهام. يُعرف هذا النوع من أنظمة التشغيل باسم MOS (نظام التشغيل متعدد المهام). يمكن أن يكون MOS نظام تشغيل الكمبيوتر المحمول أو المكتبي . مثال جيد على تعدد المهام في أجهزة الكمبيوتر هو عندما يقوم المستخدمون بتشغيل تطبيق البريد الإلكتروني ، ومتصفح الإنترنت ، ومشغل الوسائط ، والألعاب ، في نفس الوقت وإذا كان المستخدمون لا يرغبون في استخدام التطبيق يتم تشغيله في الخلفية إذا لم يتم إغلاقه. يستخدم المستخدم النهائي كل هذه التطبيقات في نفس الوقت لكن نظام التشغيل يأخذ هذا المفهوم بشكل مختلفًا بعض الشيء. دعونا نناقش كيف يدير نظام التشغيل تعدد المهام.
كما هو موضح في الشكل أعلاه ، تقسم وحدة المعالجة المركزية الوقت على ثلاثة أجزاء متساوية وتعيين كل جزء لكل مهمة / تطبيق. هذه هي الطريقة التي يتم بها تعدد المهام في معظم الأنظمة. سيكون هذا المفهوم هو نفسه تقريبًا بالنسبة إلى تعدد مهام الاردوينو ، باستثناء توزيع الوقت سيكون مختلفًا بعض الشيء. نظرًا لأن Arduino يعمل بتردد وذاكرة الوصول العشوائي (RAM) منخفض مقارنة بالكمبيوتر ، فإن الوقت الممنوح لكل مهمة سيكون مختلفًا أيضًا. اردوينو أيضا لديه الدالة delay() التي تستخدم على نطاق واسع. ولكن قبل البدء ، دعنا نناقش لماذا يجب ألا نستخدم الدالة delay() في أي مشروع.
لماذا يجب تجنب استخدام الدالة delay()
إذا تم النظر في الوثائق المرجعية للاردوينو ، نجد أن هناك نوعان من دوال التأخير ، الأول هو delay() والثاني هو delayMicroseconds() . كلتا الدالتان متطابقتان من حيث توليد التأخير. الفرق الوحيد هو أنه في الدالة delay() ، يكون الوسيط الذي يتم تمريره عدد صحيح بالمللي ثانية أي إذا كتبنا delay(1000) سيكون التأخير هو 1000 مللي ثانية أي ثانية واحدة. بالمثل ، في الدالة delayMicroseconds() ، يكون الوسيط الذى يتم تمريره بالميكروثانية أي إذا كتبنا delayMicroseconds(1000) ، فسيكون التأخير 1000 ميكروثانية أي 1 مللي ثانية.
هنا تأتي النقطة ، كلتا الدالتين توقفان مؤقتًا البرنامج لمقدار الوقت المنقضي في دالة التأخير. لذلك إذا كنا نعطي تأخيرًا لمدة ثانية واحدة ، فلن يتمكن المعالج من الانتقال إلى التعليمات التالية حتى تمر ثانية واحدة. وبالمثل ، إذا كان التأخير 10 ثوانٍ ، فسيتوقف البرنامج لمدة 10 ثوانٍ ولن يسمح للمعالج بالانتقال إلى التعليمات التالية حتى تمر 10 ثوانٍ. هذا يعوق أداء المتحكم من حيث السرعة وتنفيذ التعليمات.
أفضل مثال لتوضيح عيب دالة التأخير هو استخدام عدد 2 زر ضاغط . لنفرض أننا نريد تبديل toggle اثنين من الليدات باستخدام زرى الضغط . لذلك إذا تم الضغط على أحد زرى الضغط ، فيجب أن يضيء الليد المقابل لمدة ثانيتين ، وبالمثل إذا تم الضغط على الزر الثاني ، فيجب أن يتوهج الليد المقابل لمدة 4 ثوانٍ. ولكن عندما نستخدم delay ، إذا ضغط المستخدم على الزر الأول ، فسيتوقف البرنامج لمدة ثانيتين وإذا ضغط المستخدم على الزر الثاني قبل تأخير لمدة ثانيتين ، فلن يقبل المتحكم الإدخال حيث أن البرنامج في مرحلة التوقف halt . تشير الوثائق الرسمية لاردوينو بوضوح إلى ذلك في وصف دالة التأخير . يمكنك المراجعة والتحقق من ذلك ليكون أكثر وضوحًا.
لماذا استخدام الدالة millis() ؟
للتغلب على المشكلة الناتجة عن التأخير ، يجب على المبرمج استخدام الدالة millis() والتي هي سهلة الاستخدام بمجرد أن تصبح معتادا عليها ، وسوف تستخدم أداء وحدة المعالجة المركزية بنسبة 100 ٪ دون توليد أي تأخير في تنفيذ التعليمات. الدالة millis() هي دالة تقوم فقط بإرجاع مقدار المللي ثانية التي انقضت منذ بدأت لوحة Arduino في تشغيل البرنامج الحالي دون تجميد البرنامج. سيتم تجاوز overflow هذا الرقم الزمني (أي العودة إلى الصفر) ، بعد حوالي 50 يومًا.
تماما مثل ما أن الاردوينو لها الدالة delayMicroseconds() ، فإن لديه النسخة micro من الدالة millis() وهى micros() . الفرق بين micros و millis هو أنه سيتم تجاوز micros() بعد حوالي 70 دقيقة ، مقارنة مع millis() وهو 50 يومًا. بناءً على التطبيق ، يمكنك استخدام millis() أو micros() .
إستخدام millis() بدلا من delay() :
لاستخدام millis() للتوقيت والتأخير ، تحتاج إلى تسجيل وتخزين الوقت الذي حدث فيه الإجراء لبدء الوقت ثم التحقق على فترات زمنية ما إذا كان الوقت المحدد قد انقضى. هكذا كما ذكر ، يتم تخزين الوقت الحالي في متغير.
كود:
unsigned long currentMillis = millis();
نحتاج إلى متغيرين آخرين لمعرفة ما إذا كان الوقت المطلوب قد مر. لقد قمنا بتخزين الوقت الحالي في المتغير currentMillis ، لكننا نحتاج أيضًا إلى معرفة متى بدأت فترة التوقيت ومدة هذه الفترة. لذلك يتم الإعلان عن period و PreviousMillis. سيخبرنا الفاصل الزمني بالتأخير الزمني وسيخزن PreviousMillis آخر مرة حدث فيها الحدث.
كود:
unsigned long previousMillis;
unsigned long period = 1000;
لفهم ذلك ، دعنا نأخذ مثالاً على وميض ليد LED بسيط. ستخبرنا period = 1000 بأن الليد LED سوف يومض لمدة ثانية واحدة أو 1000 مللي ثانية.
كود:
const int ledPin = 4; // the LED pin number connected
int ledState = LOW; // used to set the LED state
unsigned long previousMillis = 0; //will store last time LED was blinked
const long period = 1000; // period at which to blink in ms
void setup() {
pinMode(ledPin, OUTPUT); // set ledpin as output
}
void loop() {
unsigned long currentMillis = millis(); // store the current time
if (currentMillis - previousMillis >= period) { // check if 1000ms passed
previousMillis = currentMillis; // save the last time you blinked the LED
if (ledState == LOW) { // if the LED is off turn it on and vice-versa
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);//set LED with ledState to blink again
}
}
هنا العبارة <if (currentMillis - previousMillis >= period)> تتحقق من ما إذا مر 1000ms . إذا مر 1000ms ، عندئذ يومض الليد ومرة أخرى يأتى إلى نفس الحالة ، ويستمر ذلك . هذا كل شيء ، لقد تعلمنا استخدام millis بدلاً من delay . هذه الطريقة لن توقف البرنامج لفترة زمنية محددة.
المقاطعات Interrupts في الاردوينو تعمل كما هو الحال في الميكروكنترولر الأخرى. تحتوي لوحة Arduino UNO على طرفين منفصلين لتوصيل المقاطعات هى الطرف 2 والطرف 3 . راجع المقاطعة فى الاردوينو.
سنقوم هنا بعرض المهام المتعددة للاردوينو من خلال التعامل مع مهمتين في نفس الوقت. ستشمل المهام وميض اثنين من الليدات في تأخير زمني مختلف جنبا إلى جنب مع زر ضغط الذي سيتم استخدامه للتحكم على حالة ON / OFF لليد . لذلك سيتم تنفيذ ثلاث مهام في وقت واحد.
الدائرة الكهربية :
برمجة الاردوينو أونو للمهام المتعددة Programming Arduino UNO for Multitasking
تتطلب برمجة الاردوينو أونو للمهام المتعددة فقط المنطق وراء كيفية عمل millis() الموضح أعلاه. يوصى بممارسة وميض ليد باستخدام millis() مرارًا وتكرارًا لجعل المنطق واضحًا ولتجعل نفسك مرتاحًا مع millis() قبل البدء في برمجة الاردوينو أونو للقيام بمهام متعددة. في هذا التدريب ، يتم استخدام المقاطعة أيضًا مع millis() في وقت واحد من أجل تعدد المهام. الزر سيكون مقاطعة. لذلك كلما تم توليد مقاطعة ، مثل الضغط على زر ، سوف يتحول الليد وضع التشغيل on أو إيقاف التشغيل off .
1- فى بداية البرنامج :
• يتم إعلان أرقام الأطراف حيث يتم توصيل الليدات والزر الضاغط .
كود:
int led1 = 6;
int led2 = 7;
int toggleLed = 5;
int pushButton = 2;
• بعد ذلك نعلن عن متغيرًات لتخزين حالة الليدات للاستخدام المستقبلي.
كود:
int ledState1 = LOW;
int ledState2 = LOW;
• تمامًا كما هو موضح أعلاه في مثال الوميض ، يتم إعلان المتغيرات period و previousmillisللمقارنة وتوليد التأخير لليدات . يومض الليد الأول بعد كل ثانية ويومض الليد الثانى بعد كل 200 مللي ثانية.
كود:
unsigned long previousMillis1 = 0;
const long period1 = 1000;
unsigned long previousMillis2 = 0;
const long period2 = 200;
• كما يتم استخدام دالة millis لتوليد تأخير منع تأثير الارتداد debounce لتجنب الضغطات المتعددة لزر الضغط.
كود:
int debouncePeriod = 20;
int debounceMillis = 0;
• ويتم استخدام ثلاثة متغيرات لتخزين حالة زر الضغط كمقاطعة ، وليد التبديل toggle LED وحالة زر الضغط.
كود:
bool buttonPushed = false;
int ledChange = LOW;
int lastState = HIGH;
2- فى الدالة setup :
• يتم تحديد (تعريف) عمل الطرف الذي سيعمل كدخل أو كخرج .
كود:
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(toggleLed, OUTPUT);
pinMode(pushButton, INPUT);
• كما يتم تحديد (تعريف) طرف المقاطعة عن طريق ربط attaching المقاطعة مع تعريف ISR ووضع Mode المقاطعة. لاحظ أنه يوصى باستخدام digitalPinToInterrupt(pin_number) عند إعلان الدالة attachInterrupt() لترجمة الرقم الرقمي الفعلي إلى رقم المقاطعة المحدد.
كود:
attachInterrupt(digitalPinToInterrupt(pushButton), pushButton_ISR, CHANGE);
3- تتم كتابة روتين خدمة المقاطعة وسيؤدي فقط إلى تغيير العلم buttonPushed. لاحظ أن روتين خدمة المقاطعة يجب أن يكون قصيرا قدر الإمكان.
كود:
void pushButton_ISR()
{
buttonPushed = true;
}
4- تبدأ الدالة loop بتخزين قيمة millis في المتغير CurrentMillis والذي سيخزن قيمة الوقت المنقضي في كل مرة تتكرر فيها الحلقة.
كود:
unsigned long currentMillis = millis();
يوجد ثلاث مهام متعددة ، وميض ليد عند كل ثانية ، ووميض ليد ثانى كل 200 مللي ثانية ، وإذا تم الضغط على زر الضغط ، يتم تبديل تشغيل/إيقاف ON / OFF ليد ثالث . لذلك سوف نكتب ثلاثة أجزاء للقيام بهذه المهمة.
• المهمة الأولى هى تبديل حالة LED بعد كل ثانية واحدة من خلال مقارنة millis المنقضي.
كود:
if (currentMillis - previousMillis1 >= period1) {
previousMillis1 = currentMillis;
if (ledState1 == LOW) {
ledState1 = HIGH;
} else {
ledState1 = LOW;
}
digitalWrite(led1, ledState1);
}
• المهمة الثانية ، وبشكل مشابه ، يتم تبديل ليد بعد كل 200 مللي ثانية من خلال مقارنة millis المنقضي.
كود:
if (currentMillis - previousMillis2 >= period2) {
previousMillis2 = currentMillis;
if (ledState2 == LOW) {
ledState2 = HIGH;
} else {
ledState2 = LOW;
}
digitalWrite(led2, ledState2);
}
• المهمة الثالثة ، أخيرًا ، يتم مراقبة العلم buttonPushed وبعد إنشاء تأخير إلغاء الارتداد 20 مللي ثانية ، فقط يتم تبديل حالة الليد LED التي تتوافق مع زر الضغط المرفق كمقاطعة.
كود:
if (buttonPushed = true) // check if ISR is called
{
if ((currentMillis - debounceMillis) > debouncePeriod && buttonPushed) // generate 20ms debounce delay to avoid multiple presses
{
debounceMillis = currentMillis; // save the last debounce delay time
if (digitalRead(pushButton) == LOW && lastState == HIGH) // change the led after push button is pressed
{
ledChange = ! ledChange;
digitalWrite(toggleLed, ledChange);
lastState = LOW;
}
else if (digitalRead(pushButton) == HIGH && lastState == LOW)
{
lastState = HIGH;
}
buttonPushed = false;
}
}
البرنامج :
كود:
/* Arduino Multitasking
Author : CircuitDigest (circuitdigest.com)
*/
int led1 = 6; // led1 connected at pin 6
int led2 = 7; // led2 connected at pin 7
int toggleLed = 5; // push button controlled led connected at pin 5
int pushButton = 2; // push button connected at pin 2 which is also interrupt pin
int ledState1 = LOW; // to determine the states of led1 and led2
int ledState2 = LOW;
unsigned long previousMillis1 = 0; //store last time LED1 was blinked
const long period1 = 1000; // period at which led1 blinks in ms
unsigned long previousMillis2 = 0; //store last time LED2 was blinked
const long period2 = 200; // period at which led1 blinks in ms
int debouncePeriod = 20; // debounce delay of 20ms
int debounceMillis = 0; // similar to previousMillis
bool buttonPushed = false; // interrupt routine button status
int ledChange = LOW; // to track the led status last
int lastState = HIGH; // to track last button state
void setup() {
pinMode(led1, OUTPUT); // define pins as input or output
pinMode(led2, OUTPUT);
pinMode(toggleLed, OUTPUT);
pinMode(pushButton, INPUT);
attachInterrupt(digitalPinToInterrupt(pushButton), pushButton_ISR, CHANGE); // use interrupt pin2
}
void pushButton_ISR()
{
buttonPushed = true; // ISR should be as short as possible
}
void loop() {
unsigned long currentMillis = millis(); // store the current time
if (currentMillis - previousMillis1 >= period1) { // check if 1000ms passed
previousMillis1 = currentMillis; // save the last time you blinked the LED
if (ledState1 == LOW) { // if the LED is off turn it on and vice-versa
ledState1 = HIGH; //change led state for next iteration
} else {
ledState1 = LOW;
}
digitalWrite(led1, ledState1); //set LED with ledState to blink again
}
if (currentMillis - previousMillis2 >= period2) { // check if 1000ms passed
previousMillis2 = currentMillis; // save the last time you blinked the LED
if (ledState2 == LOW) { // if the LED is off turn it on and vice-versa
ledState2 = HIGH;
} else {
ledState2 = LOW;
}
digitalWrite(led2, ledState2);//set LED with ledState to blink again
}
if (buttonPushed = true) // check if ISR is called
{
if ((currentMillis - debounceMillis) > debouncePeriod && buttonPushed) // generate 20ms debounce delay to avoid multiple presses
{
debounceMillis = currentMillis; // save the last debounce delay time
if (digitalRead(pushButton) == LOW && lastState == HIGH) // change the led after push button is pressed
{
ledChange = ! ledChange;
digitalWrite(toggleLed, ledChange);
lastState = LOW;
}
else if (digitalRead(pushButton) == HIGH && lastState == LOW)
{
lastState = HIGH;
}
buttonPushed = false;
}
}
}
|