C++ المضمن: كيفية كتابة كود للمتحكمات الدقيقة دون فقدان الصواب

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا C++ في الأنظمة المضمنة ليس سادية بل قوة خارقة

\n\n

عندما تسمع "تطوير الأنظمة المضمنة"، عادة ما يتبادر إلى الذهن مهندس صارم مع مكواة لحام يكتب بلغة C الخالصة ويصلي على ورقة البيانات. أو العكس - متحمس يقوم بتحميل digitalWrite() على Arduino ويفرح لأنه يومض بمصباح LED. ولكن بين هذين العالمين هناك حل وسط ذهبي - تطوير الأنظمة المضمنة بلغة C++.

\n\n

نعم، نفس لغة C++ التي تخيف بالقوالب والوظائف الافتراضية. في الواقع، المتحكمات الدقيقة الحديثة (ARM Cortex-M، RISC-V) قادرة تمامًا على هضم كود C++. علاوة على ذلك، الاستخدام الذكي للبرمجة الشيئية (OOP)، والتغليف (Encapsulation)، وحتى القوالب (Templates) يمكن أن يجعل كودك المضمن أنظف وأكثر أمانًا وأسرع.

\n\n

في هذه المقالة، سنشرح كيفية كتابة كود C++ فعال للمتحكمات الدقيقة، ونتجنب مشاكل الجداول الافتراضية، ونتعلم العمل مع العتاد على مستوى السجلات ولكن بأسلوب C++. هل نبدأ؟

\n\n

1. الأساسيات: كيف يختلف C++ المضمن عن العادي

\n\n

القاعدة الأساسية في تطوير الأنظمة المضمنة: الموارد محدودة. ليس لديك غيغابايت من RAM ولا معالج بتردد غيغاهيرتز. لديك 32 كيلوبايت من فلاش و 8 كيلوبايت من RAM. كل بايت مهم. لذلك، C++ في الأنظمة المضمنة ليس حول "سأكتب فئة بعشرة مستويات من الوراثة"، بل حول التحكم في التجريدات.

\n\n

ما يمكن استخدامه دون خوف:

\n
    \n
  • الفئات والهياكل - لتجميع البيانات والوظائف. هذا مجاني (من حيث الذاكرة) إذا لم تكن هناك وظائف افتراضية.
  • \n
  • مساحات الأسماء - لتجنب تكرار البادئات مثل hal_gpio_init().
  • \n
  • constexpr والقوالب - حسابات في وقت الترجمة، توفير في دورات الساعة.
  • \n
  • المراجع والتمرير بالمرجع الثابت - أكثر أمانًا من المؤشرات.
  • \n
\n\n

ما الأفضل عدم لمسه دون حاجة ماسة:

\n
    \n
  • الذاكرة الديناميكية (new/delete) - الكومة تتجزأ، وقد يخفق المخصص. في مشاريع bare-metal - ممنوع.
  • \n
  • الاستثناءات (try/catch) - تتطلب دعم وقت التشغيل، وتستهلك مساحة كبيرة.
  • \n
  • الوظائف الافتراضية - كل وظيفة افتراضية تنشئ جدولاً (vtable) في RAM/Flash، والاستدعاء يتم من خلال ازدواجية غير مباشرة. لكنها قد تكون مبررة أحيانًا (برامج التشغيل).
  • \n
  • RTTI (typeid) - غير ضروري تمامًا.
  • \n
\n\n

الجوهر بسيط: يُنشئ مترجم C++ كودًا بنفس كفاءة لغة C، إذا كنت لا تستخدم الميزات "الثقيلة". افحص المفكك (disassembler) - وستكون سعيدًا.

\n\n

2. التطبيق العملي: العمل مع السجلات بأسلوب 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                

المدونات

توصيات الكتب