- variant ที่ใช้ได้
- mtb-mpy และ mtb-only
ตัวอย่าง MicroPython ในบทนี้ทำงานได้บน mtb-mpy เท่านั้น (บน mtb-only ไม่มี REPL) ส่วนลำดับการทำงานฝั่ง C และสิ่งที่สังเกตได้บนหน้าจอใช้ได้ทั้งสอง variant
- Note
- เครดิต: Ready Model เป็นผลงานของ Infineon โมเดล Siren, Cough และ Factory Alarm ที่ engine นี้รันได้ — siren_lib_eval.a, cough_lib_eval.a, alarm_lib_eval.a — เป็น DEEPCRAFT™ Ready Model ที่ Imagimob AB ซึ่งเป็นบริษัทในเครือ Infineon Technologies เป็นผู้สร้าง และ Infineon เป็นผู้เผยแพร่: https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-ready-models โมเดลชุดนี้คือเหตุผลที่บอร์ดชุดนี้สาธิต Edge AI ด้านเสียงของจริงได้ TESAIoT อ้างอิงโมเดลเหล่านี้ภายใต้ Imagimob AI Model Evaluation License Agreement และปฏิบัติตามข้อกำหนดนั้น การใช้งานที่นี่เป็นไปเพื่อการวิจัยและการอบรม TESAIoT ไม่มีสิทธิ์ใด ๆ ในโมเดลเหล่านี้ ส่งต่อสิทธิ์ให้ผู้ใดไม่ได้ และไม่แจกจ่ายไฟล์ archive (ไฟล์ไลบรารีแบบสแตติก .a) ต่อ การประเมินผลบนบอร์ดของตนเองทำได้ภายใน 60 วัน (§2.1) ส่วนการนำไปใส่ในผลิตภัณฑ์ การแจกจ่ายต่อ และการใช้เชิงพาณิชย์ ทำไม่ได้ (§2.2(a), §2.2(c)) หากต้องการใช้ในการผลิต ให้ซื้อรุ่น non-evaluation จาก Imagimob/Infineon หรือฝึกโมเดลของตนเองใน DEEPCRAFT™ Studio: https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-studio โมเดลที่ฝึกเองรันบน API ชุดเดียวกันนี้ได้โดยไม่ต้องแก้ และเงื่อนไขสิทธิ์การใช้งานของโมเดลที่ฝึกเองไม่ใช่เงื่อนไขของ Ready Model รายละเอียดฉบับเต็ม การอ้างอิงข้อสัญญา และการเปิดเผยเรื่องการเปลี่ยนชื่อ symbol รายโมเดล อยู่ที่ THIRD_PARTY_NOTICES.md §2.4
-
เครดิต: โมเดลที่เฟิร์มแวร์ชุดนี้ส่งมอบก็เป็นของ Infineon เช่นกัน โมเดล motion, audio และ radar — proj_cm55/modules/ai_models/model_motion.c, model_audio.c และ model_radar.c — เป็นผลลัพธ์ที่ export ออกมาจาก DEEPCRAFT™ Studio ซึ่งเป็นเครื่องมือ Edge AI ของ Infineon และมีลิขสิทธิ์ของ Imagimob AB ซึ่งเป็นบริษัทในเครือ Infineon Technologies บรรทัดบนสุดของทุกไฟล์ระบุไว้ว่า "Copyright © 2023- Imagimob AB, All Rights Reserved." TESAIoT ไม่ได้ฝึกโมเดลเหล่านี้ ไม่ได้เป็นผู้สร้าง และไม่ได้เป็นเจ้าของ สิ่งที่เป็นของ TESAIoT คือ engine ที่ครอบอยู่ ได้แก่ ทะเบียนโมเดล (registry) ตัวจัดเส้นทางป้อนข้อมูลจากเซนเซอร์ (feed router) และตัวโหลดโมเดลตอนรัน สิ่งใดก็ตามที่ DEEPCRAFT™ Studio สร้างขึ้น หรือที่พัฒนาต่อจากโมเดล DEEPCRAFT™ ย่อมเป็นของ Imagimob และ Infineon ส่วนหัวของไฟล์เหล่านั้นสงวนสิทธิ์ไว้ทั้งหมดและไม่ได้ให้สิทธิ์ใด ๆ ที่นี่จึงไม่มีการส่งต่อสิทธิ์ให้ผู้ใด และสิทธิ์แบบ Apache-2.0 ที่ให้ไว้กับโค้ดของโครงการนี้ไม่ครอบคลุมเนื้อหาภายในไฟล์เหล่านั้น การใช้งานที่นี่เป็นไปเพื่อการวิจัยและการอบรม ไม่ใช่การใช้เชิงพาณิชย์ หากประสงค์จะนำโมเดลเหล่านี้ หรือสิ่งที่พัฒนาต่อจากโมเดลเหล่านี้ ไปใส่ในผลิตภัณฑ์ ต้องตกลงกับ Infineon และ Imagimob ก่อน ผู้ที่ต้องการฝึกโมเดลของตนเองเริ่มได้ที่ https://www.infineon.com/design-resources/embedded-software/deepcraft-edge-ai-solutions/deepcraft-studio รายละเอียดฉบับเต็มอยู่ที่ THIRD_PARTY_NOTICES.md §2.2 และ §4.3
เป้าหมายของหัวข้อนี้
เมื่อจบบทนี้ ผู้อ่านจะอธิบายได้ว่าเหตุใดเครื่องยนต์ Edge AI จึงเก็บดัชนีของโมเดลไว้ 2 ตัว — ตัวที่ร้องขอไป กับตัวที่ทำงานอยู่จริง — เหตุใดทั้งสองจึงไม่ตรงกันได้นานถึง 15 s หลังการ select ทุกครั้ง และเหตุใดเฟิร์มแวร์จึงยืนยันการ select เทียบกับดัชนีที่มันส่งออกไป ไม่ใช่ตัวเลขที่ผู้ใช้พิมพ์เข้ามา และจะทำซ้ำคำมั่นที่บันทึกไว้ได้ด้วย: edge_ai.select(13) ถูกตอบด้วย edge_ai.active() == 253
- Note
- โค้ดอยู่ที่ไหน template/ ไม่มี call site ในไฟล์ .c สำหรับฟังก์ชัน ai_engine_* ใด ๆ เลย ผู้เรียกทุกรายที่แสดงในบทนี้ถูกคอมไพล์เข้าไปใน libbento_cm55.a (สร้างจาก deepcraft_task.c, page_edge_ai.c และ tesaiot_display.c ตาม template/lib/cm55_core/PROVENANCE.txt) ส่วน binding ฝั่ง MicroPython คือ modedgeai.c ซึ่งถูกคอมไพล์เข้าไปใน libbento_mpy.a ข้อความตัดตอน (excerpt) ด้านล่างคัดมาตามตัวอักษรจากซอร์สที่อยู่ใน archive เหล่านั้น และแต่ละชิ้นระบุที่มาไว้ (file:lines ในทรีต้นทางความจริง ซึ่งถูกคอมไพล์เข้า archive ไม่ได้ส่งมอบมาเป็นซอร์ส) ไฟล์ข้อความตัดตอนเป็นข้อมูลนำเข้าสำหรับการสร้างเอกสารเท่านั้น และไม่ได้แจกจ่ายมาในแพ็กเกจนี้ MicroPython เข้าถึงเครื่องยนต์ได้ เฉพาะผ่านการเชื่อมโมเดลด้วย IPC เท่านั้น และเรียก ai_engine_* โดยตรงไม่ได้เลย
-
หลักฐานยังบาง — ไม่มีโปรแกรมตัวอย่างที่ส่งมอบมา ไม่มีโปรแกรม MicroPython ที่ใช้ edge_ai.* และทำงานได้จริงส่งมาใต้ template/ โปรแกรม J6 ที่ใช้งานได้อยู่นอกทรีที่ส่งมอบ บทนี้จึงอธิบายผ่านส่วนที่เรียกได้บน REPL ทีละคำสั่ง และไม่มีข้อใดที่สั่งให้รันไฟล์ที่ผู้อ่านไม่มี
ลำดับการทำงานจริงของเฟิร์มแวร์
การเปิดใช้งาน ตัวควบคุมจอแสดงผลสร้าง task ของการอนุมาน (inference) หลังจากยืนยันแล้วว่าจอขึ้นเรียบร้อย (tesaiot_display.c:467 หลัง display_ok = true) ความผิดพลาดใน ML runtime จึงไม่มีวันทำให้เสียหน้าจอ ai_engine_init() เป็น idempotent (เรียกซ้ำได้โดยผลไม่เปลี่ยน — ai_engine.c:1735-1738) และไล่ลองขนาด stack เป็นลำดับ {2048, 1536} เวิร์ด เมื่อล้มเหลวทั้งหมด ai_engine_stack_words() คืนค่า 0 ซึ่งเป็น gate (ด่านกั้น) ที่ตัวควบคุมทุกตัวตรวจเป็นอย่างแรก
- ที่มา
- ยกมาจาก tesaiot_display.c:462-468 (คอมไพล์รวมอยู่ใน archive สำเร็จรูป ไม่ได้ส่งมอบมาเป็นซอร์ส)
เส้นทางฝั่ง MicroPython เรียก init ซ้ำก่อนการ start ทุกครั้ง คอมเมนต์ในข้อความตัดตอนนี้คือสาระของทั้งบทย่อใน 9 บรรทัด ให้อ่านก่อนสิ่งอื่นใด:
- ที่มา
- ยกมาจาก deepcraft_task.c:631-641 (คอมไพล์รวมอยู่ใน archive สำเร็จรูป ไม่ได้ส่งมอบมาเป็นซอร์ส)
Select edge_ai.select(n) (modedgeai.c:492-497 ขอบเขต 0..255) จะแปลงดัชนีเดิมก่อนเป็นอันดับแรก — model_link_resolve_set() ที่ modedgeai.c:503 นิยามที่ ipc_model_link_defs.h:135-143: 13→INTRUDER (253), 14→ROOM (254), 15→MIC (255) ค่าที่ต่ำกว่า 16 จะเดินทางไปบนแถบ opcode เดิม ส่วนตั้งแต่ 16 ขึ้นไปจะไปในรูป SELECT_IDX โดยมีดัชนีอยู่ใน payload (modedgeai.c:507-509) การยืนยันที่ตามมาเปรียบเทียบกับ สิ่งที่ส่งออกไป ไม่ใช่สิ่งที่พิมพ์เข้ามา (modedgeai.c:513-516)
CM55 รับค่านั้นแล้วแปลงอีกครั้งอย่างเป็นอิสระ (deepcraft_task.c:785-787) จากนั้นเรียก ai_engine_init() (ซึ่งเป็น idempotent) ทันทีก่อน ai_engine_start(sel):
- ที่มา
- ยกมาจาก deepcraft_task.c:790-807 (คอมไพล์รวมอยู่ใน archive สำเร็จรูป ไม่ได้ส่งมอบมาเป็นซอร์ส)
การแปลงทั้ง 2 จุดถูกทำให้สอดคล้องกันด้วยบล็อก _Static_assert ที่ ai_engine.h:121-126 ซึ่งคอมเมนต์ของมันบันทึกไว้ว่าเกิดอะไรขึ้นเมื่อทั้งสองเคลื่อนออกจากกัน: การ select ของ Sound Watch ทุกครั้งรายงานว่า "not confirmed" บนบอร์ดที่สลับโมเดลไปเรียบร้อยแล้วจริง ๆ
Confirm modedgeai.c:373-393 วน 2 คำถามเทียบกับสิ่งที่อยู่บนสาย: Q_ACTIVE == want หมายความว่าโหลดแล้ว หากไม่ใช่ มันจะวนถาม Q_REQUESTED ต่อ และเมื่อเคยเห็น requested == want มาแล้ว การที่ requested < 0 ในภายหลังย่อมหมายความได้อย่างเดียวว่า init() ของโมเดลล้มเหลวบน ai_task มันจึงคืนค่า False ทันทีแทนที่จะรอจนครบเพดาน งบเวลาคือ EDGE_AI_CONFIRM_TRIES 750 × EDGE_AI_CONFIRM_DELAY_MS 20 = 15 s (modedgeai.c:107-108) เหตุผลที่ต้องยาวขนาดนั้น: การเปิดใช้งาน TFLite-Micro + Ethos-U55 แบบเย็นทำงานแบบประสานเวลาอยู่ภายใน init() ของโมเดล และ s_current จะเปลี่ยนค่าหลังจากที่ init() คืนค่าแล้วเท่านั้น (modedgeai.c:89-105)
2 คำถามนั้นฝั่ง CM55:
- ที่มา
- ยกมาจาก deepcraft_task.c:301-311 (คอมไพล์รวมอยู่ใน archive สำเร็จรูป ไม่ได้ส่งมอบมาเป็นซอร์ส)
ทีละขั้น
ขั้นที่ 1 — ยืนยันว่าเครื่องยนต์มีอยู่ (ทั้งสอง variant)
ไปที่ Home → Edge AI ปุ่ม Load ของหน้านี้เป็น no-op ตราบใดที่ ai_engine_stack_words() == 0:
- ที่มา
- ยกมาจาก page_edge_ai.c:348-369 (คอมไพล์รวมอยู่ใน archive สำเร็จรูป ไม่ได้ส่งมอบมาเป็นซอร์ส)
สิ่งที่ควรสังเกต badge ของชิปบนหน้านี้แสดงค่าใดค่าหนึ่งใน CHIP_RUNNING / CHIP_LOADING / CHIP_FAIL / CHIP_STOPPED โดย CHIP_FAIL ถูกเลือกที่ page_edge_ai.c:1338 เมื่อ stack_words() เป็น 0 ซึ่งแปลว่าเครื่องยนต์ไม่เคยเริ่มทำงาน ค่าอื่นใดแปลว่า task ของการอนุมานมีอยู่ ไม่มีบรรทัด UART สำหรับเรื่องนี้ เพราะ CM55 ไม่พิมพ์อะไรเลย (proj_cm55/main.c:9-10)
ขั้นที่ 2 — ติดต่อ REPL ครั้งแรก (เฉพาะ mtb-mpy)
>>> import edge_ai
>>> edge_ai.models()
สิ่งที่ควรสังเกต รายการหนึ่งชุดที่มีความยาวเท่ากับจำนวนใน registry ของ CM55 โมเดลที่คอมไพล์มาด้วยจะมาก่อน ส่วนโมเดล staged (โมเดลที่อัปโหลดเข้ามาภายหลัง) จะต่อท้ายเข้ามาทีหลัง (บท E3)
ขั้นที่ 3 — select โมเดลเดี่ยว แล้วดูดัชนีทั้งสองแยกออกจากกัน
>>> edge_ai.select(0)
True
>>> edge_ai.active()
0
สิ่งที่ควรสังเกต select() บล็อกไว้จนกว่าจะยืนยันได้ — นานถึง 15 s เมื่อเป็นการโหลดแบบเย็น — แล้วคืนค่า True ทันทีหลังจากนั้น active() จะมีค่าเท่ากับดัชนีที่เลือกไป หากอ่านค่า requested ได้กลางการโหลด จะเห็นว่ามันนำหน้า active อยู่เท่ากับความยาวของช่วง cold-init ทั้งช่วง นั่นคือความแตกต่างระหว่าง REQUESTED กับ ACTIVE และเป็นเหตุผลที่ต้องมีขั้นถัดไป
บนหน้าจอ: badge เปลี่ยนเป็น CHIP_LOADING ระหว่างช่วงนั้น แล้วเป็น CHIP_RUNNING เมื่อ ai_engine_snapshot() มีอะไรให้แสดงแล้ว (page_edge_ai.c:1409-1411)
ขั้นที่ 4 — คำมั่นที่บันทึกไว้: select(13) รายงานค่า 253
>>> edge_ai.select(13)
True
>>> edge_ai.active()
253
สิ่งที่ควรสังเกต active() รายงานค่า 253 ไม่ใช่ 13 เพราะ 13 เป็นชื่อเรียกเดิมของชุด Intruder Watch และจุดแปลงทั้ง 2 จุดจับคู่มันเข้ากับ AI_PARALLEL_INTRUDER (253) ตรงตามที่ ai_engine.h:94-96 ให้คำมั่นไว้ โปรแกรมที่เปรียบเทียบ active() กับตัวเลขที่ตนพิมพ์เข้ามาจะสรุปว่านี่คือความล้มเหลว ให้เปรียบเทียบกับดัชนีที่คลี่ออกมาได้แล้วแทน ทั้งนี้ต้องเป็น build ที่มี EDGE_AI_HAS_MIC (ai_engine.c:1850-1859) ส่วนบนอิมเมจที่มีเฉพาะการตรวจจับการเคลื่อนไหว ดัชนีของชุดจะถูกปฏิเสธ และ select() คืนค่า False หลังจากที่เครื่องยนต์พัก s_active ไว้ (ai_engine.c:1843-1849 บันทึกไว้ว่าเรื่องนี้เคยเผางบเวลา 15 s ไป 2 รอบ)
ขั้นที่ 5 — อ่านดิกชันนารีข้อมูลวินิจฉัย (diagnostics)
>>> d = edge_ai.diag()
>>> d['ml_state'], d['npu_cycles'], d['stale_drops']
สิ่งที่ควรสังเกต ได้ dict หนึ่งชุด (modedgeai.c:438-489) ซึ่งตอบมาจาก callback ของ IPC จึงยังมาถึงแม้ ai_task จะค้างอยู่ (deepcraft_task.c:356-395) คู่มือการอ่านฉบับเต็มอยู่ในบท E4 ตอนนี้ให้จำไว้เพียงว่า ml_state เพียงตัวเดียวเชื่อถือไม่ได้ (modedgeai.c:432-437)
กับดัก
- Warning
- ห้ามใช้ ai_engine_active() เป็นเงื่อนไขกั้นเส้นทางสำรอง active() คืนค่า s_current (สถานะ LOADED) และตามหลังคำขออยู่เท่ากับช่วง cold-init ทั้งช่วง ข้อบกพร่องที่บันทึกไว้ (ai_engine.c:1868-1873 และอีกครั้งที่ deepcraft_task.c:634-640): การแตะหน้าจอตั้ง s_active = <sel> แล้วส่ง START ส่วนตัวกันที่อ่าน s_current (ซึ่งยังเป็น −1 ใน tick นั้น) กลับยิง ai_engine_start(0) แล้วทับสิ่งที่เลือกไว้ให้กลับไปเป็นโมเดล 0 — "เลือก Radar กด Load แล้วได้ Motion" ให้ใช้ ai_engine_requested() เป็นเงื่อนไขกั้นแทน เพราะมันลงเป็น −1 เมื่อ init ล้มเหลวด้วยเช่นกัน ซึ่งเป็นสิ่งที่ทำให้ select() ล้มเหลวได้เร็ว
-
ให้ตรวจค่าที่ ai_engine_start() คืนกลับมา ก่อนแตะอัตราหรือสถานะใด ๆในผู้เรียกที่มีอยู่จริงในของที่ส่งมอบ อัตราเซนเซอร์ถูกยืนยันภายในกิ่งที่สำเร็จเท่านั้น (deepcraft_task.c:804-806) ดัชนีที่ผิดจะไม่แตะสถานะ และการยืนยันด้วย Q_ACTIVE จะเผยความผิดนั้นออกมาเอง
-
เรียก ai_engine_init() ก่อน ai_engine_start() เสมอ เมื่อ s_task == NULL ฟังก์ชัน start() จะคืนค่า false อย่างเงียบ ๆ (ai_engine.c:1835) สำนวนที่ส่งมอบมาจึงเรียก init ที่เป็น idempotent ซ้ำที่ทุกจุดที่ start
-
กับดักเรื่องชื่อฟิลด์ ซึ่งเก็บไว้เป็นบันทึกจากอดีต ไฟล์ template/proj_cm55/modules/ai_models/README.md ฉบับก่อนหน้าเคยแสดงตัวอย่างที่อ่าน r.valid, r.label, r.confidence ซึ่งเป็นฟิลด์ที่ ai_result_t (ai_engine.h:73-83) ไม่เคยมี ตัวอย่างนั้นจึงคอมไพล์ไม่ผ่าน ไฟล์ README ที่ส่งมอบใน release นี้แก้แล้ว (ตัวอย่างในนั้นใช้ ai_engine_snapshot() + ai_result_top_positive() แทน README.md:33-40) แต่ข้อสรุปยังคงเดิม: ai_result_t บรรจุ scores[] แบบดิบพร้อม top_class ไม่ใช่ป้ายกำกับ (label) หรือแฟล็กความถูกต้อง ส่วนการยุบรวมผลตัวจริงคือ ai_result_top_positive() (ai_engine.h:249-265) ซึ่งบท E2 แสดงการใช้งานไว้
กล่อง Variant
| mtb-mpy | mtb-only |
| ฝั่งที่สั่งงาน | edge_ai.select()/active()/diag() จาก REPL | ปุ่ม Load บนหน้าจอเท่านั้น (page_edge_ai.c:353-368) หรือโค้ด C บน CM55 ที่เขียนเองซึ่งเรียก ai_engine_start() |
| การยืนยัน | select() บล็อกอยู่ที่ลูป 2 คำถามนาน 15 s | เฝ้าดู badge ของชิป: CHIP_LOADING → CHIP_RUNNING |
| การตรวจว่าเครื่องยนต์ยังอยู่ | edge_ai.diag() | badge ≠ CHIP_FAIL; ฝั่ง C ตรวจ ai_engine_stack_words() != 0 |
| คอนโซล | มีเพียงบรรทัด [MPY] ตอนบูต ไม่มีอะไรต่อการ select | มีเพียง [HB] t=lus tasks=u — ไม่มีผลลัพธ์ทาง UART ของ Edge AI บน variant ใดเลย |