مقدمة
انتبه ، هذا ليس مقالة أخرى "Hello world" حول كيفية وميض مؤشر LED أو الدخول في أول مقاطعة له على STM32. ومع ذلك ، حاولت أن أقدم توضيحات شاملة حول جميع القضايا التي أثيرت ، وبالتالي فإن هذه المقالة ستكون مفيدة ليس فقط للعديد من المحترفين ويحلمون بأن يصبحوا مطورين (كما آمل) ، ولكن أيضًا للمبرمجين المبتدئين في المتحكم الدقيق ، حيث إن هذا الموضوع لسبب ما يتجول في عدد لا يحصى من المواقع / بلوق "معلمي البرمجة MK."

لماذا قررت أن أكتب هذا؟
على الرغم من أنني أبالغ ، بعد أن قلت في وقت سابق أن نطاقات بت الأجهزة في عائلة Cortex-M غير موصوف في الموارد المتخصصة ، لا تزال هناك أماكن يتم فيها تغطية هذه الميزة (وحتى قابلت مقالة واحدة هنا) ، ولكن من الواضح أن هذا الموضوع يحتاج إلى استكماله وتحديثه. ألاحظ أن هذا ينطبق أيضًا على موارد اللغة الإنجليزية. في القسم التالي ، سأشرح لك سبب أهمية ميزة kernel هذه.
نظرية
(وأولئك الذين يعرفونها يمكنهم القفز إلى التدريب على الفور)يعد النطاق الترددي للأجهزة ميزة من النواة نفسها ، وبالتالي لا يعتمد على عائلة الشركة المصنعة للميكروكونترولر ، وبالتالي فإن الأمر الأساسي هو أن النواة مناسبة. في حالتنا ، فليكن Cortex-M3. لذلك ، يجب البحث عن معلومات حول هذه المشكلة في مستند رسمي عن جوهر نفسه ، وهناك مثل هذا المستند ،
وهنا ، يصف القسم 4.2 بالتفصيل كيفية استخدام هذه الأداة.
هنا أود أن أجعل استطرادا تقنيا صغيرا للمبرمجين الذين ليسوا على دراية بالمجمع ، ومعظمهم الآن ، بسبب التعقيد الدعائي وعدم جدوى المجمّع لمثل ميكروكنترولر 32 بت "الخطيرة" مثل STM32 ، LPC ، إلخ. علاوة على ذلك ، يمكن للمرء أن يواجه محاولات رقابة لاستخدام المجمع في هذا المجال ، حتى على هبر. في هذا القسم ، أود أن أصف بإيجاز آلية الكتابة إلى ذاكرة MK ، والتي يجب أن توضح مزايا نطاق البت.
ساوضح مثال بسيط محدد لمعظم STM32. افترض أنني بحاجة إلى تحويل PB0 إلى ناتج للأغراض العامة. الحل النموذجي سيبدو كالتالي:
GPIOB->MODER |= GPIO_MODER_MODER0_0;
من الواضح أننا نستخدم "OR" bitwise من أجل عدم الكتابة على البتات المتبقية من السجل.
للمترجم ، وهذا يترجم إلى مجموعة من 4 تعليمات التالية:
- قم بتنزيل GPIOB-> MODER إلى سجل الأغراض العامة (RON)
- قم بتحميل القيم إلى RON الأخرى على العنوان المشار إليه في RON من p1.
- قم بإجراء bitwise OR من هذه القيمة باستخدام GPIO_MODER_MODER0_0.
- قم بتنزيل النتيجة مرة أخرى إلى GPIOB-> MODER.
أيضًا ، لا ينبغي لأحد أن ينسى أن هذا النواة يستخدم مجموعة التعليمات thumb2 ، مما يعني أنه يمكن أن يكونا مختلفين في الحجم. وألاحظ أيضًا أنه في كل مكان نتحدث فيه عن مستوى التحسين O3.
في لغة التجميع ، يبدو كما يلي:

يمكن ملاحظة أن التعليمة الأولى ليست أكثر من مجرد تعليمة زائفة مع إزاحة ، نجد عنوان السجل على عنوان الكمبيوتر الشخصي (بالنظر إلى حزام النقل) + 0x58.

