ลงมือทำ: สเปกตรัมสดจาก IMU
โมดูล 4 — วิเคราะห์สัญญาณ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เติม pipeline สี่ขั้นใน s09_fft_spectrum.py คือตัด DC คูณ Hann window หา magnitude และหา peak แล้วเขย่าบอร์ดดูแท่งสเปกตรัมกับความถี่เด่นเลื่อนตามจังหวะ พร้อมทดลองปิดทีละขั้นเพื่อเห็นว่าแต่ละขั้นแก้อะไร
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- เติมสี่ขั้นใน practice/s09_fft_spectrum.py จนแท่งสเปกตรัมขยับตามการเขย่า และความถี่เด่นที่แสดงอยู่ใกล้จำนวนครั้งที่เขย่าต่อวินาที (เช่นเขย่า 3 ครั้งต่อวินาทีได้ราว 3 Hz)
- ทดลองรันแบบไม่ตัด DC และแบบไม่คูณ window แล้วอธิบายได้ว่าสเปกตรัมเปลี่ยนไปอย่างไรและเพราะอะไร
- อธิบายได้ว่าการเก็บ N จุดด้วยการหน่วง 1000/FS ms สม่ำเสมอ และการ normalize แท่งด้วยยอดสูงสุด ส่งผลต่อการอ่านสเปกตรัมอย่างไร
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ผ่านบทเรียน 4.3 มาแล้ว รู้ว่า bin แปลงเป็น Hz อย่างไรและทำไมต้องตัด DC กับคูณ window เตรียมจังหวะการเขย่าสามแบบ (ช้า กลาง เร็ว) และนับจำนวนครั้งต่อวินาทีของแต่ละแบบไว้เทียบ
- อุปกรณ์: บอร์ด TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE
- เรียนมาก่อน: บทเรียน 4.3 — FFT และโดเมนความถี่: bin, Nyquist, DC, leakage และ Hann window
ทั้งไฟล์อ่านเป็นประโยคเดียว: เก็บ N จุด → ตัด DC → คูณ window → FFT → หา magnitude → หา peak → วาดแท่ง
ส่วนที่ให้ไว้แล้วคือการเก็บ az จาก sensors.bmi270.motion() 32 จุดโดยหน่วง 1000/FS ms สม่ำเสมอ (FFT อ่านความถี่ถูกต่อเมื่อระยะห่างเท่ากัน)
ฟังก์ชัน fft() แบบ radix-2 Cooley-Tukey ที่เขียนเป็น Python ให้อ่านได้ว่าเป็นแค่การบวกคูณเป็นระเบียบ และการวาดแท่งที่สร้างครั้งเดียวก่อนลูป
สี่ขั้นที่เราเติมคือ (1) mean = sum(buf) / N (2) re = [(buf[i] - mean) * (0.5 - 0.5 * math.cos(2 * math.pi * i / (N - 1))) for i in range(N)]
ซึ่งตัด DC แล้วคูณผ้าคลุมในบรรทัดเดียว (3) mag = [math.sqrt(re[k] * re[k] + im[k] * im[k]) for k in range(HALF)]
และ (4) kmax = max(range(1, HALF), key=lambda k: mag[k]) เริ่มที่ 1 เพื่อข้าม bin 0 ที่ DC อาจหลงเหลือ
ความถี่เด่นแสดงเป็น kmax * FS / N แท่งถูก normalize ด้วยยอดสูงสุด จึงเต็มจอเสมอไม่ว่าเขย่าแรงหรือเบา
ถ้าลืมขั้นใด อาการจะบอกเอง: ไม่ตัด DC จะเห็น bin 0 พุ่ง ไม่คูณ window แท่งข้างเคียงของยอดจะรั่วสูงกว่าที่ควร ไม่หา magnitude แท่งแบนราบ และไม่หา peak ความถี่เด่นจะค้างที่ bin 1 ความสำเร็จไม่ใช่แค่เห็นแท่งขยับ แต่ต้องบอกได้ว่า peak 3 Hz หมายถึงอะไร และทำไมเขย่าเร็วขึ้นยอดจึงเลื่อนไปทางความถี่สูง
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”s09_fft_spectrum.py ในโฟลเดอร์ examples คือฉบับอ้างอิงที่ใช้ N = 64 ส่วน s09_fft_spectrum_full.py เพิ่มการเฉลี่ยสเปกตรัมแบบ EMA
ให้แท่งนิ่ง โชว์ความถี่เด่นตัวใหญ่ด้วย Seg7 ค้างยอดสูงสุด (peak-hold) และวัดพลังงานรวม (RMS)
| ไฟล์ | ไฟล์นี้สอน |
|---|---|
| examples/s09_fft_spectrum.py | จากโดเมนเวลา สู่โดเมนความถี่ (FFT) |
| examples/s09_fft_spectrum_full.py | สเปกตรัม FFT สด ๆ จาก IMU (ฉบับเต็ม) |
ฝึกเติม
หัวข้อที่มีชื่อว่า “ฝึกเติม”คอมเมนต์ # เติม: สี่ขั้นอยู่ที่บรรทัด 95 (ตัด DC), 100 (Hann window), 110 (magnitude) และ 115 (peak)
แทนค่าเริ่มต้นด้วยคำสั่งตามคำใบ้ แล้วเขย่าบอร์ดแกน Z เร็วช้าสลับกัน ถ้าแท่งไม่ขยับ ตรวจการเยื้องบรรทัดและชื่อตัวแปรก่อน
| ไฟล์ฝึก | เรื่อง |
|---|---|
| practice/s09_fft_spectrum.py | จากโดเมนเวลา สู่โดเมนความถี่ (ฉบับฝึกเติมโค้ด) |
เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ และอ่าน วิธีใช้เฉลย ก่อน
| เฉลย | คู่กับ |
|---|---|
| solution/s09_fft_spectrum.py | practice/s09_fft_spectrum.py |
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
เรียงขั้นของ pipeline ใน s09_fft_spectrum.py (เรียงลำดับ · เป้าหมายข้อ 1)
- ก) fft(re, im)
- ข) mean = sum(buf) / N (ตัด DC)
- ค) kmax = max(range(1, HALF), key=…) (หา peak)
- ง) คูณ Hann window
- จ) mag = sqrt(re² + im²) ครึ่งแรก
เฉลย
ข → ง → ก → จ → ค — ตัด DC → window → FFT → magnitude → peak ขั้น FFT ให้ไว้แล้ว ส่วนอีกสี่ขั้นคือช่องที่เราเติม
-
เติมครบแล้ว แท่งขยับตามการเขย่า แต่ป้าย peak ค้างที่ 1.6 Hz (bin 1) ตลอด ขั้นใดยังไม่ได้เติม (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)
- ก) ตัด DC
- ข) Hann window
- ค) magnitude
- ง) หา peak (kmax ยังเป็นค่าเริ่มต้น 1)
เฉลย
ง — placeholder kmax = 1 ทำให้ความถี่เด่นชี้ bin 1 เสมอ ต้องแทนด้วย max(range(1, HALF), key=lambda k: mag[k])
-
ถ้าไม่คูณ Hann window สเปกตรัมจะเปลี่ยนอย่างไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)
- ก) bin 0 พุ่งกลบทุกอย่าง
- ข) ยอดยังอยู่ที่เดิม แต่แท่งข้างเคียงรั่วสูงกว่าที่ควร สเปกตรัมเลอะ
- ค) แท่งแบนราบทั้งหมด
- ง) ความถี่เด่นหายไป
เฉลย
ข — ขอบของหน้าต่างที่ไม่พอดีคาบเวลาของสัญญาณทำให้พลังงานรั่ว (spectral leakage) Hann กดขอบให้เป็นศูนย์จึงลดการรั่ว ส่วน bin 0 พุ่งเป็นอาการของการไม่ตัด DC
-
ถ้าลบ time.sleep_ms(int(1000 / FS)) ออกจากลูปเก็บ N จุด ผลคืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)
- ก) ไม่มีผลเพราะ FFT ไม่สนเวลา
- ข) อัตราสุ่มจริงไม่ใช่ FS อีกต่อไป การแปลง kmax · FS / N เป็น Hz จึงผิด
- ค) สเปกตรัมละเอียดขึ้นเสมอ
- ง) บอร์ดรีสตาร์ต
เฉลย
ข — สูตร f = k·FS/N ถือว่าจุดห่างกัน 1/FS วินาทีเท่ากันทุกจุด ถ้าอัตราจริงต่างไป ค่า Hz ที่แสดงจะไม่ตรงกับความจริง
MVP ของชุดบทเรียน 4.3–4.4: รัน s09_fft_spectrum.py แล้วอ่านสเปกตรัมสดออก แท่งความถี่และ peak (Hz) เปลี่ยนตามการเขย่าเร็วหรือช้าจริง
- เติมไฟล์ฝึกครบสี่ขั้น รันได้บน Emulator หรือบอร์ด
- เขย่าสามจังหวะ (ช้า กลาง เร็ว) จด peak กี่ Hz ลงบันทึกการเรียนแล้วเทียบกับจำนวนครั้งต่อวินาทีที่นับได้
- รันแบบไม่ตัด DC (ปล่อย
mean = 0.0) และแบบไม่คูณ window แล้วอธิบายว่าสเปกตรัมเปลี่ยนไปอย่างไร - อธิบายได้ว่า pipeline ตัด DC, window, magnitude และ peak อยู่ตรงไหน ทำอะไร
ชุดบทเรียนถัดไป (บทเรียน 4.5–4.6) เราจะต่อจากสเปกตรัมไปเป็นหน้าต่างเลื่อนและ feature vector ซึ่งคือสิ่งที่โมเดลเห็นจริง
บทเรียนถัดไป: บทเรียน 4.5 — feature และหน้าต่าง: สิ่งที่โมเดลเห็นจริง
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- peak ที่คุณวัดได้ตรงกับจังหวะที่นับเองแค่ไหน ถ้าคลาดเคลื่อน คิดว่ามาจากความละเอียดของ bin หรือจากมือคุณเอง
- ถ้าต้องตรวจมอเตอร์ที่หมุน 1,500 รอบต่อนาที คุณต้องตั้ง FS อย่างน้อยเท่าไร
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
เรียงขั้นของ pipeline ใน s09_fft_spectrum.py (เป้าหมายข้อ 1)
- fft(re, im)
- mean = sum(buf) / N (ตัด DC)
- kmax = max(range(1, HALF), key=...) (หา peak)
- คูณ Hann window
- mag = sqrt(re² + im²) ครึ่งแรก
ดูเฉลย
ลำดับที่ถูก: B. mean = sum(buf) / N (ตัด DC) → D. คูณ Hann window → A. fft(re, im) → E. mag = sqrt(re² + im²) ครึ่งแรก → C. kmax = max(range(1, HALF), key=...) (หา peak)
ตัด DC → window → FFT → magnitude → peak ขั้น FFT ให้ไว้แล้ว ส่วนอีกสี่ขั้นคือช่องที่เราเติม
-
เติมครบแล้ว แท่งขยับตามการเขย่า แต่ป้าย peak ค้างที่ 1.6 Hz (bin 1) ตลอด ขั้นใดยังไม่ได้เติม (เป้าหมายข้อ 1)
- ตัด DC
- Hann window
- magnitude
- หา peak (kmax ยังเป็นค่าเริ่มต้น 1)
ดูเฉลย
คำตอบ: D. หา peak (kmax ยังเป็นค่าเริ่มต้น 1)
placeholder kmax = 1 ทำให้ความถี่เด่นชี้ bin 1 เสมอ ต้องแทนด้วย max(range(1, HALF), key=lambda k: mag[k])
-
ถ้าไม่คูณ Hann window สเปกตรัมจะเปลี่ยนอย่างไร (เป้าหมายข้อ 2)
- bin 0 พุ่งกลบทุกอย่าง
- ยอดยังอยู่ที่เดิม แต่แท่งข้างเคียงรั่วสูงกว่าที่ควร สเปกตรัมเลอะ
- แท่งแบนราบทั้งหมด
- ความถี่เด่นหายไป
ดูเฉลย
คำตอบ: B. ยอดยังอยู่ที่เดิม แต่แท่งข้างเคียงรั่วสูงกว่าที่ควร สเปกตรัมเลอะ
ขอบของหน้าต่างที่ไม่พอดีคาบเวลาของสัญญาณทำให้พลังงานรั่ว (spectral leakage) Hann กดขอบให้เป็นศูนย์จึงลดการรั่ว ส่วน bin 0 พุ่งเป็นอาการของการไม่ตัด DC
-
ถ้าลบ time.sleep_ms(int(1000 / FS)) ออกจากลูปเก็บ N จุด ผลคืออะไร (เป้าหมายข้อ 3)
- ไม่มีผลเพราะ FFT ไม่สนเวลา
- อัตราสุ่มจริงไม่ใช่ FS อีกต่อไป การแปลง kmax · FS / N เป็น Hz จึงผิด
- สเปกตรัมละเอียดขึ้นเสมอ
- บอร์ดรีสตาร์ต
ดูเฉลย
คำตอบ: B. อัตราสุ่มจริงไม่ใช่ FS อีกต่อไป การแปลง kmax · FS / N เป็น Hz จึงผิด
สูตร f = k·FS/N ถือว่าจุดห่างกัน 1/FS วินาทีเท่ากันทุกจุด ถ้าอัตราจริงต่างไป ค่า Hz ที่แสดงจะไม่ตรงกับความจริง
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"ลงมือทำ: สเปกตรัมสดจาก IMU" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: a live spectrum from the IMU" 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/m04-analysis/l04-fft-spectrum-lab/
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA