14.1.1.4. تصحيح أخطاء البرنامج الثابت

يعني التصحيح على العتاد إيقاف المعالج، ووضع نقاط توقف في مصدر C، والتقدم خطوة بخطوة، وفحص المتغيرات والذاكرة والسجلات والطرفيات -- من داخل VS Code. يتطلب ذلك ثلاثة أشياء: بناء تصحيح، ومسبار تصحيح SWD (وهو Segger J-Link)، وامتداد Cortex-Debug الذي يقود arm-none-eabi-gdb مقابل خادم GDB من J-Link.

14.1.1.4.1. البناء بهدف التصحيح

أعد دائماً بناء الهدف باستخدام DEBUG=1

make -j$(nproc) TARGET=<TARGET> DEBUG=1

تُجمَّع صورة الإصدار (DEBUG=0) بالخيار -O2؛ وفي المصحح ستشاهد <optimized out> للعديد من المتغيرات، وتنطوي الدوال المضمَّنة داخل مستدعياتها، ويقفز التقدم خطوة بخطوة بشكل لا يمكن التنبؤ به. أما بناءات DEBUG=1 فتستخدم -Og -ggdb3، وهي قابلة للتصحيح مع بقائها قادرة على الإقلاع على الكاميرا. ملف ELF الذي توجّه المصحح إليه هو:

build/<TARGET>/bin/firmware.elf

(بالنسبة إلى Alif AE3، صحّح build/OPENMV_AE3/bin/firmware_M55_HP.elf -- النواة عالية الأداء.)

14.1.1.4.3. إعداد Cortex-Debug في VS Code

أنشئ .vscode/launch.json في المستودع. أبسط حالة -- حيث يكون VS Code وJ-Link والبناء جميعها على نفس جهاز Linux / macOS -- تستخدم servertype: "jlink"، الذي يجعل Cortex-Debug يشغّل خادم GDB من J-Link بنفسه:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "OpenMV J-Link",
      "type": "cortex-debug",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf",
      "servertype": "jlink",
      "device": "STM32H743VI",
      "interface": "swd",
      "runToEntryPoint": "main",
      "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin",
      "gdbPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin/arm-none-eabi-gdb"
    }
  ]
}

غيّر executable وdevice بما يناسب لوحتك (انظر الجدول أعلاه). اضغط F5 للبناء والفلاش والتشغيل حتى main والتوقف هناك.

نصيحة

لإعادة البناء تلقائياً في كل مرة تبدأ فيها التصحيح، أضف مهمة بناء إلى .vscode/tasks.json وأشِر إليها من إعداد التشغيل عبر "preLaunchTask". على سبيل المثال مهمة تشغّل make -j$(nproc) TARGET=OPENMV4 DEBUG=1، باسم "build-firmware"، إضافة إلى "preLaunchTask": "build-firmware" في الإعداد أعلاه، بحيث يعيد F5 البناء والفلاش وبدء المصحح في خطوة واحدة.

تحذير

يحتاج Cortex-Debug إلى arm-none-eabi-gdb. يأتي ضمن SDK في ~/openmv-sdk-<version>/gcc/bin لكنه ليس على PATH افتراضياً، لذا يفشل التصحيح بالرسالة "GDB executable 'arm-none-eabi-gdb' was not found". أصلح ذلك إما بضبط armToolchainPath / gdbPath كما هو موضح أعلاه، أو بإضافة ~/openmv-sdk-<version>/gcc/bin إلى PATH لديك (ينبغي حينها أن يُدرجه printenv PATH).

14.1.1.4.4. عرض سجلات الطرفيات (SVD)

وجّه Cortex-Debug إلى ملف SVD من CMSIS للحصول على عرض مُفكَّك لسجلات الطرفيات (المؤقتات وDMA وواجهة الكاميرا، إلخ) حسب الاسم والحقل البتي:

"svdFile": "/path/to/STM32H743.svd"

بالنسبة إلى STM32 وMIMXRT، احصل على SVD من حزم CMSIS الخاصة بـ ST / NXP أو من سجل SVD في Cortex-Debug. أما ملفات SVD الخاصة بـ Alif فهي مضمَّنة في مستودع البرنامج الثابت في lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (استخدم ..._CM55_HP_View.svd لنواة AE3 عالية الأداء).

14.1.1.4.6. التصحيح من سطر الأوامر باستخدام gdbrunner

يُعدّ إعداد جلسة GDB يدوياً مقابل هدف مضمَّن رقصة من خمس خطوات: شغّل خادم GDB الخاص بـ J-Link / ST-Link في نافذة واحدة مع الجهاز والمنفذ وأعلام الواجهة الصحيحة؛ وانتظر حتى يطبع Waiting for GDB connection؛ وشغّل arm-none-eabi-gdb في نافذة ثانية؛ واكتب target remote localhost:<port>؛ ووجّه gdb إلى ملف ELF. وعند انتهاء جلسة gdb، تذكّر إنهاء نافذة الخادم. إن gdbrunner أداة سطر أوامر صغيرة تطوي كل ذلك في أمر أمامي واحد. تأتي ضمن بيئة Python في OpenMV SDK، فلا شيء يحتاج إلى تثبيت؛ ونقطة الدخول المعتادة هي هدف make debug في مستودع البرنامج الثابت:

make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug

يشغّل هذا gdbrunner مع وسائط المصحح من إعداد اللوحة -- اسم جهاز J-Link، وعند الحاجة محمّل الفلاش الخارجي لـ ST-Link -- مع وجود arm-none-eabi-gdb من SDK على PATH أصلاً. الواجهة الخلفية الافتراضية هي J-Link؛ ويعمل make DEBUGGER=STLINK debug مع مسبار ST-Link بدلاً منه.

يمكن أيضاً استدعاء gdbrunner مباشرة (خارج SDK، pip install gdbrunner):

gdbrunner jlink --device STM32H743VI build/OPENMV4/bin/firmware.elf
gdbrunner stlink build/OPENMV4/bin/firmware.elf
gdbrunner qemu --machine mps2-an500 build/MPS2_AN500/bin/firmware.elf

تختار الوسيطة الموضعية الأولى الواجهة الخلفية للخادم (jlink أو stlink أو qemu)؛ أما البقية فتُمرَّر إلى تلك الواجهة الخلفية، مع قيم افتراضية تعمل مع كاميرات OpenMV. يسرد gdbrunner --help قائمة الأعلام الكاملة لكل واجهة خلفية؛ وجدول وسائط كل واجهة خلفية مدفوع بـ JSON (src/gdbrunner/backends.json)، لذا فإن إضافة خادم جديد هي تعديل إعداد لا تعديل شيفرة.

ما يفعله gdbrunner للعمل من سطر الأوامر:

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

  • الاكتشاف التلقائي لـ STM32CubeProgrammer. تبحث الواجهة الخلفية stlink في مواقع التثبيت المعتادة (~/STM32CubeProgrammer/ و/opt/st/ وشجرة إضافات STM32CubeIDE) عن أدوات STM32CubeProgrammer، حتى لا يلزم كتابة مسار --cube-prog الطويل في كل مرة. يجمّع SDK نسخته الخاصة في ~/openmv-sdk-<version>/stcubeprog/bin -- وجّه --cube-prog إليها إن لم يوجد تثبيت على مستوى النظام.

  • احترام gdbinit لكل مشروع. يُحمَّل ملف .gdbinit في الدليل الحالي بالخيار -ix -- متجاوزاً ~/.gdbinit على مستوى المستخدم -- بحيث يدخل سكربت gdb الخاص بكل مشروع (الطابعات الجميلة، والماكروهات الخاصة باللوحة، ومجموعات نقاط التوقف) بمجرد وجوده في دليل العمل. يعمل make debug من جذر المستودع، لذا ينطبق أي .gdbinit موجود هناك.

  • التشغيل التجريبي. يطبع --dryrun أمر الخادم دون تشغيله، وهو مفيد لتكييف الاستدعاء مع سكربت غلاف، أو لنسخه في إعداد مُشغِّل IDE، أو لمجرد التحقق من الوسائط التي يؤلّفها gdbrunner.

  • رؤية مخرجات الخادم. يُبقي --show-output مخرجات stdout / stderr للخادم مرئية. يقمعها الوضع الافتراضي (حتى تبقى واجهة gdb نظيفة)؛ فعّل العلَم عندما يكون الخادم نفسه هو ما يسيء التصرف.

  • الواجهة الخلفية QEMU. يصحح qemu-system-arm بناء برنامج ثابت دون توصيل أي لوحة. يختار الهدف MPS2_AN500 هذه الواجهة الخلفية في إعداد لوحته، لذا فإن make TARGET=MPS2_AN500 DEBUG=1 debug يبني لجهاز mps2-an500 الخاص بـ QEMU ويتقدم خطوة بخطوة في الشيفرة المستقلة عن المنصة -- كل ما لا يمس الطرفيات الخاصة بالكاميرا -- أثناء الطيران. (qemu-system-arm تثبيت على المضيف، وليس جزءاً من SDK.)

للتقدم خطوة بخطوة على مستوى المصدر مع مزاريب نقاط التوقف وعرض سجلات الطرفيات، يكون إعداد Cortex-Debug في VS Code أعلاه هو الأداة الأفضل؛ أما gdbrunner فهو الأداة المناسبة لكل ما يعيش على سطر الأوامر.

14.1.1.4.7. استخدام المصحح

بمجرد تشغيل جلسة (مع توقف المعالج عند main):

  • نقاط التوقف -- انقر المزراب بجوار سطر C، أو في وحدة تحكم التصحيح اكتب break <file>:<line> / break <function>. تمتلك نوى Cortex-M عدداً صغيراً من مقارنات نقاط التوقف العتادية (عادة 6--8 على M7 / H7، و8 على M55). يفشل تجاوز ذلك على شيفرة موجودة في الفلاش بصمت -- فأبقِ عدد نقاط التوقف الفعّالة معتدلاً.

  • التقدم خطوة بخطوة -- F10 للتخطي (next)، وF11 للدخول (step)، وShift+F11 للخروج (finish)، وF5 للمتابعة. التقدم على مستوى التعليمة هو stepi / nexti في وحدة تحكم التصحيح.

  • المتغيرات / المراقبة / مكدس الاستدعاء -- تُظهر لوحتا Variables وCall Stack المتغيرات المحلية وتتبُّع المكدس؛ أضِف التعبيرات إلى Watch. مرّر المؤشر فوق متغير في المصدر لرؤية قيمته. أي شيء يُظهر <optimized out> يعني أنك لست على بناء DEBUG=1.

  • نقاط مراقبة البيانات (Watchpoints) -- يوقف watch <expr> عند الكتابة إلى متغير، وrwatch عند القراءة، وawatch عند أيٍّ منهما. تدعم وحدة DWT في Cortex-M نحو 4 نقاط مراقبة عتادية -- وهي لا تُقدّر بثمن لالتقاط من الذي أفسد متغيراً.

  • السجلات والطرفيات -- يُظهر عرض Cortex Registers سجلات النواة؛ ومع ضبط svdFile، يفك عرض Peripherals تشفير كل سجل طرفي وحقل بتي (DMA والمؤقتات وواجهة الكاميرا / CSI وXSPI، إلخ) -- وهو أسرع طريقة لمعرفة سبب سوء تصرف مُشغِّل.

  • الذاكرة -- استخدم عارض الذاكرة في Cortex-Debug أو x/ في gdb لفحص مخازن الإطارات ومخازن DMA المؤقتة والبُنى مباشرة.

  • printf دون إيقاف (SWO/RTT) -- بالنسبة للمشكلات الحساسة للتوقيت، يوفر RTT أو SWO من Segger printf بحِمل شبه معدوم أثناء تشغيل الهدف. ابنِ بـ DEBUG_PRINTF=1 وأضِف rttConfig (لـ RTT) أو swoConfig (لـ SWO، الذي يحتاج إلى ساعة النواة) من Cortex-Debug. هذه هي الأداة المناسبة عندما تغيّر نقطة التوقف التوقيت الذي تحاول مراقبته.

  • قطع الاتصال -- Stop في جلسة launch يوقف الهدف؛ وDisconnect في جلسة attach يترك الكاميرا قيد التشغيل. أعِد تشغيل الكاميرا بفصل الطاقة لإعادتها إلى التشغيل الطبيعي بعد ذلك.

14.1.1.4.8. مزالق التصحيح

  • المتغيرات المُحسَّنة بعيداً. كل شيء يُظهر <optimized out> -- لقد بنيت بـ DEBUG=0. أعِد البناء بـ DEBUG=1.

  • "GDB executable not found" -- gcc/bin الخاص بـ SDK ليس على PATH؛ اضبط armToolchainPath / gdbPath.

  • "Cannot connect" / خريطة ذاكرة خاطئة -- اسم device خاطئ أو مفقود؛ استخدم السلسلة الدقيقة من الجدول.

  • نقاط التوقف لا تُصاب بصمت -- عدد كبير جداً من نقاط التوقف العتادية على شيفرة مقيمة في الفلاش؛ قلّلها.

  • مسارات المصدر لا تتطابق (ELF مبني عبر Docker) -- ابنِ بهدف Docker build-firmware-dev (نفس المسار المطلق داخل الحاوية وخارجها) أو اضبط set substitute-path في gdb.