مقدمة
إذا كنت جزء من فريق تطوير برنامج لجهاز مضمّن معقد و متعدد المهام فلا بد أنك استخدمت متحولات مجزّأة إلى بتات أو ما يسمى bit-field variables. في عملي السابق كنت في فريق تطوير لجهاز تعقب خاص بالسيارات وكان البرنامج من تطوير الشركة بالكامل من الأدنى إلى الأعلى حيث كان البرنامج مسؤول عن تنظيم التواصل بين المتحكم الصغري المستخدم وكل من GSM و GPS و Zigbee/WiFi و التواصل التسلسلي Serial RS-232 و RTC. بالإضافة إلى مهام التحصيل التماثلي ADC وبعض المخارج والمداخل الرقمية. بهذا المستوى من التعقيد إما ستحتاج لاستخدام أحد أنظمة الزمن الحقيقة RTOS الموجودة مثل FreeRTOS والتي تحتوي كجزء أصلي من ميزاتها تنظيم وجدولة الأحداث Tasks scheduling أو أنه ستستخدم منهجية معينة باستخدام لغة السي – كحالتنا هنا – حيث تم تخصيص لكل حدث متحول بطول 1 بت يشير بحدوثه True أو عدم حدوثه False.
ولكن لماذا نخصص بت واحد لكل متحول لماذا لا تحجز متحول واحد كامل. الجواب ببساطة: ذاكرة المتحكم الصغري المحدودة أصلاً ستتآكل بسرعة وستؤدي إلى نفاذ الذاكرة بسرعة. ولكن باستخدام هذه الطريقة bit-field variables ستحجز نفس المتحول وتقسمه على أكثر من حدث لأنك تحتاج لكل حدث بت واحد فقط يأخد قيمة 0 أو 1.
كحالة استخدام أخرى لهذه الطريقة؛ تمثيل بعض البروتوكولات على شكل struct والمتحولات تكون مقسمة فيها على مستوى البت وبالتالي يمكنك تمثيل الـframes مع توفير جيد في مساحة الذاكرة. يرجى مراجعة مقال استخدام الـ struct و الـunion في النظم المضمنة.
لكن كأي حل هندسي على وجه الأرض، العلاقة لا تكون دائماً ربح-ربح وإنما ربح-خسارة. إن bit-fields توفر أماكن في ذاكرة المعطيات وتوفر طريقة أسهل للنفاذ لها عوضاً عن استخدام عمليات معالجة البتات من قبل المبرمج والتي تسمى bitwise. لكن على المقابل سيقوم المعالج/المترجم compiler بهذه المهمّة بالنيابة عنك دون أن تراها حيث سيقوم بعمليةREAD-MODIFY-WRITE في كل مرة تقوم بالنفاذ لهذا البت(مجموعة البتات) لأنه كما اتفقنا هو في متحول أكبر مشترك بين عدة متحولات أخرى ذات نوع bit-field لذلك وبشكل مخفي في كل عملية نفاذ سيتم قراءة قيمة هذا المتحول المشترك وتخزينها في مكان مؤقت من الذاكرة ثم تعديل قيمة البتات المطلوبة ومن ثم إعادة قيم البتات التي لم يتم تعديلها للمتحول المشترك(هذه العملية تسمى READ-MODIFY-WRITE). بالتالي صحيح أنه قد وفرنا مكان في الذاكرة ولكن سببنا معالجة من طرف المعالج أكثر. هذا يخلق إشكالية أخرى وخاصة عند اعتماد هذه المتحولات في الأجزاء الحساسة من الكود حيث يمكن أن تحدث مقاطعة بين عملية طلب القراءة/الكتابة وبين حدوثها فعملية القراءة من هذه المتحولات غير ذريّة non-atomic وقابلة للمقاطعة.
لأجل هذه الغاية، تم إضافة ميزة في معالجات ARM Cortex-M3/M4 (والتي تستخدم نواة في كثير من المتحكمات الصغرية هذه الأيام) لتحسين هذه العملية وجعلها مدعومة من قبل الهاردوير نفسه وليس برمجياً فقط وتسمى هذه الميزة Bit-banding.
ماهي ميزة Bit-Banding ؟
أحد الأمور التي تشغل بال المطور أثناء تطوير البرنامج هو أن يكون قابل للعمل على أكثر من متحكم صغري واحد portable. إن ميزة bit-fields هي ميزة غير معيارية بين كل المترجمات compilers لذلك لا ينصح دوماً باستخدامها، ولكن عندما تكون هذه الميزة مدعومة من المعالج نفسه فإن المطور لا يقلق من استخدامها بين أكثر من متحكم صغري طالما أنها تستخدم نفس المعالج.
إن معالجات ARM Cortex-M3 لديها منطقة بطول 1 MB في ذاكرة SRAM تسمى هذه المنطقة bit-band region. في هذه المنطقة يمكن النفاذ لكل بت على حدى وبعنوان فيزيائي مستقل (ليس كحالة bit-field حيث أكثر من متحول له نفس العنوان الفيزيائي) وللنفاذ لهذه المنطقة لا يتم النفاذ بشكل مباشر وإنما عبر منطقة عناوين أخرى تسمى bit-band aliased region حيث يشير العنوان في هذه المنطقة لبت واحد من المنطقة الأصلية. وبما أن المعالج ARM Cortex-M3 ذو طول معطيات وعنونة 32 بت فإنه حجم bit-band aliased region بداهةً هو 32 MB.
إن النفاذ لهذه المنطقة bit-band region يتم عبر ناقل النظام system interface bus وعملية الـREAD-MODIFY-WRITE هنا والتي كانت تتم من قبل المترجم (الكود) في حالة الـ bit-field أصبحت تتم بمعرفة المعالج وبشكل ذرّي Atomic غير قابل للمقاطعة.
تتوفر هذه الميزة أيضاً من أجل سجلات الطرفيات بطول 1 MB وبنفس الآلية سابقة الذكر
وفي المخطط التالي توضيح لكيفية النفاذ للبتات في منطقة bit-band من خلال العناوين في منطقة bit-band alias.
ميزات استخدام الـ bit-Banding (تطبيقات)
- تغيير قيم السجلات بعملية ذريّة Atomic لا يمكن مقاطعتها.
- إنشاء متحولات من نوع bit-field ولكن لا تعتمد على المترجم لفهمها (مدعومة من الهاردوير نفسه).
- توصيف implement تطبيقات النقل التسلسلي عبر أرجل الـ GPIO (أو ما يسمى bit-banging).
مثال 1
أولاً علينا حساب قيمة عنوان النفاذ للبت في منطقة bit-band. هذه المعادلة مقتبسة من Cortex-M3 technical reference manual:
bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bit_word_addr = bit_band_base + bit_word_offset
حيث
Bit_word_offset is the position of the target bit in the bit-band memory region.
Bit_word_addr is the address of the word in the alias memory region that maps to
the targeted bit.
Bit_band_base is the starting address of the alias region.
Byte_offset is the number of the byte in the bit-band region that contains the targeted bit.
Bit_number is the bit position (0-7) of the targeted bit.
سوف نستخدم هذه الميزة من أجل جعل رجل موصولة مع ليد تأخذ قيمة 1 عندما نضغط على زر وقيمة 0 عندما نحرر الزر. وسأستخدم لهذا الغرض المتحكم الصغري EFM32LG وتحديداً الكيت EFM32LG STK3600 الذي يحوي على معالج ARM Cortex-M3.
هناك مجموعة من القيم المستخدمة للقيام بالعملية الحسابية وهي العنوان الأساس base address لمنطقة الـ bit-band للطرفيات perphiral والتي تحوي السجلات ومن ضمنها سجلات خاصة بالـ GPIO سواءً لقراءة قيمة الزر أو لإخراج القيمة لليد.
وهذا الماكرو macro خاصة بإجراء العملية الحسابية لحساب العنوان. هذا الماكرو مكتوب باستخدام C micro processor (مقالة للاستزادة)
#define PERI_BASE 0x40000000 #define BITBAND_PERI_BASE 0x42000000
#define BITBAND_PERI(a,b) ((BITBAND_PERI_BASE + (a-PERI_BASE)*32 + (b*4)))
وهذه العناوين خاصة بسجلات الخرج DOUT والدخل DIN للبوابة E و B (حسب توصيلات الكيت مع الليد والزر)
#define PE_DOUT_SET_BASE 0x400060A0 #define PE_DOUT_CLEAR_BASE 0x400060A4 #define PB_DIN_BASE 0x40006040
ملاحظة: لقد تم استخدام البنى #if #else للتنقل بين استخدام الطريقة العادية لقراءة وتخريج القيم على الـGPIO وبين استخدام الـbit-band
#include "em_device.h" #include "em_cmu.h" #include "em_emu.h" #include "em_gpio.h" #include "em_chip.h" #define bitband 0 #define PERI_BASE 0x40000000 #define BITBAND_PERI_BASE 0x42000000 #define BITBAND_PERI(a,b) ((BITBAND_PERI_BASE + (a-PERI_BASE)*32 + (b*4))) #define PE_DOUT_SET_BASE 0x400060A0 #define PE_DOUT_CLEAR_BASE 0x400060A4 #define PB_DIN_BASE 0x40006040 #define PE_b2_SET *((volatile unsigned int *)(BITBAND_PERI(PE_DOUT_SET_BASE,2))) #define PE_b2_CLEAR *((volatile unsigned int *)(BITBAND_PERI(PE_DOUT_CLEAR_BASE,2))) #define PB_b9 *((volatile unsigned int *)(BITBAND_PERI(PB_DIN_BASE,9))) int main(void) { /* Initialize chip */ CHIP_Init(); /* Enable clock for GPIO module */ CMU_ClockEnable(cmuClock_GPIO, true); /* Configure PB with alternate drive strength of 0.5mA */ GPIO_DriveModeSet(gpioPortB, gpioDriveModeLowest); /* Configure PB9 as an input for PB0 button with filter enable (out = 1)*/ GPIO_PinModeSet(gpioPortB, 9, gpioModeInput, 1); /* Configure PE2 as a push pull with alternate strength for LED drive */ GPIO_PinModeSet(gpioPortE, 2, gpioModePushPullDrive, 0); while (1) { #if bitband ==1 if(PB_b9) #else if (GPIO_PinInGet(gpioPortB, 9)) #endif { #if bitband ==1 PE_b2_SET=1; #else GPIO_PinOutSet(gpioPortE, 2); #endif } else { #if bitband ==1 PE_b2_CLEAR = 1; #else GPIO_PinOutClear(gpioPortE, 2); #endif } } }
مثال 2
1- لنضبط العنوان 0x20000000 بالقيمة 0x00000AAC عبر النفاذ المباشر دون استخدام خاصية الـ bit-band.
2- لو قرأنا العنوان 0x22000008 وهو عنوان البت الثالث في العنوان 0x20000000 ستكون قيمته 1.
3- لو كتبنا في 0x22000010 القيمة واحد سيصبح البت الخامس في 0x20000000 واحد وبالتالي ستصبح القيمة الكليّة 0x00000ABC.
[the_ad id=”2268″]مراجع ولقراءة المزيد
- The Definitive Guide to ARM Cortex-M3 by Joseph Yiu
- ARM Application Note 179.
- ARM Cortex-M3 Technical Reference Manual.