ต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 35 นาที
| ลำดับ · เรื่อง · เวลา | ไฟล์ | ลงมือทำอะไร แล้วจะเข้าใจอะไร |
|---|---|---|
1 · ข้างในของ dsp.tilt() · 10 นาที |
03_tilt_from_gravity.py |
เอียงบอร์ดแล้วดูสามคอลัมน์: ค่าดิบ · คำตอบของ dsp.tilt() · คำตอบที่ไฟล์นี้คำนวณเองด้วย atan2 · สองคอลัมน์ขวาตรงกันเสมอ และแถวล่างแสดงให้เห็นว่าถ้าแกะสลับลำดับจะอ่านได้เป็นอะไร |
| 2 · กรองก่อนค่อยตัดสิน · 10 นาที | 01_imu_step_counter.py |
เดินถือบอร์ดสิบก้าวแล้วเทียบเลขที่นับได้กับที่เดินจริง · จะเห็นว่าถ้าไม่กรองก่อนและไม่มีเวลาห้ามนับซ้ำ สั่นครั้งเดียวจะถูกนับหลายก้าว |
| 3 · เหตุการณ์เดียวไม่พอ · 15 นาที | 02_imu_fall_detection.py |
ลองวางบอร์ดลงแรง ๆ ให้มันเข้าใจผิดว่าล้ม แล้วดูว่าขั้นไหนไม่ผ่าน · การล้มคือลำดับของเหตุการณ์ ไม่ใช่ความแรงอย่างเดียว |
เปิดตามความสนใจ · นอกเกณฑ์ผ่านของชุดบทเรียนนี้ แต่ปิดช่องว่างของโมดูล
| เรื่อง | ไฟล์ | ทำไมถึงมี |
|---|---|---|
| เข็มทิศ และข้อบกพร่องที่ยังค้าง | 04_compass_and_magnetometer.py |
ครบทั้งห้าคำสั่งของ bmm350 และแสดง dsp.compass() เทียบกับ heading() ให้เห็นว่าคนละสูตร |
| fusion เต็มรูป และตัวนับก้าวสำเร็จรูป | 05_madgwick_and_pedometer.py |
dsp.Madgwick กับ dsp.Pedometer สองคลาสสุดท้ายของโมดูล พร้อมกับดักเรื่องหน่วยทั้งสองข้อ |
ติดตรงไหน เปิดอันนี้
| อาการที่เจอ | ไฟล์ที่ตอบอาการนั้น |
|---|---|
กรอกช่อง dsp.EMA(alpha= ในบันทึกการเรียน ไม่ถูก ไม่รู้จะใส่เท่าไร |
06_ema_time_constant.py — แปลง alpha เป็นเวลา tau เป็นวินาที เลือกจากตัวเลขได้เลย ไม่ต้องเดา |
| วางบอร์ดนิ่งแล้วแถบยังกระตุก ทั้งที่ใส่ตัวกรองไปแล้ว | 07_median_beats_mean.py — ค่าหลุดค่าเดียวลากค่าเฉลี่ยไปด้วย แต่ทำอะไร median ไม่ได้ เพราะ median เรียงแล้วหยิบตัวกลาง |
s06_digital_level.py — ตั้งต้นและหน้าจอimport ui
ui.screen()
import time
import sensors
import dsp
ui.clear()
time.sleep_ms(200)
COL_TEXT, COL_DIM = 0xE8EAED, 0x9AA3AF
COL_CARD = 0x171B22
COL_OK, COL_WARN, COL_BAD = 0x30A46C, 0xF5A623, 0xE5484D
TOL_MIN, TOL_MAX, TOL_STEP = 1, 30, 1
tol = 5 # เกณฑ์ยอมรับ หน่วยองศา
... # ท่า 1-2 ดูสไลด์ "แกะโค้ดจริง"
ema_roll = dsp.EMA(alpha=0.2)
ema_pitch = dsp.EMA(alpha=0.2)
roll_zero = 0.0
pitch_zero = 0.0
...
last_sec = -1 # วินาทีที่เพิ่งเขียนตัวเลขลงจอ
def clamp90(v):
return 90.0 if v > 90.0 else (-90.0 if v < -90.0 else v)
0x4A9EFF ปุ่มรอง 0x3A4150 ปุ่มหลัก 0x30A46C ตามจานสีของหลักสูตร · ตัวกรองสองตัวเพราะสองสัญญาณ ตัวกรองมี ความจำrunning = True
while running:
ok_read = True
try:
ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
roll, pitch = dsp.tilt(ax, ay, az)
except OSError:
ok_read = False
if ok_read:
roll_f = ema_roll.update(roll)
pitch_f = ema_pitch.update(pitch)
roll_show = roll_f - roll_zero
pitch_show = pitch_f - pitch_zero
for ev in ui.poll():
h = ev['handle']
if h == zero_id:
roll_zero = roll_f
pitch_zero = pitch_f
elif h == exit_id:
running = False
# ปุ่มเพิ่ม/ลด tol - ท่าที่ 5
...
roll_bar.value(int(clamp90(roll_show))) # แถบ ไฟ ตัวเลข - ท่าที่ 6
...
lbl_health.text(health)
time.sleep_ms(200)
if ok_read — รอบที่อ่านไม่ได้ก็ยังต้องวาดจอด้วยค่าเดิม แล้วเขียนบรรทัดคุณภาพ lbl_health ว่า "ค่าค้าง" ด้วยสีเตือน ถ้าปล่อยให้การวาดอยู่ใน if จอจะแช่ภาพเดิมโดยไม่มีอะไรบอกคนดู · in_tol ใช้ค่าที่ กรองแล้วและหักศูนย์แล้ว ไม่ใช่ค่าดิบ ไม่งั้นไฟจะกระพริบสลับดวงตอนค่าคาบเกี่ยวเกณฑ์ (alarm chattering) · for ev in ui.poll() อยู่ นอก if ok_read โดยตั้งใจ — เซนเซอร์ตายไม่ได้แปลว่าทั้งเครื่องต้องตายตามวันนี้เราได้: เข้าใจว่า accelerometer วัดแรงที่ดันมวลไว้ (proper acceleration) · อ่านหกแกนจากการอ่านครั้งเดียวและรู้ว่าทำไม (Eva: snapshot ชุดเดียว · Dev Kit: lock บัสครั้งเดียว) · แปลงเวกเตอร์แรงโน้มถ่วงเป็นสององศาด้วย dsp.tilt() โดยจำลำดับ (roll, pitch) ได้ · รู้ว่า gyro drift กับ accel jitter คนละปัญหากัน · สร้างเครื่องมือวัดที่มีปุ่มตั้งศูนย์แบบเดียวกับเครื่องมือจริง
รากฐานที่แตะไป: เซนเซอร์ MEMS และความหมายทางฟิสิกส์ของค่าที่คืน · atomic read บนบัสร่วม · ตรีโกณมิติกับ atan2 · low-pass แบบ recursive และแนวคิด time constant · การอินทิเกรตกับการสะสมความผิดพลาด · calibration และ tare · การเลือกช่วงสเกลให้ตรงกับปริมาณจริง
ชุดบทเรียนถัดไป: เอา accel สามแกนเดียวกันนี้ไปวาดเป็น กราฟ real-time พร้อมตารางค่าสุดขีดและปุ่มเริ่ม/หยุดบันทึก
atan2กับ EMA จะตามผู้เรียนไปทุกงานที่มีเซนเซอร์ ไม่ว่าจะเปลี่ยนภาษา เปลี่ยนบอร์ด หรือเปลี่ยนบริษัท
สังเกตช่อง "ต้องการ" ของทั้งสี่มุม — โจทย์ต่างกัน จึงเลือกฟิลเตอร์และ cadence ต่างกัน ไม่มีสูตรเดียวใช้ได้หมด
ข้อ 1 · โหมดฟองอากาศ
ทำระดับน้ำแบบฟองอากาศด้วย ui.Panel วงเล็กหนึ่งใบ แล้วขยับด้วย .pos(x, y) ตามค่า roll และ pitch ให้เหมือนฟองในหลอดแก้ว วางราบแล้วฟองต้องอยู่กลางวงกลมอ้างอิง
ข้อ 2 · เตือนเมื่อไม่ได้ระดับ
ถ้ามุมแกนใดเกิน ±3° ให้เปลี่ยนสี Seg7 เป็นแดงด้วย .color() และจุดไฟจริงหนึ่งดวงพร้อมกัน — เลือกดวงตามชื่อใน gpio.board_info()["led_names"] (ดู led_named() ใน 01_imu_vibration_monitor.py) ไม่ใช่ gpio.led(0) ตายตัว เพราะเลข 0 เป็นคนละดวง (ชื่อต่างกัน) บนสองบอร์ด · คิดต่อว่าจะกัน "กะพริบถี่" ตอนค่าแกว่งอยู่ที่ขอบพอดีได้อย่างไร
ข้อ 3 · แข่ง alpha
สร้าง dsp.EMA สองตัวด้วย alpha ต่างกัน (0.05 กับ 0.5) ป้อน roll ตัวเดียวกันเข้าทั้งคู่ จับเวลาด้วย time.ticks_ms() ว่าแต่ละตัวใช้กี่มิลลิวินาทีกว่าจะไล่ตามการเอียงทัน แล้วสรุปเป็นตัวเลขว่าแลกอะไรกับอะไร
ข้อ 4 · พิสูจน์ว่า gyro drift จริงไหม
เขียนมุมจาก gyro ล้วน ๆ ด้วย gyro_roll = gyro_roll + gx * 0.2 ทุกรอบ แสดงคู่กับ roll จาก dsp.tilt() วางนิ่งสามนาที บันทึกว่าเส้นไหนหนีกี่องศา

เขียนคำตอบลงบันทึกการเรียน แล้วเอามาเล่าให้เพื่อนฟังต้นชุดบทเรียนถัดไป


แหล่งปฐมภูมิ
วิดีโอที่ฝังไว้ในชุดบทเรียนนี้ (ตรวจแล้วว่าเปิดได้)
RLQGZl0lpjQ Working principle of an accelerometer — Bosch Sensortec · 1:01 · KuekQ-m9xpw How does an Accelerometer work? — CircuitBread · 6:10 · XRr1kaXKBsU What Everyone Gets Wrong About Gravity — Veritasium · 17:33 (เริ่ม 2:30) · PK05u9c3yWI How do MEMS gyroscopes work? — nanolearning · 13:44 (เริ่ม 4:00) — คำอธิบายว่าดูเพื่ออะไร อยู่ใต้คลิปในสไลด์ที่ฝังไว้แล้ว
เครดิตภาพ — ทุกภาพมาจาก Wikimedia Commons หรือบทความ open-access ใน PMC (CC0 / CC BY / CC BY-SA / สาธารณสมบัติ) รายละเอียดที่ CREDITS.md · ห้ามใช้ บทความไทยที่ติดอันดับสูงสุดเรื่อง gyro/accel เพราะอธิบายผิดหลักการ
ตัวอย่างโค้ด — โฟลเดอร์ examples/ ของบทเรียน 3.1–3.3 ห้าไฟล์ · คลังชุดบทเรียนนี้ถูกคัดจากหกไฟล์เหลือสองเมื่อ 14 ส.ค. (ไฟล์ที่ถอดออกเป็นเรื่องบิตกับเรื่องตัวกรอง ซึ่งเป็นของชุดบทเรียนอื่น) แล้ว เพิ่มกลับสามไฟล์เมื่อ 15 ส.ค. เพื่อปิดช่องว่างของ API ที่ชุดบทเรียนนี้เป็นเจ้าของ: 03_tilt_from_gravity.py (dsp.tilt ซึ่งเป็นแกนกลางของชุดบทเรียนและเดิมไม่มีตัวอย่างเลย) · 04_compass_and_magnetometer.py (sensors.bmm350 ทั้งห้าคำสั่ง + dsp.compass) · 05_madgwick_and_pedometer.py (dsp.Madgwick + dsp.Pedometer) · ตัวกรองที่ชุดบทเรียนนี้ยืมมาใช้อยู่ที่ 06_ema_time_constant.py และ 07_median_beats_mean.py ส่วนตระกูลเต็มอยู่ที่ 08_six_filters_one_signal.py · ปลายทางเรื่องการสั่นอยู่ที่ 01_imu_vibration_monitor.py
เปลี่ยนจากเฟิร์มแวร์รุ่นก่อน — บน Eva Kit sensors.init() scan() push() live_push() auto() ถูกปฏิเสธด้วย OSError ทั้งห้าตัว · sensors.bmi270.motion() / .acceleration() / .gyroscope() อ่านผ่าน snapshot ของคอร์จอ ส่วน .temperature() / .chip_id() ใช้ไม่ได้ · sensors.bmm350.* ทั้งห้าคำสั่งอ่านตรงจากชิปได้ เพราะอยู่คนละบัส (I3C) · บน TESAIoT Dev Kit CM33 อ่าน BMI270 ตรงจาก I2C: ห้าตัวข้างต้นไม่ถูกปฏิเสธ (แต่คอร์สนี้ไม่เรียก) และ .temperature() / .chip_id() ใช้ได้ · snapshot() มีทั้งสองบอร์ด dict หน้าตาเดียวกัน · ทิศแกน x/y เทียบขอบฐาน QWA309 ยังไม่ได้วัด · ความเร่งเป็นช่วง +/-8g (4096 LSB/g) และ gyro เป็น +/-2000 dps (16.4 LSB/dps) · ตรวจจากซอร์ส BENTO-TESAIoT-libraries/claw/common/mpy/modsensors.c, modsensors_bmm350.c, sensor_bmm350.c และ moddsp_imu.c โดยตรง
โน้ตผู้สอน: สิบสองในยี่สิบสามข้อนี้ไม่ทำให้โปรแกรม crash — มันแค่ให้คำตอบผิดอย่างเงียบ ๆ ซึ่งอันตรายกว่า · ห้าข้อล่างสุดเป็นเรื่อง **หน่วย** ล้วน ๆ และหกข้อกลางเป็นเรื่อง **เข้าใจ widget ผิดตัว** ทั้งสามกลุ่มไม่มี error ให้เห็น
โน้ตผู้สอน: เปลี่ยนจากรุ่นก่อน (15 ส.ค.): เดิมสไลด์นี้เขียนว่า "ยังไม่มีไฟล์ตัวอย่างไหนในคลังที่คำนวณ roll/pitch" ซึ่งเป็นจริงจนถึงวันนั้น · ตอนนี้ 03_tilt_from_gravity.py ทำหน้าที่นั้นแล้ว และเปิดให้เห็น atan2 สองบรรทัดที่อยู่ข้างในด้วย — งาน Build ใน s06_digital_level.py ยังเป็นการเขียนเองเหมือนเดิม ไฟล์ตัวอย่างอธิบาย *ทำไม* ไม่ได้ยกคำตอบมาให้
โน้ตผู้สอน: อ่านเสริมนอกเวลา — เรื่องนี้เป็นของบทเรียน 3.4–3.6 ไม่ใช่เกณฑ์ผ่านของวันนี้ แต่เป็นปลายทางที่ IMU ตัวเดียวกันนี้ไปได้ไกลที่สุดในงานอุตสาหกรรม: 01_imu_vibration_monitor.py วางบอร์ดบนของที่สั่นแล้วดูค่า RMS เทียบเส้นฐานที่โปรแกรมเก็บเอง · การสั่นวัดด้วยค่าเฉลี่ยไม่ได้ เพราะบวกลบหักล้างกันจนเหลือศูนย์