Consulting Services
لغة سي و متحكماتمقالات

رحلة داخل نواة الأردوينو: الملفات المصدرية – كيفية بناء نواة أردوينو جديدة – خطوات الترجمة في الأردوينو

مقدمة

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

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

شخصياً، إن رحلتي القديمة مع الأردوينو بدأت من دون أن أعرف التفصيلات في المستوى المنخفض ولكن كان لدي الحماس لمعرفة السحر الداخلي خلف الأضواء. بينما كانت خبرة الإلكترونيات تنمو استطعت فهم التصميم الإلكتروني للدارة، والتعامل المباشر مع المتحكمات بلغة السي والتعامل من أكثر من عائلة جعل فهم الجانب البرمجي من الأردوينو (نواة الأردوينو) ممكناً.

إن نواة الأردوينو Arduino core هو المكان الذي يوجد فيه تعريف التوابع الداخلية للأردوينو مثل: pinMode(), digitalWrite(), analogRead(), Serial.begin() …إلخ. هذه المقالة هي محاولة لفهم بنية نواة الأردوينو وحتى بناء نواة جديدة ، كما سنناقش خطوات بناء وترجمة الكود في الأردوينو Building process.

رحلة داخل نواة الأردوينو

كما قلنا، فإن نواة الأردوينو هي المكان الذي يوجد فيه تعريف التوابع الداخلية للأردوينو. إن الجداول التالية هي لذكر أهم الأجزاء في الأكواد للملفات المصدرية لنواة الأردوينو والمقال يجمّعها تبعاً لغرضها.

الملفات المصدريّة العامّة

