10 ++ طرق للعمل مع سجلات الأجهزة في C ++ (على سبيل المثال ، IAR و Cortex M)

اختيار المسار الأكثر أمانا
التين. أنا كيكو

صحة جيدة للجميع!

ربما تتذكر حكاية ملتحة ، وربما قصة حقيقية عن كيفية سؤال الطالب عن طريقة لقياس ارتفاع المبنى باستخدام مقياس. استشهد الطالب ، في رأيي ، بحوالي 20 أو 30 طريقة ، دون ذكر المباشر (من خلال الاختلاف في الضغط) الذي توقعه المعلم.

في نفس السياق تقريبًا ، أرغب في متابعة مناقشة استخدام C ++ لأجهزة التحكم الدقيقة والنظر في طرق العمل مع السجلات التي تستخدم C ++. وأود أن أشير إلى أنه من أجل تحقيق الوصول الآمن إلى السجلات ، لن تكون هناك طريقة سهلة. سأحاول إظهار كل إيجابيات وسلبيات الأساليب. إذا كنت تعرف المزيد من الطرق ، فقم برميها في التعليقات. لذلك دعونا نبدأ:

الطريقة الأولى: من الواضح أنها ليست الأفضل


الطريقة الأكثر شيوعًا ، والتي تُستخدم أيضًا في C ++ ، هي استخدام وصف بنيات السجل من ملف الرأس من الشركة المصنعة. من أجل العرض التوضيحي ، سوف آخذ اثنين من سجلات المنفذ A (ODR - سجل بيانات الإخراج و IDR - سجل بيانات المدخلات) من متحكم STM32F411 حتى أتمكن من القيام بـ "Hello" في عالم التطريز - وميض مؤشر LED.

