مقدمة: لماذا C++ في الأنظمة المضمنة ليس سادية بل قوة خارقة
\n\nعندما تسمع "تطوير الأنظمة المضمنة"، عادة ما يتبادر إلى الذهن مهندس صارم مع مكواة لحام يكتب بلغة C الخالصة ويصلي على ورقة البيانات. أو العكس - متحمس يقوم بتحميل digitalWrite() على Arduino ويفرح لأنه يومض بمصباح LED. ولكن بين هذين العالمين هناك حل وسط ذهبي - تطوير الأنظمة المضمنة بلغة C++.
نعم، نفس لغة C++ التي تخيف بالقوالب والوظائف الافتراضية. في الواقع، المتحكمات الدقيقة الحديثة (ARM Cortex-M، RISC-V) قادرة تمامًا على هضم كود C++. علاوة على ذلك، الاستخدام الذكي للبرمجة الشيئية (OOP)، والتغليف (Encapsulation)، وحتى القوالب (Templates) يمكن أن يجعل كودك المضمن أنظف وأكثر أمانًا وأسرع.
\n\nفي هذه المقالة، سنشرح كيفية كتابة كود C++ فعال للمتحكمات الدقيقة، ونتجنب مشاكل الجداول الافتراضية، ونتعلم العمل مع العتاد على مستوى السجلات ولكن بأسلوب C++. هل نبدأ؟
\n\n1. الأساسيات: كيف يختلف C++ المضمن عن العادي
\n\nالقاعدة الأساسية في تطوير الأنظمة المضمنة: الموارد محدودة. ليس لديك غيغابايت من RAM ولا معالج بتردد غيغاهيرتز. لديك 32 كيلوبايت من فلاش و 8 كيلوبايت من RAM. كل بايت مهم. لذلك، C++ في الأنظمة المضمنة ليس حول "سأكتب فئة بعشرة مستويات من الوراثة"، بل حول التحكم في التجريدات.
\n\nما يمكن استخدامه دون خوف:
\n- \n
- الفئات والهياكل - لتجميع البيانات والوظائف. هذا مجاني (من حيث الذاكرة) إذا لم تكن هناك وظائف افتراضية. \n
- مساحات الأسماء - لتجنب تكرار البادئات مثل
hal_gpio_init(). \n - constexpr والقوالب - حسابات في وقت الترجمة، توفير في دورات الساعة. \n
- المراجع والتمرير بالمرجع الثابت - أكثر أمانًا من المؤشرات. \n
ما الأفضل عدم لمسه دون حاجة ماسة:
\n- \n
- الذاكرة الديناميكية (new/delete) - الكومة تتجزأ، وقد يخفق المخصص. في مشاريع bare-metal - ممنوع. \n
- الاستثناءات (try/catch) - تتطلب دعم وقت التشغيل، وتستهلك مساحة كبيرة. \n
- الوظائف الافتراضية - كل وظيفة افتراضية تنشئ جدولاً (vtable) في RAM/Flash، والاستدعاء يتم من خلال ازدواجية غير مباشرة. لكنها قد تكون مبررة أحيانًا (برامج التشغيل). \n
- RTTI (typeid) - غير ضروري تمامًا. \n
الجوهر بسيط: يُنشئ مترجم C++ كودًا بنفس كفاءة لغة C، إذا كنت لا تستخدم الميزات "الثقيلة". افحص المفكك (disassembler) - وستكون سعيدًا.
\n\n2. التطبيق العملي: العمل مع السجلات بأسلوب C++
\n\nفي C المضمنة التقليدية، نكتب شيئًا مثل:
\n#define GPIOA_BASE 0x40020000\n#define GPIOA_MODER (*(volatile uint32_t*)(GPIOA_BASE + 0x00))\nGPIOA_MODER |= (1 << 10); // تعيين البت 10 (خرج)\n\nهذا يعمل، لكنه يبدو قديمًا. في C++، يمكننا إنشاء غلاف مناسب دون زيادة في التكلفة.
\n\nمثال: فئة للعمل مع الدبوس
\n#include <cstdint>\n\nclass GpioPin {\npublic:\n // منع النسخ\n GpioPin(const GpioPin&) = delete;\n GpioPin& operator=(const GpioPin&) = delete;\n\n // تعداد للأوضاع\n enum class Mode : uint32_t {\n Input = 0b00,\n Output = 0b01,\n AltFunc = 0b10,\n Analog = 0b11\n };\n\n // المُنشئ: يستقبل عنوان المنفذ ورقم الدبوس (0-15)\n GpioPin(volatile uint32_t* port_base, uint8_t pin)\n : port_(port_base), pin_(pin) {\n // يمكن تهيئة الدبوس فورًا\n }\n\n // تعيين وضع العمل\n void setMode(Mode mode) {\n volatile uint32_t* moder = port_ + 0x00; // إزاحة سجل MODER\n *moder &= ~(0b11 << (pin_ * 2));\n *moder |= (static_cast<uint32_t>(mode) << (pin_ * 2));\n }\n\n // تعيين مستوى عالٍ\n void high() {\n volatile uint32_t* bsrr