الملف المصدري
ماذا بداخله؟
Arduino.h
1- تصريح declaration لمعظم التوابع الداخلية في الأردوينو مثل pinMode() و digitalWrite() ... إلخ
2- ماكرو لبعض الثوابت مثل HIGH, LOW, INPUT, OUTPUT, .. إلخ
3- توابع ماكرو لبعض العمليات الخاصة بمعالجة البتات bitwise وبعض العمليات العامة الأخرى مثل min() و rand() … إلخ.
4- تصريح declaration لمصفوفات التقابل بين أرقام الأرجل الخاصة بالأردوينو ورقمها وسجلها الحقيقي في المتحكم الصغري (اقرأ التدوينة المصغرة المنشورة في عتادياتهذا الموضوع). إن قيم هذه المصفوفات موجودة في الملف التالي: hardware\arduino\avr\variants\standard\pins_arduino.h
main.cpp
هنا يوجد التقسيم الحقيقي للبرنامج والكود الرئيسي للمتحكم الصغري (استدعاء setup و loop):
int main(void){
init();
initVariant();
setup();
for (;;) {loop();
return 0;}

الملفات المصدريّة المتعلقة بالدخل والخرج الرقمي والتماثلي

الملف المصدري
ماذا بداخله؟
wiring_digital.c
تعريف define التوابع
digitalWrite() و digitalRead() و pinMode(), ..إلخ.
wiring_analog.c
تعريف define التوابع
analogRead() و analogWrite() ..إلخ.
wiring_shift.c
تابعين لقراءة وتخريج لتسلسل من البتات bits-streams
wiring_pulse.c
تابعين لقراءة الدخل من نوع التعديل بعرض النبضة PWM.
WInterrupts.c
تعريف بعض التوابع للتعامل مع المقاطعة الخارجية external interrupts.

الملفات المصدريّة المتعلقة بالتوابع الزمنية والمؤقتات Timers

الملف المصدري
ماذا بداخله؟
wiring.c
1- تعريف توابع زمنية مثل millis() و delay() ..إلخ.
2- تعريف Timer0 overflow ISR
3- تابع تهيئة Initialization function يستدعى لتهيئة بعض الطرفيات peripherals قبل استدعاء التابع setup في main.cpp.

الملفات المصدريّة المتعلقة بالاتصال Hardware Serial (بالمجمل)

الملف المصدري
ماذا بداخله؟
Stream.h, Stream.cpp
تعريف الصف Stream class والذي يحوي إجرائيات methods والتي تستخدم (بالوراثة) في صف HardwareSerial class مثل readUntil() و find() … إلخ
Print.cpp, Print.h
تعريف الصف Print class والذي يحوي إجرائيات methods لطباعة الرسائل (print و println). هذا الصف يتم وراثته من الصف Stream class.
Printable.h
هذا الملف يعرف صف Printable class والذي يحوي على إجرائية واحدة ويتم وراثة هذا الصف من قبل أي صف جديد لكي يصبح بالإمكان طباعة الأغراض من هذا الصف الجديد عبر تابعي الطباعة print() و println(). لمزيد من المعلومات عن هذا التابع يمكن مراجعة التدوينة المصغرة المنشورة في عتاديات.
HardwareSerial.h, HardwareSerial.cpp,
HardwareSerial_private.h
تعريف الصف HardwareSerial class.
إن الإجرائيات methods مثل print() و find() ..إلخ هي موروثة من صف Stream class.
HardwareSerialx.cpp
1- تعريف UARTx ISR.
2- التصريح عن class oject HardwareSerial Serialx.

 رحلة في نظام الملفات Files System

هذه الرحلة ستركز على الأجزاء الخاصة بملفات نواة الأردوينو و الملفات التي تدخل بعملية الترجمة والبناء للكود. بداية، كل الملفات المصدرية توضع في المكان التالي: \Arduino\hardware\arduino\{the MCU architecture}\cores\arduino

وكمكان آخر يمكن أن يوجد فيه أنوية الأردوينو للأطراف الثالثة أو الأنواع الأخرى (مثل ESP8266) \Users\{username}\AppData\Local\Arduino15\packages  هذه الأنوية يتم تحميلها طبعاً من board manager (Tools>Boards>Board Manager). سلسلة الأدوات Tool-chain (وحتى ملفات الترويس الخاصة بـ avr-gcc ) توجد هنا Arduino\hardware\tools\{the MCU architecture}  و هنا أيضاًUsers\{username}\AppData\Local\Arduino15\packages\arduino\tools.

كيفية بناء نواة أردوينو جديدة

كمثال سنقوم ببناء نواة للأردوينو من أجل متحكمات AVR. لأجل بناء نواة جديدة إننا نحتاج لأمرين:

1- ملف JSON يصف النواة الجديدة: اسمها ورقم النسخة والأدوات المتعلقة tools dependencies ورابط تحميل الملفات .. إلخ

2- الملفات المصدرية الخاصة بالنواة مضغوطة في ملف بلاحقة .zip أو .tar.bz2 أو .tar.gz

بدايةً، إن ملف JSON يجب أن يحمل اسم شبيه لهذا الشكل package_x_index.json أو اسم معبر آخر ويجب أن يحوي النموذج التالي:

{
    "packages": [
        {
            "name": "aaa",
            "maintainer": "bbb",
            "websiteURL": "http://bbb.com/",
            "email": "aaa@bbb.com",
            "help": {
                "online": "http://bbb.com/"
            },
            "platforms": [
                {
                    "name": "aaa Boards",
                    "architecture": "avr",
                    "version": "1.0.0",
                    "category": "aaa",
                    "url": "http://bbb.com/aaa.zip",
                    "archiveFileName": "aaa.zip",
                    "checksum": "SHA-256:x",
                    "size": "x",
                    "help": {
                        "online": "http://bbb.com/"
                    },
                    "boards": [
                        {
                            "name": "Arduino/Genuino Uno"
                        }
                    ],
                    "toolsDependencies": [
                    {
                      "packager": "aaa",
                      "name": "avr-gcc",
                      "version": "4.9.2-atmel3.5.3-arduino2"
                    },
                    {
                      "packager": "aaa",
                      "name": "avrdude",
                      "version": "6.3.0-arduino6"
                    }
                            ]
                        }
                    ],
            "tools": []
        }
    ]
}

والآن يجب تعديل محتوى الملف بما يتواءم مع المعلومات الجديدة للنواة. هناك العديد من الأدوات على الانترنت من أجل حساب توقيع الملف SHA-256 checksum خاص بالملف المضغوط للنواة مثل هذا المواقع. حاول أن تطلع على ملفات JSON من أجل أنوية أخرى لتفهم كيفية بناءها وتعديلها مثل esp8266 core JSON.

إن الخطوة القادمة هي إنشاء الملف المضغوط. يجب أن يحوي مجلد رئيسي واحد باسم النواة ويحوي بداخله نموذج template الملفات للنواة (bootloaders – cores – doc – libraries – tools & variants folders  – boards.txt – platform.txt  – programmers.txt).

Arduino core folder

Boards.txt و platform.txt  و programmers.txt هي ملفات يجب تعديلها أيضاً تبعاً للنواة الجديدة. بما أن هذه المقالة لوضع نظرة عامة وليس تفصيليّة سيترك مهمة معرفة محتويات هذه الملفات للقارئ وللدليل الرسمي.

platform.txt contains definitions for the CPU architecture used (compiler, build process parameters, tools used for upload, etc.) boards.txt contains definitions for the boards (board name, parameters for building and uploading sketches, etc.) programmers.txt contains definitions for external programmers (typically used to burn bootloaders or sketches on a blank CPU/board)

لتلخيص ما سبق، سوف ننشئ نموذج باسم “atadiat” بالخطوات التالية:

1- تعديل في platform.txt

name=Atadiat AVR Boards

version=1.0

2- تعديل في ملف boards.txt للإبقاء على دارة واحدة فقط في النواة الجديدة (Arduino UNO)

# See: http://code.google.com/p/arduino/wiki/Platforms


menu.cpu=Processor


##############################################################


uno.name=Arduino/Genuino Uno


uno.vid.0=0x2341

uno.pid.0=0x0043

uno.vid.1=0x2341

uno.pid.1=0x0001

uno.vid.2=0x2A03

uno.pid.2=0x0043

uno.vid.3=0x2341

uno.pid.3=0x0243


uno.upload.tool=avrdude

uno.upload.protocol=arduino

uno.upload.maximum_size=32256

uno.upload.maximum_data_size=2048

uno.upload.speed=115200


uno.bootloader.tool=avrdude

uno.bootloader.low_fuses=0xFF

uno.bootloader.high_fuses=0xDE

uno.bootloader.extended_fuses=0xFD

uno.bootloader.unlock_bits=0x3F

uno.bootloader.lock_bits=0x0F

uno.bootloader.file=optiboot/optiboot_atmega328.hex


uno.build.mcu=atmega328p

uno.build.f_cpu=16000000L

uno.build.board=AVR_UNO

uno.build.core=arduino

uno.build.variant=standard


##############################################################

3- إنشاء الملف المضغوط atadiat.zip وحساب توقيع الملف باستخدام SHA online generator.

4- إنشاء ملف JSON تبعاً للنموذج المذكور أعلاه.

5- لتجربة هذه النواة يمكنك إضافة رابط ملف JSON إلى بيئة الأردوينو Arduino IDE
(https://atadiat.com/wp-content/uploads/package_atadiat_index.json)

 

arduino preferences json

ثم لتحميل النواة الجديدة نذهب إلى  board manager .

arduino board manager

إضافة نواة جديدة من دون ملف JSON

تعتبر الطريقة السابقة هي الطريقة المعتمدة للمطورين ليتمكن المستخدمين لاحقاً من استخدام الأنوية cores الجديدة. ولكن قد يكون هناك اضرار لسبب ما لإضافة نواة إلى Arduino IDE من غير استضافة النواة على الانترنت أو حتى إنشاء ملف JSON واستضافته أيضاً. الطريقة الأسهل في هذه الحالة أن يتم إنشاء ملف باسم النواة في Documents\Arduino\hardware وداخله توضع النواة. يصبح المسار الكامل

Documents\Arduino\hardware \{core name }\{the MCU architecture}

عملية البناء والترجمة في الأردوينو

إن بيئة الأردوينو الأساسية تحوي مجموعة من العمليات للبناء والترجمة غير ظاهرة جزئياً للمطور. وبالتالي لا يمكن العثورة على ملف makefile يحدد خطوات البناء والترجمة. عوضاً عن ذلك تتيح الأردوينو للمطور إضافة بعض التعديلات لعملية البناء عبر ملف platform.txt ومع ذلك تبقى بعض الخطوات خارج تحكم المطوّر. يجب أن نبقي في الذهن أن بعض الثوابت يتم أخذها بناءً على الدارة المحددة حسب الملف board.txt وهذه الثوابت تستخدم لاحقاً في عملية البناء والترجمة.

إن عملية البناء في الأردوينو (لمتحكمات AVR على سبيل المثال) تتألف من الخطوات التالية وبالترتيب (للتأكد يمكن تفعيل خيار إخراج رسائل أثناء البناء):arduino preferences compilation

  • يتعرف الـIDE على الدارة المستخدمة والنواة المستخدمة ويستدعي بالتالي معلومات من ملف board.txt.
  • يتم إنشاء ملف مؤقت في المسار التالي Users\{username}\AppData\Local\Temp\arduino_build_x والذي سيستخدم من أجل وضع object files من أجل جميع الملفات المصدرية في النواة وأيضاً المكاتب المستخدمة إلى جانب ملفات نتائج عملية البناء مثل HEX file والذي يرفع لاحقاً على المتحكم عبر المبرمجة (راجع مقال كيفية برمجة المتحكمات).

Arduino temp folder

  • ترجمة السكيتش Compiling sketch.
  • ترجمة الملفات المصدرية للمكاتب Compiling the libraries source files.
  • ترجمة الملفات المصدرية لنواة الأردوينو بالخطوات التالية:
    • ترجمة ملفات السي.
    • ترجمة ملفات السي بلس بلس.
    • ترجمة ملفات الأسيمبلي.
  • تجميع الملفات object files في ملف أرشيفي (.a) archive file. ماهو الملف الأرشيفي؟ تحقق من الإجابة على Stackoverflow .
  • ربط كل الملفات linking لإنتاج ملف HEX النهائي.

مالذي يمكن ملف platform.txt المطور من فعله:

  • تعريف متحولات خاصة compilation/building flags/variables لإضافتهم إلى الأوامر لاحقاً.
  • تغيير نمط pattern كل مرحلة من المراحل المذكورة سابقاً في عملية البناء.
  • تغيير تعليمات البناء.
  • إضافة توسيعات لعملية البناء.

أنماط البناء في الأردوينو building patterns تبدو كالتالي:

## Compile c files
recipe.c.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.c.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.c.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}"
## Compile c++ files
recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.cpp.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}"
## Compile S files
recipe.S.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.S.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.S.extra_flags} {build.extra_flags} {includes} "{source_file}" -o "{object_file}"

هذه الأنماط تتحول إلى مجموعة من التعليمات المتتابعة ضمن النمط الواحد

Arduino building pattern

أردوينو أضافت منذ النسخة 1.6.5 من البيئة طريقة تمكن المطور من إضافة توسيعات لعملية البناء في مختلف المراحل وتسمى هذه التوسيعات  pre and post build hooks.

recipe.hooks.sketch.prebuild.NUMBER.pattern (called before sketch compilation) recipe.hooks.sketch.postbuild.NUMBER.pattern (called after sketch compilation) recipe.hooks.libraries.prebuild.NUMBER.pattern (called before libraries compilation) recipe.hooks.libraries.postbuild.NUMBER.pattern (called after libraries compilation) recipe.hooks.core.prebuild.NUMBER.pattern (called before core compilation) recipe.hooks.core.postbuild.NUMBER.pattern (called after core compilation) recipe.hooks.linking.prelink.NUMBER.pattern (called before linking) recipe.hooks.linking.postlink.NUMBER.pattern (called after linking) recipe.hooks.objcopy.preobjcopy.NUMBER.pattern (called before objcopy recipes execution) recipe.hooks.objcopy.postobjcopy.NUMBER.pattern (called after objcopy recipes execution) recipe.hooks.savehex.presavehex.NUMBER.pattern (called before savehex recipe execution) recipe.hooks.savehex.postsavehex.NUMBER.pattern (called after savehex recipe execution)

