قمنا بتضمين مترجم Lua في مشروع المتحكم الدقيق (stm32)



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

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

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

اخترت لوا كلغة البرمجة.

لماذا لوا؟


هناك العديد من لغات البرمجة النصية التي يمكنك تضمينها في مشروع متحكم دقيق. يوجد عدد قليل بسيط مثل BASIC ، PyMite ، Pawn ... لكل منها إيجابيات وسلبيات ، لم يتم تضمين مناقشة لها في قائمة القضايا التي تمت مناقشتها في هذه المقالة.

باختصار حول ما هو جيد على وجه التحديد لوا - يمكن العثور عليها في مقال "لوا في 60 دقيقة . " ألهمني هذا المقال كثيرًا ، ولأغراض دراسة أكثر تفصيلًا لهذه المسألة ، قرأت كتاب الدليل الرسمي من مؤلف كتاب Robert Jeruzalimsky باللغة الإنجليزية " البرمجة في لوا " (متوفر في الترجمة الروسية الرسمية).

أود أيضًا أن أذكر مشروع eLua. في حالتي ، لدي بالفعل طبقة منخفضة المستوى من البرمجيات الجاهزة للتفاعل مع كل من الأجهزة الطرفية لجهاز التحكم الدقيق والأجهزة الطرفية الأخرى المطلوبة الموجودة على لوحة الجهاز. لذلك ، لم أفكر في هذا المشروع (لأنه من المعترف به توفير الطبقات نفسها لربط جوهر Lua بالأجهزة الطرفية من متحكم دقيق).

حول المشروع الذي سيتم تضمين Lua فيه


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

يعتمد المشروع على متحكم stm32f405rgt6 مع 1 ميغابايت غير متقلبة و 192 كيلو بايت من ذاكرة الوصول العشوائي (الكتل القديمة الأقدم بسعة 128 كيلوبايت المستخدمة حاليًا).

يحتوي المشروع على نظام تشغيل في الوقت الفعلي لـ FreeRTOS لدعم البنية التحتية للأجهزة الطرفية. يتم تخصيص جميع الذاكرة للمهام والإشارات وغيرها من كائنات FreeRTOS بشكل ثابت في مرحلة الربط (الموجودة في منطقة .bss من RAM). جميع كيانات FreeRTOS (الإشارات ، وقوائم الانتظار ، وأكوام المهام ، وما إلى ذلك) هي أجزاء من كائنات عمومية في المناطق الخاصة في فصولها. ومع ذلك ، لا يزال يتم تخصيص كومة FreeRTOS لدعم وظائف malloc ، free ، calloc (المطلوبة للوظائف مثل printf ) التي تم إعادة تعريفها للعمل معها. هناك واجهة برمجة تطبيقات مرتفعة للعمل مع بطاقات MicroSD (FatFS) ، وكذلك تصحيح أخطاء UART (115200 ، 8N1).

حول منطق استخدام Lua كجزء من المشروع


لغرض تصحيح منطق الأعمال ، من المفترض أن يتم إرسال الأوامر بواسطة UART ، معبأة (ككائن منفصل) في الأسطر النهائية (تنتهي بالحرف "\ n" + 0 فاصل) وترسل إلى آلة lua. في حالة التنفيذ غير الناجح ، الإخراج عن طريق printf (لأنه كان متورطًا في المشروع سابقًا). عندما يتم تصحيح المنطق ، سيكون من الممكن تنزيل ملف منطق الأعمال النهائي من الملف من بطاقة microSD (غير مدرجة في مادة هذه المقالة). أيضًا ، لغرض تصحيح أخطاء Lua ، سيتم تنفيذ الجهاز داخل مؤشر ترابط FreeRTOS منفصل (في المستقبل ، سيتم تخصيص مؤشر ترابط منفصل لكل برنامج نصي منطق أعمال تم تصحيحه والذي سيتم تنفيذه مع بيئته).

إدراج الوحدة الفرعية لوا في المشروع


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

تقوم CMakeLists بإنشاء مصادر مستودع lua كمكتبة ثابتة تحتوي على أعلام تجميع الوحدات الفرعية التالية (مأخوذة من ملف تكوين وحدة فرعية في المشروع الرئيسي):

