ข้ามไปยังเนื้อหา

เสียงและหลายเซนเซอร์บนเส้นเวลาเดียว: PDM 16 kHz ประทับเวลา และ jitter

โมดูล 2 — เก็บข้อมูลจากเซนเซอร์ (DAQ) · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

เพิ่มเซนเซอร์ตัวที่สองให้ dataset เข้าใจว่าไมโครโฟน PDM ส่งเสียงเป็น PCM 16 kHz อย่างไร ย่อเฟรมเสียงเป็นค่า dBFS ค่าเดียว แล้วมัดเสียงกับ IMU ไว้บนเส้นเวลาเดียวด้วย ticks_ms และตรวจ jitter ได้

เมื่อจบบทเรียนนี้ คุณจะ:

  1. อธิบายเส้นทาง PDM → PCM 16-bit → RMS → dBFS และคำนวณ dBFS ของเฟรมจาก RMS ได้ (เช่น RMS = 3277 ได้ราว −20 dBFS)
  2. คำนวณระยะห่างระหว่างตัวอย่างเสียง Ts = 1/fs และความยาวของหนึ่งเฟรม t = N/fs ได้ และบอกว่าเฟรมที่ยาวเกินจังหวะแถวส่งผลกับ jitter อย่างไร
  3. อธิบายได้ว่าทำไมต้อง “อ่านพร้อมกัน เขียนแถวเดียว ประทับเวลาร่วม” และใช้ time.ticks_diff(time.ticks_ms(), t0) แทนการลบตรง ๆ

ผ่านชุดบทเรียน 2.1–2.2 มาแล้ว มีไฟล์ /gestures.csv ของตัวเองและเข้าใจสี่จังหวะ DAQ ถ้าจะใช้เสียงจริง เตรียมบอร์ดที่ไมโครโฟน PDM ใช้ได้ (ดูบรรทัดอุปกรณ์ด้านล่าง)

  • อุปกรณ์: บอร์ด TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE — เสียงจริงต้องมาจากไมโครโฟน PDM บนบอร์ด ตัวอย่างเสียง 06/07 ระบุว่าใช้ได้บน PSoC Edge AI Kit ส่วนบน TESAIoT Dev Kit (มี audio codec) การเปิด PDM ยังชน clock ของระบบเสียง Emulator มี PDM_PCM ที่ส่งเสียงสังเคราะห์ให้ลองโครงโปรแกรมได้เท่านั้น
  • เรียนมาก่อน: บทเรียน 2.2 — ลงมือทำ: DAQ logger เก็บ dataset ลง CSV

รัน 06_mic_level_meter.py แล้วพูดหรือปรบมือใส่ไมค์ แถบ VU กับตัวเลข dBFS จะวิ่งตาม เห็นเสียงกลายเป็นตัวเลขจริง ๆ จากนั้นดู 07_mic_record_wav.py ที่อัดเสียงดิบทั้งก้อนลงไฟล์ .wav สองตัวนี้คือวัตถุดิบที่เราจะเอามามัดกับ IMU

ไมโครโฟนบนบอร์ดเป็นแบบ PDM ที่ส่งบิต 0/1 ความเร็วสูง ฮาร์ดแวร์ machine.PDM_PCM แปลงเป็นตัวอย่างเสียง PCM 16-bit ที่ sample_rate=16000 ให้เราอ่านเข้า array.array("h", ...) ด้วย pdm.readinto(buf) ระยะห่างระหว่างตัวอย่างคือ $T_s = 1/f_s = 62.5\ \mu s$ และ 16 kHz พอสำหรับเสียงพูดและเสียงสิ่งแวดล้อมที่พลังงานส่วนใหญ่อยู่ต่ำกว่า 8 kHz ตามกฎ Nyquist เราอ่านทีละก้อน CHUNK = 512 ตัวอย่าง หนึ่งเฟรมจึงยาว $t = N/f_s = 512/16000 = 32$ ms

