- variant ที่ใช้ได้
- mtb-mpy และ mtb-only
CM55 ไม่ขึ้นกับ variant ทุกเรื่องในบทนี้ใช้ได้กับทั้งสองแพ็กเกจ
เป้าหมายของหัวข้อนี้
ต่อสาย page ใหม่เข้ากับ UI ของ CM55 ให้เข้าถึงได้ และรู้ว่าสถานะการต่อสายที่ไม่ครบแต่ละแบบมีหน้าตาอย่างไรโดยรู้จากการสร้างสถานะนั้นขึ้นมาจริงทีละแบบ ตัวอย่างที่ใช้เดินเรื่องคือ Edge AI ซึ่งเป็น page ขนาดเล็กหน้าเดียวในเทมเพลตที่ส่งมอบจริงที่มีชิ้นส่วนครบทั้งสามชิ้น และมีข้อกำหนดการเรียกใช้ create/render/destroy เขียนไว้ส่วนเทมเพลตที่ README แนะนำไว้คือ motion และ environ นั้น บทนี้ ไม่ใช้ ด้วยเหตุผลที่ระบุไว้ในหัวข้อกับดัก
ลำดับการทำงานจริงของเฟิร์มแวร์
(1) รายการใน enum — page_manager.h:65, PAGE_ID_EDGE_AI = 13 ข้อกำหนดการเรียกใช้ที่ล้อมรอบอยู่คือ ABI:
PM_MAX_PAGES มีค่า 24U (page_manager.h:21) และ _Static_assert ที่ :79-80 ปฏิเสธ page_id_t ที่เกินค่านั้น
(2) callback ทั้งสามตัว — page_def_t (page_manager.h:87-95):
typedef struct {
const char *name;
const char *subtitle;
uint32_t accent_color;
lv_obj_t *(*create_cb)(void);
void (*destroy_cb)(void);
bool cacheable;
} page_def_t;
ลงทะเบียนสำหรับ Edge AI ที่ sensorhub_ui.c:254-266 โดยมี BENTO_HAS_EDGE_AI เป็นตัวกัน (guard):
#if defined(BENTO_HAS_EDGE_AI) && (BENTO_HAS_EDGE_AI == 1)
{
page_def_t def = {
.name = "Edge AI",
.subtitle = "On-device inference",
.accent_color = UI_COLOR_ACCENT_PURPLE,
};
pm_register(&s_pm, PAGE_ID_EDGE_AI, &def);
}
ห้ามเรียก page_edge_ai_create/render/destroy โดยตรง — page manager เป็นเจ้าของวงจรชีวิต (lifecycle) และเรียกทั้ง 3 ตัวใน GFX-task context ตัวฟังก์ชันเหล่านี้คอมไพล์มาแล้ว (proj_cm55/Makefile:994 ละ page_edge_ai.c ไว้ symbol มาจาก libbento_cm55.a) ฉะนั้นหากต้องการอ่าน ซอร์ส ของ callback 3 ตัว ให้อ่าน page_motion.c:106/:210/:267
(3) การ์ดบนหน้า Home — page_home.c:147-149 ภายใน s_card_defs[]:
typedef struct {
page_id_t id;
const char *title;
const char *icon;
uint32_t color;
const lv_font_t *icon_font;
const lv_image_dsc_t *icon_img;
} home_card_def_t;
static const home_card_def_t s_card_defs[] = {
{ PAGE_ID_DASHBOARD, "Sensor Dashboard", NULL, UI_COLOR_SENSOR_IMU, NULL, &icon_dashboard },
#if ENABLE_PAGE_GPIO_RGB
{ PAGE_ID_GPIO_RGB, "GPIO & RGB Matrix", NULL, UI_COLOR_ACCENT_GREEN, NULL, &icon_touch_rgb },
#endif
#if ENABLE_PAGE_MOTOR_CTRL
{ PAGE_ID_MOTOR_CTRL, "Motor Controller", LV_SYMBOL_REFRESH, UI_COLOR_ACCENT_ORANGE, NULL, NULL },
#endif
#if defined(BENTO_HAS_EDGE_AI) && (BENTO_HAS_EDGE_AI == 1)
{ PAGE_ID_EDGE_AI, "Edge AI", NULL, UI_COLOR_ACCENT_PURPLE, NULL, &icon_ai_chip },
#endif
จุดที่ทั้งสามชิ้นมาบรรจบกันขณะทำงาน การ์ดแต่ละใบผูก card_click_cb ไว้กับ page id ของตัวเอง:
s_ctx.card_page_ids[i] = def->id;
lv_obj_add_flag(card, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(card, card_click_cb, LV_EVENT_CLICKED,
&s_ctx.card_page_ids[i]);
…ซึ่งเรียก pm_navigate ต่อ:
void pm_navigate(page_manager_t *pm, page_id_t target)
{
if (pm->animating) return;
if (target >= pm->page_count) return;
if (pm->pages[target].create_cb == NULL) return;
if (target == pm->current_page) return;
page_id_t leaving = pm->current_page;
bool cache_leaving = pm->pages[leaving].cacheable;
if (leaving == PAGE_ID_PLAYGROUND) {
pm_notify_sensor_resume();
}
if (cache_leaving) {
pm->cached_screens[leaving] = lv_screen_active();
} else {
if (pm->pages[leaving].destroy_cb) {
pm->pages[leaving].destroy_cb();
}
}
if (pm->nav_top < PM_NAV_STACK_DEPTH - 1) {
pm->nav_top++;
pm->nav_stack[pm->nav_top] = leaving;
}
lv_obj_t *new_scr;
if (pm->pages[target].cacheable && pm->cached_screens[target] != NULL) {
new_scr = pm->cached_screens[target];
pm->cached_screens[target] = NULL;
} else {
new_scr = pm->pages[target].create_cb();
}
if (new_scr == NULL) return;
lv_obj_t *leaving_scr = lv_screen_active();
lv_obj_remove_event_cb(leaving_scr, pm_unloaded_cb);
lv_obj_add_event_cb(leaving_scr, pm_unloaded_cb,
LV_EVENT_SCREEN_UNLOADED, pm);
pm->animating = true;
lv_screen_load_anim(new_scr, LV_SCR_LOAD_ANIM_MOVE_LEFT,
PM_ANIM_TIME_MS, 0, !cache_leaving);
create_cb ทำงาน หน้าจอเลื่อนไปทางซ้ายภายในเวลา PM_ANIM_TIME_MS (300, page_manager.h:29) render_cb ขับด้วย pm_render จาก timer 33 ms (sensorhub_ui.c:166) เท่านั้น พร้อม sensorhub_snapshot_t ของ tick นั้น และปุ่ม Back เรียก destroy_cb (page_manager.c:107-108)
จุดแก้ไข 5 จุดใน Makefile ที่ README ไม่ได้กล่าวถึง
template/README.md:95-97 ระบุว่ามี "three places" แต่ ไดเรกทอรี ของ page ใหม่ยังต้องการจุดต่อไปนี้ด้วยและต้องแก้ด้วยมือทั้งหมด (bento.sh:245-253 มีคำสั่ง doctor|menus|enable|disable|remove|build|flash|verify|clean — ไม่มี add):
| # | สิ่งที่ต้องแก้ | proj_cm55/Makefile |
| 1 | ค่าเริ่มต้นของ flag ENABLE_PAGE_X ?= 1 | :23-30 |
| 2 | ตัวกันอัตโนมัติ: $(if $(filter 1,$(ENABLE_PAGE_X)),$(if $(wildcard $(_PD)/x/.),,$(eval ENABLE_PAGE_X:=0))) | :65-72 |
| 3 | บรรทัด CY_IGNORE เมื่อ flag เป็น 0 | :636-700 |
| 4 | INCLUDES+=modules/page-components/<dir> — ไม่มี wildcard มีเฉพาะ _core และไดเรกทอรีที่ระบุไว้เท่านั้นที่อยู่บน include path | :737-798 |
| 5 | DEFINES+=ENABLE_PAGE_X=$(ENABLE_PAGE_X) | :897-898 |
เพิ่มเติมคือ #include "page_x.h" ใน sensorhub_ui.c:34-79
ทีละขั้น — สร้างสถานะการต่อสายทั้ง 4 แบบขึ้นมาโดยตั้งใจ
ทำตามลำดับนี้บนสำเนาของชิ้นส่วน Edge AI ที่เปลี่ยนชื่อเป็น page ของตนเอง และสร้าง CM55 ใหม่ระหว่างแต่ละขั้น เส้นทางนี้ไม่มีผลลัพธ์ออกทาง UART (proj_cm55/main.c:9-10) — สิ่งที่สังเกตได้ทั้งหมดคือสถานะบนหน้าจอ
สถานะที่ 1 — มีเฉพาะ enum
เพิ่ม PAGE_ID_X = 24, ไว้ท้าย page_id_t (เลขว่างตัวถัดไป) และเพิ่มค่า PM_MAX_PAGES หาก static assert ทำงาน
สิ่งที่ควรสังเกต build ผ่าน Home grid ไม่เปลี่ยน ไม่มีสัญญาณใด ๆ
สถานะที่ 2 — enum + pm_register แต่ไม่มีการ์ด
เพิ่มบล็อก pm_register และจุดแก้ไขใน Makefile แต่ไม่เพิ่มรายการใน s_card_defs[]
สิ่งที่ควรสังเกต build ผ่าน บอร์ดบูตขึ้น Home grid เหมือนเดิม และ page นั้นตายสนิท นี่คือสถานะของ Motion และ Environ ในของที่ส่งมอบจริง — ลงทะเบียนไว้ที่ sensorhub_ui.c:235 และ :250 เปิดใช้งานเป็นค่าเริ่มต้น (proj_cm55/Makefile:23-24) แต่ไม่ปรากฏใน page_home.c:138-200 README.md:216 ระบุข้อมูลวินิจฉัยไว้ว่า "Menu is missing from Home | its flag is 0, or s_card_defs[] has no entry. ./bento.sh menus tells you which."
สถานะที่ 3 — มีการ์ด แต่ไม่มี pm_register
คอมเมนต์บล็อก pm_register ทิ้งไว้ และคงการ์ดไว้
สิ่งที่ควรสังเกต การ์ดเรนเดอร์ออกมาตามปกติ แต่การแตะการ์ด ไม่เกิดอะไรขึ้น — pm_navigate คืนค่าออกไปเมื่อ create_cb == NULL (page_manager.c:92) เป็นการเพิกเฉยอย่างเงียบ ๆ ไม่ใช่การ crash README.md:100 และ :217 ระบุว่า "crashes the moment it is tapped" ข้ออ้างนั้นล้าสมัยแล้ว
สถานะที่ 4 — ครบทั้งสามชิ้น
นำบล็อก pm_register กลับเข้ามา
สิ่งที่ควรสังเกต การ์ดใบใหม่ปรากฏที่ตำแหน่งตามรายการใน s_card_defs[] พร้อมสีเน้นจาก .color การแตะทำให้หน้าจอเลื่อนไปทางซ้ายภายใน 300 ms render_cb เริ่มทำงานที่ประมาณ 33 Hz ปุ่ม Back เลื่อนกลับไปทางขวา (page_manager.c:198) และ destroy_cb ทำงาน
ลายเซ็นความล้มเหลวตอนคอมไพล์ 2 แบบที่ควรเห็นสักครั้ง
- ละจุดแก้ไขที่ 4 → fatal error: page_x.h: No such file or directory จาก sensorhub_ui.c เป็น error ตอนคอมไพล์ ไม่ใช่ error ตอนลิงก์
- เปลี่ยนหมายเลขของ page ที่มีอยู่เดิม → page_id_ordinal_assert.c:22-25 ทำงาน: "PAGE_ID_PLAYGROUND has moved. lib/ipc_core/libbento_ipc.a was built for ordinal 7 and stores it as an immediate; a mismatch links cleanly and compares the wrong page at runtime."
กับดัก
- Warning
- ห้ามคัดลอก motion หรือ environ ไปใช้เป็นเทมเพลต ทั้งสองอยู่ในสถานะ "registered, no card" ซึ่ง README เองเรียกว่าเป็นความผิดพลาดที่พบบ่อยที่สุด ผู้อ่านที่ลอกครบทั้ง 3 จุดจากต้นแบบที่ต่อสายไว้เพียง 2 จุดจะไม่เห็นอะไรเลย
-
ลำดับเลข (ordinal) ของ page_id_t เป็น ABI libbento_ipc.a ฝังค่า PAGE_ID_PLAYGROUND == 7 ไว้ (lib/ipc_core/PROVENANCE.txt) ให้เพิ่มไว้ท้ายสุดเสมอ ห้ามเปลี่ยนหมายเลข ห้ามใส่ตัวกันใหม่ คอมเมนต์อธิบายเหตุผลที่ page_id_ordinal_assert.c:11-13 ยังบรรยาย enum แบบเดิมที่มีตัวกัน — ตัว assert ถูกต้อง แต่คอมเมนต์ล้าสมัย
-
งาน LVGL ทั้งหมดต้องอยู่ใน GFX task create_cb/render_cb/destroy_cb ของ page ที่เขียนเองทำงานอยู่ที่นั่นอยู่แล้ว หาก page นั้นมี widget ที่ขับด้วย IPC ให้ผูก container ตอน create และตั้งเป็น NULL ตอน destroy (บท F2 และ B3)
-
page_edge_ai.h:12-14 ระบุว่า "PM_MAX_PAGES is 20 … 18 pages" ค่าจริงคือ 24 และมีการลงทะเบียน 25 รายการ (จัดทำดัชนีด้วย id จึงปลอดภัย) ห้ามกำหนดขนาดของสิ่งใดจากคอมเมนต์นั้น
กล่อง variant
เหมือนกันทั้งสอง variant บรรทัดเดียวใน proj_cm55/Makefile ที่ขึ้นกับ variant คือ gate ของ MPY ที่ :237 และป้ายเวอร์ชัน การต่อสาย page ไม่ไปแตะบรรทัดนั้น