SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
Loading...
Searching...
No Matches
H1 — TACP: โปรโตคอลสายสื่อสารของ IDE
variant ที่ใช้ได้
mtb-mpy เท่านั้น (ไม่มีการลิงก์ libbento_mpy.a เมื่อ BENTO_HAS_MPY=0)

บทนี้ไม่รวมอยู่ในชุดเอกสารของ mtb-only เพราะ variant นั้นไม่มี REPL และไม่มี TACP ซึ่งเป็นเหตุผลที่ BENTO IDE แฟลชมันไม่ได้ (variants/mtb-only.mk ~:19-20)

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

รู้กฎ 3 ข้อของ TACP: init หนึ่งครั้งต่อหนึ่ง soft reset ปั๊ม UART ภายในทุกลูปที่รอ และประกอบเฟรมของทุกคำตอบด้วยความยาวจริงของมัน พร้อมรู้จักทางออกฉุกเฉินฝั่ง ISR ที่มีไว้สำหรับวันที่ /main.py วิ่งหลุดจนทำให้ตัวปั๊มอดตาย

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

init หนึ่งครั้งต่อหนึ่ง soft reset tacp_init() ล้าง ring buffer, state machine และ FIFO ของ UART เพื่อไม่ให้ไบต์ที่ค้างอยู่ทำให้เกิดการรีเซ็ตซ้ำสองครั้ง:

/* ...context: inside the MicroPython task, at the soft_reset label ... */
soft_reset:
/* Clear TACP ring buffer, state machine, and UART FIFO on every
* soft reset so stale bytes don't cause double-resets. */
/* Peek at safe-boot flag early so g_is_safe_boot is correct before
* check_boot_mode() runs later. Defensive: if any code path calls
* tacp_poll_uart() during init, the TACP handler sees the right value.
* The flag is NOT consumed here — check_boot_mode() handles that. */
g_is_safe_boot = (s_safe_boot_once_flag == MPY_SAFE_BOOT_MAGIC);

ตัวปั๊ม tacp_poll_uart() เป็นตัวปั๊ม ไม่ใช่ตัวตรวจเงื่อนไข — จุดเรียกทั้ง 7 จุดในเทมเพลตทิ้งค่าที่คืนมาทั้งหมด ฟังก์ชันนี้ทำงานใน MicroPython task context และวางไว้ภายในทุกลูปที่รอ:

/* ...context: inside the UI IPC response wait loop ... */
mp_uint_t deadline = mp_hal_ticks_ms() + timeout_ms;
while (!ui_ipc_resp.ready) {
if ((mp_int_t)(mp_hal_ticks_ms() - deadline) >= 0) break;
MICROPY_EVENT_POLL_HOOK;
}

รูปแบบสำหรับ FreeRTOS จับคู่มันกับ vTaskDelay(pdMS_TO_TICKS(1)):

