32 كيلو عتبة للبيانات في AVR ميكروكنترولر ROM

ماذا يمكن أن يكون أسوأ من العكازات؟ فقط العكازات موثقة بشكل غير كامل.


الصورة


فيما يلي لقطة من أحدث بيئة تطوير متكاملة رسمية لأجهزة التحكم AVR ذات 8 بتات ، Atmel Studio 7 ، لغة البرمجة C. كما ترى من عمود القيمة ، يحتوي المتغير my_array على الرقم 0x8089. بمعنى آخر ، يوجد صفيف my_array في الذاكرة بدءًا من العنوان 0x8089.


في الوقت نفسه ، يعطينا عمود النوع معلومات مختلفة قليلاً: my_array عبارة عن مجموعة مكونة من 4 عناصر من النوع int16_t موجودة في ROM (يشار إليها بواسطة كلمة prog ، بخلاف بيانات RAM) ، بدءًا من العنوان 0x18089. توقف ، ولكن بعد كل شيء 0x8089! = 0x18089. ما هو العنوان الفعلي للصفيف؟


لغة C والهندسة المعمارية هارفارد


تحظى وحدات التحكم في AVR ذات 8 بتات المصنعة سابقًا بواسطة Atmel ، والآن Microchip ، بشعبية خاصة بسبب كونها أساس Arduino ، المبنية على هندسة Harvard ، أي أن الكود والبيانات موجودة في مساحات عناوين مختلفة. تحتوي الوثائق الرسمية على أمثلة التعليمات البرمجية بلغتين: المجمّع و C. في السابق ، قدمت الشركة المصنعة بيئة تطوير متكاملة مجانية تدعم المجمّع فقط. ولكن ماذا عن أولئك الذين يرغبون في البرمجة في C ، أو حتى C ++؟ كانت هناك حلول مدفوعة ، على سبيل المثال ، IAR AVR و CodeVisionAVR. أنا شخصياً لم أستخدمها أبدًا ، لأنه عندما بدأت برمجة AVR في عام 2008 ، كان هناك بالفعل WinAVR مجاني مع القدرة على الاندماج مع AVR Studio 4 ، ويتم تضمينه ببساطة في Atmel Studio 7 الحالي.


يعتمد مشروع WinAVR على مترجم GNU GCC ، والذي تم تطويره لهندسة von Neumann ، والتي تتضمن مساحة عنوان واحدة للرمز والبيانات. عند تكييف GCC to AVR ، تم تطبيق العكاز التالي: يتم تخصيص العناوين من 0 إلى 0x007fffff للرمز (ROM ، flash) ، و 0x00800100 لـ 0x0080ffff للبيانات (RAM ، SRAM). كانت هناك كل أنواع الحيل الأخرى ، على سبيل المثال ، عناوين من 0x00800000 إلى 0x008000ff سجلات ممثلة يمكن الوصول إليها بواسطة نفس رموز التشغيل مثل RAM. من حيث المبدأ ، إذا كنت مبرمجًا بسيطًا ، مثل برنامج arduino للمبتدئين ، وليس متسللًا ومختلطًا و C / C ++ في نفس البرنامج الثابت ، فلن تحتاج إلى معرفة كل هذا.


بالإضافة إلى المترجم ، يتضمن WinAVR مكتبات متعددة (جزء من مكتبة C القياسية ووحدات AVR الخاصة) في شكل مشروع AVR Libc. تم إصدار أحدث إصدار ، 2.0.0 ، منذ ما يقرب من ثلاث سنوات ، وتتوفر الوثائق ليس فقط على موقع المشروع نفسه ، ولكن أيضًا على موقع الشركة المصنعة للتحكم الدقيق. هناك أيضا ترجمات روسية غير رسمية.


البيانات في مساحة العنوان للرمز


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


unsigned char array2d[2][3] = {...}; unsigned char element = array2d[i][j]; 

و ROM مثل هذا:


 #include <avr/pgmspace.h> const unsigned char array2d[2][3] PROGMEM = {...}; unsigned char element = pgm_read_byte(&(array2d[i][j])); 

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


إذن ما المشكلة؟


هل تذكر أن عبارة 640 كيلو بايت كافية للجميع؟ تذكر كيف تحولت من بنية 16 بت إلى 32 بت ، ومن 32 بت إلى 64 بت؟ كيف كان نظام التشغيل Windows 98 يعمل بشكل غير مستقر على أكثر من 512 ميغابايت من ذاكرة الوصول العشوائي بينما تم تصميمه ل 2 GB؟ هل قمت بتحديث نظام الإدخال والإخراج الأساسي (BIOS) من أي وقت مضى حتى تعمل اللوحة الأم مع محركات أقراص ثابتة أكبر من 8 جيجابايت؟ تذكر لاعبا على 80 غيغابايت من محركات الأقراص الصلبة ، وخفض حجمها إلى 32 جيجابايت؟


