|
SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
|
ทะเบียนที่รวบรวมไว้ ทุกรายการอ้างบรรทัดที่เป็นหลักฐานยืนยัน หมายเลขของแต่ละรายการตรงกับเอกสารออกแบบที่ถือเป็นฉบับอ้างอิง เพื่อให้บทต่าง ๆ อ้างถึงด้วยหมายเลขได้
CM55 ไม่มีคอนโซล UART เป็นของ CM33_NS และ newlib stdio บน CM55 จะไปจับ mutex ที่ไม่เคยถูกเตรียมไว้แล้วหยุดคอร์ proj_cm55/bento_cm55_stdio.c จึงนิยาม printf และ puts เป็น no-op แบบ weak ซึ่งเป็นซอร์สเปิด printf ในโค้ด CM55 ที่เขียนเองจึงไม่พิมพ์อะไรออกมาจนกว่าจะนิยาม printf ของตนเอง (strong ชนะในขั้นลิงก์) หรือส่งข้อความผ่าน IPC ไปยังคอนโซลของ CM33_NS libbento_edge_ai.a ไม่ได้นิยามทั้งสองตัวนี้ (ไม่มีใน lib/edge_ai/api.txt)
CM55 ตีความคำสั่งนั้นเป็น touch_disabled = false แบบไม่มีเงื่อนไข ซึ่งยกเลิกการจับการสัมผัส (touch) ที่ task อื่นถืออยู่ และทำให้เกิด OPTIGA_COMMS_ERROR (0x0102) บนบัสที่ใช้ร่วมกัน (ipc_hsm_handler.c:1393-1405) ให้ใช้คู่ optiga_manager_touch_hold()/_release() ที่มีการนับแทน
ฟังก์ชันนี้จงใจคืนค่า true แม้ manager จะยังไม่เคย init เลย (ประกาศไว้ใน tesaiot_hsm_api.h) ตัวทดสอบว่า "manager ขึ้นแล้วหรือยัง" คือ optiga_manager_lock() การเข้าใจ 2 อย่างนี้สลับกัน คือสาเหตุที่บันทึกไว้ของข้อบกพร่องเรื่องการเข้าถึงโดยไม่ผ่าน gate (ด่านกั้น) หลายกรณี
คำขอทั้งสองทำงานแบบอะซิงโครนัส และ resume ที่ออกไปขณะที่คำขอปรับอัตรายังค้างอยู่อาจยกเลิกคำขอนั้น CM33 จะหยุดการส่งข้อมูลเซนเซอร์แทนการปรับอัตรา set_sensor_rate() กลับมาส่งข้อมูลต่อให้เองอยู่แล้ว ส่วน resume_sensor ไม่มีผู้เรียกในเฟิร์มแวร์ที่ส่งมอบจริง
active() รายงานโมเดลที่โหลดเสร็จแล้ว จึงตามหลังคำขออยู่หนึ่ง cold-init เต็ม ๆ อาการที่บันทึกไว้คือ เลือก Radar แล้วกด Load แต่ได้โมเดล Motion ให้กันด้วย ai_engine_requested() แทน
ฟังก์ชันนี้คืนผลตัดสินล่าสุดจากโมเดลตัวใดก็ได้ ภายใน set การตรวจจับเสียงไอที่ 0.94 จะถูกตามด้วยผล "unlabelled" จากสมาชิกอีกสามตัวก่อนที่ผู้อ่านจะเห็นมัน — การตรวจจับไม่ได้มาช้า แต่หายไปเลย ให้ใช้ snapshot_model() ทีละสมาชิกแทน
ไม่มีรูปแบบ set ที่เป็น idempotent ให้ใช้ ต้องกันด้วย ipc_lcd_is_panel_visible() เสมอ (ipc_core แถวที่ 2, page_playground.c:167-188) คอนโซลกับ widget ใช้ร่วมกันไม่ได้ ต้องเลือกอย่างใดอย่างหนึ่ง
lv_screen_load_anim(auto_del) ปลดหน่วยความจำอ็อบเจ็กต์ของหน้านั้น แล้ว IPC handler ที่มาทีหลังจะเขียนลงไปในอ็อบเจ็กต์เหล่านั้น (ipc_core แถวที่ 3, page_playground.c:342-349) ให้ตั้ง container ทั้งสองเป็น NULL ตอนทำลายหน้าโดยเรียงลำดับเหมือนตอนผูก
"Safe to call from any task context" แต่ "Clears the 'changed' flags after reading" (ipc_sensorhub.h:66-67) — ต่อหนึ่ง tick มีผู้อ่านได้รายเดียวเท่านั้น UI เก็บ snapshot หนึ่งชุดทุก 33 ms แล้วกระจายต่อ
ให้แปลง SSID → slot ด้วย wifi_saved_find() ก่อนการลบทุกครั้ง (wifi_connect_native.c:118-134) และคืนค่า < 0 เมื่อไม่พบ
wifi_creds_flush_if_dirty มีการเรียกเฉพาะที่ mpy_main.c:709 และ :686 (ตอน REPL ว่างหรือตอนออก) โปรแกรมที่ไม่เคยคืนกลับสู่ REPL จึงไม่เคยเก็บข้อมูลรับรองไว้อย่างถาวรเลย ส่วน mtb-only เขียนทันที (sensor_auto_task.c:784-789 เทียบกับ :828-854)
bundle ที่ค้างแบบ retained จะมาถึงทุกครั้งที่เชื่อมต่อ พบมาแล้ว 3 ครั้งเมื่อ 2026-08-07 โดยทำงานของรอบก่อนซ้ำอย่างเงียบ ๆ trustm_current_correlation_id() == NULL หมายความว่าไม่มีงานค้างอยู่ — ให้ ทิ้ง (tesaiot_pu_ingest.c:752-768) ที่จุดซึ่งใช้ symbol แบบ weak ให้ตรวจพอยน์เตอร์ฟังก์ชันก่อน แล้วจึงตรวจข้อความ
GFX task ทำงานที่ MAX−1 อุปกรณ์ที่ค้างและยืดสัญญาณนาฬิกา เมื่อมาพร้อม timeout ของ PDL แบบบล็อกตลอดกาล เคยทำให้ task ของ CM55 ที่ priority ต่ำกว่าทั้งหมดอดทำงาน ให้ใช้ timeout ที่มีขอบเวลา (2 ms) กับ disp_touch_i2c_controller_context (cm55_sensor_poll.c:49-53)
page_id_ordinal_assert.c:22-25; lib/ipc_core/PROVENANCE.txt บันทึกไว้ว่า PAGE_ID_PLAYGROUND == 7 หากไม่ตรงกัน จะเชื่อม (link) ผ่านอย่างสะอาดแล้วไปเทียบกับหน้าที่ผิด ให้เพิ่มต่อท้ายเท่านั้น ห้ามเปลี่ยนเลขลำดับ และห้ามเปลี่ยนตัวกันของรายการเดิม
พอร์ตของ MQTT คำนวณมาจาก tls_mode (mqtt_client_config.c:105-118) การแก้ port ใน /.tesaiot_config ไม่ทำให้อะไรเปลี่ยน
main.c:278-287 สัญญาณชีพ (heartbeat) มีอยู่เพราะ variant นี้ไม่มีเครื่องมือวัดใดที่ "that is not also the murder weapon" ให้แฟลชแล้วตัดไฟจ่ายไฟใหม่ ห้ามต่อเข้ากับบอร์ดที่กำลังทำงานอยู่
ห้ามใช้ strlen ของบัฟเฟอร์ที่ถูกตัดสั้น (mpy_secure แถวที่ 9, modbentoclaw.c:1972-1994)
modwifi.c:261-285 และ wifi_init.c:261-302 ทำ read-modify-write กับไฟล์นั้นโดยตรงโดยไม่จับ wifi_creds_lock และข้าม g_boot_wifi_creds ไป ไม่มี lock ระดับไฟล์อยู่นอกเหนือจาก bs_lock() ของ mtb-only (J8 thin-evidence #6)
ฟังก์ชัน tesaiot_hsm 6 ตัวจากทั้งหมด 18 ตัว เชื่อม (link) เข้ามาเฉพาะภายใต้ ENABLE_OPTIGA_CLM=1 และผู้เรียกใช้ในรูป weak symbol ได้แก่ publish_csr, tesaiot_publish_protected_update, trustm_reset_state, trustm_current_correlation_id, trustm_requested_target_oid, trustm_requested_anchor_oid (คำนำของหัวข้อ A.5; ข้อความปฏิเสธที่ ipc_hsm_handler.c:2158-2161)
task รุ่นเก่าข้ามขั้น init ของ WL_REG_ON / WCM ที่ CYW55513 ต้องการ (main.c:344-347) ไม่มีผู้เรียกที่ใดเลย ให้ใช้ install_chip_power_then_ble() แทน
การรีเซ็ตจากดีบักเกอร์ทำให้จอดับ ซึ่งดูเหมือนการแฟลชที่ล้มเหลวทุกประการ (README ในแพ็กเกจที่ส่งมอบ: ฉบับ dist mtb-mpy :51-52, mtb-only :54; ฉบับในไฟล์ zip :47-49) อธิบายไว้ในบท A1/A2 ที่จุดตรวจแรก จอดับหลังการแฟลช = การรีเซ็ต ไม่ใช่ความล้มเหลว
backlight อาจต้องการการถอดสายแล้วเสียบกลับอีกครั้งหนึ่งเมื่อบูตเย็น (README ฉบับ dist ของ mtb-only :55) จอที่ดับหลังตัดไฟแล้วจ่ายไฟใหม่ยังไม่ใช่หลักฐานว่าอิมเมจเสีย
ค่าต้องเป็น 1 หรือ 0 เปล่า ๆ เท่านั้น Makefile ที่นำค่าไปใช้เทียบด้วย ifeq ($(VAR),1) ซึ่งไวต่อช่องว่าง ดังนั้น BSP_HAS_CAPSENSE=1 (หรือคอมเมนต์ต่อท้ายค่าในบรรทัดเดียวกัน) จะประเมินได้เป็นเท็จ และ INCLUDES/CFLAGS ของคุณสมบัตินั้นจะหายไปโดยไม่มี error ที่ใดเลย — build สำเร็จ แต่เซนเซอร์ไม่มีอยู่เฉย ๆ ไฟล์นั้นระบุกฎข้อนี้ไว้เอง (bsp_features.mk:28-30 และย้ำอีกครั้งที่ :55-57) อธิบายไว้ใน flag ของ BSP ตามที่บอร์ดนี้ตั้งไว้
CY_SECTION_SHAREDMEM ipc_msg_t gpio_ipc_msg จงใจไม่ประกาศเป็น static เพราะส่วนการทำงานของ mpy_secure (ใน libbento_mpy.a) ใช้บัฟเฟอร์ตัวเดียวกันซ้ำสำหรับการหยุดและการกลับมาทำงานของการสัมผัส (touch) เนื่องจาก region m33_allocatable_shared เล็กเกินไปบนบางชุดคิท (Eva: 4 KB และเต็มแล้ว) จนให้แต่ละโมดูลมีบัฟเฟอร์ของตัวเองไม่ได้ (modgpio.c:104-107) การใช้ร่วมกันนี้ปลอดภัยบนข้อสมมติเพียงข้อเดียว — ทั้งสองโมดูลส่งจาก task ของ MicroPython เท่านั้น จึงไม่มีทางทำงานพร้อมกัน การส่งการอัปเดตสถานะ LED จาก task อื่นใด อาจทำให้การหยุดการสัมผัสของ OPTIGA ที่กำลังเดินทางอยู่เสียหายได้ อธิบายไว้ใน กับดัก
CY_RSLT_MODULE_MQTT_CONNECT_FAIL ถูกใส่ค่าตอน retry หมด (cy_mqtt_api.c:2436) ทับค่าที่อยู่ใน result เดิมทิ้ง TLS ล้มกับการถูกปฏิเสธตอนยืนยันตัวตนจึงพิมพ์รหัสเดียวกันเป๊ะ เมื่อ 2026-09-11 มีข้อผิดพลาดสามจุดคนละชั้นกัน ทั้งสามพิมพ์รหัสนี้ และรหัสไม่เคยเปลี่ยนเลยตลอดการแก้ทีละจุด
สัญญาณที่ใช้ได้จริงคือบรรทัด [TLS-DIAG] err= ที่ cy_tls.c:1860 มันอยู่ใน loop ของ mbedtls_ssl_handshake() จึงพิมพ์ทุกครั้งที่ handshake ล้ม การที่มันไม่พิมพ์ คือหลักฐานว่า TLS ผ่าน ระวังบรรทัด [TLS-DIAG] อื่น (optiga_key, heap) ที่พิมพ์ก่อน handshake — มันไม่ได้บอกอะไรเกี่ยวกับ TLS
mqtt_mtls_setup.c:232 เลือกคู่ 0xE0E1/0xE0F1 ทันทีที่ verify_cert_key_pair() เป็นจริง ใบที่ยื่นจึง ไม่ใช่ค่าที่ตั้งได้ แต่ถูกตัดสินตอนเชื่อมต่อจากผลของการ enrol
ผลคือความล้มเหลวกลับหัว — บอร์ดที่ enrol ไม่สำเร็จ ต่อได้ ส่วนบอร์ดที่ สำเร็จ ถูกปฏิเสธ จนกว่า broker จะมี CN=TESAIoT MCU CA ใน trust bundle และการตรวจ CN จะเทียบกับ device_id ไม่ใช่กับ MQTT client id (เฟิร์มแวร์ส่ง Trust M UID เป็น client id โดยเจตนา) ดู D2 — การลงทะเบียนและ Protected Update ตั้งแต่ต้นจนจบ
หลัง enrol หลายรอบ OPTIGA อาจเปิด application ไม่ได้ (optiga_util_open_application failed status 0x0102) mTLS จึงอ่านใบไม่ได้ และ MQTT ไม่เคยเริ่ม — tesaiot.connect() ไม่พิมพ์อะไรเลย ไม่มีแม้แต่ [MQTT] Start request received
HTTPS ยังทำงานตลอด เพราะยืนยันตัวตนด้วย API key ไม่แตะชิป ความไม่สมมาตรนี้ทำให้ดูเหมือนปัญหาของ MQTT หรือของ broker ทั้งที่ไม่ใช่ เช็ค optiga.init() ก่อนจะไปอ่าน log ของ broker และการแฟลชใหม่ไม่ช่วย เพราะรีเซ็ตแค่ MCU ไม่ได้รีเซ็ตชิป — ต้องตัดไฟ
ทั้งสองคอร์ลิงก์ด้วย --specs=nano.specs newlib-nano รับ length modifier ได้เพียงหนึ่งตัวอักษรจากชุด h, l, L (_WANT_IO_LONG_LONG ไม่ได้เปิด) llu จึงพิมพ์ตัวอักษร "lu" ออกมา และไม่ใช้อาร์กิวเมนต์เลย conversion ถัดจากนั้นทุกตัวจะอ่านอาร์กิวเมนต์ผิดตำแหน่ง หากมี s ตามมา ค่าจำนวนเต็มจะถูกใช้เป็น pointer และเกิด BusFault ใน memchr ← _printf_i zu, jd, td, hhu เป็นแบบเดียวกัน และ -Wformat จับไม่ได้เพราะเป็นรูปแบบที่ถูกต้องตาม C99 ให้พิมพ์ค่า 64 บิตเป็นสองครึ่ง 32 บิต (0x%08lx%08lx) หรือแปลงเป็นสตริงเอง (bento_time.c bt_i64_to_str) และ cast size_t เป็น unsigned long แล้วใช้ lu
หน้า WiFi เก็บเครือข่ายที่บันทึกไว้สูงสุด 6 เครือข่ายใน slot 5, 6, 8, 9, 10, 11 (wifi_saved.h) หนึ่ง record ขนาด 106 ไบต์ต่อหนึ่ง OID มีทั้ง SSID และรหัสผ่าน ชื่อ macro TESAIOT_SLOT_WIFI_SSID, TESAIOT_SLOT_USER0..3 ไม่ตรงกับการใช้งานจริง ที่เก็บนี้ถือว่า object ที่สั้นกว่าหนึ่ง record คือ slot ว่าง และเติม slot ว่างก่อน ค่าสั้น ๆ ที่แอปพลิเคชันเขียนไว้ใน OID เหล่านี้ (เช่น API key) จึงถูกเขียนทับเมื่อมีการบันทึก WiFi บนหน้าจอครั้งถัดไป นอกจากนี้ 0xF1D2 เก็บ hash ของ PIN หน้า HSM และ 0xF1D4 สงวนไว้สำหรับ Protected Update ข้อมูลของแอปพลิเคชันให้เก็บที่ 0xF1E0 หรือ 0xF1E1 (1500 ไบต์) ตารางเต็มอยู่ใน ipc_communication.h
CM55 รันโค้ดแบบ XIP จากชิป NOR ตัวเดียวกับที่เก็บ LittleFS ระหว่างลบ sector 256 KB (ปกติ ~0.8 วินาที สูงสุด 5.9 วินาที) ชิปไม่ตอบการอ่าน และ CM55 ที่ต้อง fetch คำสั่งถัดไปจะหยุดทำงาน จอหยุดกลางเฟรม touch ไม่ตอบสนอง และ IPC pipe ค้าง bento_storage ขอให้ CM55 พักการทำงานไว้ใน ITCM ก่อนการลบหรือเขียนทุกครั้ง (XIP guard, ipc_xip_guard.h) และปฏิเสธการเขียนหาก CM55 ไม่ตอบ ใน variant mtb-mpy ไดรเวอร์ QSPI ของ MicroPython ใช้กลไกเดียวกัน (EXT_FLASH_XIP_GUARD=1) ห้ามเรียก mtb_serial_memory_erase/write หรือ Cy_SMIF_Mem* โดยตรงขณะที่ CM55 ทำงาน ข้อมูลที่เกิดถี่ควรพักไว้ใน RAM แล้วเขียนเป็นชุด เพราะการเขียนแต่ละครั้งอาจทำให้จอหยุดได้นานเท่ากับการลบหนึ่งครั้ง
ค่าเริ่มต้น SCB_AIRCR_BFHFNMINS_VAL 0 (partition_ARMCM33.h ของ secure image) ส่ง BusFault/HardFault ไปที่ฝั่ง secure ซึ่งเก็บ frame ไว้ใน cy_faultFrame แล้ววนอยู่ใน Cy_SysLib_ProcessingFault โดยไม่พิมพ์อะไร ฝั่ง non-secure จะหยุดเงียบ ๆ ขณะที่ CM55 ยังวาดจอต่อ ระหว่างพัฒนาอาจตั้งเป็น 1 เพื่อให้ handler ฝั่ง non-secure เห็น fault ได้ (handler ใน ns_start_pse84.c เลือก stack จาก EXC_RETURN แล้ว) แต่ค่านี้ทำให้ fault ที่เกิดในโค้ด secure และ NMI ถูกส่งไปให้ฝั่ง non-secure จัดการด้วย อย่าใช้ในเฟิร์มแวร์ที่ส่งมอบ