مكتبة مولد جامع التعليمات البرمجية للميكروكونترولر AVR. الجزء 2

← الجزء 1. التعارف الأول


الجزء 3. معالجة غير مباشرة والتحكم في التدفق →


مكتبة مولد المجمع كود ل AVR Microcontrollers


الجزء 2. الابتداء


كما هو مخطط له ، في هذا الجزء سننظر بمزيد من التفصيل في ميزات البرمجة باستخدام مكتبة NanoRTOS. يمكن لأولئك الذين بدأوا القراءة من هذا المنشور التعرف على الوصف العام وقدرات المكتبة في المقالة السابقة . نظرًا للنطاق المحدود للنشر المخطط ، من المفترض أن يكون القارئ المحترم على الأقل على دراية بالبرمجة C # ، ولديه أيضًا فهم الهندسة المعمارية والبرمجة بلغة التجميع لوحدات تحكم AVR من سلسلة Mega.


من الأفضل دمج دراسة أي تقنية مع تنفيذ الأمثلة ، لذلك أقترح تنزيل المكتبة نفسها من https://drive.google.com/open؟id=1FfBBpxlJkWC027ikYpn6NXbOGp7DS-5B ومحاولة توصيله بمشروع تطبيق وحدة التحكم. إذا نجحت ، يمكنك التحرك بأمان. يمكنك استخدام أي تطبيق C أو مشروع UnitTest كقذيفة لتنفيذ الأمثلة. أنا شخصياً أحب هذا الأخير ، لأنه يتيح لك تخزين عدة أمثلة مختلفة في مكان واحد وتنفيذها حسب الحاجة. في أي حال ، سيتم إدراج نص المثال فقط في القوائم لتوفير مساحة.


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


var m = new Mega328(); 

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


  m.FCLK = 16000000; m.CKDIV8 = false; 

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


 var m = new Mega328(); //     //      //          var t = AVRASM.Text(m); //    t 

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


لقد تعلمنا بالفعل كيفية إنشاء برنامج فارغ. الآن دعونا نحاول إنشاء بعض الرموز فيه. لنبدأ مع الأكثر بدائية. دعونا نرى كيف يمكننا حل مشكلة الزيادة لمتغير ذي ثمانية بتات موجود في خلية RON تعسفية. من وجهة نظر المجمّع ، هذا هو الأمر inc [register] . أدخل الأسطر التالية في نص برنامج الشراء الخاص بنا


  var r = m.REG(); r++; 

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


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 inc R0000 .DSEG 

دعونا نلقي نظرة فاحصة على ما حدث نتيجة لذلك. الأوامر الأربعة الأولى هي تهيئة مؤشر المكدس. التالي هو تعريف اسم المتغير. وأخيرا ، لدينا شركة ، والتي بدأ كل شيء. لا شيء إضافي ، باستثناء تهيئة مكدس لم تظهر. السؤال الذي قد ينشأ عند النظر إلى هذا الرمز هو أي نوع من R0000 ؟ لدينا متغير اسمه ص ؟ في برنامج C # ، يمكن للمبرمج استخدام الأسماء نفسها بوعي وقانون ، باستخدام النطاق. من وجهة نظر المجمّع ، يجب أن تكون جميع التسميات والتعريفات فريدة من نوعها. من أجل عدم إجبار المبرمج على مراقبة تفرد الأسماء ، يتم إنشاء الأسماء افتراضيًا بواسطة النظام. ومع ذلك ، فهناك موقف ، حيث لا يزال يتعين عليك ، لأغراض التصحيح ، نقل الاسم الواعي من البرنامج إلى رمز الإخراج ، بحيث يمكن العثور عليه بسهولة. ليس مخيفا. استبدل m.REG () بـ m.REG ("r") وقم بتشغيل الكود مرة أخرى. نتيجة لذلك ، سوف نرى ما يلي


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r20 inc r .DSEG 

لذلك ، مع تسمية السجلات تسويتها. الآن دعنا نرى لماذا بدأت السجلات فجأة يتم تعيينها من 20 ، وليس من 0؟ للإجابة على هذا السؤال ، نذكر أنه ابتداءً من 16 عامًا ، تتمتع السجلات بفرصة كبيرة لتهيئتها باستخدام الثوابت. وبما أن هذه الميزة مطلوبة بشدة ، فإننا نبدأ التوزيع من النصف العلوي لزيادة فرص التحسين. ثم كل نفس ليس من الواضح - لماذا C20 وليس 16؟ والسبب هو أن ترجمة عدد من الأوامر إلى رمز المجمّع أمر مستحيل دون استخدام خلايا تخزين مؤقتة. لهذه الأغراض ، قمنا بتخصيص 4 خلايا من 16 إلى 19. هذا لا يعني أنها أصبحت غير قابلة للوصول تمامًا للمبرمج. إن تنظيم الوصول إليهم يتم تنظيمه بشكل مختلف بعض الشيء بحيث يدرك المبرمج القيود المحتملة لاستخدامه ويتصرف بوعي. نزيل تعريف السجل r من الرمز ونستبدل السطر الذي يليه
m.TempL ++ ؛
لنلقِ نظرة على النتيجة


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 inc TempL .DSEG 

