ทั้งไฟล์อ่านเป็นประโยคเดียว: "อ่าน IMU → แปลงเป็นมุม → โชว์เข็มกับมุมเด่น → เตือนเมื่อเอียงเกิน → ฟังปุ่ม back"
ตัวเลขบรรทัด (
:62,:67,:76,:96) ชี้จุดที่ต้องเติมโค้ดในไฟล์ฝึก — จำโครงนี้ไว้ เดี๋ยวไล่ทีละช่อง
ส่วนหัวของไฟล์ (จังหวะ 1–2) ให้ไว้ครบแล้ว อ่านให้เข้าใจว่าสร้างอะไรไว้บ้าง:
import ui
ui.screen()
import lcd, sensors, dsp, time
DEAD = 2.0 # deadzone กันเข็มสั่น
TILT_LIMIT = 20 # เกินนี้ = "เอียง"
arc_p = ui.Arc(x=100, y=54, min=-90, max=90, value=0) # เกจ pitch
arc_r = ui.Arc(x=490, y=54, min=-90, max=90, value=0) # เกจ roll
seg = ui.Seg7("0", x=330, y=104, color=GREEN) # มุมเด่น
panel = ui.Panel(x=250, y=205, w=290, h=64, color=0x115511)
state = ui.Label("LEVEL", x=350, y=228, color=0xFFFFFF)
back = ui.Button("< ออก", x=640, y=350, w=130, h=38); back_id = back.id()
arc_p, arc_r, seg, panel, stateDEAD กับ TILT_LIMIT เป็นค่าคงที่ที่ควบคุมพฤติกรรม — remix ง่ายๆ คือลองเปลี่ยนสองค่านี้อ่านหัวไฟล์ให้ครบก่อนเข้าลูป จะได้รู้ว่ามี widget อะไรให้เล่นบ้าง ค่าคงที่ตัวไหนคุมอะไร — เหมือนดูรายชื่อเครื่องมือก่อนลงมือ
ช่องเติมที่ 1 และ 2: ต้นลูป อ่าน IMU แล้วแปลงเป็นมุม (จังหวะ 3 ท่อน อ่าน + คำนวณ):
while True:
# เติม: อ่านค่า 6 แกนจาก IMU -> ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
ax = ay = az = gx = gy = gz = 0
pass
# เติม: แปลงเป็นมุมเอียง -> roll, pitch = dsp.tilt(ax, ay, az)
roll, pitch = 0.0, 0.0
pass
pass ด้วย ax, ay, az, gx, gy, gz = sensors.bmi270.motion() (ลบบรรทัด ax = ay = ... ทิ้งได้)pass ด้วย roll, pitch = dsp.tilt(ax, ay, az)ax,ay,az ค้างเป็น 0 → dsp.tilt ได้มุม 0 เสมอ เข็มไม่ขยับ (นี่คือเบาะแสตอน debug)สังเกตว่า remix ใช้
motion()เหมือน01เป๊ะ แต่เอาผลไปป้อนdsp.tiltเหมือน02— คุณกำลัง "ผสม" สองแอปด้วยมือตัวเอง
ช่องเติมที่ 3: วาดมุมเด่นขึ้น Seg7 (จังหวะ 3 ท่อน วาด) ส่วน event-driven ให้ไว้แล้วเป็นตัวอย่าง:
if (p, r) != last: # วาดเฉพาะตอนมุมเปลี่ยน (บทเรียนจาก 04)
arc_p.value(p); arc_r.value(r)
ang = p if abs(p) >= abs(r) else r # แกนที่เอียงมากสุด
# เติม: โชว์มุมเด่นบน Seg7 -> seg.text("%d" % ang)
pass
last = (p, r)
tilted = abs(ang) >= TILT_LIMIT # ให้ไว้แล้ว: เปลี่ยนสีเฉพาะตอนข้ามเกณฑ์
if tilted != was_tilted:
panel.color(0x881111 if tilted else 0x115511)
state.text("TILTED" if tilted else "LEVEL")
was_tilted = tilted
pass ด้วย seg.text("%d" % ang) — เอามุมเด่นขึ้นจอตัวใหญ่tilted != was_tilted ให้ไว้แล้ว เป็นตัวอย่างลาย event-driven ที่ยกจาก 04 (อ่านให้เข้าใจ)เห็นการซ้อนสองชั้นของ "วาดเฉพาะตอนเปลี่ยน" ไหม — ชั้นนอกเช็กมุมเปลี่ยน ชั้นในเช็กสถานะ (เอียง/ราบ) เปลี่ยน ทั้งคู่เพื่อไม่แตะจอเกินจำเป็น
ช่องเติมที่ 4: ท้ายลูป ฟังปุ่ม back (จังหวะ 4) แล้วออกอย่างเรียบร้อย:
for ev in ui.poll():
if ev.get('handle') == back_id:
# เติม: ออกจากลูปตอนกดปุ่ม back -> raise KeyboardInterrupt
pass
time.sleep_ms(100)
except KeyboardInterrupt:
lcd.console('<span class=ok> จบการทำงาน</span>') # ให้ไว้แล้ว: เก็บกวาดตอนจบ
pass ด้วย raise KeyboardInterrupt — โยนสัญญาณออกจากลูปไปที่ exceptโครงนี้เหมือน
01/02/04ทุกบรรทัด — พอเติมครบ คุณจะเห็นว่า "ส่วนรับปุ่ม" ของทุกแอปในคอร์สหน้าตาเดียวกันเป๊ะ copy ข้ามไฟล์ได้เลย
ก่อนลงมือ ดูปลายทางกันก่อนว่าเติมครบแล้วจอจะออกมาหน้าตาแบบไหน — เข็มมุมสองแกน ตัวเลขมุมเด่นตัวใหญ่ และป้ายสถานะที่เปลี่ยนสีเมื่อเอียงเกินเกณฑ์:
ไม่ต้องมีบอร์ดอยู่ในมือก็ฝึกได้ — กด Run บน Emulator เห็นผลทันทีในเบราว์เซอร์ พอมั่นใจแล้วค่อยกด Program to Device โค้ดไม่ต้องแก้แม้แต่บรรทัดเดียว
เปิด s02_anatomy_sensor.py ในไฟล์มี pass วางไว้ 4 จุด ตรงหัวใจของโครงร่วม:
| # | จุด (จังหวะ) | เติมด้วย | ถ้าลืม |
|---|---|---|---|
| 1 | ต้นลูป (อ่าน) | ax, ay, az, gx, gy, gz = sensors.bmi270.motion() |
เข็มไม่ขยับเลย |
| 2 | ต้นลูป (คำนวณ) | roll, pitch = dsp.tilt(ax, ay, az) |
มุมเป็น 0 ตลอด |
| 3 | มีค่าใหม่ (วาด) | seg.text("%d" % ang) |
เข็มขยับ แต่ตัวเลขใหญ่ไม่เปลี่ยน |
| 4 | ปุ่ม back (poll) | raise KeyboardInterrupt |
กด back แล้วไม่ออก |
ขั้นตอน:
# เติม: ทีละจุด แล้วแทน pass ด้วยคำสั่งตามคำใบ้สี่ช่องนี้คือหัวใจของโครงร่วม 4 จังหวะเป๊ะ — เติมครบเมื่อไร คุณ remix สามแอปเป็นของตัวเองสำเร็จ
เติมครบแล้วยังไม่จบ ลอง แก้ของ ดูสามแนว นี่คือหัวใจของ Modify ใน PRIMM:
bmi270.motion() เป็น sensors.radar() แล้วโชว์ presence/energy แทนมุม (โครง 4 จังหวะไม่ต้องแตะ)Seg7 เป็น Bar หรือเพิ่มแถบ energy · เปลี่ยนสี · เปลี่ยน TILT_LIMIT เป็น 10 แล้วดูว่าป้ายไวขึ้นไหมarc_p = ui.Arc(...) เข้าไป ในลูป แล้วดูจอกระพริบ/ค้าง จากนั้นย้ายกลับออกมา — เข้าใจกฎ "สร้างครั้งเดียว" ด้วยตาตัวเองbreak-and-fix เป็นวิธีเรียนที่ทรงพลัง: จงใจทำให้พังในที่ที่ควบคุมได้ แล้วซ่อม คุณจะจำบทเรียนนั้นแม่นกว่าอ่านเฉยๆ สิบเท่า — แต่จำค่าที่แก้ไว้ เพื่อย้อนกลับได้เสมอ
อยากเข้าใจว่าชิป IMU วัดความเร่งได้อย่างไร และทำไมแรงโน้มถ่วงถึงกลายเป็นมุมเอียงได้ ลองดูสามคลิปนี้:
วิดีโอ
ภาพ / อ้างอิงเปิด
วิดีโอ/ภาพภายนอกเป็นของเจ้าของต้นฉบับ ใช้เพื่อการศึกษา อ้างอิงลิงก์ต้นทาง — เราเปิดดูผ่านลิงก์ ไม่ได้ฝังหรือดาวน์โหลดมาเผยแพร่ซ้ำ
MVP ของบทเรียน 1.4–1.5 (เกณฑ์ผ่านของชุดบทเรียน): คุณทำ remix ที่ ต่างจากต้นฉบับจริง แล้ว อธิบายแต่ละส่วนได้ — ชี้ได้ว่าบรรทัดไหนอยู่จังหวะไหน (import / สร้างครั้งเดียว / ลูป / poll) และแต่ละคำสั่ง sensors.* / dsp.tilt ทำอะไร
01/02/04: สลับเซนเซอร์ หรือเปลี่ยนการแสดงผลอย่างมีเหตุผล"อธิบายแต่ละส่วนได้" คือหัวใจ — remix ที่รันได้แต่เจ้าของอธิบายไม่ได้ ยังไม่ผ่าน เพราะเป้าหมายของชุดบทเรียนนี้คือ "อ่านออก" ไม่ใช่แค่ "ลอกให้รัน"
ถ้าติด ให้ไต่บันไดนี้ทีละขั้น อย่าเพิ่งกระโดดไปดูเฉลย เพราะของจะเข้าหัวตอนที่คุณพยายามเองก่อน:
# เติม: ทั้ง 4 จุดในไฟล์ฝึก + ตารางหน้าที่แล้ว บอกว่าแต่ละช่องเติมอะไรs02_anatomy_sensor.py มีโครง 4 จังหวะครบทั้งไฟล์แล้ว เหลือแค่ 4 บรรทัดให้เติมs02_anatomy_sensor.py เติมครบพร้อมคอมเมนต์อธิบายทุกช่อง (อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เอง)s02_anatomy_sensor_full.py ฉบับขัดเรียบร้อย เพิ่มแถบ |a|, จำมุม peak, ปุ่ม Resetลองเขียนเองให้สุดก่อนนะ ถ้าติดจริงๆ ค่อยเปิดเฉลยดูทีละช่อง แล้วกลับมาพิมพ์เอง — เดี๋ยวเราค่อย ๆ แกะไปด้วยกัน
การ remix แอปเซนเซอร์ทำให้เราจับแนวคิดที่จะใช้ไปตลอดคอร์สได้ครบชุด:
ฝั่งโครงโปรแกรม (ใช้ทุกบทเรียนต่อจากนี้)
ui.poll เจอซ้ำในทุกแอปraise KeyboardInterrupt → except ออกอย่างมีระเบียบฝั่งเซนเซอร์ / สัญญาณ
sensors.* — อ่านค่าจริงจากฮาร์ดแวร์ (bmi270.motion(), radar())dsp.tilt — ยกงานคณิตหนักให้ฝั่ง C เราแค่เรียกใช้ผล (จะแกะข้างในใน โมดูล 3 (Processing))ทั้งหมดนี้จะกลายเป็น "มือ" ที่ติดตัว — ชุดบทเรียน DAQ (บทเรียน 2.1–2.2) เราจะเอาโครงเดียวกันนี้ไปเขียน logger เก็บข้อมูลลงไฟล์ โดยไม่ต้องเริ่มจากศูนย์
สามตัวอย่างที่เรา remix วันนี้ไม่ใช่ของเล่น ทุกตัวมีสินค้าจริงในตลาดที่ทำงานด้วยโครงเดียวกัน:
เห็นไหมว่าพอจับโครงร่วมได้ คุณไม่ได้เรียนแค่ "แอปเดียว" แต่ได้แม่แบบที่ปรับเป็นสินค้าจริงได้สารพัด — แค่เปลี่ยนช่อง "อ่าน" กับ "คำนวณ"
งานทำเอง (ท้ายบทเรียน):
s02_anatomy_sensor.py ให้ครบทั้ง 4 ช่อง รันได้จริง (Emulator หรือบอร์ด)radar()) หรือเปลี่ยนการแสดงผล แล้วจดว่าแก้อะไร เพราะอะไรใบ้ข้อ 2 — remix ที่ง่ายและเห็นผลชัด: เปลี่ยน TILT_LIMIT เป็น 10 (ป้ายไวขึ้น) หรือสลับ bmi270.motion() เป็น sensors.radar() แล้วโชว์ presence แทนมุม
วันนี้เราได้: อ่านแอปเซนเซอร์ที่ทำงานได้ให้ออก · จับโครงร่วม 4 จังหวะ · รู้จัก sensors.* + dsp.tilt · remix สามแอปเป็น Tilt Monitor ของตัวเอง
ชุดบทเรียนถัดไป (บทเรียน 1.6–1.7) เราจะเอา "การแกะโครง" แบบเดียวกันนี้ไปใช้กับ แอป Edge AI — แกะเมนูโมเดลจากบทเรียน 1.1–1.3 ให้ขาด แล้ว remix ให้สลับโมเดล + สั่งการเมื่อเจอคลาสที่ต้องการ เจอกันครับ