كمثال لتوسيع عملية البناء بعد ترجمة النواة core compilation. أولاً، سوف نضيف بعض المتحولات (يمكن تغيير التسميات كما تريد):

compiler.hook.cmd = avr-gcc-nm

compiler.hook.cmd.flag = -g

إن التوسعة هنا ستستخدم الأمر nm من أدوات GCC من أجل طباعة الرموز symbols داخل أحد ملفات object file.

recipe.hooks.core.postbuild.1.pattern = "{compiler.path}{compiler.hook.cmd}" {compiler.hook.cmd.flag} "{build.path}/core/wiring_digital.c.o"

ملاحظة: يمكن إضافة أكثر من توسعة في نفس الفئة وذلك بزيادة الرقم.

Arduino building hook

ختاماً إذا كان المطور بحاجة إلى مزيد من التحكم في عملية البناء فيجب إعطاء الاهتمام لمشاريع مثل Arduino-makefile التي قامت ببناء makefile فيه تحكم كامل وقابل لتخصيص وتعديل العملية بشكل كامل.

اقرأ أكثر

 

 

Yahya Tawil

مؤسس عتاديات وأسعى لدعم المحتوى العربي عن النظم المضمنة في الويب العربي. خبرتي في مجال النظم المضمّنة تتركز في كتابة البرامج المضمنة وتصميم الدارات المطبوعة والمخططات وإنشاء المحتوى.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

هذا الموقع يستخدم Akismet للحدّ من التعليقات المزعجة والغير مرغوبة. تعرّف على كيفية معالجة بيانات تعليقك.

زر الذهاب إلى الأعلى