SET(C_COMPILER_FLAGS "-std=gnu99;-fshort-enums;-fno-exceptions;-Wno-type-limits;-ffunction-sections;-fdata-sections;") SET(MODULE_LUA_COMP_FLAGS "-O0;-g3;${C_COMPILER_FLAGS}" 

وأعلام مواصفات المعالج المستخدم (تعيينها في قوائم CMake الجذر ):

 SET(HARDWARE_FLAGS -mthumb; -mcpu=cortex-m4; -mfloat-abi=hard; -mfpu=fpv4-sp-d16;) 

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

 add_definitions(-DLUA_32BITS) 

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

يخطط CMakeLists لربط مشروع بمكتبة lua
 add_subdirectory(${CMAKE_SOURCE_DIR}/bsp/submodules/module_lua) ... target_link_libraries(${PROJECT_NAME}.elf PUBLIC # -Wl,--start-group       #      . #  Lua    ,      #  . "-Wl,--start-group" ..._... MODULE_LUA ..._... "-Wl,--end-group") 

تحديد وظائف للعمل مع الذاكرة


نظرًا لأن Lua نفسها لا تتعامل مع الذاكرة ، يتم نقل هذه المسؤولية إلى المستخدم. ومع ذلك ، عند استخدام مكتبة lauxlib المجمعة ووظيفة luaL_newstate منها ، تكون دالة l_alloc مرتبطة كنظام ذاكرة. يتم تعريفها على النحو التالي:

 static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; /* not used */ if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); } 

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

في الآلية القياسية للعمل مع كومة FreeRTOS ، لا يحتوي ملف heap_4.c المستخدم في المشروع على وظيفة لتغيير حجم كتلة ذاكرة مخصصة مسبقًا. في هذا الصدد ، من الضروري جعل تنفيذه على أساس malloc ومجاني .

نظرًا لأنه من الممكن في المستقبل تغيير نظام تخصيص الذاكرة (باستخدام ملف heap_x.c آخر) ، فقد تقرر عدم استخدام التصميمات الداخلية للمخطط الحالي (heap_4.c) ، ولكن لإنشاء وظيفة إضافية ذات مستوى أعلى. على الرغم من أن أقل فعالية.

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

إذا لم تؤخذ هذه الحقيقة في الاعتبار ، فسيتمكن جهازك من تنفيذ مثل هذا البرنامج النصي ثلاث مرات من السطر " a = 3 \ n " ، وبعد ذلك يقع في خطأ كبير. يمكن حل المشكلة بعد دراسة الصورة المتبقية للسجلات في معالج الأخطاء الثابتة ، والتي سيكون من الممكن بها معرفة أن الحادث قد وقع بعد محاولة توسيع الجدول في أحشاء رمز المترجم الشفوي ومكتباته. إذا اتصلت ببرنامج نصي مثل " print" test " " ، فسوف يتغير السلوك بناءً على كيفية تجميع ملف البرنامج الثابت (بمعنى آخر ، السلوك غير معرف).

من أجل نسخ البيانات من الكتلة القديمة إلى الجديدة ، نحتاج إلى معرفة حجم الكتلة القديمة. FreeRTOS heap_4.c (مثل الملفات الأخرى التي توفر أساليب معالجة كومة الذاكرة المؤقتة) لا يوفر API لهذا. لذلك ، عليك الانتهاء من لك. كأساس ، أخذت وظيفة vPortFree وقطعت وظائفها إلى النموذج التالي:

VPortGetSizeBlock رمز الوظيفة
 int vPortGetSizeBlock (void *pv) { uint8_t *puc = (uint8_t *)pv; BlockLink_t *pxLink; if (pv != NULL) { puc -= xHeapStructSize; pxLink = (BlockLink_t *)puc; configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); configASSERT(pxLink->pxNextFreeBlock == NULL); return pxLink->xBlockSize & ~xBlockAllocatedBit; } return 0; } 

الآن أصبح صغيرًا ، اكتب realloc استنادًا إلى malloc ، مجانًا ، و vPortGetSizeBlock :

Realloc رمز التنفيذ على أساس malloc ، مجاني ، و vPortGetSizeBlock
 void *realloc (void *ptr, size_t new_size) { if (ptr == nullptr) { return malloc(new_size); } void* p = malloc(new_size); if (p == nullptr) { return p; } size_t old_size = vPortGetSizeBlock(ptr); size_t cpy_len = (new_size < old_size)?new_size:old_size; memcpy(p, ptr, cpy_len); free(ptr); return p; } 

إضافة دعم للعمل مع stdout


