ต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 20 นาที
| ลำดับ · เรื่อง · เวลา | ไฟล์ | ลงมือทำอะไร แล้วจะเข้าใจอะไร |
|---|---|---|
| 1 · สุ่มช้าไปแล้วได้ความถี่ผี · 8 นาที | 03_aliasing_nyquist.py |
ตั้งค่า PERIOD_MS ของทีมโดยมีเหตุผลรองรับ แทนที่จะเดาแล้วมาแก้ทีหลัง · จะเข้าใจว่าสุ่มช้าเกินไป แล้วได้ความถี่ที่ไม่เคยมีอยู่จริงบนกราฟ |
| 2 · เฝ้าการสั่นด้วยเกณฑ์ที่วัดเอง · 12 นาที | 01_imu_vibration_monitor.py |
ลอกโครงลูปอ่านเซนเซอร์ เส้นเกณฑ์ และปุ่มหยุดกราฟ ไปใส่ไฟล์ของทีมได้ทันที · จะได้รูปร่างของงานเฝ้าการสั่น คือกราฟคู่กับเส้นเกณฑ์ที่วัดมาเอง ไม่ใช่ตัวเลขที่หยิบมาจากที่อื่น |
ติดตรงไหน เปิดอันนี้
| อาการที่เจอ | ไฟล์ที่ตอบอาการนั้น |
|---|---|
| กดปุ่มเริ่ม/หยุดแล้วจอไม่เปลี่ยนอะไรเลย | 02_event_types.py — Button ส่ง clicked ส่วน Switch ส่ง toggled คนละชนิดกัน ดักผิดชนิดคือเงียบ |
อ่านเสริมนอกเวลา — เรื่องนี้อยู่นอกเกณฑ์ผ่านของบทเรียน 3.4–3.6 เพราะ MVP วันนี้วัดที่กราฟสามเส้น ตาราง และปุ่มเริ่ม/หยุดบันทึก ไม่ได้วัดสเปกตรัม: 02_fft64_two_tones.py เขียน FFT 64 จุดด้วยมือทั้งตัว แล้วชี้ว่าข้อมูลชุดเดิมมองคนละมุมได้คำตอบคนละแบบ · พอเข้าใจข้างในแล้ว ของจริงใช้ dsp.fft_mag() ที่คำนวณใน C (firmware 2026-08-20 ขึ้นไป) — ex16_spectrum_analyzer.py คือเครื่องวิเคราะห์สเปกตรัมทั้งเครื่องที่ประกอบจากมัน เก็บไว้เปิดตอนอยากต่อยอดไปงาน predictive maintenance จริง

