لنتعلم برمجة الأنظمة المدمجة

Embedded Systems Programming Bootcamp

كورس تعليمي يهدف الى اكسابك المعرفة اللازمة والمهارات التقنية المتعلقة ببرمجة وتطوير الأنظمة المدمجة وفهم خصائصها ومكوناتها

الصفحة الرئيسية
قائمة الدروس
خدمة RSS

للتواصل

6. المنافذ والأطراف Ports & Pins

2017-11-02






للمتحكم TM4C123GH6PM الموجود على البورد 43 طرف pin متعدد الإستعمال تسمى بالـ General-purpose input/output GPIO pins. وليسهل التعامل معها فإنها موزعة على 6 منافذ ports كما يلي:

المنفذ عدد الأطراف
PORT A 8
PORT B 8
PORT C 8
PORT D 8
PORT E 6
PORT F 5

ملاحظة، Port C لديه 8 أطراف لكننا لن نستخدم الأطراف التالية: PC0, PC1, PC2, PC3 حيث أنها محجوزة للـ debugger ولذلك فإننا فعلياً نستخدم 4 أطراف فقط في هذا المنفذ.

وبذلك يكون لدينا 43 طرف يمكن برمجتها ولكن 4 منها محجوز كما ذكرنا في الملاحظة السابقة، أي فعلياً هنالك 39 طرف يمكننا التعامل معها وبرمجتها.

هذه الأطراف يمكن إستخدامها كمدخل للبيانات input حيث تمكننا من إستقبال معلومات من البيئة المحيطة. أو كمخرج للبيانات output حيث تمكننا من ارسال معلومات لما قد يحيط بها أو يحتويها.

وفي الفصل 22 من دليل البيانات datasheet (ص 1328) نجد الرسم التخطيطي للمنافذ والأطراف في المتحكم:

وها هو المتحكم على البورد حيث نستطيع أن نرى أطراف المتحكم (داخل المستطيلات الخضراء) تتصل بأطراف البورد (وهي بداخل المستطيلات الصفراء) عن طريق مسارات buses لنقل البيانات (باللون البرتقالي):

وتمكننا الأطراف التي على البورد من ربط البورد (وبالتالي المتحكم) بأجزاء الكترونية أخرى من ضمنها لوح التجارب breadboard حيث يمكن ربطها بأسلاك سواء من الـ male headers أعلى البورد أو الـ female headers أسفل البورد.


عناوين المنافذ في الذاكرة على المسار APB

يوجد مسارين لنقل البيانات المتعلقة بالـ Input/Output ports وهي:

المسار الثاني يعتبر الأفضل أداء والأسرع والأول أفضل من ناحية توفير الطاقة وهو الذي سيتم إستخدامه في هذا الكورس بإذن الله.

لكل منفذ مساحة 4kbyte من الذاكرة تحتوي على سجلات registers عديدة للتحكم بالمنفذ، ومن ضمنها السجل المتعلق بتحديد وظيفة أو إتجاه كل طرف في المنفذ Direction Register والسجل الخاص بالقراءة والكتابة من والى المنفذ Data Register بالإضافة الى غيرها من السجلات المتعلقة بالمنفذ.

وفيما يلي عناوين المنافذ في الذاكرة على المسار APB من الأصغر الى الأكبر، علماً أن عنوان البداية لكل منفذ يسمى الـ base address لذلك المنفذ:

المنفذ يبدأ من ينتهي في
PORT A 0x4000.4000 0x4000.4FFF
PORT B 0x4000.5000 0x4000.5FFF
PORT C 0x4000.6000 0x4000.6FFF
PORT D 0x4000.7000 0x4000.7FFF
PORT E 0x4002.4000 0x4002.4FFF
PORT F 0x4002.5FFF 0x4002.5000

طريقة التعامل مع عناوين الذاكرة

لو أفترضنا أننا أردنا التعامل مع العنوان التالي في الذاكرة:

0x400253FC

فلو حاولنا إسناده الى متغير في لغة الـ C فإنه سيعتبر رقماً عادياً لا أكثر. ولجعل الـ C تعتبره عنواناً في الذاكرة فإننا نقوم بعملية derefrencing:

(*0x400253FC)

ولكن في هذه الحالة لن يعرف المجمع compiler كم بت bit عليه أن يقرأ من موقع الذاكرة المشار اليه. هل يقرأ 8 بت أو 16 بت أو 32 بت؟ ولذلك نقوم بتحويل القيمة casting اما لـ long أو int حيث أنه لا فرق بينهما في معمارية الـ ARM Cortex-M فكلاهما يتكون من 32 بت.

(long *)0x400253FC

وبما أن القيّم في سجلات الـ GPIO لا تحمل إشارة unsigned بمعنى أنها لا يمكن أن تكون بالسالب، فإننا نضيف عبارة unsigned على جزئية الـ casting:

(unsigned long *)0x400253FC