เก็บทุกตัวอย่างลง CSV ไม่ไหว (16,000 ค่าต่อวินาที) จึงย่อเฟรมเป็น ค่าเดียว: หา RMS (ขนาดเฉลี่ยของคลื่น) แล้วเทียบกับค่าสูงสุดของ 16-bit เป็นเดซิเบล $\text{dBFS} = 20\log_{10}(\text{RMS}/32768)$ 0 คือดังสุด ยิ่งลบยิ่งเบา เสียงดิบทั้งก้อนเก็บแยกเป็น WAV ได้ถ้าต้องการ

ปัญหาของสองเซนเซอร์คือ การ sync: ถ้าเก็บคนละไฟล์คนละจังหวะ จะจับคู่ไม่ได้ว่าเสียงไหนคู่กับท่าไหน ทางออกคือ อ่านพร้อมกัน เขียนแถวเดียว ประทับเวลาร่วม: ในหนึ่งรอบลูป อ่าน IMU และ MIC ติดกัน แล้วเขียนหนึ่งแถวที่ทั้งคู่ใช้ t_ms เดียวกัน t_ms = time.ticks_diff(time.ticks_ms(), t0) ใช้ ticks_diff เพราะตัวนับมิลลิวินาทีวนกลับศูนย์ได้ (wrap-around) ช่วงจริงระหว่างแถวจะเพี้ยนจาก 20 ms บ้างเพราะงานอ่านเสียงและเขียนไฟล์กินเวลา เรียกว่า jitter เพราะเราเก็บ t_ms จริงไว้ทุกแถว จึงตรวจย้อนหลังได้ และลด jitter ได้ด้วยการลด CHUNK ลดงานในลูป และไม่พิมพ์ console ถี่ schema ของไฟล์คือ t_ms,label,ax,ay,az,gx,gy,gz,db โดย label อยู่ทุกแถว dataset จึงพร้อม train ตั้งแต่ออกจากบอร์ด

ทาย (Predict) ก่อนรัน 06_mic_level_meter.py ว่าเงียบกับปรบมือได้ dBFS ต่างกันราวเท่าไร แล้วรันเทียบ ส่วน 07_mic_record_wav.py ใช้ดึงไฟล์ /rec.wav ออกด้วย BENTO IDE (file transfer) หรือ mpremote แล้วเปิดฟังบน PC

ไฟล์ ไฟล์นี้สอน
examples/06_mic_level_meter.py VU Meter: ไมค์ PDM -> RMS -> dBFS -> แถบระดับเสียง + peak hold
examples/07_mic_record_wav.py อัดเสียงเป็นไฟล์ .wav พร้อมหน้าจอนับถอยหลัง

สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นหรือใน shared/ ด้วย:

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. เฟรมเสียงมี RMS = 32768 ซึ่งเท่ากับค่าสูงสุดของ 16-bit ค่า dBFS เป็นเท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) 0 dBFS
    • ข) −96 dBFS
    • ค) +20 dBFS
    • ง) 32768 dBFS
    เฉลย

    ก — 20·log10(32768/32768) = 20·log10(1) = 0 dBFS คือดังสุด เสียงที่เบากว่านี้จะได้ค่าติดลบ ส่วน −96 คือค่าที่โค้ดใช้แทนความเงียบสนิท

  2. เรียงเส้นทางของเสียงจากไมโครโฟนจนเป็นคอลัมน์ db ใน CSV (เรียงลำดับ · เป้าหมายข้อ 1)

    • ก) หา RMS ของเฟรม
    • ข) บิต PDM 0/1 ความเร็วสูง
    • ค) แปลงเป็น dBFS แล้วเขียนลงแถว
    • ง) ตัวอย่าง PCM 16-bit ใน buf จาก pdm.readinto()
    เฉลย

    ข → ง → ก → ค — PDM → PCM ใน buffer → RMS → dBFS เป็นค่าเดียวต่อเฟรม ซึ่งเป็นก้าวแรกก่อนทำ spectrogram ในโมดูล 4

  3. ที่ fs = 16 kHz เฟรมขนาด N = 512 ตัวอย่างยาวเท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) 3.2 ms
    • ข) 32 ms
    • ค) 62.5 µs
    • ง) 512 ms
    เฉลย

    ข — t = N / fs = 512 / 16000 = 0.032 วินาที = 32 ms ยาวกว่าจังหวะแถว 20 ms การอ่านเสียงจึงเป็นตัวการหลักของ jitter

  4. วิธีใดทำให้ค่าเสียงกับค่า IMU ใน dataset จับคู่ตามเวลาได้แน่นอน (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) เก็บเสียงกับ IMU คนละไฟล์แล้วจับคู่ตามลำดับบรรทัดทีหลัง
    • ข) อ่านทั้งสองเซนเซอร์ในรอบเดียวของลูป แล้วเขียนแถวเดียวที่มี t_ms ร่วมกัน
    • ค) ใช้ time.time() บันทึกเวลาแยกให้แต่ละไฟล์
    • ง) ตั้ง RATE_MS ให้เท่ากับ 0
    เฉลย

    ข — หนึ่งรอบ = หนึ่งแถว = หนึ่ง t_ms ทุกคอลัมน์ของแถวจึงหมายถึงช่วงเวลาเดียวกัน การจับคู่ตามลำดับพลาดง่ายเมื่ออัตราต่างกัน

  5. ทำไมใช้ time.ticks_diff(time.ticks_ms(), t0) แทน time.ticks_ms() - t0 (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) เพราะ ticks_diff เร็วกว่า
    • ข) เพราะตัวนับมิลลิวินาทีวนกลับศูนย์ได้ (wrap-around) ticks_diff คิดช่วงเวลาให้ถูกแม้ตัวนับวนรอบ
    • ค) เพราะ ticks_ms คืนค่าเป็นวินาที
    • ง) เพราะการลบใน MicroPython ทำไม่ได้
    เฉลย

    ข — ticks_ms กับ ticks_diff คือคู่มาตรฐานของ MicroPython สำหรับวัดช่วงเวลาสั้น ๆ อย่างปลอดภัยจาก wrap-around

  • รัน VU meter แล้วจดค่า dBFS ตอนเงียบ ตอนพูด และตอนปรบมือลงบันทึกการเรียน
  • คำนวณ t = N/fs ของ CHUNK = 256 และ 1024 แล้วบอกว่าตัวไหนเสี่ยงทำให้แถวห่างเกิน 20 ms
  • เขียน schema ของไฟล์ multi-sensor ด้วยมือ แล้วบอกว่าแต่ละคอลัมน์มาจากคำสั่งอะไร

บทเรียน 2.4 เราจะเติมไฟล์ s05_multicapture.py ให้เก็บ IMU กับเสียงบนเส้นเวลาเดียวลง /multicapture.csv