اتضح لدينا 4 خطوات (والمزيد من دورات الساعة) و 14 بايت من الذاكرة المحتلة لكل عملية.
إذا كنت تريد معرفة المزيد عن هذا ، فأنا أوصي بالكتاب [2] ، بالمناسبة ، يوجد أيضًا باللغة الروسية.
نحن ننتقل إلى طريقة bit_banding.
الجوهر ، وفقًا للفلاح ، هو أن المعالج يحتوي على مساحة ذاكرة مخصصة بشكل خاص ، حيث يكتب القيم التي لا نغير فيها الأجزاء الأخرى من السجل المحيطي أو ذاكرة الوصول العشوائي. وهذا يعني أننا لسنا بحاجة إلى تحقيق النقطتين 2) و 3) الموضحة أعلاه ، ولهذا يكفي إعادة فرز العنوان وفقًا للصيغ من [1] فقط.

نحن نحاول القيام بعملية مماثلة ، المجمع:

العنوان المعاد حسابه:

أضفنا هنا تعليمة كتابة رقم 1 في RON ، ولكن على أي حال ، كانت النتيجة 10 بايت ، بدلاً من 14 ، واثنين من دورات الساعة أقل.
ولكن ماذا لو كان الفرق سخيفًا؟
من ناحية ، لا تكون المدخرات كبيرة ، خاصة في الدورات عندما تكون عادةً في رفع تردد وحدة التحكم إلى 168 ميجاهرتز. في مشروع متوسط ، ستكون اللحظات التي يمكنك فيها تطبيق هذه الطريقة من 40 إلى 80 ، على التوالي ، في البايتات يمكن أن تصل الوفورات إلى 250 بايت في حالة اختلاف العناوين. وإذا اعتبرنا أن برمجة MK مباشرة على السجلات تعتبر الآن "zashkvar" ، وأنه من "الجيد" استخدام جميع أنواع مكعبات الزهر ، فإن الوفورات يمكن أن تكون أكثر من ذلك بكثير.
أيضا ، يتم تشويه الرقم 250 بايت من حقيقة أن المكتبات رفيعة المستوى تستخدم بنشاط في المجتمع ، وتضخيم البرامج الثابتة إلى أحجام غير لائقة. عند البرمجة بمستوى منخفض ، يكون ذلك على الأقل 2 - 5٪ من حجم البرنامج لمشروع متوسط ، بهندسة معمارية مختصة وتحسين O3.
مرة أخرى ، لا أريد أن أقول إن هذا نوع من الأدوات الرائعة فائقة المخادع التي يجب أن يستخدمها كل مبرمج MK يحترم نفسه. لكن إذا كان بإمكاني خفض التكاليف حتى عن طريق جزء صغير ، فلماذا لا؟تطبيق
سيتم إعطاء جميع الخيارات فقط لتهيئة الأجهزة الطرفية ، حيث أنني لم أجد موقفًا حيث سيكون من الضروري لذاكرة الوصول العشوائي. بالمعنى الدقيق للكلمة ، بالنسبة لذاكرة الوصول العشوائي ، الصيغة متشابهة ، فقط قم بتغيير العناوين الأساسية للحساب. إذا كيف يمكنك تنفيذ هذا؟
المجمع
دعنا نذهب من أسفل ، من مجمع بلدي الحبيب.
في مشاريع المجمّع ، أقوم عادةً بتخصيص اثنين من البايتات (وفقًا للتعليمات التي تعمل معهم) RON ضمن # 0 و # 1 للمشروع بأكمله ، واستخدامهما أيضًا في وحدات الماكرو ، مما يقلل مني 2 بايت أخرى بشكل مستمر. ملاحظة ، لم أجد CMSIS في Assembler for STM ، لأنني وضعت رقم البت في الماكرو على الفور ، وليس قيمة التسجيل الخاصة به.
تطبيق مجمع غنو @ . MOVW R0, 0x0000 MOVW R1, 0x0001 @ .macro PeriphBitSet PerReg, BitNum LDR R3, =(BIT_BAND_ALIAS+(((\PerReg) - BIT_BAND_REGION) * 32) + ((\BitNum) * 4)) STR R1, [R3] .endm @ .macro PeriphBitReset PerReg, BitNum LDR R3, =(BIT_BAND_ALIAS+((\PerReg - BIT_BAND_REGION) * 32) + (\BitNum * 4)) STR R0, [R3] .endm
الأمثلة على ذلك:
أمثلة المجمّع PeriphSet TIM2_CCR2, 0 PeriphBitReset USART1_SR, 5
الميزة بلا شك لهذا الخيار هي أن لدينا سيطرة كاملة ، والتي لا يمكن قولها عن الخيارات الإضافية. وكما سيظهر القسم الأخير من المقالة ، فإن هذا القسم مهم
للغاية .
ومع ذلك ، لا يحتاج أي شخص لمشاريع MK في Assembler ، من نهاية الصفر ، مما يعني أنك بحاجة إلى التبديل إلى SI.
سهل ج
بأمانة ، تم العثور على خيار Sishny البسيط من قبلي في بداية المسار ، في مكان ما في الشبكة الواسعة. في ذلك الوقت ، قمت بالفعل بتطبيق نطاقات بت في Assembler ، وتعثرت بطريق الخطأ على ملف C ، لقد نجحت على الفور وقررت عدم اختراع أي شيء.
تنفيذ عادي C #define MASK_TO_BIT31(A) (A==0x80000000)? 31 : 0 #define MASK_TO_BIT30(A) (A==0x40000000)? 30 : MASK_TO_BIT31(A) #define MASK_TO_BIT29(A) (A==0x20000000)? 29 : MASK_TO_BIT30(A) #define MASK_TO_BIT28(A) (A==0x10000000)? 28 : MASK_TO_BIT29(A) #define MASK_TO_BIT27(A) (A==0x08000000)? 27 : MASK_TO_BIT28(A) #define MASK_TO_BIT26(A) (A==0x04000000)? 26 : MASK_TO_BIT27(A) #define MASK_TO_BIT25(A) (A==0x02000000)? 25 : MASK_TO_BIT26(A) #define MASK_TO_BIT24(A) (A==0x01000000)? 24 : MASK_TO_BIT25(A) #define MASK_TO_BIT23(A) (A==0x00800000)? 23 : MASK_TO_BIT24(A) #define MASK_TO_BIT22(A) (A==0x00400000)? 22 : MASK_TO_BIT23(A) #define MASK_TO_BIT21(A) (A==0x00200000)? 21 : MASK_TO_BIT22(A) #define MASK_TO_BIT20(A) (A==0x00100000)? 20 : MASK_TO_BIT21(A) #define MASK_TO_BIT19(A) (A==0x00080000)? 19 : MASK_TO_BIT20(A) #define MASK_TO_BIT18(A) (A==0x00040000)? 18 : MASK_TO_BIT19(A) #define MASK_TO_BIT17(A) (A==0x00020000)? 17 : MASK_TO_BIT18(A) #define MASK_TO_BIT16(A) (A==0x00010000)? 16 : MASK_TO_BIT17(A) #define MASK_TO_BIT15(A) (A==0x00008000)? 15 : MASK_TO_BIT16(A) #define MASK_TO_BIT14(A) (A==0x00004000)? 14 : MASK_TO_BIT15(A) #define MASK_TO_BIT13(A) (A==0x00002000)? 13 : MASK_TO_BIT14(A) #define MASK_TO_BIT12(A) (A==0x00001000)? 12 : MASK_TO_BIT13(A) #define MASK_TO_BIT11(A) (A==0x00000800)? 11 : MASK_TO_BIT12(A) #define MASK_TO_BIT10(A) (A==0x00000400)? 10 : MASK_TO_BIT11(A) #define MASK_TO_BIT09(A) (A==0x00000200)? 9 : MASK_TO_BIT10(A) #define MASK_TO_BIT08(A) (A==0x00000100)? 8 : MASK_TO_BIT09(A) #define MASK_TO_BIT07(A) (A==0x00000080)? 7 : MASK_TO_BIT08(A) #define MASK_TO_BIT06(A) (A==0x00000040)? 6 : MASK_TO_BIT07(A) #define MASK_TO_BIT05(A) (A==0x00000020)? 5 : MASK_TO_BIT06(A) #define MASK_TO_BIT04(A) (A==0x00000010)? 4 : MASK_TO_BIT05(A) #define MASK_TO_BIT03(A) (A==0x00000008)? 3 : MASK_TO_BIT04(A) #define MASK_TO_BIT02(A) (A==0x00000004)? 2 : MASK_TO_BIT03(A) #define MASK_TO_BIT01(A) (A==0x00000002)? 1 : MASK_TO_BIT02(A) #define MASK_TO_BIT(A) (A==0x00000001)? 0 : MASK_TO_BIT01(A) #define BIT_BAND_PER(reg, reg_val) (*(volatile uint32_t*)(PERIPH_BB_BASE+32*((uint32_t)(&(reg))-PERIPH_BASE)+4*((uint32_t)(MASK_TO_BIT(reg_val)))))
كما ترون ، قطعة بسيطة ومباشرة من التعليمات البرمجية مكتوبة بلغة المعالج. العمل الرئيسي هنا هو ترجمة قيم CMSIS إلى رقم بت ، والذي كان غائبًا عن الحاجة إلى إصدار المجمّع.
نعم ، استخدم هذا الخيار مثل هذا:
أمثلة على C عادي BIT_BAND_PER(GPIOB->MODER, GPIO_MODER_MODER0_0) = 0;
ومع ذلك ، فإن الاتجاهات الحديثة (على نطاق واسع ، وفقًا لملاحظاتي ، تقريبًا من عام 2015) تؤيد استبدال C بـ C ++ حتى بالنسبة إلى MK. وحدات الماكرو ليست الأداة الأكثر موثوقية ، لذلك كان من المفترض أن يولد الإصدار التالي.
Cpp03
هنا ، مثيرة للاهتمام للغاية ومناقشتها ، ولكن القليل من الاستخدام في ضوء تعقيدها ، مع أحد الأمثلة المخترقة من عامل ، يأتي الأداة metaprogramming.
بعد كل شيء ، تعتبر مهمة ترجمة قيمة المتغير إلى رقم بت مثالية (توجد بالفعل قيم في CMSIS) ، وفي هذه الحالة تكون عملية للتجميع الزمني.
لقد نفذت هذا على النحو التالي باستخدام القوالب:
تطبيق لـ C ++ 03 template<uint32_t val, uint32_t comp_val, uint32_t cur_bit_num> struct bit_num_from_value { enum { bit_num = (val == comp_val) ? cur_bit_num : bit_num_from_value<val, 2 * comp_val, cur_bit_num + 1>::bit_num }; }; template<uint32_t val> struct bit_num_from_value<val, static_cast<uint32_t>(0x80000000), static_cast<uint32_t>(31)> { enum { bit_num = 31 }; }; #define BIT_BAND_PER(reg, reg_val) *(reinterpret_cast<volatile uint32_t *>(PERIPH_BB_BASE + 32 * (reinterpret_cast<uint32_t>(&(reg)) - PERIPH_BASE) + 4 * (bit_num_from_value<static_cast<uint32_t>(reg_val), static_cast<uint32_t>(0x01), static_cast<uint32_t>(0)>::bit_num)))
يمكنك استخدامه بنفس الطريقة:
أمثلة على C ++ 03 BIT_BAND_PER(GPIOB->MODER, GPIO_MODER_MODER0_0) = false;
ولماذا غادر الماكرو؟ الحقيقة هي أنني لا أعرف طريقة أخرى لإدراج هذه العملية بشكل مضمون دون الانتقال إلى منطقة أخرى من رمز البرنامج. سأكون سعيدًا جدًا إذا دفعوني إلى التعليقات. لا القوالب أو الدالات المضمنة توفر مثل هذا الضمان. نعم ، والماكرو هنا يتكيف مع مهمته بشكل جيد ، لا فائدة من تغييرها لمجرد أن الشخص
المطابق يعتبر ذلك "غير آمن".
من المثير للدهشة أن الوقت لم يهدأ بعد ، دعم المترجمون بشكل متزايد C ++ 14 / C ++ 17 ، لماذا لا تستفيد من الابتكارات ، مما يجعل الكود أكثر قابلية للفهم.
Cpp14 / cpp17
تنفيذ لـ C ++ 14 constexpr uint32_t bit_num_from_value_cpp14(uint32_t val, uint32_t comp_val, uint32_t bit_num) { return bit_num = (val == comp_val) ? bit_num : bit_num_from_value_cpp14(val, 2 * comp_val, bit_num + 1); } #define BIT_BAND_PER(reg, reg_val) *(reinterpret_cast<volatile uint32_t *>(PERIPH_BB_BASE + 32 * (reinterpret_cast<uint32_t>(&(reg)) - PERIPH_BASE) + 4 * (bit_num_from_value_cpp14(static_cast<uint32_t>(reg_val), static_cast<uint32_t>(0x01), static_cast<uint32_t>(0)))))
كما ترون ، لقد استبدلت للتو القوالب بوظيفة constexpr متكررة ، والتي ، في رأيي ، أكثر وضوحًا للعين البشرية.
استخدم بنفس الطريقة. بالمناسبة ، في C ++ 17 ، من الناحية النظرية ، يمكنك استخدام وظيفة lexda constexpr العودية ، لكنني لست متأكدًا من أن هذا سيؤدي إلى بعض التبسيط على الأقل ، ولن يؤدي أيضًا إلى تعقيد ترتيب المجمع.
باختصار ، تقدم تطبيقات C / Cpp الثلاثة مجموعة من التعليمات الصحيحة على قدم المساواة ، وفقًا لقسم النظرية. لقد كنت أعمل مع جميع التطبيقات على IAR ARM 8.30 و gcc 7.2.0 لفترة طويلة.الممارسة هي العاهرة
هذا كل شيء ، على ما يبدو ، حدث. تم حساب التوفير في الذاكرة ، وتم اختيار التطبيق ، وعلى استعداد لتحسين الأداء. ليس هنا ، لقد كانت مجرد حالة من اختلاف النظرية والممارسة. ومتى كانت مختلفة؟
لم أكن لأنشرها أبداً إذا لم أقم باختبارها ، لكن كم هو واقعي يتم تقليل الحجم المشغول على المشروعات. أنا على وجه التحديد في اثنين من المشاريع القديمة استبدال هذا الماكرو مع تنفيذ منتظم دون قناع ، ونظرت إلى الفرق. النتيجة فاجأت غير سارة.
كما اتضح ، يظل مستوى الصوت بدون تغيير تقريبًا. لقد اخترت على وجه التحديد المشروعات التي تم استخدام 40 إلى 50 منها بالضبط. وفقًا للنظرية ، كان علي حفظ ما لا يقل عن 100 بايت ، وعلى الأكثر 200. في الممارسة العملية ، تبين أن الفرق كان 24 - 32 بايت. لكن لماذا؟
عادة ، عندما تقوم بإعداد الأجهزة الطرفية ، تقوم بإعداد 5-10 سجلات تقريبًا على التوالي. وعلى مستوى عالٍ من التحسين ، لا يقوم المترجم بترتيب التعليمات وفقًا لترتيب السجلات تمامًا ، ولكنه يرتب التعليمات كما تبدو صحيحة ، وأحيانًا تتداخل معها في أماكن لا يمكن فصلها على ما يبدو.
أرى خيارين (فيما يلي تكهناتي):
- أو المحول البرمجي ذكي جدًا لدرجة أنه يعلمك كيف سيكون من الأفضل تحسين مجموعة التعليمات
- أو لا يزال المترجم ليس أكثر ذكاءً من الشخص ، ويخلط نفسه عندما يواجه مثل هذه الإنشاءات
بمعنى ، اتضح أن هذه الطريقة باللغات "عالية المستوى" بمستوى عالٍ من التحسين لا تعمل بشكل صحيح إلا في حالة عدم وجود عمليات مماثلة على مقربة من إحدى هذه العمليات.
بالمناسبة ، على مستوى O0 ، تتقارب النظرية والتطبيق في أي حال ، لكنني لست مهتمًا بهذا المستوى من التحسين.
أنا ألخص
نتيجة سلبية هي أيضا نتيجة. أعتقد أن الجميع سوف يستخلص النتائج لنفسه. شخصيا ، سأستمر في استخدام هذه التقنية ، وبالتأكيد لن تكون أسوأ من ذلك.
آمل أن يكون الأمر ممتعًا وأريد أن أعبر عن الاحترام الكبير لأولئك الذين قرأوا حتى النهاية.
قائمة الأدب
- "دليل المرجع الفني لـ Cortex-M3" ، القسم 4.2 ، ARM 2005.
- الدليل النهائي ل ARM Cortex-M3 ، جوزيف ييو.
ملاحظة: لدي في حقيبتي تغطية صغيرة من الموضوعات المتعلقة بتطوير الإلكترونيات المدمجة. اسمحوا لي أن أعرف ، إذا كانت مهتمة ، وسوف تحصل عليها ببطء.
PPS بطريقة أو بأخرى ، لقد تحولت بطريقة ملتوية لإدراج مقاطع من الكود ، من فضلك قل لي كيف تتحسن ، إن أمكن. بشكل عام ، يمكنك نسخ جزء من رمز الاهتمام لتدوين المفكرة وتجنب المشاعر غير السارة في التحليل.
UPD:
بناءً على طلب القراء ، أشير إلى أن عملية ربط النطاقات نفسها عملية ذرية ، مما يمنحنا بعض الأمان عند العمل مع السجلات. هذه هي واحدة من أهم ميزات هذه الطريقة.