أيضاً، بما إن القيّم في السجلات ممكن أن تتغير من خارج الكود الذي نقوم بكتابته، فعلى سبيل المثال، لو قام المستخدم بالضغط على الزر Switch 1 فستتغير قيمة البت المقابلة في المتحكم من 1 الى 0، وهذا التغيير حدث خارج إطار الكود الذي قمنا بكتابته. ولهذا السبب فإننا سنطلب من المجمع compiler قراءة القيمة الجديدة دوماً وليس الإعتماد على قيمة مخزنة مسبقاً بسبب الـ compiler optimization. ويكون ذلك بإستخدام عبارة volatile:

(volatile unsigned long *)0x400253FC

وبما أننا نريد قيمة السجل وليس العنوان فإننا نقوم بعملية الـ derefrencing بإستخدام النجمة * وإستخدام الماكرو define# لإسناد قيمة السجل الى إسم من عندنا:

#define GPIOF_DATA   (*((volatile unsigned long *)0x400253FC))

خطوات التعامل مع أطراف الـ GPIO

الخطوات الرئيسية للتعامل مع الأطراف هي بتعديل السجلات التالية:

1. تفعيل المنفذ

ويتم ذلك عبر سجل المؤقت RCGCGPIO، ومن دون هذه الخطوة فإنه من غير الممكن التعامل مع المنفذ المطلوب. ومن أجل توفير الطاقة، فإن الـ ARM Cortex-M لا تفعّل المنافذ منذ البداية وهذه التقنية تسمى clock gating، بل يجب عليك القيام بذلك بنفسك وذلك عن طريق تفعيل المؤقت الخاص بالمنفذ المطلوب. وبالنظر الى دليل البيانات (ص 340)، نجد شرح لهذا السجل:

ولتفعيل المنافذ فإننا نتعامل مع الـ bits التالية:

ويكون التفعيل بكتابة 1 الى البت bit المطلوب، وعدم التفعيل بكتابة 0 الى تلك الـ bit.

للتعامل مع هذا السجل، ولكي يصبح لديك عنواناً كاملاً، فإنه يتوجب علينا إضافة القيمة التي يبعدها السجل Offset عن العنوان الرئيسي للمنفذ Base address الى ذلك العنوان الرئيسي للحصول على العنوان الكامل للسجل:

Base 0x400F.E000
Offset 0x608

وبذلك يكون عنوان هذا السجل register:

#define SYSCTL_RCGCGPIO_R   (*((volatile unsigned long *)0x400FE608))

2. تحديد وظيفة الأطراف (الإتجاه) مدخلات/مخرجات

وذلك بواسطة السجل GPIODIR (ص 663 في دليل البيانات) حيث يمكننا تحديد ما اذا كان الطرف للقراءة input او الكتابة output.

ومثل ما ذكرنا سابقاً، فإننا سنستخدم المسار APB للتعامل مع الـ GPIO ولذلك سنستخدم العناوين في الذاكرة التي توضح ذلك. وفيما يخص سجل الـ GPIODIR فإنه يبعد 0x400 عن العناوين السابقة offset. وبذلك يكون السجل المستخدم لكل منفذ كما يلي:

#define GPIO_PORTA_DIR_R   (*((volatile unsigned long *)0x40004400))
#define GPIO_PORTB_DIR_R   (*((volatile unsigned long *)0x40005400))
#define GPIO_PORTC_DIR_R   (*((volatile unsigned long *)0x40006400))
#define GPIO_PORTD_DIR_R   (*((volatile unsigned long *)0x40007400))
#define GPIO_PORTE_DIR_R   (*((volatile unsigned long *)0x40024400))
#define GPIO_PORTF_DIR_R   (*((volatile unsigned long *)0x40025400))

وللتحكم بالأطراف في هذه المنافذ فإننا نتعامل مع الـ bits التالية:

بكتابة 1 الى البت سيجعل الطرف الذي تتحكم به للكتابة output، وكتابة 0 سيجعل ذلك الطرف للقراءة. علماً أنه جميع الأطراف معدة لتكون للقراءة input مالم يتم تعيينها للكتابة output.

3. تفعيل الطرف

وذلك عن طريق السجل GPIODEN والذي يستخدم لتحديد ما إذا كان الطرف يستخدم للقراءة والكتابة الرقمية digital I/O أو للوظائف التماثلية analog الأخرى حيث أنه بإمكان كل طرف أن يكون له أكثر من وظيفة وهو ما يسمى بـ pin multiplexing وإلا فإننا سنحتاج الى مئات من الأطراف للقيام بجميع المهام. وبإمكان الطرف أن يستخدم للقراءة الرقمية digital I/O أو التماثلية analog او بروتوكولات التواصل مثل I2C أو UART أو SPI أو غيرها. ومن ص 683 في دليل البيانات يتضح لنا:

نلاحظ ان السجل يبعد عن العنوان الرئيسي للمنفذ offset بمقدار 0x51C. وبعد إضافتها للعنوان الرئيسي لكل منفذ تصبح العناوين كما يلي:

