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

← الجزء 2. البدء
الجزء 4. برمجة الأجهزة الطرفية والتعامل مع المقاطعات →


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


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


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


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


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


var m = new Mega328(); var dr1 = m.DREG(); var dr2 = m.DREG(); dr1.Load(0xAA55); dr2.Load(0x55AA); dr1++; dr1--; dr1 += 0x100; dr1 += dr2; dr2 *= dr1; dr2 /= dr1; var t = AVRASM.Text(m); 

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


 var m = new Mega328(); var dr1 = m.DREG(); dr1.Load(0xAA55); dr1.Low--; dr1.High += dr1.Low; var t = AVRASM.Text(m); 

كما ترون من المثال ، يمكننا العمل مع High and Low كمتغيرات تسجيل مستقلة ، بما في ذلك إجراء عمليات حسابية ومنطقية مختلفة بينها.


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


 var m = new Mega328(); var bt = m.BYTE(); //8-    var wd = m.WORD(); //16-    var arr = m.ARRAY(16); //  16  var t = AVRASM.Text(m); 

دعونا نرى ما حدث.


 RESET: ldi r16, high(RAMEND) out SPH,r16 ldi r16, low(RAMEND) out SPL,r16 .DSEG L0002: .BYTE 16 L0001: .BYTE 2 L0000: .BYTE 1 

في قسم تعريف البيانات ، لدينا تخصيص الذاكرة. لاحظ أن ترتيب التخصيص يختلف عن إعلان المتغيرات. هذه ليست صدفة. يحدث تخصيص الذاكرة للمتغيرات بعد الفرز بالترتيب التنازلي وفقًا للمعايير التالية (بترتيب الأهمية التنازلي) الحد الأقصى للقسمة مضاعف الدرجة 2 → حجم الذاكرة المخصصة. هذا يعني أننا إذا أردنا تخصيص 4 صفائف بحجم 64 و 48 و 40 و 16 بايت ، فإن ترتيب التخصيص ، بغض النظر عن ترتيب الإعلان ، سيبدو كما يلي:


الطول 64 - الحد الأقصى لمضاعفات المقسوم على الدرجة 2 = 64
الطول 48 - الحد الأقصى لمضاعفات المقسوم على الدرجة 2 = 16
طول 16 - الحد الأقصى المقسوم مضاعف درجة 2 = 16
الطول 40 - الحد الأقصى لمضاعفات المقسوم على الدرجة 2 = 8
يتم ذلك من أجل تبسيط التحكم في حدود الصفيف.
وتقليل حجم الكود في العمليات مع المؤشرات. لا يمكننا إجراء أي عمليات مباشرة بمتغيرات في الذاكرة ، وبالتالي كل ما هو متاح لنا هو القراءة / الكتابة لتسجيل المتغيرات. إن أبسط طريقة للعمل مع المتغيرات في الذاكرة هي عنونة مباشرة.


 var m = new Mega328(); var bt = m.BYTE(); // 8-    var rr = m.REG(); //    rr.Load(0xAA); //  rr   0xAA rr.Mstore(bt); //     rr.Clear(); //  rr.Mload(bt); //    var t = AVRASM.Text(m); 

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


للعمل مع عناصر الصفيف ، نستخدم بناء الجملة التالي


 var rr = m.REG(); var arr = m.ARRAY(10); rr.MLoad(arr[5]); 

يبدأ ترقيم العناصر في المصفوفة بـ 0. وهكذا ، في المثال أعلاه ، تتم كتابة القيمة 6 لعنصر المصفوفة إلى rr للخلية.


الآن يمكنك الذهاب إلى معالجة غير مباشرة. تحتوي المكتبة على نوع البيانات الخاص بها لمؤشر إلى مساحة ذاكرة RAM - MEMPtr . دعونا نرى كيف يمكننا استخدامها. نقوم بتعديل مثالنا السابق حتى يتم تنفيذ العمل مع المتغير في الذاكرة من خلال المؤشر.


 var m = new Mega328(); var bt1 = m.BYTE(); var bt2 = m.BYTE(); var rr = m.REG(); var ptr = m.MEMPTR(); //  ptr ptr.Load(bt1); //ptr   bt1 rr.Load(0xAA); // rr - 0xAA ptr.MStore(rr); //  bt1 0xAA rr.Load(0x55); // rr - 0x55 ptr.Load(bt2); //ptr   bt2 ptr.MStore(rr); //  bt2 0x55 ptr.Load(bt1); //ptr   bt1 ptr.MLoad(rr); //  rr  0xAA var t = AVRASM.Text(m); 

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


 var m = new Mega328(); var bt1 = m.ARRAY(4); //   4  var rr = m.REG(); var ptr = m.MEMPTR(); ptr.Load(bt1.Label); //ptr   bt1 rr.Load(0xAA); // rr - 0xAA ptr.MStoreInc(rr); //  bt1 0xAA ptr.MStoreInc(rr); //  bt1+1 0xAA ptr.MStoreInc(rr); //  bt1+2 0xAA ptr.MStoreInc(rr); //  bt1+3 0xAA rr.Clear(); rr.MLoad(bt1[2]); //  rr 3-   var t = AVRASM.Text(m); 

