- variant ที่ใช้ได้
- mtb-mpy และ mtb-only
sensor_auto_task.c ส่งมอบเป็นซอร์สในทั้งสองแพ็กเกจ และสร้างขึ้นจาก main() ทั้งสองฝั่ง ส่วนการเรียก sensors.auto* ที่ใช้บังคับมันจากฝั่ง Python มีเฉพาะบน mtb-mpy ส่วน API ควบคุมฝั่ง C เหมือนกันทั้งสองฝั่ง และเป็นสิ่งที่การเรียกเหล่านั้นไปเรียกใช้
เป้าหมายของหัวข้อนี้
เมื่อจบบทนี้จะอธิบายได้ว่าอะไรทำให้ตัวเลขบนหน้าจอ CM55 ขยับอยู่ตลอด ทั้งที่ไม่มีใครเรียกอะไรเลย: task บน CM33_NS หนึ่งตัว อัตราหนึ่งค่า bitmask หนึ่งชุด และการกระโดดผ่าน IPC หนึ่งครั้งเข้าสู่ ipc_sensorhub จะเปลี่ยนอัตราสำหรับโมเดล DEEPCRAFT ได้โดยไม่ทำให้แดชบอร์ดพัง บอกได้ว่า task นี้ดันเซนเซอร์ตัวใดบ้างบนบอร์ด ตัวนี้ จริง ๆ (น้อยกว่าที่ mask บอกไว้) และ — บน mtb-only — รู้ว่าเหตุใดการลบการเรียกนี้เพียงจุดเดียวจึงพา page ทุกหน้าบน CM55 ล่มไปด้วย
ลำดับการทำงานจริงของเฟิร์มแวร์
การเรียกสร้างหนึ่งครั้ง งาน 3 อย่าง sensor_auto_task_create() (sensor_auto_task.c:1513) มีการเรียกจาก proj_cm33_ns/main.c:318 บนทั้งสอง variant หลังสาขาที่จัดการที่เก็บข้อมูลและ config มันสร้างคิวคำขอ WiFi จากนั้น — และนี่คือส่วนที่ส่งผลไกลออกไปนอกเรื่องเซนเซอร์ — มันเรียก cm33_ipc_communication_setup() หากยังไม่มีใครเรียก (sensor_auto_task.c:1523-1527) แล้วจึงสร้างตัวทำงาน WiFi IPC และสุดท้ายคือ task ดันข้อมูลเอง
ตัวลูป task ตื่นทุก s_interval_ms ค่าเริ่มต้น 100 ms — 10 Hz (sensor_auto_task.c:145) — และดันเฉพาะสิ่งที่ mask เปิดใช้อนุญาต (:1420-1450) มันไม่ใช่การกวาดแบบราบเรียบ แต่มี 2 ระดับกับสวิตช์หนึ่งตัว:
- ระดับเร็ว ทุกรอบ: BMI270
- ระดับช้า ประมาณทุก 500 ms: DPS368 และ SHT40
- สวิตช์โหมดเร็ว: เมื่อช่วงเวลาลดต่ำกว่า SENSOR_AUTO_FAST_THRESHOLD_MS = 50 ms (sensor_auto_task.c:153) ลูปจะอ่านเฉพาะ accelerometer ตัวเดียว และข้ามเซนเซอร์อื่นทั้งหมด เพราะมิฉะนั้นจะทำคาบนั้นไม่ได้
เมื่อจบแต่ละรอบ ตัวนับการดันข้อมูลจะเพิ่มขึ้น และ task หลับเป็นเวลา s_interval_ms (:1487-1490)
mask มีบิตที่บอร์ดนี้ไม่ได้ใช้ sensors.auto(name, bool) รับชื่อได้หกชื่อ และ sensor_auto_task.h:18-24 นิยามบิตไว้ 6 บิต แต่การดันข้อมูลของ CapSense และ pot คอมไพล์อยู่หลัง #if BSP_HAS_CAPSENSE && !BSP_HAS_QWA309_BASEBOARD และ #if BSP_HAS_POTENTIOMETER && !BSP_HAS_QWA309_BASEBOARD (sensor_auto_task.c:1433-1439) และบอร์ดนี้ตั้ง BSP_HAS_QWA309_BASEBOARD=1 บนบอร์ดนี้บิตทั้งสองรับเข้ามา เก็บไว้ และรายงานผ่าน auto_status() แต่ ไม่มีสิ่งใดอ่านมัน นั่นไม่ใช่ข้อบกพร่อง: บนบอร์ด QWA309 ตัว CapSense และ pot อยู่ฝั่ง CM55 และ CM55 ป้อนข้อมูลเข้า hub เอง — ดู ปลายทางของข้อมูล: ipc_sensorhub
อัตราถูกหนีบไว้ 2 แห่ง ในช่วงเดียวกัน sensor_auto_set_rate() หนีบไว้ที่ 20..5000 ms (sensor_auto_task.c:1605-1609) และ sensors.auto_rate() หนีบในช่วงเดียวกันก่อนเรียกมัน (modsensors.c:1236-1240) ค่าต่ำสุดไม่ได้ตั้งขึ้นลอย ๆ คอมเมนต์ของ binding เองระบุว่า 20 ms คือ 50 Hz ซึ่งเป็นอัตราที่ใช้ฝึกโมเดลการเคลื่อนไหวของ DEEPCRAFT "so MicroPython must be able to ask for it too"
เอนจิน Edge AI ขอสิ่งเดียวกันผ่าน IPC IPC_CMD_SENSOR_AUTO_CTRL op 2 พาค่าช่วงเวลาแบบ 16 บิตไปด้วย และส่งจากเอนจิน Edge AI บน CM55 (sensor_auto_task.c:308-330) การตั้งอัตรายังปลดการหยุดชั่วคราวและสั่งให้ task ทำงานต่อในข้อความเดียวกันด้วย — โดยเจตนา เพื่อให้การเริ่มโมเดลใช้ข้อความเดียว แทนที่จะเป็น 2 ข้อความซึ่งจะชนกันบนบัฟเฟอร์ IPC ที่ใช้ร่วมกัน
ปลายทางของข้อมูล: ipc_sensorhub
ฝั่งดันข้อมูลคือ task นี้ ส่วนฝั่งรับคือ ipc_sensorhub ใน libbento_ipc.a ซึ่งมีเอกสารอยู่ที่ Sensor hub ไม่ต้องอธิบายซ้ำที่นี่ — มี 3 เรื่องเกี่ยวกับรอยต่อนี้ที่สำคัญสำหรับบทนี้:
- ipc_sensorhub_init() ต้องทำงาน หลัง cm55_ipc_communication_setup() บนฝั่ง CM55 เพราะมันลงทะเบียน callback ของ pipe
- ipc_sensorhub_snapshot() ล้างแฟล็ก changed ไปพร้อมกับการอ่าน ต่อหนึ่ง tick มีผู้อ่านได้รายเดียวเท่านั้น มิฉะนั้นผู้อ่านรายที่สองจะเห็นว่า "ไม่มีอะไรเปลี่ยน" นี่คือภาคผนวก X #9 — ภาคผนวก X — กับดักและ anti-pattern
- feed API — ipc_sensorhub_feed_bmi270, _feed_bmm350, _feed_capsense, _feed_pot — ให้ CM55 ป้อนค่าที่ตัวมันอ่านเองเข้าไปได้ โดยไม่ต้องกระโดดผ่าน IPC เลย นั่นคือเส้นทางที่ cm55_sensor_poll ใช้กับ CapSense และ pot บนบอร์ดนี้ และเป็นเหตุผลที่บิตทั้งสองใน mask ตายอยู่บนฝั่ง CM33
ทีละขั้น
ขั้นที่ 1 — ยืนยันว่า task กำลังทำงานอยู่ (ทั้งสอง variant)
บน mtb-mpy จาก REPL:
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors
print(sensors.auto_status())
บน mtb-only จากโค้ด CM33_NS ที่เขียนเอง:
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
#include "sensor_auto_task.h"
bool up = sensor_auto_is_running();
uint32_t rate = sensor_auto_get_rate();
uint32_t mask = sensor_auto_get_mask();
uint32_t count = sensor_auto_get_push_count();
- สิ่งที่ควรสังเกต
- running เป็น true, rate_ms เป็น 100 บนบอร์ดที่ยังไม่มีใครเปลี่ยนอัตรา และ push_count เพิ่มขึ้นประมาณ 10 ครั้งต่อวินาที รูปร่างของ dict คือ running, rate_ms, push_count, mask (modsensors.c:1246) ไม่มีอะไรพิมพ์ออกมาบน variant ใดเลย — task นี้เงียบโดยการออกแบบ บรรทัดเดียวที่มันปล่อยออกมาได้คือความล้มเหลวในการสร้าง 2 กรณีที่ sensor_auto_task.c:1519 และ :1545 บนหน้าจอ ค่าบนแดชบอร์ดขยับได้เอง นั่นคือ task นี้ และเป็นสิ่งที่สังเกตได้อย่างเดียวที่ไม่ต้องใช้เครื่องมือใดเลย
ขั้นที่ 2 — เปลี่ยนอัตราให้เป็น 50 Hz สำหรับโมเดล แล้วเปลี่ยนกลับ
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors
sensors.auto_rate(20)
print(sensors.auto_status()['rate_ms'])
sensors.auto_rate(100)
- สิ่งที่ควรสังเกต
- ได้ 20 และในระหว่างที่ยังอยู่ที่ 20 ms ค่าด้านสิ่งแวดล้อมบนหน้าจอจะ หยุดปรับปรุง — ความดัน ความชื้น และแมกนีโตมิเตอร์อยู่นิ่งทั้งหมด นั่นคือสวิตช์โหมดเร็วที่ sensor_auto_task.c:1421 ทำงานตรงตามที่มันบอกไว้: ต่ำกว่า 50 ms ลูปจะอ่านเฉพาะ accelerometer ตัวเดียว เรื่องนี้ทำให้ผู้ที่คาดว่า "เร็วขึ้น" แปลว่า "เร็วขึ้นสำหรับทุกอย่าง" ประหลาดใจ สำหรับเซนเซอร์ห้าใน 6 ตัวมันหมายถึงสิ่งตรงกันข้าม และเป็นการแลกที่ถูกต้อง เพราะคาบ 20 ms รักษาไว้ไม่ได้หากยังต้องบริการการอ่านบารอมิเตอร์ระดับ 500 ms ไปด้วย
การขอค่าที่อยู่นอกช่วงจะไม่โยนข้อผิดพลาดใด ๆ: auto_rate(1) ถูกหนีบเป็น 20 และ auto_rate(99999) เป็น 5000 ทั้งในตัว binding และซ้ำอีกครั้งในตัวตั้งค่าฝั่ง C
ขั้นที่ 3 — ปิดเซนเซอร์หนึ่งตัว
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors
sensors.auto('sht40', False)
print(hex(sensors.auto_status()['mask']))
sensors.auto('sht40', True)
- สิ่งที่ควรสังเกต
- mask เสียบิตที่ 2 ไป (SENSOR_AUTO_SHT40, sensor_auto_task.h:20) และค่าความชื้นบนหน้าจอค้างอยู่ที่ค่าที่ดันไปครั้งสุดท้าย — มันไม่ว่างเปล่าและไม่แสดง error เพราะ hub เก็บค่าล่าสุดที่ได้รับไว้ ชื่อที่ไม่รู้จักจะโยน ValueError (modsensors.c:1224)
- Warning
- ทำแบบเดียวกันนี้กับ 'capsense' หรือ 'pot' บนบอร์ดนี้แล้ว จะไม่มีอะไรที่สังเกตเห็นได้เกิดขึ้น บิตใน mask เปลี่ยน และ auto_status() รายงานค่านั้น แต่ไม่มีโค้ดใดอ่านมันที่นี่ — 2 ตัวนั้นป้อนมาจาก CM55 ดู ปลายทางของข้อมูล: ipc_sensorhub
ขั้นที่ 4 — หยุดและเริ่ม task ทั้งตัว
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors
sensors.auto(False)
print(sensors.auto(), sensors.auto_status()['running'])
sensors.auto(True)
- สิ่งที่ควรสังเกต
- ค่าที่เคลื่อนไหวทุกค่าบนหน้าจอ CM55 หยุดพร้อมกัน และกลับมาทำงานพร้อมกัน sensor_auto_stop() ระงับ task ไปเลย แทนที่จะเพียงตั้ง flag (sensor_auto_task.c:1591-1599) — คอมเมนต์อธิบายเหตุผลไว้ว่า การใช้ flag อย่างเดียวจะเหลือชุดข้อมูล IPC อีกหนึ่งชุดค้างอยู่ระหว่างทาง ขณะที่ UI กำลังตรวจสอบ CM55 หลังการ soft-reset
ขั้นที่ 5 — mtb-only: การเรียกที่ไม่ได้เกี่ยวกับเซนเซอร์
อ่าน proj_cm33_ns/main.c:271-289 ในสาขา mtb-only งาน 3 อย่างที่ MicroPython task เคยทำตอนบูตต้องย้ายมาเกิดใน main() แทน และงานที่สามคือสิ่งนี้:
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.
- สิ่งที่ควรสังเกต
- บนการบูต mtb-only ที่ถูกต้อง: บรรทัดสัญญาณชีพทุก 10 s และ page บน CM55 ที่ตอบสนองหากลบหรือย้ายลำดับของ sensor_auto_task_create() build ยังลิงก์ผ่าน การบูตยังพิมพ์สัญญาณชีพออกมา และ page ข้ามคอร์ทุกหน้าจะเงียบไปโดยไม่มี error ที่ใดเลย รูปแบบความล้มเหลวนั้น — ลิงก์สะอาด บูตเงียบ UI ตาย — คือเหตุผลที่ variants/mtb-only.mk:15-17 บันทึกความเป็นเจ้าของไว้เป็นลายลักษณ์อักษร ดู B1 — เดินดูลำดับการบูตของ CM33_NS สำหรับลำดับการบูตฉบับเต็ม และ B3 — แกนหลักของ IPC: การตั้งค่า deferred binding และ snapshot สำหรับตัว pipe เอง
กับดัก
- Warning
- ภาคผนวก X #9 — ต่อหนึ่ง tick มีผู้อ่าน snapshot ได้รายเดียว ipc_sensorhub_snapshot() ล้างแฟล็ก changed ไปพร้อมกับการอ่าน ผู้อ่านรายที่สองใน tick เดียวกันจะเห็น hub ที่ไม่มีอะไรเปลี่ยน ให้อ่าน snapshot ครั้งเดียวแล้วกระจายต่อ
-
ต่ำกว่า 50 ms คือการอ่านเซนเซอร์ตัวเดียว ไม่ใช่ 6 ตัว สวิตช์โหมดเร็วทำงานเงียบ ๆ หากค่าด้านสิ่งแวดล้อม "หยุดทำงาน" หลังการเปลี่ยนอัตรา นี่คือสาเหตุ
-
สองใน 6 บิตของ mask ไม่ทำอะไรเลยบนบอร์ดนี้ capsense และ pot คอมไพล์ออกจากลูปดันข้อมูลเมื่อ BSP_HAS_QWA309_BASEBOARD=1 การสลับค่าทั้งสองเปลี่ยนเพียง mask ที่รายงานออกมา ไม่มีอย่างอื่น
-
ห้ามเรียก sensor_auto_task_create() สองครั้ง โดยคาดว่าจะเป็นการเริ่มใหม่ มันคืนค่าออกมาทันทีเมื่อ handle มีอยู่แล้ว (sensor_auto_task.c:1514) ให้ใช้ sensor_auto_start() / sensor_auto_stop()
-
ห้ามลบ sensor_auto_task_create() ออกจาก main() ของ mtb-only ด้วยเหตุผลว่า "บอร์ดนี้ไม่มีเซนเซอร์ให้ดัน" มันเป็นเจ้าของการตั้งค่า IPC pipe ดูขั้นที่ 5
กล่อง variant
| mtb-mpy | mtb-only |
| การสร้าง task | main.c:318 | main.c:318 — บรรทัดเดียวกัน การเรียกเดียวกัน |
| การตั้งค่า IPC pipe | ทำโดยเส้นทางของ mpy_main หรือโดย task นี้ แล้วแต่ว่าใครถึงก่อน | ทำโดย main() เอง และมีตัวกันซ้ำอีกครั้งภายใน task นี้ |
| ส่วนที่ใช้ควบคุม | sensors.auto(), auto_rate(), auto_status() | sensor_auto_start/stop/set_rate/set_mask/enable/disable, sensor_auto_get_* |
| การหนีบอัตรา | 20..5000 ms ใช้สองชั้น | 20..5000 ms ใช้ชั้นเดียว |
| สิ่งที่สังเกตได้ | auto_status() กับค่าที่เคลื่อนไหวบนหน้าจอ | ค่าที่เคลื่อนไหวบนหน้าจอ ส่วน [HB] พิสูจน์ scheduler ไม่ใช่ task นี้ |