/* ...context: inside the joystick IPC response wait ... */
for (int w = 0; w < 50 && !js_ipc_response.ready; w++) {
vTaskDelay(pdMS_TO_TICKS(1));

การประกอบเฟรมของคำตอบ tacp_claw_respond() รับ payload_len เป็นค่าที่ snprintf คืนมาจริงแคสต์เป็น uint16_t — ห้ามใช้ strlen ของบัฟเฟอร์ที่ถูกตัดสั้น:

static void claw_respond(uint8_t sub_cmd, const char *payload, uint16_t payload_len) {
tacp_claw_respond(sub_cmd, payload, payload_len);
}
static void claw_respond_err(const char *msg, uint16_t msg_len) {
}
/*******************************************************************************
* READY frame — sent once at boot to announce firmware readiness.
* IDE listens for this instead of polling STATUS with delay+retry.
******************************************************************************/
void bentoclaw_send_ready(void) {
bentoclaw_ensure_init();
claw_ipc_init();
int n = snprintf(s_work_small, sizeof(s_work_small),
"{\"fw\":\"%s\",\"board\":\"%s\",\"tools\":%u,\"proto\":2}",
BENTOCLAW_VERSION, MICROPY_HW_BOARD_NAME, (unsigned)TOOL_COUNT);
tacp_claw_respond(TACP_CLAW_READY, s_work_small, (uint16_t)n);
/* Push initial stats to CM55 LCD so it shows real tool count immediately */
claw_ipc_send_stats();

เฟรม TACP_CLAW_READY ส่งออกหลังจาก tacp_init(), mp_init(), การเมานต์ VFS และ boot/main.py เท่านั้น (mpy_main.c:697-703) เฟรมเป็นข้อมูลไบนารี และห้ามผสมข้อความตัวอักษรเข้าไปในสตรีม (modbentoclaw.c:1967-1971)

ทางออกฉุกเฉินฝั่ง ISR เรียกจาก IPC ISR โดยมี BENTO_HAS_MPY เป็นตัวกัน สำหรับกรณีที่ /main.py วนไม่รู้จบจนทำให้ tacp_poll_uart() อดตาย:

/* ...context: inside the sensor-ctrl IPC ISR callback ... */
if (msg->cmd == IPC_CMD_DELETE_MAIN_PY) {
/* Graceful path: polled by tacp_poll_uart() in task context (works when
* the MP loop is alive, e.g. at the REPL). */
s_delete_pending = true;
/* Robust path: break the script + force a MicroPython soft reset DIRECTLY
* from this IPC ISR, so the Delete button works even when a runaway
* /main.py loop monopolises the CPU and starves tacp_poll_uart(). The
* soft reset keeps SRAM (s_delete_main_flag) intact -> boot removes
* /main.py. ISR-safe (no allocation, no hard reset). Ported from
* Game-libraries 8a0689a (HW-verified on Eva). */
#if BENTO_HAS_MPY
#endif /* no VM: there is no /main.py to delete */

ทีละขั้น

ขั้นที่ 1 — ดูเฟรม READY

แฟลช ตัดไฟแล้วจ่ายไฟใหม่ เปิด BENTO IDE ที่พอร์ตของบอร์ด

สิ่งที่ควรสังเกต IDE รายงานว่าบอร์ดพร้อม บนคอนโซลดิบจะเห็น [MPY] GC heap u KB @ p in s (mpy_main.c:552-554) มาก่อนหน้านั้น ส่วนเฟรม READY เองเป็นข้อมูลไบนารีและไม่พิมพ์ออกมา

ขั้นที่ 2 — พิสูจน์ว่าตัวปั๊มอยู่ในลูปที่รอ

>>> import time
>>> while True: time.sleep_ms(100)

สิ่งที่ควรสังเกต ปุ่ม stop/reset ของ IDE ยังใช้ได้ — time.sleep_ms ผ่าน MICROPY_EVENT_POLL_HOOK ซึ่งเป็นที่อยู่ของตัวปั๊ม ส่วนลูปที่วนกินเวลาโดยไม่ยอมคืนคิว (ไม่มี sleep ไม่มี poll hook) จะไม่ปั๊ม และ IDE จะเสียการติดต่อกับบอร์ดจนกว่าจะใช้ทางออกฉุกเฉินฝั่ง ISR หรือ hard reset

ขั้นที่ 3 — อ่าน ring buffer (ตัวอย่างที่เขียนขึ้นเอง)

tacp_ring_buf_read() และ tacp_ring_buf_readable() ส่งออกมาโดย ไม่มีผู้เรียกที่ใดเลย (นิยามอยู่ที่ tacp.c:885/:889) ตัวอย่างของทั้งสองเป็นตัวอย่างที่เขียนขึ้นเองและกำกับไว้เช่นนั้นในคู่มืออ้างอิง mpy_secure ให้ถือว่าเป็นเครื่องมือช่วยตรวจสอบ ไม่ใช่ส่วนหนึ่งของเส้นทางโปรโตคอลที่ส่งมอบจริง

กับดัก

Warning
payload_len ต้องเป็นความยาวจริง การใช้ strlen กับบัฟเฟอร์ที่ snprintf ตัดสั้นไปแล้วทำให้ประกอบเฟรมด้วยจำนวนไบต์ที่ผิด และทำให้ IDE หลุดจังหวะ
การเรียก tacp_poll_uart() ก่อน tacp_init() เป็นการอ่าน ring ที่ยังไม่ได้ init สำนวนการ init ทำงานหนึ่งครั้งต่อหนึ่ง soft reset จาก mpy_main.c:567-576
/main.py ที่วนไม่รู้จบยังขวางการคงอยู่ถาวรของข้อมูลรับรองด้วย ตัว flush ข้อมูลรับรองแบบเลื่อนเวลาทำงานเฉพาะที่ mpy_main.c:709 และ :686 โปรแกรมที่ไม่เคยกลับมาที่ REPL จึงไม่เคยบันทึกอะไรเลย (ภาคผนวก X #11)

กล่อง variant

mtb-mpy เท่านั้น บน mtb-only ตัว IDE เข้าถึงบอร์ดไม่ได้ สัญญาณเดียวบนคอนโซลคือ [HB] t=lus tasks=u