في هذا المثال ، استفدنا من القدرة على زيادة مؤشر عند الكتابة إلى الذاكرة.
بعد ذلك ، ننتقل إلى قدرة المكتبة على التحكم في تدفق الأوامر. إذا كان الأمر أسهل ، فكيف يمكنك برمجة القفزات والحلقات الشرطية وغير المشروطة باستخدام المكتبة. أسهل طريقة لإدارة ذلك هي استخدام أوامر التنقل الخاصة بالتسمية. يتم الإعلان عن التصنيفات في برنامج بطريقتين مختلفتين. الأول هو أنه مع فريق AVRASM.Label ، نقوم بإنشاء ملصق للاستخدام في المستقبل ، ولكن لا ندخله في رمز البرنامج. تُستخدم هذه الطريقة لإنشاء قفزات للأمام ، أي في الحالات التي يجب أن يسبق فيها الأمر قفزة التسمية. لتعيين التسمية في المكان المطلوب لرمز المجمّع ، يجب تشغيل الأمر AVRASM.newLabel ([متغير التسمية المنشأة مسبقًا]) . للعودة ، يمكنك استخدام بناء جملة أبسط عن طريق تعيين تسمية وتعيين قيمتها إلى متغير مع أمر واحد AVRASM.newLabel () بدون معلمات.


أبسط نوع من الانتقال هو انتقال غير مشروط. للاتصال به ، نستخدم الأمر GO ([jump_mark]] . دعونا نرى كيف يبدو مع مثال.


 var m = new Mega328(); var r = m.REG(); //  var lbl1 = AVRASM.Label;//        m.GO(lbl1); r++; //    r++; AVRASM.NewLabel(lbl1);//  //  var lbl2 = AVRASM.NewLabel();//    r--; //    r--; m.GO(lbl2); var t = AVRASM.Text(m); 

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


 var m = new Mega328(); var rr1 = m.REG(); var rr2 = m.REG(); rr1.Load(0x22); rr2.Load(0x33); m.IF(rr1 == rr2, () => { AVRASM.Comment(" - ,  "); }); var t = AVRASM.Text(m); 

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


 var m = new Mega328(); var rr1 = m.REG(); var rr2 = m.REG(); rr1.Load(0x22); rr2.Load(0x33); m.IF(rr1 == rr2, () => { AVRASM.Comment(" - ,  "); },()=> { AVRASM.Comment(" - ,   "); }); AVRASM.Comment(" "); 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 ldi R0000,34 ldi R0001,51 cp R0000,R0001 brne L0002 ;---  - ,   --- xjmp L0004 L0002: ;---  - ,    --- L0004: ;---   --- .DSEG 

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


 var m = new Mega328(); var rr1 = m.REG(); rr1.Load(0x22); rr1--; m.IFEMPTY(() =>AVRASM.Comment(",    0")); var t = AVRASM.Text(m); 

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


 LOOP(Register iter, Action<Register, string> Condition, Action<Register, string> body) 

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


 m.LOOP(m.TempL, (r, l) => m.GO(l), (r,l) => { }); 

وترد نتيجة التجميع أدناه.


 L0002: xjmp L0002 

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


 var m = new Mega328(); var rr1 = m.REG(); var rr2 = m.REG(); var arr = m.ARRAY(16); var ptr = m.MEMPTR(); ptr.Load(arr[0]); //     rr2.Load(16); //    rr1.Load(0xAA); //   m.LOOP(rr2, (r, l) => //rr2     . { r--; //   m.IFNOTEMPTY(l); // ,   }, (r,l) => ptr.MStoreInc(rr1)); //   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 ldi YL, LOW(L0002+0) ldi YH, HIGH(L0002+0) ldi R0001,16 ldi R0000,170 L0003: st Y+,R0000 dec R0001 brne L0003 L0004: .DSEG L0002: .BYTE 16 

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


 var m = new Mega328(); var block1 = AVRASM.Label; var block2 = AVRASM.Label; var block3 = AVRASM.Label; var ptr = m.ROMPTR(); ptr.Load(block1); //     var loop = AVRASM.NewLabel(); AVRASM.Comment("   "); m.GOIndirect(ptr); //   ,     AVRASM.NewLabel(block1); AVRASM.Comment("  1"); ptr.Load(block2); m.GO(loop); AVRASM.NewLabel(block2); AVRASM.Comment("  2"); ptr.Load(block3); m.GO(loop); AVRASM.NewLabel(block3); AVRASM.Comment("  3"); ptr.Load(block1); m.GO(loop); var t = AVRASM.Text(m); 

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


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


 var m = new Mega328(); var block1 = AVRASM.Label; var block2 = AVRASM.Label; var block3 = AVRASM.Label; var arr = m.ARRAY(6); var ptr = m.MEMPTR(); //    m.Temp.Load(block1); m.Temp.Store(arr[0]); m.Temp.Load(block2); m.Temp.Store(arr[2]); m.Temp.Load(block3); m.Temp.Store(arr[4]); ptr.Load(arr[0]); //     var loop = AVRASM.NewLabel(); m.SWITCH(ptr); //   ,     AVRASM.NewLabel(block1); AVRASM.Comment("  1"); ptr.Load(arr[2]); //       m.GO(loop); AVRASM.NewLabel(block2); AVRASM.Comment("  2"); m.Temp.Load(block3); ptr.MStore(m.Temp); //       m.GO(loop); AVRASM.NewLabel(block3); AVRASM.Comment("  3"); ptr.Load(arr[0]); //       m.GO(loop); 

في هذا المثال ، سيكون تسلسل الانتقال كالتالي: Block1 → Block2 → Block3 → Block1 → Block3 → Block1 → Block3 → Block1 ... تمكنا من تنفيذ خوارزمية يتم فيها تنفيذ أوامر Block2 فقط في الدورة الأولى.


في الجزء التالي من المنشور ، سننظر في العمل مع الأجهزة الطرفية ، وتنفيذ المقاطعات ، والروتين ، وأكثر من ذلك بكثير.

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


All Articles