SDK สำหรับ TESAIoT Dev Kit
คู่มืออ้างอิง API และ Tutorial (MTB & µPython)
Loading...
Searching...
No Matches
variant ที่ใช้ได้
mtb-mpy และ mtb-only

task ของเรดาร์และสาย DSP ของมันเป็นซอร์สของเทมเพลตฝั่ง CM55 ในทั้ง 2 แพ็กเกจ ส่วนการเรียก sensors.radar* ทั้ง 3 ตัวเป็นไคลเอนต์ IPC บาง ๆ ที่วางอยู่บน task นั้น และมีเฉพาะบน mtb-mpy บน mtb-only ให้อ่านสถานะเดียวกันผ่านตัวแปร extern และฟังก์ชันวินิจฉัย 2 ตัว ซึ่งเป็นสิ่งที่หน้าจอบนบอร์ดทำอยู่ในทั้งสอง variant

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

เมื่อจบบทนี้จะดึงค่าแฟล็กการมีอยู่ ค่าพลังงาน และระยะถึงยอดคลื่นแรกออกจาก BGT60TR13C ได้ ตั้งเกณฑ์การตรวจจับและเก็บค่าฐานของ clutter ใหม่ได้ และ — เมื่อตัวเลขค้าง ซึ่งเป็นความล้มเหลวที่เซนเซอร์ตัวนี้มีอยู่จริง — อ่านชุดข้อมูลวินิจฉัย 3 ค่า 2 ชุดที่บอกได้ว่า สิ่งใด ใน 3 สิ่งที่หยุดไป: ลิงก์ SPI ตัวจัดลำดับเฟรม หรือตัว task เอง

Note
ไม่มีผู้เรียก sensors.radar_range() และ sensors.radar_config() ไม่มี call site ที่ใดเลยในซอร์สที่ส่งมอบ และ sensors.radar() ก็ไม่มีเช่นกันภายในแพ็กเกจนี้ ตัวอย่าง Python ทุกชิ้นในบทนี้ เขียนขึ้นเอง ส่วนฝั่ง C มีผู้เรียกที่มีอยู่จริงในของที่ส่งมอบ — task ของเรดาร์ทำงานตั้งแต่บูต CM55 ในทั้งสอง variant — ลำดับการทำงานด้านล่างจึงอ่านมาจากโค้ดที่ทำงานจริง ไม่ใช่คิดขึ้นเอง

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

เรดาร์อยู่บน CM55 ทั้งหมด tesaiot_radar_task() (radar_task.h:96) ยก SPI ขึ้น ตั้งค่า BGT60TR13C แล้วจึงวนถาม: อ่านหนึ่งเฟรมคำนวณพลังงาน ปรับแฟล็กการมีอยู่ ตัวแปรทั่วไปชนิด volatile 3 ตัวพาผลลัพธ์ไปให้ผู้ที่ต้องการ — tesaiot_radar_presence_detected (:72), tesaiot_radar_current_energy (:77) และ tesaiot_radar_initialized (:82) ไม่มี lock: เป็นการอ่านเวิร์ด 3 ครั้ง

สาย DSP เป็นผู้อ่านรายที่สองของเฟรมชุดเดียวกัน radar_dsp.c/h รันไปป์ไลน์ขนาด 128 ตัวอย่าง — RADAR_DSP_N มีค่า 128 (radar_dsp.h:29) — ผ่าน radar_dsp_process() (:48) เก็บ snapshot ของระยะไว้ให้ radar_dsp_snapshot() (:59) ดึงไปใช้ และรับเกณฑ์การตรวจจับผ่าน radar_dsp_set_threshold_x10() (:52) ค่าเริ่มต้นที่คอมไพล์มาคือ RADAR_DSP_THRESHOLD_DB 6.0 (radar_dsp.h:32)

MicroPython เป็นไคลเอนต์ ไม่ใช่ไดรเวอร์ การเรียกฝั่ง Python ทั้ง 3 ตัวเป็นการเดินทางไป-กลับผ่าน IPC เข้าสู่ task นั้น ด้วยงบการลองใหม่และ timeout ชุดเดียวกัน: ลองส่ง 20 ครั้ง ห่างกันครั้งละ 100 µs แล้วรอคำตอบ 500 ms (modsensors.c:301-303) sensors.radar() ส่ง IPC_CMD_RADAR_STATUS (modsensors.c:306) ส่วน radar_range() และ radar_config() ใช้ฟังก์ชันช่วยตัวเดียวกันคือ radar_dsp_ipc_roundtrip() (modsensors.c:367) จึงใช้คู่บัฟเฟอร์ในหน่วยความจำร่วมชุดเดียวกัน — โดยเจตนา เพราะพื้นที่ร่วมเต็มแล้วบนชุดคิตบางรุ่นความล้มเหลวทั้ง 2 แบบโยน OSError: "radar IPC send failed" เมื่อ pipe ไม่รับข้อความ และ "radar IPC timeout" เมื่อ CM55 ไม่ตอบภายใน 500 ms

radar_range() มีรูปแบบความล้มเหลวแบบที่สี่ซึ่งไม่ใช่ความล้มเหลวของ IPC หากคำตอบกลับมาโดย initialized เป็น false มันจะโยน OSError("radar dsp not running") (modsensors.c:417-419) — ลิงก์ปกติดี เพียงแต่สาย DSP ยังไม่ขึ้น

ความหมายของเกณฑ์ รวมถึงค่าพิเศษ sensors.radar_config(threshold_db) (modsensors.c:444):

  • 0.0 หมายถึง เก็บค่าฐานของ clutter (สัญญาณสะท้อนจากวัตถุนิ่ง) ใหม่ มันส่งคำสั่งตั้งค่าด้วยค่า 0 และการเก็บใหม่ใช้เวลาประมาณ 160 ms ให้เคลียร์เป้าหมายที่เคลื่อนไหวออกจากฉากก่อน มิฉะนั้นจะอบเป้าหมายเหล่านั้นเข้าไปในค่าฐานด้วย
  • ค่าอื่นใดต้องอยู่ในช่วง 0.1 .. 60.0 dB เหนือค่าฐาน มิฉะนั้นจะโยน ValueError("threshold 0.1..60.0 dB (0=recal)") ค่าที่ส่งไปอยู่ในหน่วยหนึ่งในสิบของ dB

ไม่มีวิธีอ่านค่าเกณฑ์ปัจจุบันกลับมา

Edge AI ระบายชุด chirp ชุดเดียวกัน ภายใต้ BENTO_HAS_EDGE_AI=1 ฟังก์ชัน radar_ai_frame_next() (radar_task.h:147) ส่ง chirp ขนาด 128 ตัวอย่างทุกชุดให้โมเดลตามลำดับ ผ่าน ring ลึก 8 ช่อง ไฟล์ header บันทึกไว้ว่าเหตุใดจึงต้องมี ring นี้: ช่องเก็บแบบเก็บเฉพาะชุดล่าสุดทิ้ง chirp ทุกชุดยกเว้นชุดสุดท้ายของแต่ละรอบการประมวลผลทำให้ chirp เพียง 88 ชุดต่อวินาทีจาก 200 ชุดต่อวินาทีของเรดาร์ไปถึงโมเดล และทำลายรูปแบบดอปเพลอร์ระหว่าง chirp ที่เครือข่ายใช้จำแนก มันเป็น API แบบผู้อ่านรายเดียว — คือการป้อนข้อมูลให้ ai_engine บน CM55 — และไม่ควรเพิ่มผู้อ่านรายที่สอง

ทีละขั้น

ขั้นที่ 1 — การมีอยู่และพลังงาน

ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors, time
for _ in range(20):
r = sensors.radar()
print(r['initialized'], r['presence'], "%.3f" % r['energy'])
time.sleep_ms(500)
สิ่งที่ควรสังเกต
initialized เป็น True เมื่อ task บน CM55 bring-up เสร็จแล้ว ค่า presence พลิกเป็น True เมื่อมีคนเคลื่อนไหวอยู่หน้าบอร์ด และเป็น False หลังจากที่หยุดไปครู่หนึ่งส่วน energy เป็นค่า float ที่สูงขึ้นตามการเคลื่อนไหว — มันเป็นระดับสัญญาณสำหรับแสดงผลและดีบัก ไม่ใช่หน่วยที่สอบเทียบแล้ว และไฟล์ header ก็ระบุไว้เช่นนั้น (radar_task.h:75-77) หาก initialized เป็น False และคงเป็น False อยู่ แสดงว่าเซนเซอร์ไม่เคยขึ้นบน CM55 และไม่มีการเรียกฝั่ง Python ใดเปลี่ยนสิ่งนั้นได้

บนหน้าจอ หน้าเรดาร์สะท้อนตัวแปรทั่วไป 2 ตัวเดียวกันนี้ หน้าจอกับ REPL จึงไม่มีทางขัดแย้งกันได้

ขั้นที่ 2 — ระยะถึงยอดคลื่นแรก

ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors, time
while True:
d = sensors.radar_range()
if d['target']:
print("%.2f m peak %.1f dB res %.3f m seq %d"
% (d['distance_m'], d['peak_db'], d['resolution_m'], d['seq']))
else:
print("no target")
time.sleep_ms(300)
สิ่งที่ควรสังเกต
มีห้า key: distance_m, peak_db, resolution_m, target, seq เดินเข้าหาบอร์ดแล้ว distance_m จะลดลง distance_m == 0.0 หมายถึงไม่มีเป้าหมาย ที่เหนือเกณฑ์ ไม่ได้หมายถึง "ศูนย์เมตร" และ target คือค่าบูลีนที่บอกเรื่องนี้ตรง ๆ ส่วน resolution_m คือความกว้างของ bin ระยะทางจึงมีความหมายในระดับความละเอียดประมาณนั้นเท่านั้น ค่า seq เพิ่มขึ้นทีละหนึ่งต่อหนึ่งเฟรม DSP: หากมันหยุดเดินหน้าในขณะที่การเรียกยังคืนค่าอยู่ แสดงว่าสาย DSP หยุดชะงัก และสิ่งที่อ่านอยู่คือ snapshot เก่า — ให้ไปที่ขั้นที่ 4

การได้ OSError("radar dsp not running") ที่นี่หมายความว่า initialized เป็น false ในคำตอบ: สายยังไม่ขึ้น ซึ่งต่างจาก "ไม่มีเป้าหมาย"

ขั้นที่ 3 — ตั้งเกณฑ์ แล้วเก็บค่าฐานใหม่

ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import sensors, time
sensors.radar_config(12.0) # 12 dB above the clutter baseline
time.sleep(1)
print(sensors.radar_range())
# Clear the scene of moving targets, then:
sensors.radar_config(0.0) # re-capture the baseline, ~160 ms
time.sleep_ms(300)
print(sensors.radar_range())
สิ่งที่ควรสังเกต
การเรียกทั้ง 2 ครั้งคืนค่า None เกณฑ์ที่สูงขึ้นทำให้ target เป็น False สำหรับการสะท้อนที่อ่อน — กำแพงจะหยุดเป็นเป้าหมายก่อนที่คนจะหยุดเป็นเป้าหมาย หลังการเก็บค่าฐานใหม่โดยฉากว่าง clutter ที่นิ่งจะหลุดออกไป และคนที่เดินเข้ามาจะปรากฏได้ชัดหากเก็บค่าฐานใหม่ขณะที่มีคนยืนอยู่หน้าบอร์ด ก็เท่ากับกำหนดให้เซนเซอร์ถือว่าคนคนนั้นเป็นเฟอร์นิเจอร์ วิธีแก้คือเก็บค่าฐานใหม่อีกครั้งโดยให้ฉากว่าง

radar_config(0.05) หรือ radar_config(61) จะโยน ValueError ค่าศูนย์เป็นค่าเดียวนอกช่วง 0.1..60.0 ที่ใช้ได้ และมันหมายถึงสิ่งอื่นโดยสิ้นเชิง

ขั้นที่ 4 — เมื่อตัวเลขค้าง (ทั้งสอง variant)

ขั้นนี้เป็นขั้นวินิจฉัย และเป็นภาษา C ในทั้งสอง variant — ไม่มี binding ฝั่ง Python สำหรับฟังก์ชันสถิติ 2 ตัวนี้

ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
#include "radar_task.h"
uint32_t tries = 0, fails = 0; int32_t last_rc = 0;
uint32_t loops = 0, frames = 0, phase = 0;
tesaiot_radar_recover_stats(&tries, &fails, &last_rc);
tesaiot_radar_loop_stats(&loops, &frames, &phase);
สิ่งที่ควรสังเกต
ทั้ง 2 ฟังก์ชันเป็นการอ่านเวิร์ดชนิด volatile 3 ครั้งโดยไม่ใช้ lock และเรียกจาก task อื่นได้อย่างปลอดภัย อาร์กิวเมนต์ตัวใดก็เป็น NULL ได้ ให้อ่าน 2 ครั้งห่างกันหนึ่งวินาทีแล้วเปรียบเทียบ — ไฟล์ header อธิบายตารางการตัดสินใจไว้ครบถ้วน (radar_task.h:98-129):
ค่าที่อ่านได้ ความหมาย
tries ไต่ขึ้น fails เป็น 0 ตัวจัดลำดับเฟรมหยุดชะงัก และ watchdog กำลังแก้ให้วินาทีละครั้ง — เป็นเรื่องจริง และแก้ตัวเองได้
tries และ fails ไต่ขึ้นพร้อมกัน ลิงก์ SPI ล่ม การเริ่มตัวจัดลำดับใหม่ใช้ไม่ได้ และจะไม่มีวันใช้ได้
tries นิ่ง ในขณะที่ frames ค้าง ตัว task ของเรดาร์เองไม่ได้ทำงาน
loops ค้าง โดย phase อยู่ที่ค่าของ SPI ติดอยู่ในการวนที่ไม่มีขอบเขตภายในชั้นแพลตฟอร์มของผู้ผลิต รออยู่บนแฟล็กที่มีเพียงอินเทอร์รัปต์ของ SCB เท่านั้นที่ล้างได้ ไม่มีสิ่งใดที่อยู่ถัดไปกู้มันได้
loops ไต่ขึ้น frames ค้าง ลูปปกติดี และเซนเซอร์หยุดส่งข้อมูล

อาการบน UI ที่ว่า "พลังงานค้างหลังผ่านไปหนึ่งถึงสองนาที" คืออาการของ 3 แถวแรก และการเรียก 2 ตัวนี้คือสิ่งที่แยกมันออกจากกัน การเดาระหว่างทั้งสามจากหน้าจอทำไม่ได้

กับดัก

Warning
distance_m == 0.0 คือ "ไม่มีเป้าหมาย" ไม่ใช่ "ศูนย์เมตร" ให้ตรวจ target แทน
radar_config(0.0) ไม่ใช่ "เกณฑ์เท่ากับศูนย์" มันคือการเก็บค่าฐานใหม่ และใช้เวลาประมาณ 160 ms ให้เคลียร์ฉากก่อน
ค่า seq ที่ค้างพร้อมกับการเรียกที่สำเร็จหมายถึง snapshot เก่า การที่การเดินทางไป-กลับผ่าน IPC สำเร็จนั้นพิสูจน์ลิงก์ ไม่ได้พิสูจน์เซนเซอร์
radar_range() และ radar_config() ใช้คู่บัฟเฟอร์ในหน่วยความจำร่วมชุดเดียวกัน (modsensors.c:364-367) ทั้งสองถูกเรียกจาก MicroPython task เท่านั้น ห้ามเพิ่มผู้เรียกรายที่สามบน task อื่น
ห้ามเพิ่มผู้อ่านรายที่สองให้ radar_ai_frame_next() มันไม่ใช้ lock ได้ก็เพราะมีผู้อ่านรายเดียว (radar_task.h:139-142) เคอร์เซอร์ตัวที่สองจะทำให้ตัวแรกเสียหาย
การเรียกเรดาร์ทั้ง 3 ตัวมีอยู่เฉพาะเมื่อ BSP_HAS_RADAR=1 ทั้งสามอยู่ภายใน #if BSP_HAS_RADAR ในตารางลงทะเบียน (modsensors.c:1359-1363) บนบอร์ดที่ไม่มี flag นี้ sensors.radar จะเป็น AttributeError ไม่ใช่ OSError

กล่อง variant

mtb-mpy mtb-only
การมีอยู่ / พลังงาน sensors.radar() ผ่าน IPC อ่าน tesaiot_radar_presence_detected และ tesaiot_radar_current_energy ตรง ๆ บน CM55
ระยะ sensors.radar_range() radar_dsp_snapshot() (radar_dsp.h:59)
เกณฑ์ sensors.radar_config(db) radar_dsp_set_threshold_x10() (radar_dsp.h:52)
ข้อมูลวินิจฉัย ไม่มีจากฝั่ง Python tesaiot_radar_recover_stats(), tesaiot_radar_loop_stats() — ขั้นที่ 4
การป้อนข้อมูลให้ Edge AI เข้าถึงจากฝั่ง Python ไม่ได้ radar_ai_frame_next() ผู้อ่านรายเดียว
สิ่งที่สังเกตได้ หน้าเรดาร์ พร้อมกับ dict หน้าเรดาร์อย่างเดียว