02_fft64_two_tones.py — บนคือคลื่นที่ป้อนเข้าไป ล่างซ้ายคือสเปกตรัมที่ออกมา วาดบนตารางไฟ 8x8 ด้วย DotMatrix.set_pixels() · ภาพจับที่ขั้นแรก ป้อนโทนเดียว 3 รอบเข้าไป ยอดจึงขึ้นที่ bin 3 เดี่ยว ๆ และเพราะความละเอียดต่อ bin เท่ากับ fs/N = 1.000 Hz เลข bin กับเลขความถี่จึงบังเอิญตรงกันพอดีที่ 3 · บรรทัดล่างคือราคาที่ประหยัดได้จริง FFT เร็วกว่า DFT ตรง ๆ 21 เท่าที่ N = 64 · ภาพหน้าจอจริงจากบอร์ด Eva Kit บันทึกโดยผู้สอนชุดบทเรียนนี้เหลือตัวอย่างสามไฟล์ หลังตัดเรื่องเลข int16 · บิตมาสก์ · การแพ็กฟิลด์ลงไบต์ ออกจากหลักสูตร ทั้งสามเรื่องไม่มีบทเรียนไหนสอนแล้ว และไม่มีไฟล์ไหนต้องแก้ค่าคงที่ก่อนกด Run
s07_accel_chart.py — ตั้งต้น กราฟ ตาราง ไฟ และปุ่มสองปุ่มimport ui
ui.screen()
import time
import sensors
COL_TEXT, COL_DIM = 0xE8EAED, 0x9AA3AF
COL_AX = 0x4A9EFF # แกน X - เส้นที่ 1 ของจานสีเส้นข้อมูล สีฟ้า
COL_AY = 0x8E7BFF # แกน Y - เส้นที่ 2 ของจานสีเส้นข้อมูล สีม่วง
COL_AZ = 0x2FB6A8 # แกน Z - เส้นที่ 3 ของจานสีเส้นข้อมูล สีเขียวน้ำทะเล
COL_RUN = 0x4A9EFF # สีเน้นของจานบทบาท - ใช้กับไฟบอกว่ากำลังบันทึกอยู่
PERIOD_MS = 200 # คาบการสุ่มที่เราเลือก
...
chart = ui.Chart(x=24, y=52, w=292, h=144, min=-20, max=20, color=COL_AX)
s_ay = chart.add_series(COL_AY)
s_az = chart.add_series(COL_AZ)
tbl = ui.Table(x=332, y=52, w=436, h=280, cols=4, value=16)
... # col_width + add_row สี่แถว - ท่าที่ 2
led_rec = ui.Led(x=24, y=212, w=48, h=48, color=COL_RUN, value=1)
lbl_rec = ui.Label("กำลังบันทึก", x=88, y=216, color=COL_TEXT, value=20)
lbl_dt = ui.Label("คาบลูป -- ms", x=88, y=252, color=COL_DIM, value=20)
... # ปุ่มเริ่ม/หยุด + start_id stop_id - ท่าที่ 3
lo = [None, None, None]
hi = [None, None, None]
- ไว้ก่อน ไม่ใช่เว้นว่าง เพราะช่องว่างเปล่าอ่านได้สองแบบ คือ "ยังไม่มีค่า" กับ "จอเสีย" ส่วนขีดอ่านได้แบบเดียว · lo กับ hi เกิดนอกลูปเพราะมันคือความจำของโปรแกรม · เส้นในกราฟใช้สามสีแยกแกน ส่วนตารางใช้ ตัวอักษร X Y Z แยกแทน เพื่อให้อ่านออกแม้พิมพ์เป็นขาวดำ · start_id กับ stop_id เก็บครั้งเดียวตอนสร้าง เพราะบนจอมีสองปุ่มrunning = True
last_ms = time.ticks_ms()
last_sec = -1
while True:
if running:
try:
ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
except OSError:
pass
chart.set_next(0, int(ax))
chart.set_next(s_ay, int(ay))
chart.set_next(s_az, int(az))
note(0, ax) # กราฟลืมจุดที่เก่ากว่า 50 จุด ตารางไม่ลืม
now = time.ticks_ms()
dt = time.ticks_diff(now, last_ms)
last_ms = now
for ev in ui.poll(): # poll นอก if running เสมอ ไม่งั้นปุ่มตายตอนหยุด
h = ev.get('handle')
if h == start_id:
running = True
lo = [None, None, None]
hi = [None, None, None]
led_rec.value(1)
elif h == stop_id:
running = False
led_rec.value(0) # หรี่ ไม่ใช่หาย
sec = time.ticks_ms() // 1000
if sec != last_sec: # ตัวเลขขยับวินาทีละครั้ง กราฟขยับ 5 ครั้ง
last_sec = sec
for i, v in enumerate((ax, ay, az)):
tbl.cell(i + 1, 1, cell(lo[i]))
lbl_dt.text("คาบลูป {} ms".format(dt))
lbl_rec.text(rec_msg) # ชีพจร - ส่งข้อความเดิมซ้ำทุกรอบโดยตั้งใจ
time.sleep_ms(PERIOD_MS)
ui.poll()อยู่ นอกif running:— ปุ่มหยุดที่ฆ่าทางกลับของตัวเองคือบั๊กที่เจอบ่อยที่สุดในหน้าที่มีปุ่มหยุด และมันดูเหมือนจอค้าง ทั้งที่โปรแกรมยังวิ่งครบทุกบรรทัด
วันนี้เราได้: เข้าใจว่า time series คืออะไรและอัตราสุ่มมีผลอย่างไร · รู้จัก Nyquist และ aliasing พร้อมตัวเลขของลูปเราเอง · ใช้ ui.Chart กับหลาย series ได้ · รู้ว่าทำไมต้อง int() และมันแลกอะไรไป · เลือกช่วงแกน Y ด้วยเหตุผลของตัวเอง · ทำปุ่มเริ่ม/หยุดบันทึกด้วย flag ที่ไม่ฆ่าลูป · วัดคาบลูปจริงและเอาขึ้นจอ · และรู้ว่าทำไมลูปที่มีแต่กราฟถึงช้ากว่าลูปที่มีป้ายด้วย 40 เท่า
รากฐานที่แตะไป: การสุ่มตัวอย่างและคาบการสุ่ม · ทฤษฎีบทไนควิสต์และ aliasing · การสูญเสียความละเอียดจากการแปลงเป็นจำนวนเต็ม · ring buffer และคิวที่มีขนาดจำกัด · การวัดเวลาด้วยตัวนับที่วนกลับ · สถานะที่ต้องอยู่นอกลูป · การใช้สีเป็นภาษาสื่อความหมาย · การทำให้โปรแกรมรายงานสมรรถนะของตัวเอง
คำถามคิดต่อ: ถ้าต้องส่งข้อมูลนี้ขึ้นคลาวด์ในบทเรียน 4.4–4.6 จะส่งทุกจุดที่ 5 Hz หรือส่งเฉพาะตอนที่มีอะไรน่าสนใจ · ค่าอะไรควรสรุปก่อนส่ง · ถ้าเน็ตหลุดสามนาที ข้อมูลช่วงนั้นควรหายไปเลยหรือควรเก็บไว้
เก็บไฟล์ของวันนี้ไว้ให้ดี บทเรียน 3.7–3.9 เราจะเปิดมันขึ้นมาต่อยอด ไม่ได้เริ่มใหม่
ทั้งสี่มุมใช้แนวคิดเดียวกับวันนี้ทั้งหมด ต่างกันที่ อัตราสุ่ม ความยาวหน้าต่างเวลา และสิ่งที่ทำต่อกับข้อมูล
ข้อ 1 · เครื่องกำเนิดสัญญาณของทีม
ต่อยอดจากโค้ดอุ่นเครื่อง ทำหน้าที่ป้อนคลื่นสี่แบบ (สี่เหลี่ยม ไซน์ สามเหลี่ยม ฟันเลื่อย) ลงกราฟโดยไม่ใช้เซนเซอร์เลย แล้วใส่ปุ่มสลับรูปคลื่น บันทึกว่าคลื่นแบบไหนดูผิดเพี้ยนเร็วที่สุดเมื่อเพิ่มความถี่ และเทียบกับที่ Nyquist ทำนายไว้
ข้อ 2 · กราฟความละเอียดสูง
เปลี่ยนไปใช้ int(ax * 100) พร้อมขยายช่วงเป็น min=-2000, max=2000 แล้ววางเทียบกับกราฟเดิม บันทึกว่าเห็นรายละเอียดอะไรเพิ่มขึ้น และเสียอะไรไปหรือไม่
ข้อ 3 · ตารางทดลอง cadence
ทดลอง PERIOD_MS ที่ 500, 200, 100, 50 และ 20 บันทึกคาบลูปจริงที่วัดได้ในแต่ละค่า แล้วหาว่าจุดไหนที่เริ่มเห็นอาการเฟรมหาย · โจทย์เพิ่ม: ทำการทดลองซ้ำสองรอบ รอบหนึ่งมีบรรทัด lbl.text() รอบหนึ่งไม่มี แล้วเทียบตัวเลข
ข้อ 4 · เส้นดิบเทียบเส้นกรอง
เพิ่ม series ที่สี่เป็นค่าที่ผ่าน dsp.EMA(alpha=0.2) ของแกนใดแกนหนึ่ง วางทับเส้นดิบของแกนเดียวกัน แล้วอธิบายว่าฟิลเตอร์แลกอะไรกับอะไร (ลองปรับ alpha เป็น 0.05 กับ 0.5 ประกอบ)
เขียนคำตอบลงบันทึกการเรียน แล้วเอามาเล่าให้เพื่อนฟังต้นชุดบทเรียนถัดไป

แหล่งปฐมภูมิ
.../ipc_ui/ipc_ui.c — IPC_UI_TIMER_MS, IPC_UI_FAST_TIMER_MS, IPC_UI_MAX_PER_TICK, IPC_UI_FAST_TIMEOUT_MS, ui_arm_fast_mode() (สไลด์ผู้สอน)วิดีโอที่ฝังไว้ในชุดบทเรียนนี้ (ตรวจแล้วว่าเปิดได้)
nac9qyJT-sY An intuitive introduction to oversampling and noise shaping — Analog Snippets · 12:08 · plq_Nmud5CM ADC Quantization and Resolution — Microchip Developer Help · ยาวยังไม่ยืนยัน · yYHHuhDwhec Sigma-Delta ADC: Oversampling, Noise Shaping & Decimation — Byte-Sized Learning · 5:01 · RLQGZl0lpjQ Working principle of an accelerometer — Bosch Sensortec · 1:01 — คำอธิบายว่าดูเพื่ออะไร อยู่ใต้คลิปในสไลด์ที่ฝังไว้แล้ว
เครดิตภาพ — Wikimedia Commons และ PMC open-access (CC0 / CC BY / CC BY-SA / PD) · รายละเอียดที่ CREDITS.md