هنا ، على ما يبدو ، تجدر الإشارة إلى أن مجمّع الإخراج يتطلب التفسير الصحيح لملف الاتصال مع التعريفات ووحدات الماكرو Common.inc من حزمة التطوير. يحتوي بالفعل على جميع وحدات الماكرو والتعريفات الضرورية ، بما في ذلك مطابقة الاسم لخلايا التخزين المؤقتة. وهي TempL = r16 و TempH = r17 و TempQL = r18 و TempQH = r19. في هذه الحالة ، لم نستخدم أي أوامر من شأنها استخدام خلايا تخزين مؤقتة للعمل ، لذا فإن قرارنا باستخدامها في عملية TempL مقبول تمامًا. وما الذي يجب أن نفعله إذا كنا متأكدين تمامًا من أنه لا يوجد تأجيل لثوابت لمتغيرنا ولا نريد إنفاق خلايا ثمينة من النصف العلوي عليها؟ العودة تعريفنا إلى شفرة المصدر عن طريق تغييره إلى var r = m.REGL ("r") ؛ وتقييم نتيجة العمل


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r4 inc r .DSEG 

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


  var m = new Mega328(); var r = m.REGL("r"); r++; var rr = m.REGL("rr"); rr--; var rrr = m.REGL("rrr"); rrr.Clear(); var t = AVRASM.Text(m);  . RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r4 inc r .DEF rr = r5 dec rr .DEF rrr = r6 clr rrr .DSEG 

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


  var m = new Mega328(); var r = m.REGL("r"); r++; var rr = m.REGL("rr"); rr--; r.Dispose(); var rrr = m.REGL("rrr"); rrr.Clear(); var t = AVRASM.Text(m); 

 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF r = r4 inc r .DEF rr = r5 dec rr .UNDEF r .DEF rrr = r4 clr rrr .DSEG 

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


أثناء تحليل الأمثلة ، أظهرنا بالفعل على طول الطريق كيفية إجراء عمليات الإرسال الأحادي على السجلات. الآن دعونا نرى كيف هي الأمور مع البث المتعدد. تسمح بنية المعالج بحد أقصى من الإرشادات التي تحتوي على عنوانين (خاصة الإرشادات المسببة للتآكل ، باستثناء إثنين من الإرشادات التي وضعت النتيجة في سجلات ثابتة). يجب فهم ذلك بطريقة تحتوي على المعامل الأول في العملية بعد تنفيذه النتيجة. بناء الجملة الخاص [register1] [العملية] = [register2] يتم توفيره لهذا النوع من العمليات. دعونا نرى كيف يبدو في الممارسة العملية. دعنا نحاول الإعلان وإضافة متغيرين للتسجيل.


  var m = new Mega328(); var op1 = m.REG(); var op2 = m.REG(); op1 += op2; var t = AVRASM.Text(m); 

نتيجة لذلك ، سوف نرى


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 .DEF R0001 = r21 add R0000,R0001 .DSEG 

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


  var m = new Mega328(); var op1 = m.REG(); var op2 = m.REG(); op1.Load(0x10); op2.Load('s'); op1.Load(op2); var t = AVRASM.Text(m); 

هنا أعلنا وتهيأ متغيرين بالرقم والرمز ، ثم قمنا بنسخ قيمة المتغير op2 في الخلية op1. من الواضح أن الرقم يجب أن يقع في حدود 0-255 حتى لا يحدث أي خطأ. ستكون النتيجة


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 .DEF R0001 = r21 ldi R0000,16 ldi R0001,'s' mov R0000,R0001 .DSEG 

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


  var m = new Mega328(); var op1 = m.REGL(); var op2 = m.REGL(); op1.Load(0x10); op2.Load('s'); op1.Load(op2); var t = AVRASM.Text(m); 

نحن نحصل عليها


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r4 .DEF R0001 = r5 ldi TempL,16 mov R0000,TempL ldi TempL,'s' mov R0001,TempL mov R0000,R0001 .DSEG 

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


  var m = new Mega328(); var op1 = m.REG(); op1.Load(0x10); op1 -= 10; var t = AVRASM.Text(m); 

ستكون النتيجة


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 ldi R0000,16 subi R0000,0x0A .DSEG 

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


  var m = new Mega328(); var op1 = m.REG(); op1.Load(0x10); op1 += 10; var t = AVRASM.Text(m); 

 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 ldi R0000,16 subi R0000,0xF6 .DSEG 

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


  var m = new Mega328(); var op1 = m.REG(); op1.Load(0x10); op1 >>= 5; var t = AVRASM.Text(m); 

ستكون النتيجة


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DEF R0000 = r20 ldi R0000,16 swap R0000 andi R0000,15 lsr R0000 .DSEG 

ليس كل شيء واضح هنا ، لكنه يدير فريقين بشكل أسرع مما لو تم استخدام حل أمامي لفرق العمل الخمسة.


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

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


All Articles