تخطتني المشكلة الأولى عندما حاولت إنشاء صفيف لا يقل عن 32 كيلو بايت في ROM. لماذا في ROM ، وليس في ذاكرة الوصول العشوائي؟ لأنه في الوقت الحاضر ، AVRs 8 بت مع أكثر من 32 كيلو بايت من ذاكرة الوصول العشوائي ببساطة غير موجودة. ومع أكثر من 256 ب - موجودة. ربما هذا هو السبب في اختيار منشئي المحول البرمجي 16 ب (2 ب) للمؤشرات في ذاكرة الوصول العشوائي (وفي الوقت نفسه لنوع int) ، والذي يمكن العثور عليه في قراءة فقرة أنواع البيانات الموجودة في الفصل 11.14 ما السجلات التي يستخدمها المترجم C؟ وثائق AVR Libc. أوه ، ونحن لن نخرق ، ولكن هنا هي السجلات ... لكن العودة إلى الصفيف. اتضح أنه لا يمكنك إنشاء كائن أكبر من 32767 B (2 ^ (16 - 1) - 1 B). لا أعرف لماذا كان من الضروري جعل طول الكائن كبيرًا ، ولكن هذه حقيقة: لا يمكن لأي كائن ، حتى صفيف متعدد الأبعاد ، أن يبلغ طوله 32،768 B أو أكثر. يشبه إلى حد ما وجود قيود على مساحة العنوان للتطبيقات ذات 32 بت (4 جيجابايت) في نظام التشغيل 64 بت ، أليس كذلك؟


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


ننتقل مرة أخرى إلى الفقرة أنواع البيانات: المؤشرات هي 16 بت. نحن نطبق هذه المعرفة على الفصل 5 من البيانات في مساحة البرنامج. لا ، النظرية لا غنى عنها ؛ هناك حاجة إلى الممارسة. كتبت برنامج اختبار ، وأطلقت مصحح أخطاء (لسوء الحظ ، برنامج ، وليس أجهزة) ورأيت أن وظيفة pgm_read_byte قادرة على إرجاع البيانات فقط التي تناسب عناوينها 16 بت (64 كيلو بايت ؛ شكرًا ، وليس 15). ثم يحدث تجاوز السعة ، يتم تجاهل الجزء الأقدم. إنه منطقي ، بالنظر إلى أن المؤشرات 16 بت. ولكن يثور سؤالان: لماذا لم يتم كتابة ذلك في الفصل 5 (سؤال بلاغي ، ولكن كان هو الذي دفعني إلى كتابة هذه المقالة) وكيفية التغلب على الحد الأقصى البالغ 64 كيلوبايت من ذاكرة ROM دون التبديل إلى أداة التجميع.


لحسن الحظ ، بالإضافة إلى الفصل 5 ، هناك 25.18 pgmspace.h ملف مرجعي آخر ، نتعلم منه أن مجموعة pgm_read_* هي مجرد pgm_read_*_near لـ pgm_read_*_near ، والتي تقبل عناوين 16 بت ، وهناك أيضًا pgm_read_*_far ، ويمكنك إرسال عنوان 32 بت يوريكا!


نكتب الرمز:


 unsigned char element = pgm_read_byte_far(&(array2d[i][j])); 

إنه يجمع ، لكنه لا يعمل كما نود (إذا كان array2d يقع بعد 32 كيلو بايت). لماذا؟ نعم ، لأن & العملية تقوم بإرجاع رقم 16 بت موقّع! من المضحك أن pgm_read_*_near العائلة عناوين 16 بت غير موقعة ، أي أنه قادر على العمل مع 64 كيلوبايت من البيانات ، وأن & العملية مفيدة فقط لـ 32 كيلوبايت.


دعنا ننتقل. ماذا لدينا في pgmspace.h إلى جانب pgm_read_* ؟ pgm_get_far_address(var) ، التي لديها بالفعل نصف صفحة من الوصف ، وتحل محل & العملية.


صحيح ربما:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d[i][j])); 

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


نضع عكازًا آخر: ننتقل من مؤشرات الصفيف إلى حساب المؤشر:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d) + i*3*sizeof(unsigned char) + j*sizeof(unsigned char)); 

الآن كل شيء يعمل.


الاستنتاجات


إذا كتبت في C / C ++ لـ Microcontrollers 8 بت AVR باستخدام برنامج التحويل البرمجي GCC وقم بتخزين البيانات في ROM ، ثم:


  • بحجم ROM لا يزيد عن 32 كيلو بايت ، فلن تواجه مشكلات من خلال قراءة بيانات الفصل 5 فقط في مساحة البرنامج ؛
  • بالنسبة إلى ROM أكبر من 32 كيلو بايت ، يجب استخدام pgm_read_*_far مجموعة الوظائف pgm_read_*_far ، pgm_get_far_address بدلاً من & ، مؤشر حساب بدلاً من مؤشرات الصفيف ، ولا يمكن أن يتجاوز حجم أي كائن 32،767 B.

المراجع


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


All Articles