int main() { GPIOA->ODR ^= (1 << 5) ; GPIOA->IDR ^= (1 << 5) ; //,      } 

دعونا نرى ما يحدث هنا وكيف يعمل هذا التصميم. يحتوي رأس المعالج الدقيق على بنية GPIO_TypeDef وتعريف مؤشر لهيكل GPIOA . يبدو مثل هذا:

 typedef struct { __IO uint32_t MODER; //port mode register, Address offset: 0x00 __IO uint32_t OTYPER; //port output type register, Address offset: 0x04 __IO uint32_t OSPEEDR; //port output speed register, Address offset: 0x08 __IO uint32_t PUPDR; //port pull-up/pull-down register, Address offset: 0x0C __IO uint32_t IDR; //port input data register, Address offset: 0x10 __IO uint32_t ODR; //port output data register, Address offset: 0x14 __IO uint32_t BSRR; //port bit set/reset register, Address offset: 0x18 __IO uint32_t LCKR; //port configuration lock register, Address offset: 0x1C __IO uint32_t AFR[2]; //alternate function registers, Address offset: 0x20-0x24 } GPIO_TypeDef; #define PERIPH_BASE 0x40000000U //Peripheral base address in the alias region #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000U) #define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000U) #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 

لوضعها بكلمات بشرية بسيطة ، فإن البنية GPIO_TypeDef لنوع GPIO_TypeDef "تقع" على العنوان GPIOA_BASE ، وعند الوصول إلى حقل معين من البنية ، فإنك تشير أساسًا إلى عنوان هذه البنية + الإزاحة إلى عنصر من هذه البنية. إذا قمت بإزالة #define GPIOA ، #define GPIOA الرمز كما يلي:

 ((GPIO_TypeDef *) GPIOA_BASE)->ODR ^= (1 << 5) ; ((GPIO_TypeDef *) GPIOA_BASE)->IDR ^= (1 << 5) ; // 

بالنسبة إلى لغة البرمجة C ++ ، يتم تحويل عنوان عدد صحيح إلى نوع مؤشر إلى بنية GPIO_TypeDef . ولكن في C ++ ، عند استخدام التحويل C ، يحاول المحول البرمجي إجراء التحويل بالتسلسل التالي:

  • const_cast
  • static_cast
  • static_cast بجانب const_cast ،
  • reinterpret_cast
  • reinterpret_cast بجانب const_cast

أي إذا تعذر على المحول البرمجي تحويل النوع باستخدام const_cast ، فسيحاول تطبيق static_cast وما إلى ذلك. نتيجة لذلك ، الدعوة:

 ((GPIO_TypeDef *) GPIOA_BASE)->ODR ^= (1 << 5) ; 

لا يوجد شيء مثل:

 reinterpret_cast<GPIO_TypeDef *> (GPIOA_BASE)->ODR ^= (1 << 5) ; 

في الواقع ، بالنسبة لتطبيقات C ++ ، سيكون من الصحيح "سحب" الهيكل على العنوان مثل هذا:

 GPIO_TypeDef * GPIOA{reinterpret_cast<GPIO_TypeDef *>(GPIOA_BASE)} ; 

في أي حال ، بسبب نوع التحويل ، هناك ناقص كبير لهذا الأسلوب لـ C ++. يتكون في حقيقة أنه لا يمكن استخدام reinterpret_cast لا في constexpr ووظائف constexpr ، أو في معلمات القالب ، وهذا يقلل بشكل كبير من استخدام ميزات C ++ لأجهزة التحكم الصغيرة.
سأشرح هذا مع الأمثلة. من الممكن القيام بذلك:

  struct Test { const int a; const int b; } ; template<Test* mystruct> constexpr const int Geta() { return mystruct->a; } Test test{1,2}; int main() { Geta<&test>() ; } 

لكن لا يمكنك فعل ذلك بالفعل:

 template<GPIO_TypeDef * mystruct> constexpr volatile uint32_t GetIdr() { return mystruct->IDR; } int main() { //GPIOA  reinterpret_cast<GPIO_TypeDef *> (GPIOA_BASE) //  ,        GetIdr<GPIOA>() ; // } //      : struct Port { constexpr Port(GPIO_TypeDef * ptr): port(*ptr) {} GPIO_TypeDef & port ; } //  GPIOA  reinterpret_cast,   //  constexpr      constexpr Port portA{GPIOA}; //    

وبالتالي ، فإن الاستخدام المباشر لهذا النهج يفرض قيودًا كبيرة على استخدام C ++. لن نتمكن من تحديد موقع الكائن الذي يريد استخدام GPIOA في ROM باستخدام أدوات اللغة ، ولن نتمكن من الاستفادة من metaprogramming لمثل هذا الكائن.
بالإضافة إلى ذلك ، بشكل عام ، هذه الطريقة ليست آمنة (كما يقول شركاؤنا الغربيون). بعد كل شيء ، فمن الممكن تماما لجعل بعض NON-FUN
فيما يتعلق بما سبق ، نلخص:

الأشياء الجيدة


  • يتم استخدام العنوان من الشركة المصنعة (تم التحقق منه ، لا يوجد لديه أخطاء)
  • لا توجد إيماءات وتكاليف إضافية تتخذها وتستخدمها
  • سهولة الاستخدام
  • الجميع يعرف ويفهم هذه الطريقة.
  • لا النفقات العامة

سلبيات


  • استخدام محدود من metaprogramming
  • عدم القدرة على استخدامها في بناة constexpr
  • عند استخدام برامج الالتفاف في الفئات ، فإن الاستهلاك الإضافي لذاكرة الوصول العشوائي هو مؤشر لكائن من هذه البنية
  • يمكنك جعل غبي
الآن دعونا نلقي نظرة على الطريقة رقم 2

طريقة 2. وحشية


من الواضح أن كل مبرمج مدمج يضع في الاعتبار عناوين جميع السجلات لجميع ميكروكنترولر ، لذلك يمكنك ببساطة استخدام الطريقة التالية ، والتي تتبع من الأولى:

 *reinterpret_cast<volatile uint32_t *>(GpioaOdrAddr) ^= (1 <<5) ; *reinterpret_cast<volatile uint32_t *>(GpioaIdrAddr) ^= (1 <<5) ; // 

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

الطريقة الثالثة: من الواضح والأكثر صحة


إذا حدث الوصول إلى السجلات من خلال حقل البنية ، فبدلاً من مؤشر إلى كائن الهيكل ، يمكنك استخدام عنوان البنية الصحيح. عنوان الهياكل موجود في ملف الرأس من الشركة المصنعة (على سبيل المثال ، GPIOA_BASE لـ GPIOA) ، لذلك لا تحتاج إلى تذكره ، ولكن يمكنك استخدامه في القوالب وفي تعبيرات constexpr ، ثم "تراكب" البنية على هذا العنوان.

 template<uint32_t addr, uint32_t pinNum> struct Pin { using Registers = GPIO_TypeDef ; __forceinline static void Toggle() { //     addr Registers *GpioPort{reinterpret_cast<Registers*>(addr)}; GpioPort->ODR ^= (1 << pinNum) ; } }; int main() { using Led1 = Pin<GPIOA_BASE, 5> ; Led1::Toggle() ; } 

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

الطريقة 4. التفاف Exoteric


لخبراء الكود المفهوم ، يمكنك عمل غلاف على السجل بحيث يكون ملائماً للوصول إليهم وتبدو "جميلة" ، وإنشاء مُنشئ ، وإعادة تعريف المشغلين:

 class Register { public: explicit Register(uint32_t addr) : ptr{ reinterpret_cast<volatile uint32_t *>(addr) } { } __forceinline inline Register& operator^=(const uint32_t right) { *ptr ^= right; return *this; } private: volatile uint32_t *ptr; //    }; int main() { Register Odr{GpioaOdrAddr}; Odr ^= (1 << 5); Register Idr{GpioaIdrAddr}; Idr ^= (1 << 5); // } 

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

طريقة 4،5. التفاف Exoteric مع نمط


نضيف حبة من metaprogramming ، ولكن ليس هناك فائدة كبيرة من هذا. تختلف هذه الطريقة عن الطريقة السابقة فقط حيث يتم نقل العنوان ليس إلى المنشئ ، ولكن في معلمة القالب ، نقوم بحفظ بعض السجلات عند نقل العنوان إلى المنشئ ، إنها جيدة بالفعل:

 template<uint32_t addr> class Register { public: Register() : ptr{reinterpret_cast<volatile uint32_t *>(addr)} { } __forceinline inline Register &operator^=(const uint32_t right) { *ptr ^= right; return *this; } private: volatile std::uint32_t *ptr; }; int main() { using GpioaOdr = Register<GpioaOdrAddr>; GpioaOdr Odr; Odr ^= (1 << 5); using GpioaIdr = Register<GpioaIdrAddr>; GpioaIdr Idr; Idr ^= (1 << 5); // } 

وهكذا ، نفس أشعل النار ، وجهة نظر جانبية.

طريقة 5. معقولة


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

 template<uint32_t addr> class Register { public: __forceinline Register &operator^=(const uint32_t right) { *reinterpret_cast<volatile uint32_t *>(addr) ^= right; return *this; } }; using GpioaOdr = Register<GpioaOdrAddr>; GpioaOdr Odr; Odr ^= (1 << 5); using GpioaIdr = Register<GpioaIdrAddr>; GpioaIdr Idr; Idr ^= (1 << 5); // 

يمكنك البقاء هنا والتفكير قليلا. هذه الطريقة تحل على الفور مشكلتين سبق أن ورثتا عن الطريقة الأولى. أولاً ، الآن يمكنني استخدام المؤشر إلى كائن Register في القالب ، وثانياً ، يمكنني تمريره إلى مُنشئ constexrp .

 template<Register * register> void Xor(uint32_t mask) { *register ^= mask ; } Register<GpioaOdrAddr> GpioaOdr; int main() { Xor<&GpioaOdr>(1 << 5) ; //  } //   struct Port { constexpr Port(Register& ref): register(ref) {} Register & register ; } constexpr Port portA{GpioaOdr}; 

بالطبع ، من الضروري مرة أخرى ، إما أن يكون لديك ذاكرة eidetic لعناوين السجلات ، أو لتحديد جميع عناوين السجلات يدويًا في مكان ما في ملف منفصل ...

الأشياء الجيدة


  • سهولة الاستخدام
  • القدرة على استخدام metaprogramming
  • القدرة على استخدامها في بناة constexpr

سلبيات


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

عظيم ، ولكن لا يزال هناك الكثير من السلبيات ...

الطريقة 6. أكثر ذكاء من المعقول


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

 template<uint32_t addr> class Register { public: __forceinline inline static void Xor(const uint32_t mask) { *reinterpret_cast<volatile uint32_t *>(addr) ^= mask; } }; int main() { using namespace Case6 ; using Odr = Register<GpioaOdrAddr>; Odr::Xor(1 << 5); using Idr = Register<GpioaIdrAddr>; Idr::Xor(1 << 5); // } 

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

طريقة 7. إزالة الغباء


من الواضح أنني أقوم دائمًا بعمل NON-FUNNY في الكود وأكتب شيئًا في السجل ، وهو في الواقع ليس مخصصًا للكتابة. لا بأس ، بالطبع ، لكن الغباء يجب حظره. دعنا نمنع القيام بهذا الهراء. للقيام بذلك ، نقدم هياكل مساعدة:

  struct WriteReg {}; struct ReadReg {}; struct ReadWriteReg: public WriteReg, public ReadReg {}; 

الآن يمكننا تعيين السجلات للكتابة ، والسجلات للقراءة فقط:

 template<uint32_t addr, typename RegisterType> class Register { public: //       WriteReg,    // ,  ,       __forceinline template <typename T = RegisterType, class = typename std::enable_if_t<std::is_base_of<WriteReg, T>::value>> Register &operator^=(const uint32_t right) { *reinterpret_cast<volatile uint32_t *>(addr) ^= right; return *this; } }; 

الآن ، دعونا نحاول تجميع Idr ونرى أن الاختبار لا يتم Idr ، لأن المشغل ^= Idr غير موجود:

  int main() { using GpioaOdr = Register<GpioaOdrAddr, WriteReg> ; GpioaOdr Odr ; Odr ^= (1 << 5) ; using GpioaIdr = Register<GpioaIdrAddr, ReadReg> ; GpioaIdr Idr ; Idr ^= (1 << 5) ; //,  Idr    } 

لذلك ، الآن هناك المزيد من الإيجابيات ...

الأشياء الجيدة


  • سهولة الاستخدام
  • القدرة على استخدام metaprogramming
  • القدرة على استخدامها في بناة constexpr
  • رمز مضغوط سريع ، كما في الخيار 1
  • عند استخدام برامج الالتفاف في الفئات ، لا توجد تكلفة RAM إضافية ، حيث لا يتم إنشاء الكائن ، ولكن يتم استخدام الأساليب الثابتة دون إنشاء كائنات
  • لا يمكنك أن تفعل الغباء

سلبيات


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

لذلك دعونا نزيل الفرصة لإنشاء فصل لتوفير المزيد

الطريقة 8. بدون NONSENSE وبدون كائن فئة


كود على الفور:

  struct WriteReg {}; struct ReadReg {}; struct ReadWriteReg: public WriteReg, public ReadReg {}; template<uint32_t addr, typename T> class Register { public: __forceinline template <typename T1 = T, class = typename std::enable_if_t<std::is_base_of<WriteReg, T1>::value>> inline static void Xor(const uint32_t mask) { *reinterpret_cast<volatile int*>(addr) ^= mask; } }; int main { using GpioaOdr = Register<GpioaOdrAddr, WriteReg> ; GpioaOdr::Xor(1 << 5) ; using GpioaIdr = Register<GpioaIdrAddr, ReadReg> ; GpioaIdr::Xor(1 << 5) ; //,  Idr    } 

نضيف واحد زائد ، ونحن لا ننشئ كائنًا. ولكن المضي قدما ، لا يزال لدينا سلبيات

الطريقة 9. الطريقة 8 مع تكامل البنية


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

 namespace Case9 { struct WriteReg {}; struct ReadReg {}; struct ReadWriteReg: public WriteReg, public ReadReg {}; template<uint32_t addr, typename T> class Register { public: __forceinline template <typename T1 = T, class = typename std::enable_if_t<std::is_base_of<WriteReg, T1>::value>> inline static void Xor(const uint32_t mask) { *reinterpret_cast<volatile int*>(addr) ^= mask; } }; template<uint32_t addr> struct Gpio { using Moder = Register<addr, ReadWriteReg>; //      using Otyper = Register<addr + OtyperShift, ReadWriteReg> ; using Ospeedr = Register<addr + OspeedrShift,ReadWriteReg> ; using Pupdr = Register<addr + PupdrShift,ReadWriteReg> ; using Idr = Register<addr + IdrShift, ReadReg> ; using Odr = Register<addr + OdrShift, WriteReg> ; }; int main() { using Gpioa = Gpio<GPIOA_BASE> ; Gpioa::Odr::Xor(1 << 5) ; Gpioa::Idr::Xor((1 << 5) ); //,  Idr    } 

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

الطريقة 10. التفاف فوق السجل من خلال مؤشر إلى عضو في البنية


يستخدم مثل هذا المفهوم كمؤشر لعضو البنية والوصول إليها .

 template<uint32_t addr, typename T> class RegisterStructWrapper { public: __forceinline template<typename P> inline static void Xor(PT::*member, int mask) { reinterpret_cast<T*>(addr)->*member ^= mask ; //   ,     . } } ; using GpioaWrapper = RegisterStructWrapper<GPIOA_BASE, GPIO_TypeDef> ; int main() { GpioaWrapper::Xor(&GPIO_TypeDef::ODR, (1 << 5)) ; GpioaWrapper::Xor(&GPIO_TypeDef::IDR, (1 << 5)) ; // return 0 ; } 

الأشياء الجيدة


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

سلبيات


  • يمكنك جعل الحماقة وحتى التكهن بشأن شمولية الكود.

طريقة 10.5. الجمع بين الطريقة 9 و 10


لمعرفة تحول السجل بالنسبة إلى بداية البنية ، يمكنك استخدام المؤشر لعضو البنية: عضو volatile uint32_t T::*member ، سيعود إزاحة عضو الهيكل بالنسبة إلى بدايته بالبايت. على سبيل المثال ، لدينا بنية GPIO_TypeDef ، ثم العنوان &GPIO_TypeDef::ODR سيكون 0x14.
لقد تغلبنا على هذه الفرصة ونحسب عناوين السجلات من الطريقة 9 ، باستخدام المترجم:

 struct WriteReg {}; struct ReadReg {}; struct ReadWriteReg: public WriteReg, public ReadReg {}; template<uint32_t addr, typename T, volatile uint32_t T::*member, typename RegType> class Register { public: __forceinline template <typename T1 = RegType, class = typename std::enable_if_t<std::is_base_of<WriteReg, T1>::value>> inline static void Xor(const uint32_t mask) { reinterpret_cast<T*>(addr)->*member ^= mask ; } }; template<uint32_t addr, typename T> struct Gpio { using Moder = Register<addr, GPIO_TypeDef, &GPIO_TypeDef::ODR, ReadWriteReg>; using Otyper = Register<addr, GPIO_TypeDef, &GPIO_TypeDef::OTYPER, ReadWriteReg>; using Ospeedr = Register<addr, GPIO_TypeDef, &GPIO_TypeDef::OSPEEDR, ReadWriteReg>; using Pupdr = Register<addr, GPIO_TypeDef, &GPIO_TypeDef::PUPDR, ReadWriteReg>; using Idr = Register<addr, GPIO_TypeDef, &GPIO_TypeDef::IDR, ReadReg>; using Odr = Register<addr, GPIO_TypeDef, &GPIO_TypeDef::ODR, WriteReg>; } ; 

يمكنك العمل مع السجلات بشكل أكثر غرابة:

 using namespace Case11 ; using Gpioa = Gpio<GPIOA_BASE, GPIO_TypeDef> ; Gpioa::Odr::Xor(1 << 5) ; //Gpioa::Idr::Xor((1 << 5) ); //,  Idr    

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

مكافأة. نحن نقدم امتداد لغة ورمز parsim باستخدام Phyton


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

 template<classname = [PortName]> class Gpio[Portname] { __forceinline inline static void Xor(const uint32_t mask) { GPIO[PortName]->ODR ^= mask ; } }; int main() { using GpioA = Gpio<"A"> ; GpioA::Xor(5) ; } 

لكن لسوء الحظ هذه اللغة لا تدعم. لذلك ، فإن الحل الذي يستخدمه الناس هو تحليل الكود باستخدام Python. أي تم تقديم بعض امتدادات اللغة. يتم تغذية الكود ، باستخدام هذا الامتداد ، إلى محلل Python ، والذي يترجمه إلى رمز C ++. يشبه هذا الرمز ما يلي: (مثال مأخوذ من مكتبة المودم ؛ فيما يلي المصادر الكاملة ):

 %% set port = gpio["port"] | upper %% set reg = "GPIO" ~ port %% set pin = gpio["pin"] class Gpio{{ port ~ pin }} : public Gpio { __forceinline inline static void Xor() { GPIO{{port}}->ODR ^= 1 << {{pin}} ; } } //        class Gpio5 : public Gpio { __forceinline inline static void Xor() { GPIO->ODR ^= 1 << 5 ; } } //     using Led = Gpio5; Led::Xor(); 


تحديث: مكافأة. ملفات SVD ومحلل على Phyton


نسيت أن أضيف خيار آخر. يقوم ARM بإصدار ملف وصف تسجيل لكل مُصنع SVD. يمكنك من خلالها إنشاء ملف C ++ مع وصف للسجلات. قام بول أوزبورن بتجميع كل هذه الملفات على جيثب . كما كتب سيناريو بيثون لتحليلهم.

هذا كل شيء ... مخيلتي استنفدت. إذا كنت لا تزال لديك أفكار ، فلا تتردد في. مثال على كل الأساليب يكمن هنا.

مراجع


Typesafe تسجيل الوصول في C ++
جعل الأشياء تفعل أشياء - الوصول إلى الأجهزة من C ++
جعل الأشياء تفعل أشياء - الجزء 3
جعل الأشياء تفعل أشياء ، هيكل تراكب

Source: https://habr.com/ru/post/ar459204/


All Articles