ลงมือทำ: เก็บ IMU กับเสียงลงไฟล์เดียว
โมดูล 2 — เก็บข้อมูลจากเซนเซอร์ (DAQ) · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เติมสี่ก้าวในลูปของ s05_multicapture.py คือประทับเวลา อ่าน IMU อ่านเสียง และเขียนแถว จนได้ /multicapture.csv ที่ทุกแถวมัด IMU กับระดับเสียงบน t_ms เดียวกัน แล้วตรวจไฟล์และ jitter ด้วยตัวเอง
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- เติมสี่ช่องใน practice/s05_multicapture.py จนการกดปุ่ม label หนึ่งครั้งเพิ่ม 200 แถวลง /multicapture.csv ที่มีคอลัมน์ t_ms, label, ax..gz และ db ครบ
- ตรวจไฟล์ที่เก็บได้ว่า t_ms เพิ่มขึ้นต่อชุดและห่างกันราว 20 ms ค่า db เปลี่ยนตามเสียงจริง และวินิจฉัยได้ว่าช่องใดยังว่างจากอาการในไฟล์
- อธิบายได้ว่าทำไมต้องเปิด PDM นอกลูปครั้งเดียว และคืนไมโครโฟนด้วย pdm.deinit() ในบล็อก finally
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ผ่านบทเรียน 2.3 มาแล้ว เข้าใจ dBFS เส้นเวลาร่วม และ jitter เตรียมบอร์ดที่เก็บเสียงได้ และเปิด REPL ไว้ตรวจไฟล์หลังเก็บเสร็จ
- อุปกรณ์: บอร์ด TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE — เก็บเสียงจริงต้องใช้บอร์ดที่ไมโครโฟน PDM ใช้ได้ (ผู้เขียนทดสอบบน PSoC Edge AI Kit บน TESAIoT Dev Kit การเปิด PDM ยังชน clock ของระบบเสียง) Emulator ให้เสียงสังเคราะห์ใช้ซ้อมโครงโปรแกรมได้ แต่รันผ่านบน Emulator ไม่ใช่หลักฐานว่าจะรันผ่านบนบอร์ด
- เรียนมาก่อน: บทเรียน 2.3 — เสียงและหลายเซนเซอร์บนเส้นเวลาเดียว: PDM 16 kHz ประทับเวลา และ jitter
หัวใจทั้งไฟล์อยู่ใน record() แต่ละรอบของ for _ in range(BURST) เดินสี่ก้าวเป๊ะ: (1) ประทับเวลา
t_ms = time.ticks_diff(time.ticks_ms(), t0) (2) อ่าน IMU ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
(3) อ่านเสียง pdm.readinto(buf) แล้ว db = dbfs(buf) ที่ให้ไว้แล้ว (4) เขียนแถว
f.write("%d,%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f,%.1f\n" % (t_ms, label, ax, ay, az, gx, gy, gz, db))
ทั้งสี่ก้าวเกิดในรอบเดียวก่อน time.sleep_ms(RATE_MS) จึงถือว่าเป็นเวลาเดียวกัน ลำดับสำคัญ: อ่านทั้งคู่ก่อนเขียนแถวเสมอ
ไม่งั้นข้อมูลจะเหลื่อมกันหนึ่งรอบ
ส่วนบนของไฟล์ให้ไว้แล้ว: เปิด PDM_PCM(0, sck="P8_5", data="P8_6", sample_rate=16000) ครั้งเดียวนอกลูป เหมือนสร้าง widget ครั้งเดียว
เพราะเปิดปิดซ้ำทั้งกินเวลาและเสี่ยงชน เตรียม buffer และเขียนหัวตารางถ้าไฟล์ยังไม่มี t0 ถูกตั้งใหม่ทุกครั้งที่กดปุ่ม label
แต่ละชุดจึงเริ่มนับเวลาจากศูนย์ของตัวเอง ตอนออก บล็อก finally เรียก pdm.deinit() คืนไมโครโฟนเสมอ ไม่งั้นโปรแกรมเสียงตัวถัดไป
อาจเปิด PDM ไม่ได้เพราะฮาร์ดแวร์ยังถูกจองอยู่
เก็บเสร็จแล้ว ตรวจก่อนเชื่อ: จำนวนแถวควรเท่ากับ BURST × จำนวนครั้งที่กด, t_ms เพิ่มขึ้นในแต่ละชุด และ db เปลี่ยนตามเสียงจริง
ถ้า t_ms เป็น 0 ทุกแถวแปลว่าก้าว 1 ยังว่าง ถ้า db นิ่งแปลว่าก้าว 3 ยังว่างหรือไมค์ไม่ได้เสียง ถ้าไฟล์ว่างแปลว่าก้าว 4 ยังว่าง
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”s05_multicapture_full.py เก็บทั้ง CSV และ WAV เสียงดิบคู่กัน วัดช่วงเวลาจริงต่อแถวและ jitter สูงสุด แล้วสรุปจำนวนแถว
peak dBFS และช่วงเวลาเฉลี่ยต่อ label ลงไฟล์ manifest เปิดอ่านหลังไฟล์ฝึกทำงานแล้ว เพื่อดูว่า dataset ระดับใช้งานจริงต้องเก็บอะไรเพิ่ม
| ไฟล์ | ไฟล์นี้สอน |
|---|---|
| examples/s05_multicapture_full.py | Multi-Sensor Sync Capture (ฉบับเต็ม) |
ฝึกเติม
หัวข้อที่มีชื่อว่า “ฝึกเติม”ไฟล์มี # เติม สี่จุดที่บรรทัด 78, 83, 88 และ 92 เรียงตามสี่ก้าว แทน pass (และค่าสำรองที่เป็นศูนย์) ด้วยคำสั่งตามคำใบ้
แล้ว Program to Device เลือก label ทำท่าและส่งเสียงพร้อมกันราวสี่วินาที ดูจำนวนแถวขึ้นบนจอ แล้วเปิด CSV ตรวจ
| ไฟล์ฝึก | เรื่อง |
|---|---|
| practice/s05_multicapture.py | เก็บ 2 เซนเซอร์พร้อมกันบนเส้นเวลาเดียว (ฉบับฝึกเติมโค้ด) |
เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ และอ่าน วิธีใช้เฉลย ก่อน
| เฉลย | คู่กับ |
|---|---|
| solution/s05_multicapture.py | practice/s05_multicapture.py |
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
เรียงสี่ก้าวในหนึ่งรอบของลูป record() (เรียงลำดับ · เป้าหมายข้อ 1)
- ก) pdm.readinto(buf) แล้ว db = dbfs(buf)
- ข) t_ms = time.ticks_diff(time.ticks_ms(), t0)
- ค) f.write(…) หนึ่งแถว
- ง) ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
เฉลย
ข → ง → ก → ค — ประทับเวลา → อ่าน IMU → อ่านเสียง → เขียนแถว แล้วค่อย sleep_ms ไปรอบถัดไป ทุกค่าในแถวจึงเป็นเวลาเดียวกัน
-
เปิด CSV แล้วพบว่า t_ms เป็น 0 ทุกแถว แต่ IMU และ db เปลี่ยนตามจริง ช่องใดยังว่าง (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)
- ก) ก้าว 1 ประทับเวลา
- ข) ก้าว 2 อ่าน IMU
- ค) ก้าว 3 อ่านเสียง
- ง) ก้าว 4 เขียนแถว
เฉลย
ก — บรรทัดสำรอง t_ms = 0 ยังอยู่ เส้นเวลาจึงหาย จับคู่ตามเวลาไม่ได้ ต้องเติม ticks_diff
-
คอลัมน์ db ของทุกแถวเป็น −96.0 ตลอดแม้ปรบมือใกล้บอร์ด สาเหตุที่น่าจะเป็นคืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)
- ก) ยังไม่ได้เติม pdm.readinto(buf) buffer จึงเป็นศูนย์ หรือไมโครโฟนไม่ได้รับเสียง
- ข) RATE_MS ตั้งสูงเกินไป
- ค) ลืม \n ท้ายบรรทัด
- ง) t0 ไม่ได้ตั้งใหม่
เฉลย
ก — dbfs() คืน −96.0 เมื่อ RMS เป็นศูนย์ ซึ่งเกิดเมื่อ buffer ไม่เคยถูกเติมด้วยเสียงจริง
-
ข้อใดเป็นเหตุผลที่ต้องเรียก pdm.deinit() ใน finally (เลือกทุกข้อที่ถูก) (เลือกได้หลายข้อ · เป้าหมายข้อ 3)
- ก) ไม่ว่าจะออกด้วยปุ่ม back หรือ error ไมโครโฟนจะถูกคืนเสมอ
- ข) ถ้าไม่คืน โปรแกรมเสียงตัวถัดไปอาจเปิด PDM ไม่ได้เพราะฮาร์ดแวร์ยังถูกจอง
- ค) deinit() ทำให้ไฟล์ CSV ถูกบันทึก
- ง) เป็นนิสัยเดียวกับ edge_ai.stop() คือทิ้งเครื่องไว้ในสถานะที่รู้แน่
เฉลย
ก, ข, ง — ไฟล์ CSV ถูกปิดและ flush เองเมื่อออกจาก with ส่วน deinit() มีไว้คืนฮาร์ดแวร์ไมโครโฟน
MVP ของชุดบทเรียน 2.3–2.4: dataset ที่ log อย่างน้อยสองเซนเซอร์บนเส้นเวลาเดียว ไฟล์ /multicapture.csv มีคอลัมน์ t_ms + IMU + db และค่าจริงเปลี่ยนตามท่าและเสียง
- เติมไฟล์ฝึกครบสี่ช่อง รันบนบอร์ดจนได้
/multicapture.csv - เก็บครบสาม label โดยแต่ละ label ทำท่าและส่งเสียงต่างกัน แล้วยืนยันใน CSV ว่า
dbและ IMU เปลี่ยนตามจริง - ตรวจเส้นเวลา:
t_msห่างกันใกล้ 20 ms ไหม จดลงบันทึกการเรียนว่าเจอ jitter ตรงไหน แล้วลองลดCHUNKเทียบ - อธิบายได้ว่าโค้ดประทับ
t_msตรงไหน อ่านสองเซนเซอร์ตรงไหน และทำไมต้องอยู่ในรอบเดียว
โมดูลถัดไป (Processing) เราจะแปลงสัญญาณดิบที่เก็บมาเป็นปริมาณเชิงฟิสิกส์ เช่น มุมเอียง ความสูง และระดับเสียง แล้วโชว์เป็นเกจสด
บทเรียนถัดไป: บทเรียน 3.1 — จากตัวเลขดิบสู่ปริมาณทางฟิสิกส์: มุมเอียง พลังงาน ความสูง และ dBFS
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- jitter ที่คุณวัดได้มากที่สุดเกิดตอนไหน และคุณคิดว่ามาจากงานส่วนใดในลูป
- ถ้าต้องเพิ่มเซนเซอร์ตัวที่สาม (เช่นความดัน) ลงไฟล์เดียวกัน คุณต้องแก้ส่วนไหนบ้าง
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
เรียงสี่ก้าวในหนึ่งรอบของลูป record() (เป้าหมายข้อ 1)
- pdm.readinto(buf) แล้ว db = dbfs(buf)
- t_ms = time.ticks_diff(time.ticks_ms(), t0)
- f.write(...) หนึ่งแถว
- ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
ดูเฉลย
ลำดับที่ถูก: B. t_ms = time.ticks_diff(time.ticks_ms(), t0) → D. ax, ay, az, gx, gy, gz = sensors.bmi270.motion() → A. pdm.readinto(buf) แล้ว db = dbfs(buf) → C. f.write(...) หนึ่งแถว
ประทับเวลา → อ่าน IMU → อ่านเสียง → เขียนแถว แล้วค่อย sleep_ms ไปรอบถัดไป ทุกค่าในแถวจึงเป็นเวลาเดียวกัน
-
เปิด CSV แล้วพบว่า t_ms เป็น 0 ทุกแถว แต่ IMU และ db เปลี่ยนตามจริง ช่องใดยังว่าง (เป้าหมายข้อ 2)
- ก้าว 1 ประทับเวลา
- ก้าว 2 อ่าน IMU
- ก้าว 3 อ่านเสียง
- ก้าว 4 เขียนแถว
ดูเฉลย
คำตอบ: A. ก้าว 1 ประทับเวลา
บรรทัดสำรอง t_ms = 0 ยังอยู่ เส้นเวลาจึงหาย จับคู่ตามเวลาไม่ได้ ต้องเติม ticks_diff
-
คอลัมน์ db ของทุกแถวเป็น −96.0 ตลอดแม้ปรบมือใกล้บอร์ด สาเหตุที่น่าจะเป็นคืออะไร (เป้าหมายข้อ 2)
- ยังไม่ได้เติม pdm.readinto(buf) buffer จึงเป็นศูนย์ หรือไมโครโฟนไม่ได้รับเสียง
- RATE_MS ตั้งสูงเกินไป
- ลืม \n ท้ายบรรทัด
- t0 ไม่ได้ตั้งใหม่
ดูเฉลย
คำตอบ: A. ยังไม่ได้เติม pdm.readinto(buf) buffer จึงเป็นศูนย์ หรือไมโครโฟนไม่ได้รับเสียง
dbfs() คืน −96.0 เมื่อ RMS เป็นศูนย์ ซึ่งเกิดเมื่อ buffer ไม่เคยถูกเติมด้วยเสียงจริง
-
ข้อใดเป็นเหตุผลที่ต้องเรียก pdm.deinit() ใน finally (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 3)
- ไม่ว่าจะออกด้วยปุ่ม back หรือ error ไมโครโฟนจะถูกคืนเสมอ
- ถ้าไม่คืน โปรแกรมเสียงตัวถัดไปอาจเปิด PDM ไม่ได้เพราะฮาร์ดแวร์ยังถูกจอง
- deinit() ทำให้ไฟล์ CSV ถูกบันทึก
- เป็นนิสัยเดียวกับ edge_ai.stop() คือทิ้งเครื่องไว้ในสถานะที่รู้แน่
ดูเฉลย
คำตอบ: A. ไม่ว่าจะออกด้วยปุ่ม back หรือ error ไมโครโฟนจะถูกคืนเสมอ · B. ถ้าไม่คืน โปรแกรมเสียงตัวถัดไปอาจเปิด PDM ไม่ได้เพราะฮาร์ดแวร์ยังถูกจอง · D. เป็นนิสัยเดียวกับ edge_ai.stop() คือทิ้งเครื่องไว้ในสถานะที่รู้แน่
ไฟล์ CSV ถูกปิดและ flush เองเมื่อออกจาก with ส่วน deinit() มีไว้คืนฮาร์ดแวร์ไมโครโฟน
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"ลงมือทำ: เก็บ IMU กับเสียงลงไฟล์เดียว" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: IMU and sound in one file" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/edge-ai-developer/m02-daq/l04-multicapture-lab/
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA