العمل مع قائمة الدبابيس ، في C ++ لأجهزة التحكم الدقيقة (باستخدام CortexM كمثال)


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


في مقال سابق ، وعدت بالكتابة عن كيفية العمل مع قائمة الموانئ.
يجب أن أقول على الفور أن كل شيء قد تم بالفعل تحديده من قبل بالفعل في عام 2010 ، إليك المقالة: العمل مع منافذ الإدخال / الإخراج الخاصة بوحدات التحكم الدقيقة في C ++ . الشخص الذي كتب هذا في عام 2010 هو ببساطة وسيم.


لقد شعرت بالحرج قليلاً لأنني سأفعل ما تم فعله بالفعل منذ 10 سنوات ، لذلك قررت ألا أنتظر عام 2020 ، لكن أن أفعل ذلك في عام 2019 من أجل تكرار القرار منذ 9 سنوات ، لن يكون هذا الأمر غبيًا.


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


مهمة


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


في الواقع ، يمكن عرض ما نريد القيام به مع الكود:


using Pin1 = Pin<GPIO, 1>; using Pin2 = Pin<GPIOB, 1>; using Pin3 = Pin<GPIOA, 1>; using Pin4 = Pin<GPIOC, 2>; using Pin5 = Pin<GPIOA, 3>; int main() { //    Pin    : //   GPIOA  10 GPIOA->BSRR = 10 ; // (1<<1) | (1 << 3) ; //   GPIOB  2 GPIOB->BSRR = 2 ; // (1 << 1) //   GPIOC  6 GPIOB->BSRR = 6 ; // (1 << 1) | (1 << 2); PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5>::Set() ; return 0; } 

حول سجل BSRR

بالنسبة لأولئك الذين ليسوا على علم بشؤون المتحكم الدقيق ، فإن سجل GPIOA->BSRR مسؤول عن التركيب الذري أو إعادة تعيين القيم على أرجل المتحكم الدقيق. هذا السجل هو 32 بت. أول 16 بت هي المسؤولة عن وضع 1 على الساقين ، والثانية 16 بت لتحديد 0 على الساقين.


على سبيل المثال ، من أجل ضبط رقم الساق 3 على 1 ، تحتاج إلى تعيين البت الثالث على 1 في سجل BSRR . لإعادة تعيين العدد 3 إلى 0 ، تحتاج إلى ضبط 19 بت على 1 في نفس سجل BSRR .


يمكن تمثيل مخطط عام لخطوات حل هذه المشكلة على النحو التالي:



حسنًا ، بكلمات أخرى:


للمترجمين القيام به من أجلنا:


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

ثم البرنامج


  • اضبط هذه القيمة

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


لنبدأ مع البدعة الأولى: التحقق من أن القائمة تحتوي على دبوس فريد.


تحقق من القائمة للتفرد


دعني أذكرك بأن لدينا قائمة من الدبابيس:


 PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5> ; 

من غير قصد يمكن القيام بذلك:


 PinsPack<Pin1, Pin2, Pin3, Pin4, Pin1> ; //     Pin1 

أود أن يترجم المترجم مثل هذا الخطأ ويخبر عازف البيانو بذلك.


سوف نتحقق من القائمة للتفرد على النحو التالي:


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

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


الرمز الذي تم استعارته من زميل قام باستعارة الفكرة من Loki
 namespace PinHelper { template<typename ... Types> struct Collection { }; /////////////////   NoDuplicates   LOKI //////////////// template<class X, class Y> struct Glue; template<class T, class... Ts> struct Glue<T, Collection<Ts...>> { using Result = Collection<T, Ts...>; }; template<class Q, class X> struct Erase; template<class Q> struct Erase<Q, Collection<>> { using Result = Collection<>;}; template<class Q, class... Tail> struct Erase<Q, Collection<Q, Tail...>> { using Result = Collection<Tail...>;}; template<class Q, class T, class... Tail> struct Erase<Q, Collection<T, Tail...>> { using Result = typename Glue<T, typename Erase<Q, Collection<Tail...>>::Result>::Result;}; template <class X> struct NoDuplicates; template <> struct NoDuplicates<Collection<>> { using Result = Collection<>; }; template <class T, class... Tail> struct NoDuplicates< Collection<T, Tail...> > { private: using L1 = typename NoDuplicates<Collection<Tail...>>::Result; using L2 = typename Erase<T,L1>::Result; public: using Result = typename Glue<T, L2>::Result; }; ///////////////// LOKI //////////////// } 

كيف يمكن استخدام هذا الآن؟ نعم ، الأمر بسيط للغاية:


 using Pin1 = Pin<GPIOC, 1>; using Pin2 = Pin<GPIOB, 1>; using Pin3 = Pin<GPIOA, 1>; using Pin4 = Pin<GPIOC, 2>; using Pin5 = Pin<GPIOA, 3>; using Pin6 = Pin<GPIOC, 1>; int main() { //  Pin1  ,    Pin6      using PinList = Collection<Pin1, Pin2, Pin3, Pin4, Pin1, Pin6> ; using TPins = typename NoDuplicates<PinList>::Result; //  static_assert.        // : Collection<Pin1, Pin2, Pin3, Pin4, Pin1, Pin6> //   : Collection<Pin1, Pin2, Pin3, Pin4> // ,    static_assert(std::is_same<TPins, PinList>::value, ":    ") ; return 0; } 

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


بالمناسبة ، لضمان القائمة الصحيحة من المنافذ ، يمكنك استخدام الطريقة التالية:
 //        // PinsPack<Port<GPIOB, 0>, Port<GPIOB, 1> ... Port<GPIOB, 15>> using GpiobPort = typename GeneratePins<15, GPIOB>::type //      using GpioaPort = typename GeneratePins<15, GPIOA>::type int main() { //    :  GPIOA.0  1 Gpioa<0>::Set() ; // GPIOB.1  0 Gpiob<1>::Clear() ; using LcdData = Collection<Gpioa<0>, Gpiob<6>, Gpiob<2>, Gpioa<3>, Gpioc<7>, Gpioa<4>, Gpioc<3>, Gpioc<10>> ; using TPinsLcd = typename NoDuplicates<LcdData>::Result; static_assert(std::is_same<TPinsB, LcdData>::value, ":        LCD") ; // A      LcdData::Write('A'); } 

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


 int main() { return 0 ; } 

دعنا نضيف بعض الكود ونحاول أن نجعل طريقة Set() لتعيين المسامير في القائمة.


ميناء تركيب طريقة التثبيت


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


الكود الذي نريده
 using Pin1 = Pin<GPIOA, 1>; using Pin2 = Pin<GPIOB, 2>; using Pin3 = Pin<GPIOA, 2>; using Pin4 = Pin<GPIOC, 1>; using Pin5 = Pin<GPIOA, 3>; int main() { PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5>::Set() ; //      3   // GPIOA->BSRR = 14 ; // (1<<1) | (1 << 2) | (1 << 3) ; // GPIOB->BSRR = 4 ; // (1 << 2) // GPIOB->BSRR = 2 ; // (1 << 1); } 

لذلك ، نعلن عن فئة تحتوي على قائمة من الدبابيس ، وفيها نحدد الطريقة الثابتة العامة Set() .


 template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; public: __forceinline static void Set(std::size_t mask) { } } ; 

كما ترون ، تأخذ طريقة Set(size_t mask) نوعًا من القيمة (قناع). هذا القناع هو الرقم الذي تحتاجه لوضعه في المنافذ. بشكل افتراضي ، يكون 0xffffffff ، مما يعني أننا نريد وضع جميع الدبابيس في القائمة (بحد أقصى 32). إذا قمت بتمرير قيمة أخرى هناك ، على سبيل المثال ، 7 == 0b111 ، فيجب تثبيت أول 3 دبابيس فقط في القائمة ، وما إلى ذلك. أي قناع تراكب على قائمة دبوس.


قائمة ميناء


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


يتم تعيين دبابيس لدينا إلى منافذ مختلفة:


 using Pin1 = Pin<Port<GPIOA>, 1>; using Pin2 = Pin<Port<GPIOB>, 2>; using Pin3 = Pin<Port<GPIOA>, 2>; using Pin4 = Pin<Port<GPIOC>, 1>; using Pin5 = Pin<Port<GPIOA>, 3>; 

تحتوي هذه الدبابيس الخمسة على 3 منافذ فريدة (GPIOA و GPIOB و GPIOC). إذا أعلنا قائمة PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5> إلى الحصول على قائمة بثلاثة منافذ منها: Collection<Port<GPIOA>, Port<GPIOB>, Port<GPIOC>>


تحتوي فئة Pin على نوع المنفذ وبصورة مبسطة تبدو كما يلي:


 template<typename Port, uint8_t pinNum> struct Pin { using PortType = Port ; static constexpr uint32_t pin = pinNum ; ... } 

بالإضافة إلى ذلك ، لا تزال بحاجة إلى تحديد بنية لهذه القائمة ، وستكون مجرد بنية تتطلب عددًا متغيرًا من وسيطات النماذج


 template <typename... Types> struct Collection{} ; 

الآن نحدد قائمة بالمنافذ الفريدة ، وفي الوقت نفسه نتأكد من أن قائمة المسامير لا تحتوي على نفس المسامير. هذا سهل القيام به:


 template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; private: //      using TPins = typename NoDuplicates<Collection<Ts...>>::Result; //           static_assert(std::is_same<TPins, Collection<Ts...>>::value, ":    ") ; //     using Ports = typename NoDuplicates<Collection<typename Ts::PortType...>>::Result; ... } ; 

المضي قدما ...


قائمة ميناء الالتفافية


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


نظرًا لأنه يجب علينا تجاوز قائمة غير معروفة حجمها بشكل مؤكد ، فستكون الوظيفة عبارة عن قالب مع عدد متغير من المعلمات.


سنتجول "بشكل متكرر" ، في حين لا تزال هناك معلمات في القالب ، سوف نقوم باستدعاء دالة بنفس الاسم.


 template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; private: __forceinline template<typename Port, typename ...Ports> constexpr static void SetPorts(Collection<Port, Ports...>, std::size_t mask) { // ,       if constexpr (sizeof ...(Ports) != 0U) { Pins::template WritePorts<Ports...>(Collection<Ports...>(), mask) ; } } } 

لذلك ، تعلمنا كيفية تجاوز قائمة المنافذ ، ولكن بالإضافة إلى الالتفافية ، تحتاج إلى القيام ببعض الأعمال المفيدة ، أي تثبيت شيء ما في المنفذ.


 __forceinline template<typename Port, typename ...Ports> constexpr static void SetPorts(Collection<Port, Ports...>, std::size_t mask) { //      auto result = GetPortValue<Port>(mask) ; //      Port::Set(result) ; if constexpr (sizeof ...(Ports) != 0U) { Pins::template WritePorts<Ports...>(Collection<Ports...>(), mask) ; } } 

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


وعلى الرغم من أنه في المقالة العمل مع منافذ الإدخال / الإخراج من ميكروكنترولر في C ++ ، هو مكتوب أنه في طريقة مماثلة قرر المترجم أنه تم تمرير ثابت وحساب قيمة الكتابة إلى المنفذ في مرحلة التجميع ، لم يقم برنامج التحويل البرمجي الخاص بي بخداع كهذه إلا بأقصى قدر ممكن من التحسين.
أود GetValue() تنفيذها في وقت الترجمة مع أي إعدادات مترجم.


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


لضمان النقل الواضح لقيمة ثابتة ، سنقوم بعمل طريقة إضافية مع mask مرور في القالب:


 __forceinline template<std::size_t mask, typename Port, typename ...Ports> constexpr static void SetPorts(Collection<Port, Ports...>) { using MyPins = PinsPack<Ts...> ; //    compile time,    value    constexpr auto result = GetPortValue<Port>(mask) ; Port::Set(result) ; if constexpr (sizeof ...(Ports) != 0U) { MyPins::template SetPorts<mask,Ports...>(Collection<Ports...>()) ; } } 

وبالتالي ، يمكننا الآن تجاوز قائمة Pins ، وسحب المنافذ منها ووضع قائمة فريدة من المنافذ التي ترتبط بها ، ثم الانتقال إلى قائمة المنافذ التي تم إنشاؤها وتعيين القيمة المطلوبة في كل منفذ.
يبقى لحساب هذه القيمة .


حساب القيمة التي يجب تعيينها في المنفذ


لدينا قائمة بالمنافذ التي حصلنا عليها من قائمة Pin ، على سبيل المثال هذه قائمة: Collection<Port<GPIOA>, Port<GPIOB>, Port<GPIOC>> .
يجب أن تأخذ عنصرًا في هذه القائمة ، على سبيل المثال ، منفذ GPIOA ، ثم في قائمة Pin ، يمكنك العثور على كافة الدبوس المرفقة بهذا المنفذ وحساب قيمة التثبيت في المنفذ. ثم افعل نفس الشيء مع المنفذ التالي.


مرة أخرى: في حالتنا ، قائمة الدبابيس التي يمكن من خلالها الحصول على قائمة بالمنافذ الفريدة هي كما يلي:
 using Pin1 = Pin<Port<GPIOC>, 1>; using Pin2 = Pin<Port<GPIOB>, 1>; using Pin3 = Pin<Port<GPIOA>, 1>; using Pin4 = Pin<Port<GPIOC>, 2>; using Pin5 = Pin<Port<GPIOA>, 3>; using Pins = PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5> ; 

لذلك بالنسبة لمنفذ GPIOA ، يجب أن تكون القيمة (1 << 1 ) | (1 << 3) = 10 (1 << 1 ) | (1 << 3) = 10 ، ولمنفذ GPIOC - (1 << 1) | (1 << 2) = 6 (1 << 1) | (1 << 2) = 6 ، و GPIOB (1 << 1 ) = 2


تقبل وظيفة الحساب المنفذ المطلوب ، وإذا كان Pin موجودًا على نفس المنفذ مثل المنفذ المطلوب ، فيجب عليه تعيين البت المطابق لموضع Pina في القائمة ، واحد (1) في القناع.
ليس من السهل شرحها بالكلمات ، فمن الأفضل أن ننظر إلى الشفرة في نصها الصحيح:


 template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; private: __forceinline template<class QueryPort> constexpr static auto GetPortValue(std::size_t mask) { std::size_t result = 0; //  ,       // 1. ,        // 2.            // e (.     ), ,  Pin   0  //        10,      //    ( )  (1 << 10)    // 3.    1   // 4.   1-3      pass{(result |= ((std::is_same<QueryPort, typename Ts::PortType>::value ? 1 : 0) & mask) * (1 << Ts::pin), mask >>= 1)...} ; return result; } } ; 

تحديد القيمة المحسوبة لكل منفذ إلى المنافذ


الآن نحن نعرف القيمة التي يجب تعيينها في كل منفذ. يبقى إكمال طريقة Set() العامة ، والتي ستكون مرئية للمستخدم بحيث يتم تسمية كل هذا الاقتصاد:


 template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; __forceinline static void Set(std::size_t mask) { //        SetPorts(Ports(), mask) ; } } 

كما هو الحال في SetPorts() بإنشاء طريقة قالب إضافية لضمان نقل mask كثابت ، بتمريره في سمة القالب.


 template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; //    0xffffffff,      32  __forceinline template<std::size_t mask = 0xffffffffU> static void Set() { SetPorts<mask>(Ports()) ; } } 

في الشكل النهائي ، سيبدو صفنا لقائمة Pin كما يلي:
 using namespace PinHelper ; template <typename ...Ts> struct PinsPack { using Pins = PinsPack<Ts...> ; private: using TPins = typename NoDuplicates<Collection<Ts...>>::Result; static_assert(std::is_same<TPins, Collection<Ts...>>::value, ":    ") ; using Ports = typename NoDuplicates<Collection<typename Ts::PortType...>>::Result; template<class Q> constexpr static auto GetPortValue(std::size_t mask) { std::size_t result = 0; auto rmask = mask ; pass{(result |= ((std::is_same<Q, typename Ts::PortType>::value ? 1 : 0) & mask) * (1 << Ts::pin), mask>>=1)...}; pass{(result |= ((std::is_same<Q, typename Ts::PortType>::value ? 1 : 0) & ~rmask) * ((1 << Ts::pin) << 16), rmask>>=1)...}; return result; } __forceinline template<typename Port, typename ...Ports> constexpr static void SetPorts(Collection<Port, Ports...>, std::size_t mask) { auto result = GetPortValue<Port>(mask) ; Port::Set(result & 0xff) ; if constexpr (sizeof ...(Ports) != 0U) { Pins::template SetPorts<Ports...>(Collection<Ports...>(), mask) ; } } __forceinline template<std::size_t mask, typename Port, typename ...Ports> constexpr static void SetPorts(Collection<Port, Ports...>) { constexpr auto result = GetPortValue<Port>(mask) ; Port::Set(result & 0xff) ; if constexpr (sizeof ...(Ports) != 0U) { Pins::template SetPorts<mask, Ports...>(Collection<Ports...>()) ; } } __forceinline template<typename Port, typename ...Ports> constexpr static void WritePorts(Collection<Port, Ports...>, std::size_t mask) { auto result = GetPortValue<Port>(mask) ; Port::Set(result) ; if constexpr (sizeof ...(Ports) != 0U) { Pins::template WritePorts<Ports...>(Collection<Ports...>(), mask) ; } } __forceinline template<std::size_t mask, typename Port, typename ...Ports> constexpr static void WritePorts(Collection<Port, Ports...>) { Port::Set(GetPortValue<Port>(mask)) ; if constexpr (sizeof ...(Ports) != 0U) { Pins::template WritePorts<mask, Ports...>(Collection<Ports...>()) ; } } public: static constexpr size_t size = sizeof ...(Ts) + 1U ; __forceinline static void Set(std::size_t mask ) { SetPorts(Ports(), mask) ; } __forceinline template<std::size_t mask = 0xffffffffU> static void Set() { SetPorts<mask>(Ports()) ; } __forceinline static void Write(std::size_t mask) { WritePorts(Ports(), mask) ; } __forceinline template<std::size_t mask = 0xffffffffU> static void Write() { WritePorts<mask>(Ports()) ; } } ; 

نتيجة لذلك ، يمكن استخدام كل شيء على النحو التالي:


 using Pin1 = Pin<GPIOC, 1>; using Pin2 = Pin<GPIOB, 1>; using Pin3 = Pin<GPIOA, 1>; using Pin4 = Pin<GPIOC, 2>; using Pin5 = Pin<GPIOA, 3>; using Pin6 = Pin<GPIOA, 5>; using Pin7 = Pin<GPIOC, 7>; using Pin8 = Pin<GPIOA, 3>; int main() { //1.   ,     3 ,  : // GPIOA->BSRR = (1 << 1) | (1 << 3) // GPIOB->BSRR = (1 << 1) // GPIOC->BSRR = (1 << 1) | (1 << 2) PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5>::Set() ; //   Set<0xffffffffU>() //2.   ,  3 ,  : // GPIOA->BSRR = (1 << 1) // GPIOB->BSRR = (1 << 1) // GPIOC->BSRR = (1 << 1) | (1 << 2) PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5, Pin6>::Set<7>() ; //3.          , //   someRunTimeValue     ,  //  SetPorts   constexpr    PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5>::Set(someRunTimeValue) ; using LcdData = PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5, Pin6, Pin7, Pin8> ; LcdData::Write('A') ; } 

يمكن العثور على مثال أكمل هنا:
https://onlinegdb.com/r1eoXQBRH


سرعة


كما تتذكر ، أردنا أن نجعل مكالماتنا تتحول إلى 3 خطوط ، وتم تعيينها على المنفذ A 10 ، والمنفذ B - 2 والمنفذ C - 6


 using Pin1 = Pin<GPIO, 1>; using Pin2 = Pin<GPIOB, 1>; using Pin3 = Pin<GPIOA, 1>; using Pin4 = Pin<GPIOC, 2>; using Pin5 = Pin<GPIOA, 3>; int main() { //    Pin    : //   GPIOA  10 GPIOA->BSRR = 10 ; // (1<<1) | (1 << 3) ; //   GPIOB  2 GPIOB->BSRR = 2 ; // (1 << 1) //   GPIOC  6 GPIOB->BSRR = 6 ; // (1 << 1) | (1 << 2); PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5>::Set() ; return 0; } 

دعونا نرى ما حدث مع إيقاف التشغيل بالكامل.



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


في الواقع هذا كل شيء. من يهتم ، الكود هنا .


مثال هنا .


https://onlinegdb.com/ByeA50wTS

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


All Articles