บทเรียนถัดไป: บทเรียน 2.4 — ลงมือทำ: เก็บ IMU กับเสียงลงไฟล์เดียว

  • งานไหนในชีวิตจริงที่ต้องดูเสียงกับการเคลื่อนไหวพร้อมกันถึงจะตัดสินถูก
  • ถ้าเก็บเสียงกับ IMU มาคนละไฟล์แล้ว คุณยังพอกู้ให้ตรงเวลาได้ไหม ต้องมีข้อมูลอะไรเพิ่ม

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. เฟรมเสียงมี RMS = 32768 ซึ่งเท่ากับค่าสูงสุดของ 16-bit ค่า dBFS เป็นเท่าไร (เป้าหมายข้อ 1)

    1. 0 dBFS
    2. −96 dBFS
    3. +20 dBFS
    4. 32768 dBFS
    ดูเฉลย

    คำตอบ: A. 0 dBFS

    20·log10(32768/32768) = 20·log10(1) = 0 dBFS คือดังสุด เสียงที่เบากว่านี้จะได้ค่าติดลบ ส่วน −96 คือค่าที่โค้ดใช้แทนความเงียบสนิท

  2. เรียงเส้นทางของเสียงจากไมโครโฟนจนเป็นคอลัมน์ db ใน CSV (เป้าหมายข้อ 1)

    1. หา RMS ของเฟรม
    2. บิต PDM 0/1 ความเร็วสูง
    3. แปลงเป็น dBFS แล้วเขียนลงแถว
    4. ตัวอย่าง PCM 16-bit ใน buf จาก pdm.readinto()
    ดูเฉลย

    ลำดับที่ถูก: B. บิต PDM 0/1 ความเร็วสูง → D. ตัวอย่าง PCM 16-bit ใน buf จาก pdm.readinto() → A. หา RMS ของเฟรม → C. แปลงเป็น dBFS แล้วเขียนลงแถว

    PDM → PCM ใน buffer → RMS → dBFS เป็นค่าเดียวต่อเฟรม ซึ่งเป็นก้าวแรกก่อนทำ spectrogram ในโมดูล 4

  3. ที่ fs = 16 kHz เฟรมขนาด N = 512 ตัวอย่างยาวเท่าไร (เป้าหมายข้อ 2)

    1. 3.2 ms
    2. 32 ms
    3. 62.5 µs
    4. 512 ms
    ดูเฉลย

    คำตอบ: B. 32 ms

    t = N / fs = 512 / 16000 = 0.032 วินาที = 32 ms ยาวกว่าจังหวะแถว 20 ms การอ่านเสียงจึงเป็นตัวการหลักของ jitter

  4. วิธีใดทำให้ค่าเสียงกับค่า IMU ใน dataset จับคู่ตามเวลาได้แน่นอน (เป้าหมายข้อ 3)

    1. เก็บเสียงกับ IMU คนละไฟล์แล้วจับคู่ตามลำดับบรรทัดทีหลัง
    2. อ่านทั้งสองเซนเซอร์ในรอบเดียวของลูป แล้วเขียนแถวเดียวที่มี t_ms ร่วมกัน
    3. ใช้ time.time() บันทึกเวลาแยกให้แต่ละไฟล์
    4. ตั้ง RATE_MS ให้เท่ากับ 0
    ดูเฉลย

    คำตอบ: B. อ่านทั้งสองเซนเซอร์ในรอบเดียวของลูป แล้วเขียนแถวเดียวที่มี t_ms ร่วมกัน

    หนึ่งรอบ = หนึ่งแถว = หนึ่ง t_ms ทุกคอลัมน์ของแถวจึงหมายถึงช่วงเวลาเดียวกัน การจับคู่ตามลำดับพลาดง่ายเมื่ออัตราต่างกัน

  5. ทำไมใช้ time.ticks_diff(time.ticks_ms(), t0) แทน time.ticks_ms() - t0 (เป้าหมายข้อ 3)

    1. เพราะ ticks_diff เร็วกว่า
    2. เพราะตัวนับมิลลิวินาทีวนกลับศูนย์ได้ (wrap-around) ticks_diff คิดช่วงเวลาให้ถูกแม้ตัวนับวนรอบ
    3. เพราะ ticks_ms คืนค่าเป็นวินาที
    4. เพราะการลบใน MicroPython ทำไม่ได้
    ดูเฉลย

    คำตอบ: B. เพราะตัวนับมิลลิวินาทีวนกลับศูนย์ได้ (wrap-around) ticks_diff คิดช่วงเวลาให้ถูกแม้ตัวนับวนรอบ

    ticks_ms กับ ticks_diff คือคู่มาตรฐานของ MicroPython สำหรับวัดช่วงเวลาสั้น ๆ อย่างปลอดภัยจาก wrap-around

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"เสียงและหลายเซนเซอร์บนเส้นเวลาเดียว: PDM 16 kHz ประทับเวลา และ jitter" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Audio and several sensors on one timeline: 16 kHz PDM, timestamps and jitter" 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/l03-audio-and-timeline/

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA