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.2. العتاد: J-Link عبر SWD¶
صِل Segger J-Link بدبابيس SWD في الكاميرا (SWDIO وSWCLK وGND، وVCC الهدف للمرجعية؛ الكاميرا مُغذّاة عبر USB كالمعتاد). يعمل أي من J-Link EDU / Base / Pro. يختلف موضع ظهور دبابيس التصحيح من كاميرا إلى أخرى -- فالعديد من اللوحات تحتوي على موصّل JTAG/SWD مخصص، بينما يكشف بعضها عن SWD على ترويسة الإدخال/الإخراج أو على لبادات الاختبار -- لذا راجع مخطط أطراف تلك اللوحة ومخططها الكهربائي في وثائق عتاد OpenMV لمعرفة الدبابيس التي يجب توصيلها. ثبّت حزمة برامج ووثائق J-Link من segger.com على الجهاز الذي يُوصَل به المسبار فعلياً. حافظ على تحديثها بشكل معقول -- فبرامج J-Link الأقدم لن تتعرف على أسماء الأجهزة الأحدث (STM32N6 وMIMXRT وAlif).
تحتاج كل وحدة MCU إلى اسم الجهاز الدقيق في J-Link حتى يحمّل المسبار محمّل الفلاش الصحيح وخريطة الذاكرة الصحيحة:
الكاميرا ( |
MCU |
|
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
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.5. Windows: جسر J-Link بين WSL ↔ Windows¶
لا يستطيع WSL 2 رؤية جهاز USB الخاص بـ J-Link مباشرة، لذا يكون التقسيم كالتالي: يقدّم Windows المسبار (حيث يكون موصولاً) ويعمل VS Code + gdb في WSL ويصلان إليه عبر TCP.
على Windows، ثبّت حزمة Segger J-Link وأوصِل J-Link بمنفذ USB في Windows.
على Windows، شغّل خادم J-Link البعيد (الذي يأتي ضمن حزمة J-Link): شغّله مع توصيل J-Link وانقر OK. اسمح له بالمرور عبر جدار حماية Windows عند الطلب. تُظهِر النافذة عنوان IP الذي يقدّم المسبار عليه -- دوّنه.
في WSL، ابنِ بـ
DEBUG=1وتأكد من إمكانية الوصول إلىarm-none-eabi-gdb(اضبطarmToolchainPathكما سبق).في VS Code داخل WSL، أبقِ
servertype: "jlink"-- يعمل خادم GDB في WSL ويصل إلى المسبار عبر الخادم البعيد -- وأضِفserverpath+ipAddress{ "name": "OpenMV J-Link (Windows host)", "type": "cortex-debug", "request": "launch", "cwd": "${workspaceFolder}", "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf", "servertype": "jlink", "serverpath": "/opt/SEGGER/JLink/JLinkGDBServer", "ipAddress": "192.168.x.x", "device": "STM32H743VI", "interface": "swd", "runToEntryPoint": "main", "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin" }
اضبط
ipAddressعلى العنوان الذي تُظهره نافذة الخادم البعيد. هذا هو الجسر بأكمله.
نصيحة
بديل لجسر خادم GDB: usbipd-win. بدلاً من تشغيل خادم على Windows، يمكنك إرفاق جهاز USB الخاص بـ J-Link مباشرة داخل WSL باستخدام usbipd-win. من PowerShell بصلاحيات المسؤول:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> هو معرّف ناقل J-Link من usbipd list.) يظهر المسبار حينها داخل WSL، وتستخدم إعداد servertype: "jlink" البسيط على نفس الجهاز من إعداد Cortex-Debug في VS Code دون عنوان IP ودون خادم Windows منفصل. يتطلب جسر خادم GDB إعداداً أقل للاستخدام العَرَضي؛ أما usbipd-win فهو أكثر ملاءمة للتطوير الروتيني.
نصيحة
استخدم "request": "attach" لتصحيح البرنامج الثابت وهو قيد التشغيل بالفعل دون إعادة تعيينه أو إعادة فلاشه -- وهو مثالي لالتقاط تعليق في الميدان. واستخدم "request": "launch" لإعادة التعيين وفلاش ملف ELF والبدء من جديد عند runToEntryPoint.
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.