เป้าหมายของหัวข้อนี้
เมื่อจบบทนี้ บอร์ดจะรันโค้ดที่เขียนขึ้นเอง ไม่ใช่ "ภายในไม่กี่นาที" เพราะการดึง dependency มีขนาดราว 1.9 GB มี patch series ที่ต้องนำไปใช้กับของที่ดึงมา และการ build ครอบคลุม 3 คอร์ ขั้นสุดท้ายคือหัวใจของบทนี้ ได้แก่ โปรแกรมแรกที่อ่านเซนเซอร์จริงและขับ LED จริง ตามรูปแบบของแพ็กเกจที่ถืออยู่
ลำดับการทำงานจริงของเฟิร์มแวร์
คำสั่งทั้งหมดคือ bootstrap ที่ส่งมอบจริง ยกมาจาก README ที่แตกไฟล์ zip ออกมาได้ (README.md:35-44) และตรวจทานกับ README ฉบับหน้า dist ที่ยาวกว่า (release/dist/<variant>/README.md ซึ่งไม่ได้อยู่ในไฟล์ zip)
- แพ็กเกจ 2 แบบ ต่างกันเท่าที่ตารางนี้บอก
| mtb-mpy | mtb-only |
| คืออะไร | ModusToolbox พร้อม MicroPython VM: REPL บน UART, /boot.py และ /main.py, extension module และลิงก์ TACP ที่ BENTO IDE ใช้แฟลช | ภาษา C ล้วนบน FreeRTOS ไม่มี VM ไม่มี REPL ไม่มี /main.py ไม่มี TACP |
| สิ่งที่ต้องมีเพิ่ม | micropython-psoc-edge-psoc-edge-main/ วางข้างเทมเพลต (154 MB เป็น repository แยก) | ไม่มี |
| บรรทัดบอกความมีชีวิต | [MPY] GC heap u KB @ p in s หนึ่งครั้งตอนบูต (mpy_main.c:552-554) | [HB] t=lus tasks=u ทุก 10 วินาที (proj_cm33_ns/main.c:110-112) |
| ป้ายบนหน้าจอ Home | v<BENTOCLAW_VERSION>-mtb_mpy | v<BENTOCLAW_VERSION>-mtb_only |
| วิธีแฟลช | make program ผ่าน KitProg หรือแฟลชจาก BENTO IDE | make program ผ่าน KitProg เท่านั้น — IDE สื่อสารด้วย TACP ผ่าน UART ของ REPL และ variant นี้ไม่มีทั้ง 2 อย่าง (variants/mtb-only.mk:19-20, :29-31) |
| โปรแกรมแรก | โค้ดไม่กี่บรรทัดที่ REPL | task หนึ่งตัวใน proj_cm33_ns/main.c |
ไม่ต้องเลือกเอง เพราะ zip แต่ละตัวตั้งค่าเริ่มต้นเป็น variant ของตัวเองไว้แล้ว (common.mk:85 ตั้ง BENTO_VARIANT?=mtb-mpy, :87-89 ปฏิเสธค่าอื่นทุกค่า ส่วนฉบับในไฟล์ zip ของ mtb-only ระบุ mtb-only) ห้ามส่ง BENTO_VARIANT= ทางบรรทัดคำสั่งสำหรับแพ็กเกจที่ดาวน์โหลดมา การส่งค่านี้โดยมีช่องว่างต่อท้ายเป็นกับดักที่ Makefile ป้องกันไว้แล้ว (common.mk:83-85) flag เพียงตัวเดียว ที่ไปถึงโค้ด C คือ BENTO_HAS_MPY (variants/mtb-only.mk:28, :33) ทุก #if BENTO_HAS_MPY ที่จะพบในบทหลัง ๆ คือสวิตช์ตัวนี้ตัวเดียว
การปักรุ่น toolchain: ต้องเป็น ModusToolbox 3.6 เท่านั้น พร้อม ARM GCC ที่มากับรุ่นนั้น (ฉบับในไฟล์ zip README.md:189; ฉบับ dist mtb-mpy:28, mtb-only:29) README ในไฟล์ zip อธิบายว่าเหตุใดรุ่นใหม่กว่าจึงไม่ได้ดีกว่า: Configurator รุ่นหลังจะสร้าง BSP ขึ้นใหม่จาก design.modus แล้วปล่อยข้อความแจ้งเตือนที่ -Werror=cpp เปลี่ยนให้กลายเป็น error ในไฟล์ที่ไม่เคยแก้ไขเลย
ผังของ workspace — ให้แตกไฟล์ zip ไว้ ต่ำกว่า workspace หนึ่งชั้น
<your workspace>/
micropython-psoc-edge-psoc-edge-main/ 154 MB, its own repository
mtb_shared/ created by make getlibs
bento-firmware-template-mtb-mpy/ unzip this here
ทีละขั้น
ขั้นที่ 0 — จัดหา MicroPython port
- Warning
- การได้มาซึ่ง MicroPython port: รอการตัดสินใจช่องทางแจกจาก BDH — ขั้นนี้จงใจปล่อยไว้โดยยังไม่ตัดสิน การ build แบบ mtb-mpy ต้องมี micropython-psoc-edge-psoc-edge-main/ (154 MB เป็น repository แยกของตัวเอง) วางอยู่ข้างเทมเพลตที่แตกไฟล์ออกมา README ทั้งสองฉบับในแพ็กเกจไม่ได้ให้ URL หรือวิธีจัดหาไว้ และบทนี้ไม่ประดิษฐ์ขึ้นเอง เมื่อ BDH ตัดสินใจเรื่องช่องทางแจกแล้ว กล่องนี้จะแทนที่ด้วยคำสั่งที่ชัดเจน ระหว่างนี้ ให้วาง port ไว้ที่พาธที่แสดงไว้ข้างต้นด้วยวิธีใดก็ตามที่ BDH ให้มา แล้วไปต่อที่ขั้นที่ 1
- สิ่งที่ควรสังเกต
- การรัน ls .. จากภายในเทมเพลตที่แตกไฟล์แล้วต้องเห็น micropython-psoc-edge-psoc-edge-main/ เป็นไดเรกทอรีข้างเคียง
ขั้นที่ 1 — แตกไฟล์และตรวจสอบสิ่งที่ได้รับมา
cd bento-firmware-template-mtb-mpy
(cd lib && ./verify.sh) # signature + digest of every shipped file
lib/verify.sh ตรวจลายเซ็นดิจิทัลและค่า digest ของ archive (ไฟล์ไลบรารีแบบสแตติก .a) ที่คอมไพล์มาแล้วทุกตัวที่ส่งมอบ (README ฉบับ dist mtb-mpy:34, mtb-only:36) README ทั่วไปในไฟล์ zip เริ่มที่ขั้นถัดไป ส่วนบรรทัด verify อยู่บนหน้า dist
- สิ่งที่ควรสังเกต
- verify.sh จบด้วยรหัส 0 และรายงานว่า archive ทุกตัวใต้ lib/ ผ่านการตรวจสอบ หากจบด้วยค่าที่ไม่ใช่ 0 แปลว่าแพ็กเกจนี้ไม่ใช่ตัวที่ลงลายเซ็นไว้ — ให้หยุด
ขั้นที่ 2 — Doctor
./bento.sh doctor # toolchain + tree check (variant-aware)
README ในไฟล์ zip README.md:35 ("is everything present?"); ฉบับ dist mtb-mpy:35, mtb-only:37 คำสั่ง doctor ตรวจ toolchain และตรวจต้นไม้ไฟล์ที่เทมเพลตนี้จงใจไม่พามาด้วย คำแนะนำของ README ในไฟล์ zip (:54-56): "Fix whatever it reports before building; the errors you get otherwise are long and unhelpful."
- สิ่งที่ควรสังเกต
- doctor ระบุชื่อ ModusToolbox 3.6 กับ GCC ของรุ่นนั้น บน workspace ที่เพิ่งตั้งใหม่จะรายงานว่าไม่มี mtb_shared/ ซึ่งคือขั้นที่ 3 ไม่ใช่ความล้มเหลว แต่ ห้ามรายงานว่าไม่มี MicroPython port หากรายงานเช่นนั้น ให้กลับไปที่ขั้นที่ 0
ขั้นที่ 3 — ดึง dependency ทีละโปรเจกต์
for p in proj_cm33_s proj_cm33_ns proj_cm55; do (cd $p && make getlibs); done
README ในไฟล์ zip README.md:37-41; ฉบับ dist mtb-mpy:37-38, mtb-only:39-40 ("dependencies are fetched PER PROJECT — there is no top-level getlibs") README ในไฟล์ zip บันทึกผลของการรันในโปรเจกต์เดียวไว้ว่า "running it only in proj_cm33_ns fetches 33 of the 41 assets and the build then stops inside ninja on a missing optiga-trust-m file." ขั้นนี้คือขั้นขนาด 1.9 GB จะใช้เวลานานเท่าที่การเชื่อมต่อของเครื่องนั้นทำได้
- สิ่งที่ควรสังเกต
- ../mtb_shared/ ปรากฏขึ้นข้างเทมเพลตและมีไฟล์อยู่ภายใน การรัน make getlibs ทั้ง 3 ครั้งจบโดยไม่มี error สังเกตชื่อคำสั่งด้วย ขั้นนี้ใช้ make รันภายในแต่ละโปรเจกต์ ไม่ใช่ bento.sh
ขั้นที่ 4 — นำ patch series ของ third-party ไปใช้ และพิสูจน์ว่าลงจริง
(cd ../mtb_shared \
&& for p in $(cat ../bento-firmware-template-mtb-mpy/third_party_patches/series); do
patch -p1 -F0 --forward < "../bento-firmware-template-mtb-mpy/third_party_patches/$p" || exit 1
done \
&& shasum -a 256 -c ../bento-firmware-template-mtb-mpy/third_party_patches/PATCHED.sha256)
README ฉบับ dist mtb-mpy:40-45, mtb-only:42-48 ส่วน README ในไฟล์ zip อธิบายข้อกำหนดเดียวกันในรูปความเรียง (:51-53) ว่าเป็นการแก้ไขเฉพาะเครื่องต่อ asset ใต้ mtb_shared ที่ getlibs ไม่ได้ให้มา — "one stops the build, the rest fail silently, including the one that binds the OPTIGA key into the TLS session" ลำดับการนำ patch ไปใช้เป็นไปตามที่ระบุใน third_party_patches/series และใช้ -F0 เพื่อให้ patch ที่ลงไม่สนิทหยุดลูปทันที แทนที่จะเดาตำแหน่งแล้วลงต่อ ส่วน shasum -c เทียบกับ PATCHED.sha256 คือหลักฐาน เพราะตัว build เองจะไม่ยอมเริ่มหาก asset ที่ patch แล้วหายไปหรือผิด และจะบอกด้วยว่าไฟล์ใด
- สิ่งที่ควรสังเกต
- patch ทุกครั้งรายงานว่า hunk ลงครบ และ shasum -a 256 -c พิมพ์ OK ให้ทุกไฟล์ในรายการ บรรทัด FAILED ที่นี่จะกลายเป็นการที่ build ปฏิเสธไม่ยอมเริ่มในขั้นที่ 5 โดยเรียกชื่อไฟล์เดียวกัน หากแพ็กเกจที่ได้รับมาไม่มี third_party_patches/ มาด้วย README ในไฟล์ zip บอกให้ขอมา (:53)
ขั้นที่ 5 — สร้างทั้ง 3 คอร์
README ฉบับ dist mtb-mpy:47, mtb-only:50 README ในไฟล์ zip เสนอวิธีเดียวกันผ่าน wrapper (./bento.sh build, README.md:43, "~10 minutes for a clean build of all three cores") ใช้ได้ทั้งสองแบบ เพราะ wrapper เรียก make ตัวเดียวกัน
- สิ่งที่ควรสังเกต
- Build complete ปรากฏครบทั้ง 3 คอร์ — CM33_S, CM33_NS, CM55 — และมีบรรทัด undefined reference เป็นศูนย์ error จาก linker ที่ชี้ไปยัง symbol ซึ่งมีอยู่ในซอร์สจริง คืออาการของ object ที่ค้างเก่า ไม่ใช่ฟังก์ชันที่หายไป ให้ลบ proj_cm55/build แล้ว build ใหม่ (บน variant นี้ให้ลบ proj_cm33_ns/build-micropython ด้วย) บันทึกการรันที่ปกติของ README ฉบับ dist เอง (mtb-mpy:57-62) ระบุว่า Build complete, 3 cores, 0 undefined references
ขั้นที่ 6 — แฟลช เปิดคอนโซล แล้วตัดไฟจ่ายไฟใหม่
README ฉบับ dist mtb-mpy:48, mtb-only:51; ฉบับในไฟล์ zip ใช้ ./bento.sh flash (README.md:44)
บอร์ดมีช่องทางข้อความเพียงช่องเดียว คือ UART ของ KitProg ที่ CM33_NS เป็นเจ้าของ (proj_cm55/main.c:9-10: "CM55 must NOT use printf/retarget-io. CM33_NS owns the UART for MicroPython REPL.") ให้เปิดค้างไว้ที่ 115200 8N1 ก่อนตัดไฟจ่ายไฟใหม่ อัตรานี้ระบุไว้ใน README ฉบับ dist ของ mtb-only (:57: "Expect on the UART at 115200") พอร์ตจะปรากฏเป็น /dev/tty.usbmodem* บน macOS, /dev/ttyACM* บน Linux และเป็นพอร์ต COM บน Windows
screen /dev/tty.usbmodem1103 115200
# or: picocom -b 115200 /dev/tty.usbmodem1103
# or: python3 -m serial.tools.miniterm /dev/tty.usbmodem1103 115200
- Warning
- จากนั้นให้ตัดไฟแล้วจ่ายไฟใหม่ การรีเซ็ตจากดีบักเกอร์ทำให้จอดับ ซึ่งดูเหมือนการแฟลชที่ล้มเหลวทุกประการ (README ฉบับในไฟล์ zip :47-49: "the display backlight needs a cold 0→1 edge and stays dark otherwise, which looks exactly like a failed flash"; ฉบับ dist mtb-mpy:51-52, mtb-only:54) จอที่ดับทันทีหลัง make program เป็นสภาพที่คาดไว้ ยังสรุปไม่ได้ว่าอิมเมจเสีย ให้ถอดสาย USB ออกแล้วเสียบกลับ นี่คือภาคผนวก X #21
- สิ่งที่ควรสังเกต
- การแฟลชจบด้วยบรรทัด verified … bytes จาก programmer จากนั้น หลังถอดสายแล้วเสียบกลับ บนคอนโซลอนุกรมจะได้
[MPY] GC heap %u KB @ %p in %s
พิมพ์หนึ่งครั้งโดย bento_libs/claw/common/mpy/mpy_main.c:552-554 (u คือขนาด heap เป็น KB, p คือแอดเดรสของ heap, s คือชื่อ region) บรรทัดนั้นคือสัญญาณบอกความมีชีวิตของ mtb-mpy หมายความว่า task ของ VM ขึ้นแล้วและวาง heap เรียบร้อย คอมเมนต์เหนือบรรทัดนั้น (:509-511) อธิบายว่าเหตุใดจึงพิมพ์ทุกครั้งที่บูต: build ที่วาง heap ไว้ใน SOCMEM ทำงานช้าลงจนวัดได้ และบรรทัดนี้คือวิธีแยกอาการนั้นออกจากการถดถอยด้านสมรรถนะเรื่องอื่น
จากนั้นคือหน้าจอ: ตารางการ์ดของหน้า Home พร้อมป้ายเวอร์ชันตามที่ตารางข้างต้นระบุ (proj_cm55/modules/page-components/_core/page_home.c:485-489) คอมเมนต์เหนือป้ายนั้น (:481-484) อธิบายว่าเหตุใดจึงต้องมี: เคยมีบอร์ด mtb-only ที่ "introduced itself on screen as mtb_mpy, and the only way to tell the truth was a serial console."
ขั้นที่ 7 — โปรแกรมแรก
กด Enter บนคอนโซลอนุกรมเดิม จะได้พรอมป์ของ MicroPython จากนั้นอ่านเซนเซอร์จริงและขับ LED จริง
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
>>> import sensors, gpio
>>> sensors.init()
True
>>> ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
>>> print(ax, ay, az)
>>> gpio.led(2).brightness(30)
- สิ่งที่ควรสังเกต
- sensors.init() (modsensors.c:523) พิมพ์หนึ่งบรรทัด — sensors: all sensors initialized OK (modsensors.c:573-575) — แล้วคืนค่า True sensors.bmi270.motion() (modsensors.c:230) คืนค่า float 6 ตัวภายใต้ lock เดียว ขณะวางนิ่ง แกนหนึ่งของ accelerometer อ่านได้ประมาณ 9.8 และอีกสองแกนประมาณ 0 — แกนใดขึ้นกับท่าที่วางบอร์ดไว้ ส่วน gpio.led(2) (modgpio.c:587) คือ RGB_RED ซึ่งเป็นหนึ่งในสามขาที่มีเส้นทาง TCPWM (modgpio.c:159-166) brightness(30) บนขานั้นจึงเป็นการหรี่ด้วยฮาร์ดแวร์ที่ คงระดับไว้ จนกว่าจะเรียกครั้งถัดไป (modgpio.c:211) บน LED1/LED2 (ดัชนี 0 และ 1) การเรียกเดียวกันเป็นพัลส์ bit-bang ยาว 12 ms ที่จบลงในสภาพดับ — เรื่องเต็มอยู่ที่ J4 — LED และปุ่มกด
- ต่อยอด: ลูปที่ใช้ค่าจากเซนเซอร์จริง
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors, gpio, time
sensors.init()
red = gpio.led(2)
while True:
ax, ay, az = sensors.bmi270.acceleration()
red.brightness(min(100, int(abs(ax) * 10)))
time.sleep_ms(100)
- สิ่งที่ควรสังเกต
- เอียงบอร์ดไปด้านข้างแล้วไฟแดงจะสว่างขึ้น วางราบแล้วจะหรี่ลง sensors.bmi270.acceleration() (modsensors.c:159) คืน 3-tuple หน่วย m/s² และ binding เป็นผู้ take sensor_i2c_lock(100) ให้เองทุกครั้งที่อ่าน จึงไม่ต้องจัดการ lock เอง (J1 — บัสของเซนเซอร์และ lock ของมัน) Ctrl-C หยุดลูปได้ เพราะ time.sleep_ms() คืนคิวให้ VM ระหว่างรอบ วางไฟล์นี้เป็น /main.py แล้วบอร์ดจะรันเองทุกครั้งที่บูต
กับดัก
- ภาคผนวก X #21 — ตัดไฟแล้วจ่ายไฟใหม่หลังการแฟลชทุกครั้ง (ขั้นที่ 6) หากข้ามขั้นนี้ จะเสียเวลา "ไล่จุดบกพร่อง" ให้กับเฟิร์มแวร์ที่ไม่ได้มีปัญหาอะไรเลย
- จอดับหลังการแฟลช = การรีเซ็ตจากดีบักเกอร์ ไม่ใช่การแฟลชที่ล้มเหลว เป็นข้อเดียวกัน ที่ย้ำอีกครั้งเพราะเป็นกับดักที่มีโอกาสกินเวลาไปหนึ่งชั่วโมงมากที่สุด
- การรัน make getlibs เพียงโปรเจกต์เดียว ได้ asset มา 33 จาก 41 ตัว แล้ว ninja หยุดเพราะไฟล์ของ optiga-trust-m หายไป (README ในไฟล์ zip README.md:38-40)
- การข้าม patch series build จะไม่ยอมเริ่มและบอกชื่อ asset ที่ยังไม่ได้ patch — แต่เกิดขึ้นเฉพาะเมื่อ PATCHED.sha256 ไม่ตรงเท่านั้น ส่วน cy_tls.c ที่ไม่ได้ patch และบังเอิญไม่ตรงกับรายการใดเลย คือความล้มเหลว เงียบ ๆ ที่ README เตือนไว้ กุญแจ OPTIGA จะไม่เคยผูกเข้ากับ TLS และ mTLS จะล้มเหลวในภายหลังมาก ที่บท C4
- การส่ง BENTO_VARIANT= ให้ zip ที่ดาวน์โหลดมา ไม่จำเป็น เพราะ common.mk:85 ในไฟล์ zip ตั้งค่าเริ่มต้นไว้ถูกต้องแล้ว ช่องว่างต่อท้ายค่าเป็นกับดักที่บันทึกไว้เป็นเอกสารแล้ว (common.mk:83-85)
- การรอบรรทัด "boot OK" ความสำเร็จบน CM33_NS เงียบ ยกเว้นบรรทัดบอกความมีชีวิตในตารางข้างต้นเท่านั้น การลงทะเบียน PSA/OPTIGA ที่ proj_cm33_ns/main.c:237-244 พิมพ์ข้อความเฉพาะตอนล้มเหลว ผู้อ่านต้องตีความจากสิ่งที่ไม่ปรากฏ — บัญชีเต็มของสิ่งที่พิมพ์ออกมาได้จริงอยู่ที่ ภาคผนวก W — แผนที่สัญญาณ
- การใช้บรรทัดที่ปิดเสียงไว้เป็นหลักฐาน [WiFi-Boot], [WiFiIPC], [TESAIOT_CFG], [TESAIOT_IPC] และแบนเนอร์ BOOT_VERBOSE ทุกอัน มีอยู่ในซอร์สแต่ไม่เคยพิมพ์ออกมา หากเอกสารใดบอกให้รอบรรทัดเหล่านี้ เอกสารนั้นเก่าเกินจริง
ขอบเขตการใช้กับแต่ละ variant
- variant ที่ใช้ได้
- mtb-mpy และ mtb-only ลำดับคำสั่งเหมือนกันทั้งสอง variant ความต่างทั้งหมดอยู่ในตารางที่หัวบท: MicroPython port (เฉพาะ mtb-mpy), บรรทัดบอกความมีชีวิต, ป้ายบนหน้าจอ, ช่องทางการแฟลช และรูปแบบของโปรแกรมแรก