كما أصبح معروفًا من الوصف الرسمي ، فإن مترجم lua نفسه غير قادر على العمل مع I / O. لهذه الأغراض ، يتم توصيل إحدى المكتبات القياسية. للإخراج ، فإنه يستخدم دفق stdout . وظيفة luaopen_io من المكتبة القياسية مسؤولة عن الاتصال بالدفق . لدعم العمل مع stdout (على عكس printf ) ، ستحتاج إلى تجاوز وظيفة fwrite . أعدت تعريفه بناءً على الوظائف الموضحة في المقالة السابقة .

وظيفة الكتابة
 size_t fwrite(const void *buf, size_t size, size_t count, FILE *stream) { stream = stream; size_t len = size * count; const char *s = reinterpret_cast<const char*>(buf); for (size_t i = 0; i < len; i++) { if (_write_char((s[i])) != 0) { return -1; } } return len; } 

بدون تعريفها ، سيتم تنفيذ وظيفة الطباعة في lua بنجاح ، ولكن لن يكون هناك إخراج. علاوة على ذلك ، لن يكون هناك أي أخطاء في مكدس Lua الخاص بالجهاز (حيث تم تنفيذ الوظيفة رسميًا بنجاح).

بالإضافة إلى هذه الوظيفة ، سنحتاج إلى وظيفة fflush (حتى يعمل الوضع التفاعلي ، والذي سيتم مناقشته لاحقًا). نظرًا لأنه لا يمكن تجاوز هذه الوظيفة ، فسيتعين عليك تسميتها بشكل مختلف قليلاً. الوظيفة عبارة عن نسخة مجردة من وظيفة fwrite وتهدف إلى إرسال ما هو موجود الآن في المخزن المؤقت مع التنظيف اللاحق (بدون نقل إضافي للعربة).

وظيفة Mc_fflush
 int mc_fflush () { uint32_t len = buf_p; buf_p = 0; if (uart_1.tx(tx_buf, len, 100) != mc_interfaces::res::ok) { errno = EIO; return -1; } return 0; } 


استرداد سلاسل من منفذ تسلسلي


للحصول على سلاسل لآلة lua ، قررت كتابة فئة بسيطة من محطات uart ، وهي:

  • يتلقى البيانات على بايت المنفذ التسلسلي بايت (في المقاطعة) ؛
  • يضيف البايت المستلمة إلى قائمة الانتظار ، حيث يستقبلها الدفق ؛
  • في دفق من البايتات ، إذا لم يكن هذا عبارة عن تغذية سطر ، يتم إرسالها مرة أخرى بالشكل الذي وصلت إليه ؛
  • إذا وصلت تغذية السطر (' \ r ') ، فسيتم إرسال وحدتي بايت من إرجاع النقل الطرفي (" \ n \ r ") ؛
  • بعد إرسال الاستجابة ، يتم استدعاء معالج البايت الذي وصل (كائن تخطيط السطر) ؛
  • يتحكم في الضغط على مفتاح حذف الأحرف (لتجنب حذف أحرف الخدمة من نافذة المحطة الطرفية) ؛

روابط إلى المصادر:


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

الإعدادات في FreeRTOSConfig.h
 #define configPRIO_BITS 4 #define configKERNEL_INTERRUPT_PRIORITY 0XF0 //   FreeRTOS API   //   0x8 - 0xF. #define configMAX_SYSCALL_INTERRUPT_PRIORITY 0x80 

في المشروع نفسه ، يعين كائن الفئة nvic أولوية المقاطعة 0x9 ، والتي يتم تضمينها في النطاق الصحيح (يتم وصف الفئة nvic هنا وهنا ).

تشكيل سلسلة لآلة لوا


يتم نقل وحدات البايت المتلقاة من كائن uart_terminal إلى مثيل لفئة بسيطة serial_cli ، والتي توفر الحد الأدنى من الواجهة لتحرير السلسلة ونقلها مباشرة إلى سلسلة العمليات التي يتم فيها تنفيذ lua-machine (عن طريق استدعاء وظيفة رد الاتصال). عند قبول الحرف '\ r' ، يتم استدعاء وظيفة رد الاتصال. يجب أن تقوم هذه الوظيفة بنسخ الخط إلى نفسها و "تحرير" عنصر التحكم (حيث يتم حظر استقبال وحدات البايت الجديدة أثناء المكالمة. هذه ليست مشكلة في التدفقات ذات الأولوية بشكل صحيح وسرعة UART المنخفضة بشكل كافٍ).

روابط إلى المصادر:

  • ملفات وصف serial_cli هنا وهنا ؛
  • إنشاء كائن فئة كجزء من المشروع هنا .

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

تمرير سلسلة إلى مترجم Lua وتنفيذه


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

المراجع:


تم تصميم الفصل وفقًا لنمط singleton Myers ، حيث لا توجد حاجة لإعطاء عدة أوضاع تفاعلية داخل نفس الجهاز. يستقبل كائن الفئة lua_repl البيانات من كائن الفئة serial_cli هنا .

نظرًا لأن المشروع يحتوي بالفعل على نظام موحد لتهيئة الكائنات العالمية وصيانتها ، يتم تمرير المؤشر إلى كائن الفئة lua_repl إلى كائن مشغل الفئة العالمية :: base هنا . في طريقة بدء كائن من class player :: base (تم إعلانها هنا . يطلق عليها أيضًا من main) ، يتم استدعاء طريقة init الخاصة بالكائن من فئة lua_repl بأولوية مهمة FreeRTOS 3 (في المشروع ، يمكنك تعيين أولوية المهمة من 1 إلى 4. حيث 1 هي أدنى أولوية ، و 4 هي الأعلى). بعد التهيئة الناجحة ، تبدأ الفئة العالمية في جدولة FreeRTOS ويبدأ الوضع التفاعلي عمله.

قضايا ترقية


فيما يلي قائمة بالمشكلات التي واجهتها أثناء منفذ Lua بالجهاز.

يتم تنفيذ البرامج النصية 2-3 سطر واحد من متغير الاحالة ، ثم كل شيء يقع في خطأ الثابت


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

عند محاولة طباعة قيمة ، يقع المترجم في خطأ فادح


كان من الصعب بالفعل اكتشاف المشكلة ، لكن في النهاية تمكنت من معرفة أن snprintf كان يستخدم للطباعة. نظرًا لأن lua تخزن القيم بمضاعفة (أو تطفو في حالتنا) ، فإن printf (ومشتقاته) مع دعم النقطة العائمة مطلوب (كتبت عن تعقيدات printf هنا ).

متطلبات الذاكرة غير المتطايرة (فلاش)


فيما يلي بعض القياسات التي أجريتها للحكم على مقدار الذاكرة غير المتطايرة (فلاش) التي يجب تخصيصها لدمج جهاز Lua في المشروع. تم إجراء التجميع باستخدام gcc-arm-none-eabi-8-2018-q4-major. تم استخدام نسخة لوا 5.4. أدناه في القياسات ، تعني عبارة "بدون لوا" عدم إدراج المترجم الشفهي وطرق التفاعل معه ومع مكتباته ، فضلاً عن كائن من فئة lua_repl في المشروع. تبقى جميع الكيانات ذات المستوى المنخفض (بما في ذلك التخطيات لوظائف printf و fwrite ) في المشروع. حجم كومة FreeRTOS هو 1024 * 25 بايت. يشغل الباقي كيانات مشاريع عالمية.

جدول ملخص النتائج كما يلي (جميع الأحجام بالبايت):
بناء الخياراتبدون لواالأساسية فقطلوا مع مكتبة قاعدةلوا مع قاعدة المكتبات ، coroutine ، الجدول ، سلسلةluaL_openlibs
-O0 -g3103028220924236124262652308372
-O1 -g374940144732156916174452213068
-Os -g071172134228145756161428198400

متطلبات ذاكرة الوصول العشوائي


نظرًا لأن استهلاك ذاكرة الوصول العشوائي يعتمد تمامًا على المهمة ، سأقدم جدولًا موجزًا ​​للذاكرة المستهلكة فور تشغيل الجهاز بمجموعة مختلفة من المكتبات (يتم عرضه بواسطة أمر الطباعة (collectgarbage ("count") * 1024 ).
هيكلذاكرة الوصول العشوائي المستخدمة
لوا مع مكتبة قاعدة4809
لوا مع قاعدة المكتبات ، coroutine ، الجدول ، سلسلة6407
luaL_openlibs12769

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

بالإضافة إلى ذلك ، يتم تخصيص 4 كيلوبايت أيضًا لمكدس المهام ، حيث يتم تنفيذ Lua-machine.

مزيد من الاستخدام


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

النتائج


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

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


All Articles