#define GPIO_PORTA_DEN_R   (*((volatile unsigned long *)0x4000451C))
#define GPIO_PORTB_DEN_R   (*((volatile unsigned long *)0x4000551C))
#define GPIO_PORTC_DEN_R   (*((volatile unsigned long *)0x4000651C))
#define GPIO_PORTD_DEN_R   (*((volatile unsigned long *)0x4000751C))
#define GPIO_PORTE_DEN_R   (*((volatile unsigned long *)0x4002451C))
#define GPIO_PORTF_DEN_R   (*((volatile unsigned long *)0x4002551C))

وعند كتابة 1 للبت المقابل للمنفذ فإنه يتم تفعيل الخصائص الرقمية digital للطرف، وعند كتابة 0 فإنه يتم تفعيل الخصائص التماثلية analog للطرف.

4. الكتابة أو القرائة من الطرف

ويتم ذلك عن طريق سجل GPIODATA. ففي الطرفيات المخصصة للكتابة output فإنه يمكن تشغيل الطرف وجعله high بإرسال القيمة 1 اليه، وإذا ما أردنا إطفائه low فإننا نرسل له القيمة 0. وفي الأطراف المخصصه للقراءة input فإنه يمكن قراءة البت bit المقابلة للطرف لمعرفة اذا ماتم تفعيلها أم لا. وفي ص 662 من دليل البيانات نرى:

وصف السجل يخبرنا بأن الـ offset 0x000 ولكن هذا لإستخدام تقنية تسمى bit banding وهي تحجز لنا مكان في الذاكرة يتسع للتعامل مع كل طرف (أو بت) على حده ولكننا نريد التعديل على الـبتات الثمانية 8 bits، التي تقابل الأطراف في المنافذ، مرة واحدة. وللقيام بذلك فإننا نستخدم offset 0x3FC فتصبح لدينا العناوين:

#define GPIO_PORTA_DATA_R   (*((volatile unsigned long *)0x400043FC))
#define GPIO_PORTB_DATA_R   (*((volatile unsigned long *)0x400053FC))
#define GPIO_PORTC_DATA_R   (*((volatile unsigned long *)0x400063FC))
#define GPIO_PORTD_DATA_R   (*((volatile unsigned long *)0x400073FC))
#define GPIO_PORTE_DATA_R   (*((volatile unsigned long *)0x400243FC))
#define GPIO_PORTF_DATA_R   (*((volatile unsigned long *)0x400253FC))

جميع السجلات عبارة عن 32 بت (4 بايت) ما عدا GPIODATA حيث أنها مجموعة من السجلات تتكون من 256 سجل وكل سجل منها يتكون من 4 بايت أيضاً بما مجموعه 1024 بايت. ولكن الذي يهمنا الآن هو السجل بالعنوان الذي يحتوي على offset 0x3FC


طريقة أخرى للإشارة الى الذاكرة

بدلاً من حساب موقع السجل ومن ثم كتابته كما يلي:

#define GPIO_PORTF_DATA_R   (*((volatile unsigned long *)0x400253FC))

فإنه بإمكاننا إضافة العنوان الرئيسي base address للمنفذ الى الـ offset للسجل المطلوب:

#define GPIO_PORTF_BASE   0x40025000
#define GPIO_PORTF_DATA   (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x3FC)))
#define GPIO_PORTF_DIR    (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x400)))
#define GPIO_PORTF_DEN    (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x51C)))

وميزة هذه الطريقة إنها أوضح وتجعل عملية حساب العناوين أقل عرضة للخطأ ويمكن نسخ ما سبق لباقي المنافذ الأخرى مع تغيير الـ base address لكل منفذ بالإضافة الى تغيير إسم المنفذ، فبدلاً من F نغيره الى A, B, C … الخ.


للعمل على المسار AHB بدلاً من APB

إذا ما أردت أن تتعامل مع المسار AHB وهو الأحدث والأكثر كفائة، فكل ما عليك هو كتابة قيمة معينة الى السجل GPIOHBCTL (ص 258 في دليل البيانات) ثم إستخدام عناوين الـ AHB بدلاً من APB التي أستخدمناها سابقاً. علماً أنه بالإمكان أن يكون لديك بعض المنافذ تستخدم المسار APB وبعضها يستخدم AHB أو جميعها تستخم نفس المسار، الأمر عائد اليك.

#define SYSCTL_GPIOHBCTL_R   (*((volatile unsigned long *)0x400FE06C))

اذا أردت أن تجعل المنفذ F يستخدم المسار AHB على سبيل المثال، فبالعودة الى شرح السجل في دليل البيانات نجد أنه كل ماعليك فعله هو كتابة 1 في البت 5 في السجل، كما يلي:

SYSCTL_GPIOHBCTL_R |= 0x20; // 0001 0000, or
SYSCTL_GPIOHBCTL_R |= (1U << 5); // 0001 0000 

ملاحظة: هذه المقالة تحت التحديث المستمر،، وملاحظاتكم وإقتراحاتكم ستساعد بإذن الله في إخراجها بالصورة التي كتبت من أجلها وهي تمهيد الطريق أمامكم لتعلم برمجة الأنظمة المدمجة