เป้าหมายของหัวข้อนี้
อะไรทำงานก่อน scheduler บน CM33_NS และเหตุใดลำดับจึงเป็นสิ่งที่รับน้ำหนักไว้ทั้งหมด 14 ขั้นใน proj_cm33_ns/main.c ไล่ตามไฟล์ที่ส่งมอบจริง โดยขั้นเดียวที่ variant ทั้งสองแยกทางกันแสดงเป็นแผงเทียบข้างกัน เมื่อจบบทนี้จะบอกได้ว่าถ้าย้ายขั้นใดใน 14 ขั้นนั้น จะพังตรงไหน
ลำดับการทำงานจริงของเฟิร์มแวร์
proj_cm33_ns/main.c โดย main() เริ่มที่ :209 ขั้นที่ 1-6 และ 8-14 เหมือนกันทั้งสอง variant ส่วนขั้นที่ 7 คือจุดแยก
- cybsp_init() — :214-217; CY_ASSERT(0) เมื่อล้มเหลว
- __enable_irq() — :220
- init_potentiometer_adc() ใต้ #if BSP_HAS_POTENTIOMETER — :222-225 เหตุผลที่ขั้นนี้อยู่ตรงนี้ มาจากคอมเมนต์ที่ :132-137: "CM33_NS must initialise + start the SAR ADC before CM55's cm55_sensor_poll can read VR1-4." CM55 ปล่อยให้ทำงานที่ขั้นที่ 12 ดังนั้น ADC ต้องเดินอยู่แล้วก่อนหน้านั้น
- init_retarget_io() — :228 UART มีอยู่จริงนับจากบรรทัดนี้เป็นต้นไป อะไรที่อยู่ก่อนบรรทัดนี้พิมพ์ไม่ได้ ขั้นที่ 1-3 จึงไม่มีสิ่งที่สังเกตได้
- PSA: ลงทะเบียนไดรเวอร์ OPTIGA SE ก่อน แล้วจึง psa_crypto_init() — :230-247:
{
if (psa_ret != PSA_SUCCESS) {
printf("[BOOT] optiga_psa_register failed: %d\n", (int)psa_ret);
}
psa_ret = psa_crypto_init();
if (psa_ret != PSA_SUCCESS) {
printf("[BOOT] psa_crypto_init failed: %d\n", (int)psa_ret);
}
}
คอมเมนต์นั้นคือข้อกำหนด: ลงทะเบียนไดรเวอร์ของ secure element (ชิปนิรภัยแยกส่วน) แล้วจึง init PSA และต้องทำ ก่อนการใช้ TLS ใด ๆ ความล้มเหลวพิมพ์ออกมาทั้งสองแบบ ส่วนความสำเร็จไม่พิมพ์อะไรเลย
- แบนเนอร์ตอนบูต — #ifdef BOOT_VERBOSE คอมไพล์ทิ้งไปแล้ว (:249-254)
- จุดแยกของ variant — :256-315:
#if BENTO_HAS_MPY
BaseType_t xResult = xTaskCreate(
mpy_task_entry,
"MicroPython",
MPY_TASK_STACK_SIZE / sizeof(StackType_t),
NULL,
MPY_TASK_PRIORITY,
NULL
);
if (pdPASS != xResult) {
printf("ERROR: Failed to create MicroPython task\r\n");
CY_ASSERT(0);
}
#else
xTaskCreate(bento_heartbeat_task, "HB", 256, NULL, 1, NULL);
if (!bento_storage_init()) {
printf("ERROR: storage unavailable — config and WiFi credentials "
"will use defaults\r\n");
}
if (!tesaiot_config_init()) {
printf("ERROR: tesaiot_config_init failed\r\n");
}
cm33_ipc_communication_setup();
#endif
- แผง mtb-mpy: มี xTaskCreate(mpy_task_entry, "MicroPython", …) ครั้งเดียว (stack 8 KB, priority 3, :117-118) การเมานต์ที่เก็บข้อมูล การโหลด config และ IPC pipe ไม่เกิดขึ้นตรงนี้ แต่เกิดภายใน task นั้นหลัง scheduler เริ่มทำงานแล้ว: mpy_main.c:592 เมานต์ VFS, :559 tesaiot_config_init(), :561 ipc_tesaiot_refresh_status()
- แผง mtb-only: มีการเรียก 4 ครั้งตามลำดับ — task สัญญาณชีพ (heartbeat) (:303), bento_storage_init() (:306-309), tesaiot_config_init() (:310-312), cm33_ipc_communication_setup() (:313) คอมเมนต์ที่ :273-290 บอกว่าเหตุใดสิ่งเหล่านี้ต้องอยู่ตรงนี้: "Three jobs the MicroPython task performed at boot have to happen here, or they happen nowhere — and all three fail silently" — และสำหรับ pipe: "Without this the link is clean, the boot is quiet, and every CM55 page that talks to CM33 is dead."
มีความต่างเรื่องลำดับหนึ่งข้อที่ต้องสังเกต: บน mtb-only config โหลด (:310) ก่อน ipc_tesaiot_handler_init() (ขั้นที่ 10) เพื่อให้การอ่าน tls_mode ครั้งแรกของ handler นั้นเห็นค่าจริง ส่วนบน mtb-mpy config โหลดทีหลัง ภายใน task ของ VM ซึ่งเป็นเหตุให้ mpy_main.c:601 ต้องเรียก ipc_tesaiot_refresh_status() ตามหลัง
- sensor_auto_task_create() — :318; ตัวฟังก์ชันอยู่ที่ bento_libs/claw/common/mpy/sensor_auto_task.c:1487 สร้างคิวคำขอของ WiFi ตั้ง IPC pipe หากยังไม่ได้ตั้ง (เป็น idempotent — ฝาแฝดฝั่ง CM33 ใน cm55_ipc_communication.c คืนค่าออกตั้งแต่ต้นเมื่อ flag ตั้งไว้แล้ว การเรียกของ mtb-only ที่ขั้นที่ 7 จึงไม่เป็นงานซ้ำซ้อน) สร้าง worker ของ WiFi IPC ก่อนลงทะเบียน IPC callback ของตัวเอง (คอมเมนต์ที่ :1503-1505: worker บล็อกรออยู่ที่คิว และ priority 2 เปิดให้ thread ของ TCP/IP, WCM และ WHD แทรกคิวได้ระหว่าง cy_wcm_init()) ลงทะเบียน sensor_auto_ctrl_callback แล้วจึงสร้าง task ของเซนเซอร์
void sensor_auto_task_create(void) {
if (s_auto_task_handle != NULL) return;
if (s_wifi_req_queue == NULL) {
s_wifi_req_queue = xQueueCreate(AUTO_WIFI_REQ_QUEUE_LEN, sizeof(wifi_ipc_req_t));
if (s_wifi_req_queue == NULL) {
printf("ERROR: Failed to create SensorAuto WiFi queue\r\n");
}
}
if (!s_ipc_initialized) {
cm33_ipc_communication_setup();
Cy_SysLib_Delay(50);
s_ipc_initialized = true;
}
if (s_wifi_ipc_task_handle == NULL) {
BaseType_t wres = xTaskCreate(
wifi_ipc_worker_task,
AUTO_WIFI_IPC_TASK_NAME,
AUTO_WIFI_IPC_STACK_WORDS,
NULL,
AUTO_WIFI_IPC_TASK_PRIORITY,
&s_wifi_ipc_task_handle
);
if (wres != pdPASS) {
printf("ERROR: Failed to create WiFiIPC task\r\n");
}
}
if (!s_ctrl_cb_registered) {
cy_en_ipc_pipe_status_t st = Cy_IPC_Pipe_RegisterCallback(
CM33_IPC_PIPE_EP_ADDR,
sensor_auto_ctrl_callback,
(uint32_t)CM33_IPC_SENSOR_CTRL_CLIENT_ID);
s_ctrl_cb_registered = (st == CY_IPC_PIPE_SUCCESS);
if (!s_ctrl_cb_registered) {
printf("WARN: SensorAuto IPC ctrl callback registration failed (%lu)\r\n",
(unsigned long)st);
}
}
#if defined(USE_KIT_PSE84_EVAL_EPC2) && !defined(CM33_OWNS_I2C_SENSORS)
s_enabled_mask = SENSOR_AUTO_BMM350;
#endif
BaseType_t res = xTaskCreate(
sensor_auto_task_body,
AUTO_TASK_NAME,
AUTO_TASK_STACK_SIZE,
NULL,
AUTO_TASK_PRIORITY,
&s_auto_task_handle
);
- init_hsm_optiga_security() — :321 → ipc_hsm_handler_init() (proj_cm33_ns/ipc_hsm_handler.c:2496): มี semaphore 2 ตัว task แบบ static 2 ตัว (HSM_IPC, HsmProv) และ Cy_IPC_Pipe_RegisterCallback หนึ่งครั้ง สิ่งที่จงใจไม่ให้มี: optiga_manager_init() ฟังก์ชันนี้เป็นสิ่งแรกที่ hsm_task_func ทำ หลัง scheduler เริ่มทำงานแล้ว (ipc_hsm_handler.c:1364) เพราะถ้าเรียกจาก main() จะไปถึง xTimerCreate() ภายใน critical section ที่ยังไม่มี scheduler (:1356-1363) การเรียกที่นี่ครั้งหนึ่งเคยดูเหมือนไม่มีพิษภัย และก็ไม่ได้เป็นเช่นนั้น
- ipc_tesaiot_handler_init() — :324 (modules/tesaiot_config/ipc_tesaiot_handler.c:370): อ่าน tesaiot_config_get_ptr()->tls_mode สร้าง task TESAIOT_CFG และลงทะเบียน callback ของตัวเอง ส่วนข้อความ [TESAIOT_IPC] handler initialized ปิดเสียงไว้ที่ :26 จึงไม่ปรากฏเลย
- init_gfxss_clocks() — เรียก Cy_SysClk_PeriGroupSlaveInit 3 ครั้ง (GPU, DC, MIPI-DSI):
static void init_gfxss_clocks(void)
{
Cy_SysClk_PeriGroupSlaveInit(CY_MMIO_GFXSS_GPU_PERI_NR,
CY_MMIO_GFXSS_GPU_GROUP_NR,
CY_MMIO_GFXSS_GPU_SLAVE_NR,
CY_MMIO_GFXSS_GPU_CLK_HF_NR);
Cy_SysClk_PeriGroupSlaveInit(CY_MMIO_GFXSS_DC_PERI_NR,
CY_MMIO_GFXSS_DC_GROUP_NR,
CY_MMIO_GFXSS_DC_SLAVE_NR,
CY_MMIO_GFXSS_DC_CLK_HF_NR);
Cy_SysClk_PeriGroupSlaveInit(CY_MMIO_GFXSS_MIPIDSI_PERI_NR,
CY_MMIO_GFXSS_MIPIDSI_GROUP_NR,
CY_MMIO_GFXSS_MIPIDSI_SLAVE_NR,
CY_MMIO_GFXSS_MIPIDSI_CLK_HF_NR);
}
design.modus ของ BSP ไม่ได้รวม GFXSS ไว้ CM33_NS จึงต้องเปิดสัญญาณนาฬิกาเหล่านี้ก่อน CM55 จึงจะ init ไปป์ไลน์ของจอแสดงผลได้
- init_cm55_boot() → Cy_SysEnableCM55(MXCM55, CM55_APP_BOOT_ADDR,
CM55_BOOT_WAIT_TIME_USEC):
static void init_cm55_boot(void)
{
Cy_SysEnableCM55(MXCM55, CM55_APP_BOOT_ADDR, CM55_BOOT_WAIT_TIME_USEC);
}
เหตุที่ IPC ต้องมาก่อนบรรทัดนี้: callback ทั้งสามจากขั้นที่ 8-10 ลงทะเบียนบน pipe ที่ตั้งค่าเสร็จก่อน CM55 จะเริ่มทำงาน โค้ดไม่ได้ระบุเรื่องนี้ไว้ในประโยคเดียว ต้องประกอบขึ้นเองจากจุดลงทะเบียน (sensor_auto_task.c:1521, ipc_hsm_handler.c:2519, ipc_tesaiot_handler.c:400) จากการปล่อยคอร์ตรงนี้ และจากรูปแบบความล้มเหลวที่ :284-288
- บล็อก BLE — #if ENABLE_PAGE_BENTO_BUDDY (:342-376) ไม่ขึ้นกับ BENTO_HAS_MPY แต่คอมไพล์ทิ้งไปตามค่าเริ่มต้น (Makefile:64,:305 ENABLE_PAGE_BENTO_BUDDY ?= 0) ดูบทกลุ่ม I
- vTaskStartScheduler() — :379 ไม่คืนค่ากลับมาเลย และมี CY_ASSERT(0) ต่อท้าย
ทีละขั้น
ขั้นที่ 1 — บูตโดยเปิดคอนโซลไว้ แล้วอ่านขั้น PSA
กดรีเซ็ตขณะที่เทอร์มินัลจากบท A0 ต่ออยู่
- สิ่งที่ควรสังเกต
- ไม่มีอะไรจากขั้นที่ 1-4 (ยังไม่มี UART) และไม่มีอะไรจากขั้นที่ 5 เมื่อสำเร็จ มีเพียงรูปของความล้มเหลวเท่านั้น: [BOOT] optiga_psa_register failed: d หรือ [BOOT] psa_crypto_init failed: d (main.c:238, :242) ความสำเร็จเงียบ หากเห็นบรรทัดใดบรรทัดหนึ่ง การทำงานของ TLS ทุกอย่างในบทกลุ่ม C และ D จะล้มเหลวในภายหลัง ให้แก้ข้อนี้ก่อน
ขั้นที่ 2 — ดูจุดแยกคลี่ตัว (แยกตาม variant)
- สิ่งที่ควรสังเกต — mtb-only
- ความล้มเหลวของที่เก็บข้อมูลหรือของ config พิมพ์ออกมาทันที ตามลำดับนี้: storage: SMIF setup failed 0x%08lx หรือ storage: mount failed (d); volume left untouched (bento_storage.c:108, :131) จากนั้น ERROR: storage unavailable — config and WiFi credentials will use defaults (main.c:306-308) แล้วอาจตามด้วย ERROR: tesaiot_config_init failed (main.c:310-312) บนบอร์ดที่ปกติจะไม่มีบรรทัดเหล่านี้เลย และสิบวินาทีหลัง scheduler เริ่มทำงาน [HB] t=lus tasks=u บรรทัดแรกจะมาถึง (main.c:110-112)
- สิ่งที่ควรสังเกต — mtb-mpy
- [MPY] GC heap u KB @ p in s (mpy_main.c:552-554) — task ของ VM เริ่มทำงานแล้ว การเมานต์ VFS การโหลด config และการอ่านข้อมูลรับรองเกิดขึ้นหลังบรรทัดนี้ภายใน task และเงียบเมื่อสำเร็จ ให้สังเกตความต่าง: สคริปต์เมานต์ของฝั่ง mpy จะฟอร์แมตวอลุมเมื่อการเมานต์ผิดพลาด ทุกกรณี (mpy_main.c:487-490, except: เปล่า → mkfs) ส่วน mtb-only ไม่ฟอร์แมตเลย (บท G1)
ขั้นที่ 3 — ยืนยันว่าปล่อย CM55 ให้ทำงานตอนที่ IPC พร้อมแล้ว
- สิ่งที่ควรสังเกต
- หน้าจอ Home เรนเดอร์ออกมา (บท B2) ความล้มเหลวของ CM55 เองเป็นรหัส LED ไม่ใช่ข้อความ — LED2 สลับสถานะทุก 50 ms หมายถึง tesaiot_display_init() ล้มเหลว (proj_cm55/main.c:193-199) LED1 ทุก 50 ms หมายถึง cybsp_init ของ CM55 ล้มเหลว (:180-188) ทั้ง 2 ดวงที่ 100 ms หมายถึงสร้าง app_task ไม่สำเร็จ (:217-224) ดูบท A3 บน mtb-mpy การที่ ui._diag() ที่ REPL ตอบกลับมาได้พิสูจน์ว่า pipe จากขั้นที่ 8-12 ขึ้นแล้ว
ขั้นที่ 4 — บอกให้ได้ว่าย้ายอะไรแล้วพังตรงไหน
สำหรับแต่ละข้อต่อไปนี้ ให้บอกผลที่ตามมาก่อนอ่านคำตอบ
- ย้ายขั้นที่ 3 ไปไว้หลังขั้นที่ 12 → cm55_sensor_poll ของ CM55 จะอ่าน SAR ADC ที่ยังไม่เดิน (main.c:132-137)
- สลับการเรียก 2 ตัวในขั้นที่ 5 → PSA ไม่มีไดรเวอร์ SE ตอนที่ TLS ลงลายเซ็นครั้งแรก ความล้มเหลวจะโผล่ที่บท C4 ไม่ใช่ที่นี่
- บน mtb-only ถ้าเอา cm33_ipc_communication_setup() ออกจากขั้นที่ 7 → การ init handler ในขั้นที่ 9-10 จะ RegisterCallback บน pipe ที่ยังไม่ได้ตั้งค่า "the link is clean, the boot is quiet, and every CM55 page that talks to CM33 is dead" (main.c:284-288)
- เรียก optiga_manager_init() ในขั้นที่ 9 → ได้ xTimerCreate() ภายใน critical section ที่ยังไม่มี scheduler (ipc_hsm_handler.c:1356-1363)
- ย้ายขั้นที่ 11 ไปไว้หลังขั้นที่ 12 → การเริ่มระบบจอแสดงผล (bring-up) ของ CM55 ล้มเหลวที่ขั้นที่ 1 (Cy_GFXSS_Init) และ LED2 กะพริบหนึ่งครั้งวนซ้ำ
- สิ่งที่ควรสังเกต
- คำตอบที่ได้ตรงกับแหล่งอ้างอิงทุกข้อ ขั้นนี้ไม่มีการแฟลชอะไรเลย
กับดัก
- การคาดหวังข้อความตอนบูต CM33_NS: Booting CM55..., CM33_NS: CM55 boot initiated, [BOOT] HSM OPTIGA handler OK และแบนเนอร์ ล้วนเป็น BOOT_VERBOSE จึงคอมไพล์ทิ้งไปแล้ว ส่วน [TESAIOT_IPC] handler initialized ปิดเสียงด้วยมาโคร (ipc_tesaiot_handler.c:26)
- ภาคผนวก X #11 — การเขียนข้อมูลรับรองลงจริง (flush) ของ mtb-mpy ไม่เคยทำงานเมื่อ /main.py วนลูปไม่จบ (mpy_main.c:709/:686) การอ่านตอนบูตในแผง mpy ของขั้นที่ 7 ไม่มีปัญหา แต่ การเขียนกลับ เลื่อนไปให้ตัว flush ที่ทำงานตอน REPL ว่าง ดูบท C1
- ภาคผนวก X #19 — symbol ของ HSM ที่ใช้แบบ weak handler ในขั้นที่ 9 เรียก publish_csr และพวกเดียวกันผ่านพอยน์เตอร์แบบ weak ซึ่งเป็น NULL เว้นแต่ตั้ง ENABLE_OPTIGA_CLM=1 ดูบท D3
- ภาคผนวก X #16 — การต่อดีบักเกอร์บน mtb-only หากต่อเข้าไปเพื่อไล่ดูขั้นเหล่านี้บนบอร์ด mtb-only ที่กำลังทำงาน CM33 จะค้างอยู่ในลูปของ boot ROM (main.c:292-302) ให้ใช้วิธี halt-on-reset จากการแฟลชใหม่แทน
- ขั้น ADC ของโพเทนชิออมิเตอร์เป็นขั้นแบบมีเงื่อนไข BSP_HAS_POTENTIOMETER เป็น 1 บน Eva Kit และบนบอร์ดฐานของ TESAIoT Dev Kit แต่เป็น 0 บน AI Kit เปล่า ๆ อย่ามองหาขั้นนี้บนบอร์ดที่ไม่มี
ขอบเขตการใช้กับแต่ละ variant
- variant ที่ใช้ได้
- mtb-mpy และ mtb-only — โดยจุดแยกที่ขั้นที่ 7 แสดงเป็น 2 แผง ขั้นที่ 1-6 และ 8-14 เป็นโค้ดเดียวกันทั้งสอง variant ส่วนการเรียก mpy_secure บนเส้นทางนี้ (tacp_init, lfs_wifi_creds_* จาก mpy_main.c:570, :589-604) มีอยู่เฉพาะภายใต้ BENTO_HAS_MPY=1 (proj_cm33_ns/Makefile:458-462)