السلام عليكم و رحمة الله وبركاته
كنت قد لاحظت في الأونة الأخيرة صدور عدة مراجع عربية للغة C و هذا امر جيد و لكن لم أجد أي كتاب أو محتوى عربي كامل عن لغة C المضمنة لتطبيقات المتحكمات الصغرية microcontroller فقررت أن أبدء بترجمة كتاب
Embedded C Programming and the ATMEL AVR
و بعد ان ترجمت ما يقارب 30% من محتوى الكتاب خطرت لي فكرة مشاركة ما تمت ترجمته من أجزاء الكتاب على هيئة دروس و بالتالي الإستفادة من تصحيح الأخطاء الإملائية و تقييم المحتوى من قبل القراء , و خصوصا أنني أنوي تعديل بعض من محتوى الكتاب ليتماشى مع المتحكمات الجديدة من عائلة AVR مثل ATmega328 المستخدم في Arduino , أخيرا أرحب بأي إقتراح أو ملاحظة أو تصحيح لتحسين محتوى الكتاب .
م.عبدالله جلول
مفاهيم أولية
تعد كتابة برامج C مثل عملية بناء منزل من حيث المعنى . فبعد وضع الأساسات , يستخدم الرمل و الاسمنت لتشكيل القرميد . ومن ثم يتم تكديس هذه الصفوف فوق بعضها لتشكيل البناء , وفي برامج C يتم وضع مجموعة من التعليمات معا لتكوين التوابع ثم يتم التعامل مع هذه التوابع كعمليات ذات مستوى متقدم والتي تركب معا لتشكيل البرنامج .يجب أن يحتوي أي برنامج C على تابع واحد على الأقل أسمه main() و يعد هذا التابع أساس برنامج C سواء بشكل مباشر أو غير مباشر ومع أن جميع التوابع قد تكون شاملة و كاملة إلا أنه يمكن ربطها مع بعضها البعض بواسطة المتحولات و البارمترات .
يعد التابع main() المهمة ذات المستوى الأدنى و ذلك لأنه يمثل أول تابع يتم استدعاؤه من قبل النظام الذي يشغل البرنامج . و في العديد من الحالات يحتوي التابع main() على القليل من العبارات فقط , و التي لا تقوم بأي عمل سوى تبدئة و توجيه عمل البرنامج إلى تابع أخر .
يبدو برنامج C المضمن في أبسط أشكاله كما يلي :
int main(void)
{
while(1) // do forever..
{;}
}
تتم عملية ترجمة البرنامج السابق بشكل كامل ولكنك لن ترى شيئا يحدث على مخارج المتحكم . يمكننا زخرفة هذا البرنامج بحيث نستدل على عملية تنفيذه , و بالتالي نبداء بفهم العناصر التكوينية للغة .
#include <stdio.h>
int main(void)
{
printf("hello world!"); /* the classic c test program . */
while(1) // do forever..
{;}
}
سيقوم هذا البرنامج بطباعة العبارة Hello World! على الخرج القياسي و الذي يمثل في معظم الحالات البوابة التسلسلية و سينتظر المتحكم الصغري إلى ما لانهاية أو حتى تتم عملية إعادة التشغيل Reset . يوضح ذلك الفروقات الأساسية بين برنامج خاص بالحاسب الآلي و برنامج مصمم من أجل المتحكمات الصغرية المضمنة , أي أن تطبيقات المتحكمات تحتوي على حلقة غير منتهية. يحتوي الحاسوب الشخصي على نظام تشغيل فعند انتهاء تنفيذ برنامج ما فإنه يعيد التحكم إلى نظام التشغيل الموجود في الحاسب . بينما لا تحتوي المتحكمات نظام تشغيل ولا يسمح لها بالخروج من البرنامج في أي وقت , مثل حلقة While(1) في المثال السابق , وهذا ما يمنع البرنامج من الانتهاء ومن القيام بأعمال عشوائية قد تكون غير مرغوب بها سوف نشرح الحلقة While لاحقا . كما يقدم البرنامج السابق مثالا عن موجهات المترجم (Compiler) وهو #include إذ يقوم هذا الموجه بإعلام المترجم لكي يُضمّن الملف stdio.h كجزء من البرنامج و بالتالي يصبح التابع printf متاحا في البرنامج وذلك لأن تعريفه موجود ضمن الملف stdio.h سوف نشرح هذه المفاهيم لاحقا . إليكم الآن بعض العناصر البرمجية الملحوظة في المثال السابق :
; تستعمل الفاصلة المنقوطة للدلالة على نهاية العبارة البرمجية و العبارة في ابسط حالاتها تتألف من فاصلة منقوطة فقط .
{} تستعمل الأقواس المعقوفة للدلالة على بداية و نهاية محتوى التابع كما تستعمل هذه الأقواس عندما نريد معالجة سلسلة عبارات معا ككتلة واحدة.
"text" تستعمل فواصل التنصيص للدلالة على بداية ونهاية سلسلة محارف نصية .
/*..*/ أو // تستعمل للدلالة على التعليقات .
تمثل التعليقات ملاحظات المبرمج وتعد أساسية لقراءة و فهم البرنامج وهذا صحيح طالما أن البرنامج سيقرؤه أشخاص آخرون أو المبرمج نفسه ولكن بعدة فترة من الزمن .
تعمل التعليقات المستخدمة في المثال السابق على شرح وظيفة كل سطر من الشيفرة إذ يجب أن يشرح التعليق الوظيفة الفعلية لسطر البرنامج وليس فقط التعليمة المستخدمة في هذا السطر .
إن محدد التعليقات التقليدي هو /*..*/ وعندما يصادف المترجم الرمز /* فإنه يتجاهل النص الذي يليه حتى وإن كان مكتوب على عدة اسطر ويستمر بذلك حتى يصادف الرمز */ و الذي يدل على نهاية التعليق لاحظ أن المثال السابق يستخدم هذا المحدد .
أما المحدد // فيجعل المترجم يتجاهل النص الوارد بعده ولكن حتى نهاية السطر فقط . و قد استعملنا هذا المحدد في السطر الثاني من البرنامج السابق .
و بالانتقال إلى التفاصيل علينا تذكر بعض المصطلحات و القواعد الأساسية :
- المعرف (identifier) هو اسم متحول أو ثابت أو تابع مكون من حروف أو من خط سفلي (_) تليه سلسلة من الحروف و/أو الأرقام و/أو الخطوط السفلية.
- يتم التمييز بين الحروف الصغيرة و الكبيرة في لغة C .
- ليس هناك قيود على طول أسم المعرف عموما ولكن تتعرف بعض المترجمات فقط على طول محدد للاسم مثلا أول 32 حرف فأنتبه لذلك !.
- توجد بعض الكلمات ذات معنى خاصة بالنسبة للمترجم و تعد كلمات محجوزة ويجب كتابة هذه الكلمات بحروف صغيرة ولا يسمح أبدا باستعمالها كمعرفات يبين الجدول التالي هذه الكلمات المحجوزة .
union
|
register
|
float
|
define
|
auto
|
unsigned
|
return
|
for
|
do
|
break
|
void
|
short
|
goto
|
double
|
bit
|
volatile
|
signed
|
if
|
eeprom
|
bool
|
while
|
sizeof
|
inline
|
else
|
case
|
true
|
static
|
int
|
enum
|
const
|
false
|
switch
|
interrupt
|
extern
|
continue
|
typedef
|
long
|
flash
|
default
|
المتحولات و الثوابت
حان الوقت للنظر إلى البيانات المخزنة على شكل ثوابت أو متحولات , المتحولات هي قيم قابلة للتغيير أما الثوابت فلا يمكن تغييرها , تستعمل الثوابت و المتحولات وفق عدة صيغ و أحجام و تخزن في ذاكرة البرنامج وفق عدة اشكال متنوعةأنواع المتحولات
يتم التصريح عن المتحول بواسطة كلمة محجوزة تشير إلى نوعه وحجمه ويليها المعرف (أي أسم المتحول) :
unsigned char Peabody;
int dogs, cats;
long int total_dogs_and_cats;
Byte
|
Bits
|
Maximum
|
Minimum
|
Type
|
1
|
8
|
127
|
-128
|
(signed) char
|
1
|
8
|
255
|
0
|
unsigned
char
|
2
|
16
|
32767
|
-32768
|
(signed) int
|
2
|
16
|
65535
|
0
|
unsigned int
|
4
|
32
|
2147483647
|
-2147483648
|
(signed) long
|
4
|
32
|
4294967295
|
0
|
unsigned
long
|
4
|
32
|
3.438
|
-3.438
|
float
|
8
|
64
|
1.7308
|
-1.7308
|
double
|
ملاحظة : كلمة signed اختيارية و لن تؤثر في تعريف المتحول .
مدى المتحولات
كما ذكرنا سابقا يجب التصريح عن المتحولات و الثوابت قبل استعمالها , يمثل مدى المتحول إمكانية الوصول إليه ضمن البرنامج و يمكن التصريح عن المتحول بحيث يكون ذي مدى محلي (local) أو عام (global) .المتحولات المحلية
المتحولات المحلية (local variables) هي حيز الذاكرة الذي يحجزه التابع عند تنفيذه , ويكون موقعه عادة ضمن مكدس البرنامج أو المكدس الذي يخلقه المترجم , ولا يمكن الوصول لهذه المتحولات من قبل توابع اخرى , أي أن مدى هذه المتحولات محدود بالتوابع التى تحتوي تصريحا عنها و لذلك يمكن التصريح عن المتحول في عدة توابع بدون حدوث أي تضارب و ذلك لأن المترجم يرى هذه المتحولات كأنها جزء من التابع فقط .المتحولات العامة
المتحولات العامة (global variable) أو الخارجية هي حيز الذاكرة الذي يحدده المترجم ويمكن الوصول إليه من قبل كل التوابع في البرنامج ويمكن تعديل محتوى المتحول العام من قبل أي تابع و يحافظ على قيمته بحيث يمكن استعماله من قبل توابع اخرى .يتم عموما تصفير المتحولات العامة وجعل قيمتها مساوية للصفر وذلك عند بدء عمل التابع الأساسي main() و غالبا ما تتم هذه العملية من قبل شيّفرة بدء التشغيل التي يولدها المترجم وهي غير مرئية للمبرمج .
نبين في الشيفرة التالية مثالا عن مدى المتحولات :
unsigned char globey; // a global
variable
void function_z(void) // this is a function called from main()
{
unsigned int tween; // a local variable
tween = 12; // ok because tween is local
globey = 47; // ok because globey is global
main_loc = 12; // this line will generate an
// error because main_loc is local to main
}
int main(void)
{
unsigned char main_loc; // a variable local to main()
globey = 43; // ok because globey is a global to function_z
while(1) // do forever..
{;}
}
الثوابت
كما وضحنا سابقا الثوابت تمثل قيمة ثابتة لا يمكن تغييرها خلال تنفيذ البرنامج تعد الثوابت في العديد من الحالات جزءا من البرنامج المترجم نفسه و تتوضع في ذاكرة قابلة للقراءة فقط (ROM) وليس في ذاكرة الوصول العشوائي القابلة للتعديل (RAM) ففي عبارة الإسناد التالية :
x = 3 + y;
العدد 3 هو عدد ثابت إذ يقوم المجمع بتشفيره مباشرة ضمن عملية الجمع كما وقد تكون الثوابت على شكل محارف أو سلسلة محارف نصية :
printf("hello
world!");
x = 'B';
كما يمكنك التصريح عن ثابت ما باستعمال الكلمة المحجوزة const و تحديد نوعه و حجمه . إذا يتطلب الأمر معرفا و قيمة وذلك لإتمام عملية التصريح :
const char c = 57;
يؤدي تعريف متحول ما كثابت إلى تخزين ذاك المتحول في حيز شيفرة البرنامج بدلا من حيز تخزين المتحولات المحدودة في الذاكرة RAM وهذا ما يساعد على تقنين حيز الذاكرة RAM المحدودة .
الثوابت العددية
يمكن التصريح عن الثوابت العددية وفق عدة طرق وذلك بالإشارة إلى أساسها العددي وبالتالي جعل البرنامج أسهل للقراءة . يمكن كتابة الثوابت Integer (الصحيحة) و long integer كما يلي :- الصيغة العشرية بدون بادئة مثل (1234) .
- الصيغة الثنائية مع البادئة 0b مثل (0b11100101) .
- الصيغة الست عشرية مع بادئة 0x مثل (0xc1) .
- الصيغة الثمانية مع البادئة 0 مثل (0123) .
كما توجد بعض المعدلات لتعريف حجم الثابت بشكل أفضل :
- الثوابت من نوع Unsigned integer قد تأخذ اللاحقة U مثلا (10000U) .
- الثوابت من نوع Long integer قد تأخذ اللاحقة L مثل (99L) .
- الثوابت من نوع Long Unsigned integer قد تأخذ اللاحقة UL مثل (99UL) .
- الثوابت من نوع Float قد تأخذ اللاحقة F مثل (1.23F) .
- الثوابت من نوع Char يجب وضعها ضمن فاصلتين علويتين مثل ('A' or 'a') .
الثوابت المحرفية
قد تكون الثوابت المحرفية من النوع القابل للطباعة مثل (A..Z and 0..9) أو من النوع الغير قابل للطباعة مثل (محرف الجدولة TAB محرف الإرجاع Carriage return) ويمكن وضع المحارف القابلة للطباعة ضمن فواصل علوية مثل ('a') كما يمكن استعمال الشرطة العكسية يليها القيمة الثمانية أو الست عشرية للمحرف حسب ترميز ASCII ضمن الفاصلة العلوية وذلك لتمثيل الثوابت المحرفية :
't' يمكن تمثيله كما يلي '\164' ثماني
أو
'\x74' ست عشري .
't' يمكن تمثيله كما يلي '\164' ثماني
أو
'\x74' ست عشري .
يبين الجدول التالي بعض المحارف الغير قابلة للطباعة و التي يتم التعرف عليها في لغة C :
القيمة الست عشرية المكافئة
|
التمثيل
|
المحرف
|
'\x07'
|
'\a'
|
BEL
|
'\x08'
|
'\b'
|
Backspace
|
'\x09'
|
'\t'
|
TAB
|
'\x0a'
|
'\n'
|
LF (new line)
|
'\x0b'
|
'\v'
|
VT
|
'\x0c'
|
'\f'
|
FF
|
'\x0d'
|
'\r'
|
CR
|
التعدادات و التعريفات
تعد سهولة القراءة في لغة C مهمة جدا لذلك تستعمل التعدادات و التعريفات بحيث يستطيع المبرمج استبدال الأعداد بأسماء أو بجمل ذات معنى أكثر تعبيرا .تمثل التعدادات تعريفا لثوابت إذ تستخدم الكلمة المحجوزة enum لإسناد قيم ثوابت صحيحة (integer) متتابعة إلى لائحة معرفات :
int num_val; // declare an integer
variable
enum { zero ,one ,two , three}; // declare an enumeration
num_val = two; // the same as: num_val = 2
enum { start = 10, next1, next2, end_val};
في هذه الحالة يأخذ Satart القيمة 10 و Next1 القيمة 11 و Next2 القيمة 12 و End_val القيمة 13 . إذا تستعمل التعدادات كبديل لأعداد صحيحة والتي يريد المبرمج ربطها مع كلمات أو جمل ذات معنى .
تستعمل التعريفات بطريقة مشابهة إلى حد ما للتعدادات وذلك من حيث كونها تسمح باستبدال سلسلة محارف نصية بأخرى لنأخذ المثال التالي
enum { red_led_on = 1, green_led_on, both_leds_on };
#define leds PORTA
PORTA = 0x01; // means turn
the red led on
leds = red_led_on; // means the same thing
العبارة #define هي موجه معالجة أولية في الواقع ليست موجهات المعالجة الأولية جزءا من الصيغة القواعدية للغة C ولكن تم قبولها نظرا لشيوع استعمالها و المعالجة الأولية هي خطوة منفصلة عن الترجمة الفعلية للبرنامج و تتم قبل بدء عملية الترجمة فعليا , سوف نتطرق لها لاحقا إن شاء الله .
صفوف التخزين
يمكن التصريح عن المتحولات وفق ثلاث صفوف تخزين : auto , static , register الخيار الأول auto هو الصف الافتراضي وهذا ما يعني أن الكلمة auto المحجوزة غير ضرورية .Automatic
لا يتم تحديد قيمة ابتدائية لمتحول محلي من الصف auto عند تخصيصه ولذلك فالمبرمج معني بالتأكد من احتواء المتحول على قيمة مقبولة قبل استعماله أو إسناد قيمة افتراضية له عند التصريح عنه . يتم تحرير هذه المساحة من الذاكرة عند الخروج من التابع أي ان القيم ستصبح غير صحيحة عند العودة إلى التابع من جديد يتم التصريح عن المتحول من الصف auto كما يلي :
auto int value_1;
أو
int value_1; // this is the
common , default form
ويمكن إسناد قيمة افتراضية للمتحول أثناء التصريح عنه كما يلي :
auto int value_1 = 10;
أو
int value_1 = 10;
في المثال السابق تم التصريح عن المتحول و إسناد القيمة 10 إليه كقيمة ابتدائية . Static
يكون المتحول المحلي الستاتيكي أي الساكن فعالا في التابع الذي تم تعريفه ضمنه ( أي لا يمكن الوصول إليه من التوابع الأخرى ) ولكنه مخصص في حيز عام من الذاكرة . يتم تبدئة قيمة المتحول الستاتيكي بالقيمة صفر عند الدخول أول مرة إلى التابع إذا لم يتم إسناد إي قيمة ابتدائية إليه و يحافظ على قيمته الأخيرة عند الخروج من التابع وهذا ما يسمح بالحفاظ على صحة قيمة المتحول مع كل عملية إعادة دخول إلى التابع .
static int value_2;
static int value_3 = 90;
في المثال السابق المتحول value_2 سيتم تبدئته بالقيمة 0 و المتحول value_3 تمت تبدئته بالقيمة 90 .
register
يشبه المتحول المحلي من الصف register المتحول من الصف auto من حيث عدم تبدئته وكونه آنيا و الفرق بينهما هو أن المترجم سيحاول إدخال سجل آلة فعلي في المعالج الصغري كمتحول وذلك للحد من تعليمات الآلة اللازمة للوصول إلى المتحول . يتوفر عدد قليل جدا من السجلات مقارنة بالذاكرة الكلية في آلة نموذجية إذا سيتم استعمال هذا الصف باقتصاد و ذلك بهدف تسريع العمل.
register char
value_4;
تحويل النوع
int x; // a signed, 16-bit, integer (-32768 to 32767)
char y; // a signed, 8-bit, character (-128 to 127)
x = 12;
قد يكون تحويل النوع للمتحولات السابقة كما يلي :
y = (char)x + 3; // x is converted to a character and then
3 is added.
// and the value is then placed into y.
x = (int)y; // y is extended up to an integer, and assigned to x.
int z; // declare z
int x = 150; // declare and
initialize x
char y = 63; // declare and
initialize y
z = (y * 10) + x;
لذلك يجب استعمال تحويل النوع للتخلص من هذه النوعية من المشاكل أي يمكننا كتابة المعادلة الأخيرة من الشيفرة السابقة بالشكل التالي :
حيث يُعامل المترجم المتحول y على أنه عدد صحيح (16 بت) في هذه العملية فقط . و هذا يؤدي إلى وضع القيمة الصحيحة 630 في المكدس كناتج عملية ضرب 16 بتاً . ثم يتم جمع x إلى القيمة الصحيحة في المكدس و ذلك للحصول على النتيجة الصحيحة 780 (0x30C) و أخيرا يتم إسناد القيمة 780 إلى المتحول z .
تعد لغة c مرنة جداً , و ستعطيك كل ما تطلبه . إذ يفترض المترجم أن المبرمج يعرف ما يريد القيام به .ففي المثال السابق , إذا كانت قيمة y هي 6 بدلا من 63 عندها لن يكون لدينا أي خطأ. إذاً , يتوجب عليك عند كتابة العبارات البرمجية التفكير دوماً بالقيم العظمى التي قد تأخذها العبارة , و بقيمة ناتج عمليات الضرب و الجمع .
القاعدة الجديدة التي يمكن إتباعها هي : "عند الشك قم بتحويل النوع" . و نفذ دوماً عملية تحويل المتحولات , إلا إذا كنت متأكداً من عدم حاجتك لذالك .
z = ((int)y * 10) + x;
تعد لغة c مرنة جداً , و ستعطيك كل ما تطلبه . إذ يفترض المترجم أن المبرمج يعرف ما يريد القيام به .ففي المثال السابق , إذا كانت قيمة y هي 6 بدلا من 63 عندها لن يكون لدينا أي خطأ. إذاً , يتوجب عليك عند كتابة العبارات البرمجية التفكير دوماً بالقيم العظمى التي قد تأخذها العبارة , و بقيمة ناتج عمليات الضرب و الجمع .
القاعدة الجديدة التي يمكن إتباعها هي : "عند الشك قم بتحويل النوع" . و نفذ دوماً عملية تحويل المتحولات , إلا إذا كنت متأكداً من عدم حاجتك لذالك .
4 التعليقات
إضغط هنا لـ التعليقاتجزاك الله كل خير و بانتظار المزيد من هذه السلسلة
ردبارك الله فيك وجزاك خيرا استمر فنحن بحاجه شديده لهذا العلم
ردواياك اخي الكريم و شكرا لكلماتك الطيبة :)
ردمستمرين ان شاء الله في تقديم كل ماهو مفيد لإثراء المحتوي العربي العلمي :-bd
فين باقي الاجزاء يا هندسة
ردنحن ننتظرك بفارغ الصبر
تحويل كودإخفاء محول الأكواد الإبتساماتإخفاء