ไม่มีบอร์ดก็ฝึกเรียก API ฝั่งอ่านได้ (Emulator เลียนแบบ API ชุดเดียวกัน แต่ไม่มี IPC จริงในเบราว์เซอร์):
ai_engine.h กับ ipc_model_link_defs.h คู่จอs18_under_the_hood.pylinks/model/active/result/latency) แล้วกด RunEmulator เหมาะกับการซ้อมที่บ้าน เพราะ API ฝั่งอ่านเหมือนบอร์ดจริง — สิ่งที่ต่างคือเซนเซอร์และผลโมเดลเป็นค่าจำลอง และไม่มี IPC ข้ามคอร์จริง ตัวเลข latency กับการยืนยัน select จึงต้องดูบนบอร์ด

จอ emulator ที่รันได้จริง — BENTO Edge AI Emulator บนเบราว์เซอร์ รันโค้ดชุดเดียวกับบอร์ด
page_edge_ai ฉบับจำลอง วาด verdict + confidence bars เหมือนบนบอร์ดจริงai_engine.h แล้วชี้ทีละชั้นได้เลยว่า log ไหนโผล่ตรงไหนบนจอก่อนลงมือ เราดูหน้าตาปลายทางก่อน จะได้รู้ว่า log สามชั้นที่เราจะ trace (transport → control → result) ไปโผล่ตรงไหน
บนบอร์ดจริงเราส่องของจริง จะได้เห็น latency ที่ NPU ใช้จริง:
s18_under_the_hood.py ใน BENTO IDE กด Program to Devicelatency()latency() — NPU vs CPU floatactive() ทันทีหลัง select ในใจ: มันตรงกับที่ขอไหม (confirm by observation ทำงาน)จุดที่ควรสังเกต: โมเดล int8 บน NPU มักเร็วกว่า float32 บน CPU float kernel อย่างเห็นได้ — นี่คือหลักฐานสดของบทเรียน "int8 → NPU" ที่เราเดินมาทั้งคอร์ส
ช่องเติมที่ 1: ถามว่า edge_ai คุยกับ CM55 ผ่านช่องไหน:
# เติม: อ่านว่ามี link backend อะไรบ้าง -> links = edge_ai.links()
links = None
pass
lcd.console(' [0 transport] links() = %s -> IPC model link (deepcraft_task.c)'
% (links,))
pass ด้วย links = edge_ai.links() — คืน tuple เช่น ('ipc',)('ipc',) แปลว่าปลายทางฝั่ง CM55 คือ deepcraft_task.c — สะพานเดียวที่ Python คุยกับ ai_enginelinks เป็น None แล้ว log ชั้น transport ว่างเปล่านี่คือชั้นล่างสุด รู้ก่อนว่า "คุยผ่านอะไร" แล้วค่อยไล่ขึ้นไปว่า "คุยเรื่องอะไร"
ช่องเติมที่ 2: ดึง descriptor ของโมเดลหนึ่งตัวมาส่อง:
def show_descriptor(mi):
# เติม: pull descriptor ของโมเดล index mi -> desc = edge_ai.model(mi)
desc = None
pass
lcd.console(' [1 registry] model(%d): name=%s sensor=%s labels=%s'
% (mi, desc['name'], SENSOR[desc['sensor']], desc['labels']))
pass ด้วย desc = edge_ai.model(mi) — map ไป Q_MODEL → ai_engine_model(i) บน CM55model(n) ดึงตัวเดียวเจาะๆ ต่างจาก models() ที่ดึงทั้งก้อน — เหมาะเวลาอยากดูโมเดลที่เลือกname/sensor/labels) มาจาก ROW macro ใน ai_engine.c ไม่ใช่ค่าที่ Python เดาเช็กว่า
sensorเป็นเลข0/1/2แล้วSENSOR[...]แปลงเป็น IMU/RADAR/MIC — ตรงกับai_sensor_tฝั่ง C เป๊ะ
ช่องเติมที่ 3: select() ให้ไว้แล้ว (ในตัวมัน poll Q_ACTIVE เอง) เราเติม active() เพื่อ เห็น ว่าสลับจริง:
edge_ai.select(sel) # ให้ไว้แล้ว: ส่ง SELECT(0x90+n) + รอยืนยัน
# เติม: อ่านว่า engine สลับไปโมเดลไหนแล้ว -> cur = edge_ai.active()
cur = -1
pass
active_lb.text("active(): %d" % cur)
lcd.console(' [2 control] active() = %d <- s_current @ai_engine.c' % cur)
pass ด้วย cur = edge_ai.active() — คืน s_current (โมเดลที่ สลับจริง)cur ต้องเท่ากับ sel ที่เพิ่งขอ — นี่คือ confirm-by-observation ที่เห็นกับตาtry เพราะ select() โยน OSError ได้ถ้าไม่ยืนยันใน 25×20 msจำ gotcha:
active()=s_currentไม่ใช่s_activeเอกสารภายในของเฟิร์มแวร์เตือนว่าอย่าเอาactive()ไป guard ค่า default — ใช้requested()แทน
ช่องเติมที่ 4 และ 5: หัวใจ MVP วันนี้ อ่าน verdict แล้วดูเวลาอนุมาน:
if running:
# เติม: pull verdict ล่าสุด -> r = edge_ai.result()
r = None
pass
if r and r['seq'] != last_seq:
last_seq = r['seq']
verdict.text(r['label'] or '-')
conf.text("conf: %.0f %%" % (r['conf'] * 100))
# เติม: อ่านเวลาอนุมานครั้งล่าสุด -> ms = edge_ai.latency()
ms = 0.0
pass
lat.text("latency: %.2f ms" % ms)
r = edge_ai.result() — map ไป Q_RESULT → ai_result_t s_res (publish lock-free)ms = edge_ai.latency() — map ไป s_res.inference_us / 1000 เวลาที่ NPU ใช้จริงseq ก่อนเสมอ วาด log เฉพาะตอนมี verdict ใหม่ (เหมือนหน้าจอ 30 Hz ทำ)
r['seq']ที่เราเช็กทุกบทเรียน จริงๆ คือs_res.seqที่publish()เพิ่มขึ้นทุก verdict — วันนี้เรารู้แล้วว่ามันมาจากไหน
เปิด s18_under_the_hood.py ในไฟล์มี pass วางไว้ 5 จุด ตรงคำสั่งฝั่งอ่านของ edge_ai:
| # | จุด | เติมด้วย | map ไป |
|---|---|---|---|
| 1 | transport | links = edge_ai.links() |
IPC model link (deepcraft_task.c) |
| 2 | registry | desc = edge_ai.model(mi) |
Q_MODEL → ai_engine_model(i) |
| 3 | control | cur = edge_ai.active() |
Q_ACTIVE → s_current |
| 4 | result | r = edge_ai.result() |
Q_RESULT → ai_result_t s_res |
| 5 | result | ms = edge_ai.latency() |
s_res.inference_us / 1000 |
ขั้นตอน:
# เติม: ทีละจุด แล้วแทน pass ด้วยคำสั่งตามคำใบ้ai_engine.h กับ ipc_model_link_defs.h ชี้ว่าแต่ละบรรทัดมาจากไหนห้าช่องนี้คือคำสั่งฝั่ง "อ่าน" ทั้งห้าของ
edge_aiเป๊ะ — เติมครบเมื่อไร คุณ trace ทั้งสแตกได้จากปลาย MicroPython
อยากเข้าใจ NPU / tri-core / edge AI ให้ลึกกว่าในบทเรียน ลองตามลิงก์เหล่านี้ (เป็น ลิงก์ ไปดูเอง ไม่ได้ฝังวิดีโอไว้ในสไลด์):
วิดีโอเพื่อการศึกษา
เอกสารทางการ (NPU / runtime)
ภาพ / บทความอ้างอิง (สถาปัตยกรรม tri-core · NPU)
วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง
MVP ของบทเรียน 7.1–7.2 (เกณฑ์ผ่านของชุดบทเรียน): ผู้เรียน อธิบายสแตก ได้ แล้วชี้ ฟังก์ชันหรือฟิลด์ใน ai_engine.h / ipc_model_link_defs.h ของทั้งสามชั้น (transport / control / result) ได้อย่างน้อยชั้นละหนึ่งจุด
active() (s_current) ต่างจากโมเดลที่ "ขอ" (s_active) ยังไง และทำไม publish() ถึง lock-freeชุดบทเรียนนี้ "ผ่าน" ไม่ใช่แค่ "รันได้" — คุณต้องชี้ได้ว่าค่าที่
result()คืนมา มีต้นทางเป็นฟิลด์ไหนในai_result_tและวิ่งผ่าน query plane อันไหน
ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะสแตกจะเข้าหัวตอนที่คุณไล่มันเองก่อน:
# เติม: ทั้ง 5 จุดในไฟล์ฝึก + ตารางหน้าที่แล้ว บอกว่าแต่ละช่องเติมอะไรและ map ไปไหนs18_under_the_hood.py มีโครงครบทั้งไฟล์แล้ว เหลือแค่ 5 บรรทัดฝั่งอ่านให้เติมs18_under_the_hood.py เติมครบพร้อมคอมเมนต์อธิบายทุกชั้น (อ่านให้เข้าใจ ปิดไฟล์ แล้วไล่สแตกเอง)s18_under_the_hood_full.py เพิ่มแผนที่สแตก + result() field map + on_result callback + เทียบ latency int8/float32ลองไล่เองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละชั้น แล้วกลับมาชี้ไฟล์:บรรทัดเอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน
การส่องสแตกครั้งนี้รวบยอดแนวคิดที่เราเดินมาทั้ง 17 ชุดบทเรียน ให้เห็นว่ามันประกอบกันยังไง:
ฝั่งสถาปัตยกรรม / ระบบ
s_models[] ประกอบตอน build จาก ROW macro; s_active นำ s_current.tflite→Vela→NPUฝั่ง MicroPython / API
links/model/active/result/latency ทุกตัวเป็น pullทั้งหมดนี้คือ "แผนที่" ที่ทำให้ชุดบทเรียนถัดไป (บทเรียน 7.3–7.4) เราเพิ่มโมเดลของตัวเองได้ — เพราะเรารู้แล้วว่าต้องไปแตะ ROW ตรงไหน Makefile บรรทัดไหน
การอ่านสแตกเป็นไม่ใช่ academic — วิศวกร Edge AI จริงต้องทำสามอย่างนี้ที่ต้องเข้าใจใต้ฝากระโปรง:
เห็นไหมว่าทุกอย่างที่เราแกะวันนี้ ไม่ใช่ทฤษฎีลอยๆ — มันคือสิ่งที่คุณต้องรู้เพื่อจะ "แก้/ต่อ/จูน" ระบบจริง ไม่ใช่แค่ "เรียกใช้"
งานทำเอง (ท้ายบทเรียน):
s18_under_the_hood.py ให้ครบทั้ง 5 ช่อง กด Trace แล้ว log สามชั้นขึ้นครบ (Emulator หรือบอร์ด)ai_engine.h หรือ ipc_model_link_defs.h ของ SDK (เช่น active() → ai_engine_active() / MODEL_LINK_Q_ACTIVE, result() → ai_result_t)latency() แล้วอธิบายว่าทำไมต่างกัน (ใบ้: NPU vs CPU float kernel)ใบ้ข้อ 3 — โมเดล int8 (Motion/Cough) วิ่งบน Ethos-U55 NPU ส่วน float32 (Push/Siren) วิ่งบน CPU float kernel เวลาจึงต่างกันแม้อยู่ภาพเดียว
วันนี้เราได้: เข้าใจ tri-core และตำแหน่งงาน Edge AI · แกะ ai_engine/registry/s_active vs s_current · เห็น IPC model link สอง plane · รู้ว่า TFLite-Micro คือ runtime จริงที่รัน int8+float32 · trace ทั้งเส้น sensor→dict ด้วย 5 คำสั่งฝั่งอ่าน
ชุดบทเรียนถัดไป (บทเรียน 7.3–7.4) เราจะ เพิ่มโมเดลของตัวเอง ให้โผล่ใน
edge_ai.models()จริง — สามการแก้ (Makefile / ROW / ไฟล์โมเดล) บนแผนที่ที่เราเพิ่งวาดวันนี้ เจอกันครับ