SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (ModusToolbox)
Loading...
Searching...
No Matches
B1 — เดินดูลำดับการบูตของ CM33_NS

เป้าหมายของหัวข้อนี้

อะไรทำงานก่อน scheduler บน CM33_NS และเหตุใดลำดับจึงเป็นสิ่งที่รับน้ำหนักไว้ทั้งหมด 14 ขั้นใน proj_cm33_ns/main.c ไล่ตามไฟล์ที่ส่งมอบจริง โดยขั้นเดียวที่ variant ทั้งสองแยกทางกันแสดงเป็นแผงเทียบข้างกัน เมื่อจบบทนี้จะบอกได้ว่าถ้าย้ายขั้นใดใน 14 ขั้นนั้น จะพังตรงไหน

ลำดับการทำงานจริงของเฟิร์มแวร์

proj_cm33_ns/main.c โดย main() เริ่มที่ :209 ขั้นที่ 1-6 และ 8-14 เหมือนกันทั้งสอง variant ส่วนขั้นที่ 7 คือจุดแยก

  1. cybsp_init():214-217; CY_ASSERT(0) เมื่อล้มเหลว
  2. __enable_irq():220
  3. 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 ต้องเดินอยู่แล้วก่อนหน้านั้น
  4. init_retarget_io():228 UART มีอยู่จริงนับจากบรรทัดนี้เป็นต้นไป อะไรที่อยู่ก่อนบรรทัดนี้พิมพ์ไม่ได้ ขั้นที่ 1-3 จึงไม่มีสิ่งที่สังเกตได้
  5. PSA: ลงทะเบียนไดรเวอร์ OPTIGA SE ก่อน แล้วจึง psa_crypto_init():230-247:
/* ...context: inside main(), before any TLS use ... */
/* Phase G: PSA Crypto + OPTIGA SE driver init.
* MUST be called BEFORE any TLS operations (WiFi, MQTT, HTTPS).
* Order: register SE driver FIRST, then init PSA crypto subsystem.
* psa_crypto_init() makes PSA hash functions available for x509 cert parsing
* (required when MBEDTLS_USE_PSA_CRYPTO is enabled). */
{
psa_status_t psa_ret = optiga_psa_register();
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 ใด ๆ ความล้มเหลวพิมพ์ออกมาทั้งสองแบบ ส่วนความสำเร็จไม่พิมพ์อะไรเลย

  1. แบนเนอร์ตอนบูต — #ifdef BOOT_VERBOSE คอมไพล์ทิ้งไปแล้ว (:249-254)
  2. จุดแยกของ variant:256-315:
/* ...context: inside main() ... */
#if BENTO_HAS_MPY
/* Create MicroPython REPL task */
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
/* mtb-only. Three jobs the MicroPython task performed at boot have to
* happen here, or they happen nowhere — and all three fail silently:
*
* mount / vfs_mount_script -> bento_storage_init()
* load config mpy_main was the only caller of
* tesaiot_config_init()
* configure IPC pipe sensor_auto_task_create() was the ONLY boot-path
* caller of cm33_ipc_communication_setup(); the two
* handler inits below RegisterCallback on that pipe
* and do not set it up themselves. Without this the
* link is clean, the boot is quiet, and every CM55
* page that talks to CM33 is dead.
*
* (Boot WiFi credentials are read inside sensor_auto_task's C path.)
*
* A failed mount is reported and NOT formatted — the mpy variant formats
* on any mount error, but wiping /main.py and the config to recover a
* transient SMIF fault is the wrong trade on a board people work on. */
/* ...context: inside main(), mtb-only branch ... */
/* Liveness heartbeat over UART, every 10 s.
*
* This variant has no REPL, so with the boot prints muted the only sign of
* life used to be the debugger — and attaching the debugger to a running
* board parks CM33 in a boot-ROM loop (measured 2026-08-28; the mpy
* variant tolerates the same attach, cause not established). One line
* every ten seconds is what let that be discovered at all: a heartbeat
* that kept beating for six minutes detached, after an hour of postmortems
* that all blamed the firmware. Keep it until the variant has an
* instrument that is not also the murder weapon. */
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() ตามหลัง

  1. 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;
}
/* Create WiFiIPC worker task BEFORE registering IPC callback.
* Worker blocks on queue — zero CPU when idle. Priority 2 ensures
* TCPIP(4)/WCM(4)/WHD(5) threads can preempt during cy_wcm_init(). */
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);
}
}
/* SensorAuto task: reads sensors and pushes data via IPC to CM55.
*
* Eva Kit (USE_KIT_PSE84_EVAL_EPC2):
* CM55 cm55_sensor_poll owns SCB0 I2C (P8[0]/P8[1]) for BMI270,
* CapSense, Potentiometer. But BMM350 is on the separate I3C bus
* (P3[0]/P3[1]) — no contention. So we create the task on Eva Kit
* too, but restrict the enabled mask to BMM350 only. */
#if defined(USE_KIT_PSE84_EVAL_EPC2) && !defined(CM33_OWNS_I2C_SENSORS)
/* Eva Kit AI-Core: CM55 cm55_sensor_poll owns SCB0 I2C — only BMM350 (I3C) */
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
);
  1. init_hsm_optiga_security():321ipc_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) การเรียกที่นี่ครั้งหนึ่งเคยดูเหมือนไม่มีพิษภัย และก็ไม่ได้เป็นเช่นนั้น
  2. 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 จึงไม่ปรากฏเลย
  3. init_gfxss_clocks() — เรียก Cy_SysClk_PeriGroupSlaveInit 3 ครั้ง (GPU, DC, MIPI-DSI):
/*******************************************************************************
* GFXSS Clock Init (GPU + Display Controller + MIPI-DSI)
*
* Our BSP design.modus does NOT include GFXSS, so init_cycfg_peripherals()
* never enables these peripheral clocks. CM33_NS must enable them before
* CM55 can initialise the display pipeline (DCNano + VGLite + 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 ไปป์ไลน์ของจอแสดงผลได้

  1. 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

  1. บล็อก BLE — #if ENABLE_PAGE_BENTO_BUDDY (:342-376) ไม่ขึ้นกับ BENTO_HAS_MPY แต่คอมไพล์ทิ้งไปตามค่าเริ่มต้น (Makefile:64,:305 ENABLE_PAGE_BENTO_BUDDY ?= 0) ดูบทกลุ่ม I
  2. 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)