14.1.1.3. المحاكيات#

تبني قيمتان لـ TARGET البرنامج الثابت ليعمل داخل محاكٍ على مضيفك، دون عتاد متصل. وهما موجودتان كي يتسنى تشغيل البرنامج الثابت واختباره في أي مكان.

TARGET

النواة

NPU

المحاكي

MPS2_AN500

Cortex-M7

بلا

QEMU

MPS3_AN547

Cortex-M55

Ethos-U55 (256 MACs)

Arm FVP -- Corstone SSE-300

إن MPS2_AN500 هو الهدف الأساسي Cortex-M7 بلا NPU. يعمل تحت QEMU، الذي يبدأ بسرعة وهو الطريقة الخفيفة لتمرين الشيفرة المستقلة عن المنصة ومجموعة الاختبارات.

إن MPS3_AN547 هو Cortex-M55 مع NPU من نوع Ethos-U55، يعمل على Fast Models من Arm -- الـ FVP، الذي ينمذج النظام الفرعي المرجعي Corstone SSE-300. هذا هو الهدف لتمرين الـ NPU ومسار نموذج ML في المحاكاة.

كلاهما يوفّر نظام ملفات ROM وذاكرة RAM وافرة (والـ NPU على M55)، لذا تعمل نصوص الرؤية وML دون تعديل.

لا يوجد مستشعر حقيقي، لكن وحدة csi لا تزال تعمل مع مستشعر افتراضي -- إذ تُرجِع csi.CSI.snapshot() نمط اختبار اصطناعيًا متحركًا (رقعة شطرنج متمرّرة بالتدرج الرمادي، وتدرّجًا لونيًا في RGB565)، لا مشهدًا حيًّا. ولمعالجة محتوى صورة حقيقي، حمّله من ملف بدلًا من ذلك: صورة image.Image من تجهيزة في نظام ملفات ROM، أو دفق image.ImageIO للإطارات المسجَّلة.

14.1.1.3.1. البناء والتشغيل#

كل محاكٍ هو أداة مضيف تثبّتها بنفسك. تبني هدفًا كأي هدف آخر (انظر بناء البرنامج الثابت)، ثم تُشغّله بـ run تحت المحاكي المطابق -- إذ يبني run صورة ROMFS، ويُقلِع البرنامج الثابت، ويتركه يعمل مع اتصال تسلسلي يمكنك الالتحاق به.

14.1.1.3.1.1. MPS2_AN500 (QEMU)#

يأتي QEMU في معظم مديري الحزم بوصفه محاكي نظام Arm:

  • Linux (Debian / Ubuntu) -- ثبّته بـ apt:

    sudo apt install qemu-system-arm
    
  • macOS -- ثبّته بـ Homebrew:

    brew install qemu
    

ثم ابنِ الهدف وشغّله:

make -j$(nproc) TARGET=MPS2_AN500
make TARGET=MPS2_AN500 run

14.1.1.3.1.2. MPS3_AN547 (Arm FVP)#

الـ FVP هو منصة Arm الافتراضية الثابتة لـ Corstone SSE-300، تُشحَن جزءًا من Arm Virtual Hardware. وهي متاحة لـ Linux فقط -- على جهاز Mac، استخدم هدف QEMU أعلاه بدلًا من ذلك.

نزّل الحزمة، واستخرجها، وضع مجلد bin/ الخاص بها على PATH لديك:

wget https://artifacts.tools.arm.com/avh/11.31.28/avh-linux-x86_11.31_28_Linux64.tar.gz
mkdir -p ~/fvp
tar --strip-components=1 -xzf avh-linux-x86_11.31_28_Linux64.tar.gz -C ~/fvp
export PATH="$HOME/fvp/bin:$PATH"

يحتاج الـ FVP أيضًا إلى مكتبات Python الخاصة بالـ SDK على LD_LIBRARY_PATH عند تشغيله. صدّرها مرةً واحدة لكل صدفة من جذر المستودع -- إذ تُبقي قراءة SDK_VERSION الأمر صحيحًا مع تحديث المستودع للـ SDK المثبَّت:

export LD_LIBRARY_PATH="$HOME/openmv-sdk-$(cat SDK_VERSION)/python/lib:$LD_LIBRARY_PATH"

ثم ابنِ الهدف وشغّله:

make -j$(nproc) TARGET=MPS3_AN547
make TARGET=MPS3_AN547 run

14.1.1.3.2. تشغيل مجموعة الاختبارات#

تعيش اختبارات الوحدة في scripts/unittest/tests/، مع صور تجهيزاتها وبياناتها في scripts/unittest/data/. وتُشغَّل بـ mpremote، الذي يضمّ scripts/unittest/ إلى المحاكي العامل وينفّذ run.py الخاص به.

رابط المحاكي بطيء مع mpremote الافتراضي، لذا استخدم النسخة المضمَّنة في المستودع مع رقعة التسلسلي مطبَّقة (طبّقها مرةً واحدة):

patch -N -p1 -d lib/micropython < tools/mpremote-qemu-serial.patch

بالنسبة لـ MPS2_AN500 تحت QEMU، شغّل الهدف -- فهو يطبع الطرفية الزائفة التي يقع عليها منفذه التسلسلي (مثل /dev/pts/5):

make TARGET=MPS2_AN500 run

ثم، من صدفة أخرى، وجّه mpremote المُرقَّع إلى ذلك الجهاز:

python3 lib/micropython/tools/mpremote/mpremote.py connect /dev/pts/5 \
    mount scripts/unittest/ run scripts/unittest/run.py

بالنسبة لـ MPS3_AN547 تحت الـ FVP، يكون المنفذ التسلسلي مأخذ telnet على المنفذ 5555 بدلًا من ذلك:

make TARGET=MPS3_AN547 run
python3 lib/micropython/tools/mpremote/mpremote.py connect socket://localhost:5555 \
    mount scripts/unittest/ run scripts/unittest/run.py

يكتشف run.py كل اختبار وينفّذه، طابعًا سطر PASSED / FAILED لكل اختبار مع التوقيت وملخصًا في النهاية.

14.1.1.3.3. إضافة اختبار#

تُكتشَف الاختبارات تلقائيًا. لإضافة واحد، ضع ملفًا في scripts/unittest/tests/ يُعرّف دالة unittest(data_path, temp_path) تُرجِع True عند نجاح الاختبار وFalse عند فشله:

# scripts/unittest/tests/circles.py
def unittest(data_path, temp_path):
    import image

    img = image.Image(data_path + "/shapes.ppm", copy_to_fb=True)
    circles = img.find_circles(threshold=5000, x_margin=30, y_margin=30, r_margin=30)
    return len(circles) == 1 and circles[0][0:] == (118, 56, 22, 5856)

تشير الوسيطتان data_path وtemp_path إلى نظامَي الملفات اللذين يراهما المحاكي أثناء تشغيل المجموعة:

المسار

مدعوم بـ

/remote

مجلد scripts/unittest/ الخاص بالمضيف، مضموم حيًّا عبر اتصال mpremote. يعيش هنا run.py وtests/ التي يكتشفها، لذا تَسري إضافة اختبار أو تحريره في التشغيل التالي دون إعادة بناء. والـ temp_path هو /remote/temp على الجهاز، وهو scripts/unittest/temp/ الخاص بمضيفك -- مجلد عمل قابل للكتابة للاختبارات التي تنشئ ملفات.

/rom

نظام ملفات ROM للقراءة فقط الخاص باللوحة، المبني داخل صورة البرنامج الثابت من romfs_config.json: تجهيزات الاختبار (من scripts/unittest/data/) ونماذج ML المضمَّنة. والـ data_path هو /rom -- تصل إلى تجهيزة بـ data_path + "/<file>".

لذا فإن اختبارًا جديدًا لا يحتاج إلى إعادة بناء، لكن تجهيزة جديدة تحتاج: إذ تُفتَّح صورة ROM وفقًا لـ romfs_config.json بدلًا من ملفات البيانات الفردية، لذا افرض إعادة البناء بلمس romfs_config.json الخاص باللوحة (أو make TARGET=<TARGET> clean) والتشغيل مجددًا.

تُؤكَّد القيم المتوقعة ضمنيًا في الاختبار، لذا لا يوجد ملف خرج متوقع منفصل لصيانته. ولتخطّي اختبار في وقت التشغيل -- مثلًا عندما يحتاج إلى عتاد لا ينمذجه المحاكي -- أطلِق استثناءً تحتوي رسالته على "SKIPPED".