برمجة المعالجات Xmega بلغة البرمجة C

إن لغة البرمجة C تعتبر من أقوى لغات البرمجة بشكل عام . وذلك لأنها اللغة الأقرب إلى لغة الآلة التي يتعامل معها المعالج مباشرة . ولكنها لغة عالية المستوى يمكن فهمها بسهولة بمجرد قرائة التعليمات البرمجية .
لن نطيل الحديث عن تعريف لغة البرمجة C فهي غنية عن التعريف . وتحتاج دورة خاصة لشرح جميع أساسيات هذه اللغة . ولكن سوف نبدأ بتعلم قواعد هذه اللغة و كيفية كتابة التطبيقات للمعالجات التحكمية باستخدامها.
وأفضل طريقة للتعلم هي باستخدام الأمثلة . ولنأخذ المثال التالي :

void main()
{

}

هذا برنامج كامل مكتوب بلغة C … !

لنوضح ذلك :
أولا – تعتمد لغة C مبدأ كتابة التعليمات البرمجية ضمن توابع ولا يسمح بكتابة التعليمات خارج هذه التوابع .
لذلك لابد من تعريف تابع واحد على الأقل لنضع التعليمات بداخله .
وهنا في البرنامج السابق لدينا تابع اسمه main وهذا التابع من نوع void . وسنشرح مالفائدة من نوع التابع لاحقاً .

ثانيا – يلي اسم التابع الأقواس المغلقة ( ) . وهي ضرورية ومن قواعد اللغة الأساسية .
وتفيد للدلالة على أن ما قبلها هو عبارة عن إسم تابع وليس متحول أو تعليمة برمجية .
وكذلك يمكن أن نضع ضمن هذه الأقواس أسماء متحولات نستخدمها ضمن التعليمات المكتوبة داخل التابع.

ثالثا – الأقواس المنحنية { , } تحدد بداية ونهاية التعليمات والأوامر الخاصة بهذا التابع . فكل ما نكتبه بين هذه الأقواس يعتبر من التعليمات التي يتم تنفيذها عند استدعاء هذا التابع .

مثال عن برنامج بسيط

لنكتب برنامج مكون من تابعين كما يلي :

int foo()
{
  return 10;
}

void main()
{
  int x;
  
  x = foo(); 

}

هذا مثال عن برنامج يحتوي تابعين . الأول foo من نوع int . والثاني هو main .

نوع التابع يحدد نوع النتيجة التي يعيدها التابع عندما ينتهي من تنفيذ تعليماته الداخلية . وهنا النوع int يعني أن التابع foo سيعيد رقم صحيح .
أما التعليمة return فتحدد القيمة التي سيعيدها التابع . وهي في هذه الحالة الرقم 10 .
بينما التابع main هو من نوع void و يعني أنه غير محدد النوع ولا يعيد أي قيمة .

بالنظر للتابع main نجد أننا استخدمنا من جديد كلمة int ولكن هنا لا نعرّف تابع , وإنما نعرّف متحول من نوع عدد صحيح .
هذا المتحول اخترنا له الاسم x .
وهذه هي طريقة تعريف المتحولات في لغة البرمجة C . حيث نكتب أولا نوع المتحول ثم بعده إسم المتحول .
ونلاحظ أن في نهاية السطر يوجد الفاصلة المنقوطة ; التي تدل على نهاية التعليمة . وهي من قواعد اللغة .
حيث كل تعليمة برمجية تنتهي بفاصلة منقوطة , إلا في حالة تعريف التوابع فلا نضع بعد اسم التابع فاصلة منقوطة .

التعليمة التي تليها هي عملية استدعاء للتابع foo ثم وضع القيمة الناتجة عنه في المتحول x .
وبالتالي التعليمة تقول أن x تساوي القيمة التي يعيدها التابع foo .

الآن أصبحت طريقة تعريف التابع وكتابة التعليمات واضحة . لكن أي من هذه التوابع سوف يقوم المعالج بتنفيذه أولا عند الإقلاع ؟
لكل برنامج نقطة بداية . وبداية البرنامج المكتوب بلغة البرمجة C هو التابع main . بحيث عندما يبدأ المعالج بالعمل يقوم بتنفيذ التعليمات المكتوبة ضمن التابع main . أما باقي التوابع لا يتم تنفيذها إلا حين يتم استدعائها .
وبذلك يكون تسلسل تنفيذ البرنامج هو أن يبدأ بالتابع main , ويقوم بتنفيذ تعليمات هذا التابع بالتسلسل . بحيث أول تعليمة هي :

int x ;

وتعني حجز مساحة في الذاكرة للمتحول x وحجم هذه المساحة تتعلق بنوع المتحول . وفي هذه الحالة int تعني عدد صحيح وتحتاج مساحة 2 بايت من الذاكرة .
ثم ينتقل لتنفيذ التعليمة التالية :

x = foo();

في لغة البرمجة C يتم تنفيذ التعليمة من اليمين إلى اليسار . أي في هذه التعليمة يتم أولا استدعاء التابع foo وذلك يكون بالانتقال إلى هذا التابع وتنفيذ تعليماته بالتسلسل , حيث يوجد تعليمة واحدة فقط ضمن هذا التابع , وهي return التي تعني الخروج من التابع و إرجاع قيمة الرقم 10 .
ثم توضع النتيجة التي تعود من التابع في المتحول x . وهكذا تصبح قيمة x = 10 .
وبذلك ينتهي تنفيذ التابع main وينتهي معه تنفيذ البرنامج بشكل كامل.

بالنظر إلى البرنامج السابق نلاحظ أننا قمنا بكتابة تعريف للتابع foo في بداية البرنامج وذلك لأنه سوف يتم استدعائه لاحقا في التابع الذي يليه main . وهذه من قواعد لغة البرمجة , حيث يجب أن يتم تعريف التابع قبل استدعائه . فلا يمكن كتابته بعد التابع الذي يتم استدعائه بداخله .

تخزين البرنامج وحفظه في ملفات

البرنامج السابق يتم كتابته وتخزينه في ملف وليكن اسمه app.c .
حيث اللاحقة c تعني أن هذا الملف يحتوي كود مكتوب بلغة البرمجة C . أما اسم التابع فهو اختياري. ولكن يجب ان يكون باللغة الانكليزية.

من ميزات لغة البرمجة C أنها تسمح بتوزيع البرنامج ضمن ملفات متعددة . فعلى سبيل المثال يمكن أن نكتب التابع foo في ملف منفصل ثم استدعائه في الملف الأساسي app.c . مما يسمح بكتابة برامج كبيرة بشكل منظم وموزع على ملفات متعددة .

ولنفرض أننا كتبنا التابع foo في ملف اسمه functions.c . يصبح لدينا ملفين وهما كما يلي :

// functions.c هذا الملف

int foo()
{
  return 10;
}
// app.c هذا الملف 

extern int foo();

void main()
{
  int x;
  
  x = foo(); 

}

لدينا بعض التوضيحات الجديدة هنا . أولا : الخطوط المائلة // تدل على أن كل شيء يكتب بعدها لا قيمة له برمجيا . وهو مجرد تعليق أو ملاحظة يكتبها المبرمج لسبب ما . وهنا استخدمنا هذه الميزة لكتابة تعليق يوضح اسم الملف .
إذاً الملف الأول اسمه functions.c ويحتوي فقط تابع واحد تم شرحه سابقاً .
الملف الثاني app.c يحتوي التابع الرئيسي في البرنامج ولكن مع تغيير بسيط .
التعليمة الجديدة extern تفيد في تعريف تابع خارج هذا الملف . ثم نكتب بعدها تعريف التابع بدون أن نكتب محتوى التابع من تعليمات.
أي أنها تقول : يوجد تابع اسمه foo وهو من نوع int ولكن موجود في مكان ما في ملف ثاني من ملفات البرنامج .

أما موضوع ربط هذه الملفات مع بعضها وتحويلها الى البرنامج النهائي , فهي مهمة بيئة البرمجة . حيث تتضمن برامج تقوم بتحويل هذه الأوامر المكتوبة بشكل نص مقروء إلى ملف واحد نسميه الملف التنفيذي . يتم نقله إلى ذاكرة المعالج لينفذه .
بيئة البرمجة التي نستخدمها هي Microchip Studio . وهي برنامج متكامل يمكننا من خلاله أن نكتب البرنامج بلغة C . ويحتوي ضمنه Compiler خاص بهذه اللغة ومجاني متاح للجميع . ويسمح لنا مباشرة بنقل هذا البرنامج إلى المعالج باستخدام أدوات البرمجة المتوفرة .

السؤال الآن ماذا لو كان هناك عدد كبير من التوابع الموزعة ضمن الملفات . هل سوف نستخدم طريقة extern من أجل تعريفات التوابع ؟
بالتأكيد هناك حل أفضل لهذه الحالة . وهو من ميزات لغة البرمجة C . حيث يمكن أن نكتب تعريف التوابع في ملف منفصل . وهذا الملف من نوع h . أي أن لاحقة الملف هي الحرف h , ونقوم بتضمين هذا الملف في البرنامج . والخطوة التالية توضح ذلك .

كتابة برنامج C لمعالج XMega

لننتقل لمرحلة متقدمة ونتعلم أكثر باستخدام برنامج حقيقي لمعالجات XMega . كما في المثال التالي :

#include <avr/io.h>
#include <util/delay.h>

void main()
{
   PORTA_DIRSET = PIN0_bm;
   
   while(1)
   {
      PORTA_OUTSET = PIN0_bm;
      _delay_ms(500);

      PORTA_OUTCLR = PIN0_bm;
      _delay_ms(500);
   }
}

الجديد هنا هو كلمة include , التي تفيد في تضمين ملف يحتوي تعريفات توابع ومتحولات وغيرها من التعريفات .
بهذه الطريقة يصبح كل ما يحتويه الملف المضمن عبر هذه التعليمة مرئيا لكل توابع وتعليمات البرنامج .
أما avr/io.h فهو اسم الملف الذي قمنا بتضمينه . وهو ملف أساسي بالنسبة لمعالجات XMega . سنتعرف عليه لاحقا .
وكذلك util/delay.h أيضا هو ملف يحتوي تعريف توابع التأخير أو الانتظار كما سنرى في البرنامج .
بقي لدينا الإشارة # . هذه الإشارة تستخدم قبل كتابة الأوامر الخاصة لبيئة البرمجة . حيث هنا وضعنا هذه الإشارة قبل تعليمة include وهي تعليمة تخص بيئة البرمجة لتقوم بتضمين ملف ثاني في البرنامج . أي أنها تفيد خلال مرحلة كتابة البرنامج في بيئة البرمجة , أما المعالج فلا علاقة له بالأوامر التي تبدأ بهذه الإشارة .

لنبدأ بشرح البرنامج من التابع الرئيسي main . حيث أول تعليمة ينفذها المعالج هي :

PORTA_DIRSET = PIN0_bm

هذه التعليمة فيها رموز غير معروفة بالنسبة لنا . وحتى غير معروفة بالنسبة لبيئة البرمجة لأنها ليست كلمات خاصة بلغة البرمجة C . وإنما هي أسماء متحولات معرفة مسبقا . مثل المتحول X على سبيل المثال .
المتحول الأول PORTA_DIRSET وهو عبارة عن أحد المسجلات الخاصة بالمعالج . مهمته تحديد أي من أطراف المعالج هي طرف خرج . أي عملياً تحديد إتجاه نقل البيانات من و إلى المعالج عبر أطرافه الخارجية . بحيث كل خانة من خانات هذا المسجل , والتي هي 8 خانات , تدل على طرف من أطراف المعالج . والتي ترتبط داخلياً مع البوابة A .
أما PIN0_bm فهي ليست متحول ولا تابع وإنما عبارة عن رقم ثابت وقيمته 1 أو 0x01 ويدل على الطرف الأول من البوابة A للمعالج .
وبالتالي التعليمة السابقة تقول أن الطرف الأول من البوابة A أصبح طرف خرج للمعالج .

المسجل أو المتحول PORTA_DIRSET وكذلك القيمة PIN0_bm تم تعريفها مسبقا في الملف io.h الموجود في المجلد avr , ولذلك قمنا بتضمينه باستخدام الكلمة include في بداية البرنامج . وهو ملف يحتوي جميع تعريفات مسجلات المعالج وعناوينها .

بمتابعة البرنامج نجد الحلقة while والتي تعني أن يتم تنفيذ ما تحويه هذه الحلقة من تعليمات طالما أن الشرط الموجود بين الاقواس محقق. وفي حالتنا هنا الشرط هو رقم 1 . أي انه لن يتغير وسيبقى 1 . وهذا ما نسميه بالحلقة اللانهائية . أي ان التعليمات في داخلها سيتم تنفيذها بشكل متكرر بشكل غير منتهي .
لننظر داخل تعليمات هذه الحلقة . ونجد أولا التعليمة :

PORTA_OUTSET = PIN0_bm

هذه التعليمة هي تعليمة إخراج البيانات على أطراف المعالج . وتعني أن تضع القيمة PIN0_bm في المسجل PORTA_OUTSET . حيث كل خانة من هذا المسجل ترتبط بطرف من أطراف البوابة A . وما نضعه في هذا المسجل ينعكس على شكل ارتفاع في جهد الطرف الخارجي للمعالج . أي تصبح قيمته 1 منطقي .
وبهذا تقول التعليمة : إجعل الطرف الأول من البوابة A في حالة جهد مرتفع .

أما التابع delay_ms التي في السطر الثاني . فمهمتها أن ينتظر المعالج فترة زمنية تتحدد بالرقم الذي نضعه بين أقواس التابع . وهذه القيمة تقدر بالميلي ثانية . وفي حالتنا هنا 500 ميلي ثانية أي نصف ثانية .
هذا التابع معرف مسبقا في الملف delay.h الموجود في المجلد util . الذي تم تضمينه في بداية البرنامج .

بمتابعة النظر في الحلقة يتبع عملية الانتظار تعليمة جديدة وهي : PORTA_OUTCLR = PIN0_bm
وهي تشبه تعليم الإخراج تماما ولكن مع اختلاف بسيط . وهو أن القيمة التي نضعها في المسجل تنعكس على شكل إنخفاض في الجهد ليصبح 0 فولت . أو 0 منطقي على طرف المعالج .
والتعليمة تقول : إجعل الطرف الأول من البوابة A في حالة جهد منخفض .

ثم نكرر تعليمة الانتظار مرة ثانية قبل إعادة تنفيذ الحلقة .
وبهذا يكون البرنامج هو عملية تغيير جهد الطرف الأول من البوابة A بشكل متكرر بفوارق زمنية تقدر بنصف ثانية .
أي على طرف المعالج نلاحظ نبضات مربعة عرضها ثانية واحدة أو 1Hz .

لكن لدينا ملاحظة هنا . وهي أنه حتى تعمل تعليمة الانتظار delay_ms بشكل جيد , يجب أن نقوم بتعريف سرعة المعالج في البرنامج .
وهذا يتم عن طريق تعريف ثابت نسميه F_CPU . وهذا الثابت يساوي قيمة سرعة المعالج على شكل رقم .
ونستخدم الأمر define لهذا الغرض . حيث نكتب بعده إسم الثابت , وبعده فراغ , ومن ثم القيمة .
ويجب أن يتم تعريف هذا الثابت في بداية البرنامج . كما في البرنامج التالي حيث سرعة المعالج 2000000Hz :

#define F_CPU   2000000

#include <avr/io.h>
#include <util/delay.h>

void main()
{
   PORTA_DIRSET = PIN0_bm;
   
   while(1)
   {
      PORTA_OUTSET = PIN0_bm;
      _delay_ms(500);

      PORTA_OUTCLR = PIN0_bm;
      _delay_ms(500);
   }
}

أصبح البرنامج كاملاً , وبعد بناء هذا البرنامج وتحويله إلى ملف تنفيذي يمكن تنفيذه على المعالج مباشرة .

وبذلك نكون قد كتبنا أول برنامج بسيط مكتوب بلغة البرمجة C لمعالجات Xmega .
لنحول هذا البرنامج إلى ملف تنفيذي يفهمه المعالج . نستخدم بيئة البرمجة Microchip Studio .
حيث نقووم بإنشاء مشروع جديد ضمن بيئة البرمجة . ثم نكتب البرنامج الذي نريده ضمن ملف ونقوم بحفظه مع تحديد لاحقة الملف من نوع c . على سبيل المثال app.c .
ثم من القائمة الرئيسية في بيئة البرمجة نختار الأمر Build الذي يقوم بكل العمليات اللازمة لصناعة الملف التنفيذي .
ثم نجد هذا الملف التنفيذي في مجلد المشروع الذي أنشأناه . وما علينا سوى نقله للمعالج عن طريق المبرمجة المتوفرة .

هذه كانت لمحة سريعة عن بعض أساسيات لغة البرمجة C بشكل عام . وكيفية استخدامها لبرمجة المعالجات التحكمية .
في المرحلة القادمة سوف نتعلم أكثر عن المعالج XMega والمزيد حول لغة البرمجة C .