import ui
import sensors
import time
ui.screen() # เปิดหน้าจอ Playground แบบว่าง (ล้าง widget เดิมทุกตัวให้เอง)
time.sleep_ms(200) # ให้ CM55 ตามทันก่อนเราเริ่มสร้างของใหม่
...
BG_CARD = 0x171B22 # พื้นการ์ด เทาเข้มอมน้ำเงิน
COL_WHITE = 0xE8EAED # ตัวเลขพระเอก
COL_GRAY = 0x9AA3AF # ข้อความประกอบ / สถานะเงียบ เทาอ่อน
...
COL_STAT = 0x30A46C # เขียว "ปกติ"
...
# ไม่มี sensors.init() ทั้งสองบอร์ด (Eva: ได้ OSError / Dev Kit: ไม่จำเป็น)
try:
sensors.bmi270.motion() # อ่านทิ้งหนึ่งครั้ง ให้การรอไปเกิดตรงนี้
except OSError:
print("อ่านเซนเซอร์รอบแรกยังไม่ได้ - ลองใหม่ในลูป")
เรียกอ่านค่าครั้งแรกแล้วเงียบไปสิบกว่าวินาที นั่นคือการรอ ไม่ใช่การค้าง (Eva วัดได้ถึง 16 วินาที · Dev Kit ยังไม่ได้วัด) — อย่าเพิ่งถอดสาย USB และบน Dev Kit ห้ามโยกสวิตช์บนฐาน นั่นคือสวิตช์ไฟ
ห้ามเรียก sensors.init() บน Eva Kit — เฟิร์มแวร์ปฏิเสธด้วย OSError เพราะคอร์จอ (CM55) เป็นเจ้าของบัส I2C ของเซนเซอร์ทั้งชุด ขับบัสซ้อนแล้วบอร์ดค้างจนต้องถอดไฟ · บน Dev Kit บรรทัดนั้นผ่านแต่ไม่จำเป็น เฟิร์มแวร์ปลุกทุกตัวไว้ตั้งแต่บูต — ไฟล์ของคอร์สจึงไม่มี init() และรันได้ทั้งสองบอร์ด
ค่ามาจากไหน — Eva: CM55 อ่านเซนเซอร์ให้ทุก 200 ms แล้วเก็บเป็นภาพสแกน · Dev Kit: CM33 อ่าน IMU และลูกบิดตรงจากบัสของตัวเอง ส่วนแถบสัมผัสถามคอร์จอ · ui.screen() ล้าง widget เดิมทั้งหมด ไม่เรียกแล้วของจากสคริปต์ก่อนหน้าจะกินงบตั้งแต่ยังไม่เริ่ม
imu_panel = ui.Panel(x=24, y=100, w=368, h=136,
color=BG_CARD, min=COL_IMU, max=12, value=2)
imu_title = ui.Label("ความเร่ง BMI270 (m/s2)", x=40, y=108, color=COL_IMU, value=16)
# Chart เก็บ 50 จุด รับเฉพาะจำนวนเต็ม เราจึงคูณ 10 ก่อนป้อน (-150..150 = -15.0..+15.0)
imu_chart = ui.Chart(x=40, y=136, w=336, h=56, min=-150, max=150, color=COL_IMU)
sy = imu_chart.add_series(COL_STAT) # แกน Y เขียว
sz = imu_chart.add_series(0x448AFF) # แกน Z ฟ้า
imu_val = ui.Label("X+0.0 Y+0.0 Z+9.8", x=40, y=200, color=COL_WHITE, value=20)
ui.Chart รับเฉพาะ จำนวนเต็ม เราจึงคูณค่าจริงด้วย 10 ก่อนป้อน แล้วตั้งแกน −150 ถึง 150 (= −15.0 ถึง +15.0 m/s²) ป้อน int(ax) ตรง ๆ ความละเอียดจะเหลือขั้นละ 1 m/s² กราฟเป็นขั้นบันได · series แรกได้สีจาก color= ของ Chart (COL_IMU สีม่วงประจำการ์ด ไม่ใช่สีเตือน) series ที่สองและสามได้จาก add_series() ซึ่งคืน index มาให้เก็บไว้ใช้ตอน set_next() · imu_val วางที่ y=200 คือใต้กราฟแต่ยังอยู่ในกรอบการ์ด (100 + 136 = 236)


กราฟบอกแนวโน้ม ตัวเลขบอกค่าปัจจุบัน — ใส่คู่กันเสมอ อย่างละครึ่งไม่พอสำหรับคนตัดสินใจ
comp_panel = ui.Panel(x=408, y=100, w=360, h=136,
color=BG_CARD, min=COL_COMP, max=12, value=2)
comp_title = ui.Label("เข็มทิศ BMM350", x=424, y=108, color=COL_COMP, value=16)
compass = ui.Compass(x=424, y=132, w=96, h=96, color=COL_COMP)
comp_deg = ui.Label("000 deg", x=544, y=140, color=COL_WHITE, value=28)
comp_dir = ui.Label("N", x=544, y=188, color=COL_COMP, value=24)
...
DIRS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
...
comp_dir.text(DIRS[int((heading + 22.5) / 45.0) % 8])
ui.Compass ใช้ w เป็น เส้นผ่านศูนย์กลาง — ไฟล์ส่ง h เท่ากันไว้ด้วย เพื่อให้ด่านตรวจพิกัดอ่านขนาดของมันได้ · รับค่าทิศผ่าน .value(องศา) โดย 0 คือทิศเหนือ · สีของเข็มทิศคือ COL_COMP สีประจำการ์ด ไม่ใช่สีเตือน
หน้าปัดกินซ้าย ตัวเลขกินขวา ตัวเลของศาได้ฟอนต์ 28 ใหญ่ที่สุดในหน้าจอ เพราะเป็นค่าที่ต้องอ่านจากไกลที่สุด ส่วนตัวอักษรทิศได้ 24 · แปลงองศาเป็นชื่อทิศด้วยเลขคณิตบรรทัดเดียว ไม่ต้องมี if ยาว ๆ — บวก 22.5 ก่อนหารด้วย 45 คือ "เลื่อนขอบช่อง" ให้ทิศเหนือกินช่วง 337.5–22.5 องศา แทนที่จะเป็น 0–45

ตัวเลขให้ความแม่นยำ ตัวอักษรทิศให้ความหมาย คนที่ยืนดูใช้อย่างหลังก่อนเสมอ
sensors.bmm350 มีห้าชื่อ — และหนึ่งในนั้นเรายังตอบเรื่องหน่วยไม่ได้การ์ดเข็มทิศใช้ heading() ตัวเดียว แต่ชิปเปิดให้เราอีกสี่ชื่อ ทุกตัว ไม่รับอาร์กิวเมนต์เลย และโยน OSError เมื่ออ่านไม่สำเร็จ
| เรียกอะไร | คืนอะไรกลับมา | ใช้ตอนไหน |
|---|---|---|
heading() |
float 0 ถึง 360 องศา 0 คือทิศเหนือ |
ค่าเดียวที่การ์ดเข็มทิศต้องใช้ |
magnetic() |
tuple สามค่า (mx, my, mz) เป็น float |
อยากเห็นสามแกนดิบ เช่นตรวจว่ามีแม่เหล็กเข้ามาใกล้ |
cal_reset() |
None และ พิมพ์หนึ่งบรรทัดลง REPL ว่าให้หมุนบอร์ดครบรอบ |
เริ่มคาลิเบรต hard-iron ใหม่ |
cal_status() |
dict สามคีย์ {'valid': bool, 'offset_x': float, 'offset_y': float} |
ดูว่าค่าชดเชยลู่เข้าหรือยัง |
chip_id() |
int |
ยืนยันว่ากำลังคุยกับชิปถูกตัว ตอนสงสัยว่าสายหลุด |
สามข้อที่ทำให้เข็มทิศบนการ์ดนิ่งหรือไม่นิ่ง
heading() เท่านั้นที่ขยับตัวคาลิเบรต — magnetic() ไม่ได้ป้อนอะไรให้มันเลย โปรแกรมที่เรียกแต่ magnetic() จะรอค่าชดเชยจนวันตายก็ไม่ลู่เข้าheading() คืนค่าเฉลี่ยของสิบครั้งหลังสุด เฉลี่ยแบบวงกลมเพื่อไม่ให้พังตอนข้ามรอยต่อ 0 กับ 360 องศา สิบครั้งแรกหลังบูตจึงเป็นค่าที่ยังเฉลี่ยไม่เต็มถัง — อย่าเพิ่งตัดสินจากค่าแรกcal_status()['valid'] เป็น True ก็ต่อเมื่อครบสองเงื่อนไข คือเก็บครบ 50 ตัวอย่าง และ ค่าที่กวาดได้กระจายพอทั้งสองแกน หมุนบอร์ดไม่ครบรอบ เงื่อนไขที่สองไม่ผ่าน · cal_reset() ไม่ได้เขียนอะไรลงชิป มันล้างตัวแปรฝั่งบอร์ดเท่านั้น ค่าคาลิเบรตจึงหายทุกครั้งที่รีเซ็ตชิปตัวนี้ตั้ง ODR ไว้ที่ 25 Hz อ่านถี่กว่านั้นจะได้ค่าเดิมซ้ำ ไม่ใช่ค่าใหม่ — ลูป 200 ms ของเราอยู่ห่างจากเพดานนี้มาก จึงไม่ต้องกังวล
sensors.bmm350 — ข้อบกพร่องที่ยังเปิดอยู่สองข้อ พูดตรง ๆ ดีกว่าให้ทีมไปเจอเองหนึ่ง หน่วยของ magnetic() ตอนนี้ตอบไม่ได้ว่าคืออะไร คอมเมนต์ในไดรเวอร์และป้ายในไฟล์ตัวอย่างเขียนว่า uT (ไมโครเทสลา) แต่ภาพที่จับจากบอร์ดวัดขนาดรวมสามแกนได้ราว 1532 ขณะที่สนามแม่เหล็กโลกทั้งใบอยู่ที่ 25 ถึง 65 uT — ตัวเลขกับหน่วยเป็นจริงพร้อมกันไม่ได้ อย่างน้อยหนึ่งอย่างผิด และเรายังไม่ได้ตัดสินว่าอันไหน
สิ่งที่ ไม่ ผิดคือทิศ: heading() วัดบนบอร์ดจริงได้ 215.8 เมื่อ 14 ส.ค. 2026 ซึ่งตรงกับความจริง เพราะการหาทิศใช้ atan2 ซึ่งสนใจแค่ อัตราส่วน ระหว่างสองแกน ตัวคูณที่ผิดเท่ากันทั้งคู่จึงหักล้างกันหมด นี่คือเหตุผลที่ข้อบกพร่องนี้อยู่มาได้นานโดยไม่มีใครสังเกต
กติกาของชุดบทเรียนนี้: ห้ามเขียนตัวเลขจาก magnetic() ลงรายงานพร้อมหน่วย uT ให้เขียนว่า "หน่วยของบอร์ด" และเทียบเป็น ค่าต่างจากเส้นฐานที่ทีมวัดเอง เสมอ — ซึ่งบังเอิญเป็นสิ่งที่ 04_magnet_presence.py กับ 05_door_open_switch.py ทำอยู่แล้วทั้งคู่ ทั้งสองไฟล์จึงยังใช้ได้ตามปกติ เกณฑ์ของมันเป็น "เบี่ยงไปเท่าไรจากตอนเริ่ม" ไม่ใช่ "กี่ไมโครเทสลา"
สอง cal_reset() กับ cal_status() ยังไม่มีใครรันบนบอร์ดจริงแม้แต่ครั้งเดียว ทั้ง Eva Kit และ Dev Kit ไฟล์ที่ใช้มันคือ 14_hard_iron_calibration.py และมันเขียนคำเตือนข้อนี้ไว้ในหัวไฟล์ของตัวเอง เปิดได้ ลองได้ แต่ถ้าได้ OSError นั่นคือข้อมูลใหม่ที่ยังไม่มีใครมี ให้บอกผู้สอน
ตัวเลขที่ถูกกับหน่วยที่ถูกเป็นคนละเรื่องกัน — ระบบวัดที่บอกไม่ได้ว่าหน่วยของตัวเองคืออะไร ยังไม่ใช่ระบบวัดที่เสร็จ
cap_led0 = ui.Led(x=40, y=288, w=48, h=48, color=COL_TOUCH, value=0)
ui.Label("ปุ่ม 0", x=96, y=300, color=COL_GRAY, value=16)
cap_led1 = ui.Led(x=160, y=288, w=48, h=48, color=COL_TOUCH, value=0)
ui.Label("ปุ่ม 1", x=216, y=300, color=COL_GRAY, value=16)
cap_pct = ui.Label("0 %", x=272, y=256, color=COL_WHITE, value=20)
cap_bar = ui.Bar(x=40, y=348, w=288, h=16, min=0, max=100, value=0, color=COL_TOUCH)
...
pot_arc = ui.Arc(x=376, y=288, w=88, h=88, min=0, max=100, value=0)
pot_seg7 = ui.Seg7(x=480, y=288, w=112, h=44, color=COL_POT, min=0, max=100)
pot_volt = ui.Label("0.000 V", x=480, y=340, color=COL_WHITE, value=20)
ทำไมการ์ดสัมผัสใช้ Bar ไม่ใช่ Slider ทั้งที่หน้าตาคล้ายกัน — เพราะ Slider เป็นของที่ คนลาก ส่วน Bar เป็นของที่ เครื่องแสดง ค่าที่นิ้วเลื่อนอยู่บนแถบ CapSense จริง ๆ ถ้าใช้ Slider คนดูจะเข้าใจผิดว่าลากบนจอได้ นี่คือหลัก HMI ที่เรียกว่า ความสอดคล้องระหว่างหน้าตากับสิ่งที่ทำได้จริง · การ์ด pot ใช้สองตัวคู่กันด้วยเหตุผลเดียวกับกราฟ+ตัวเลข: Arc ตอบว่า "อยู่ตรงไหนของช่วง" ในพริบตา ส่วน Seg7 ให้ตัวเลขที่จดลงบันทึกการเรียนได้
ปุ่ม CapSense ใช้ ไฟสองดวง แทนป้ายที่เปลี่ยนสี — ถ่ายจอเป็นขาวดำแล้วยังแยกออกว่าดวงไหนติด · ไฟหรี่ ไม่ใช่หาย ตอนไม่ได้แตะ:
cap_led0.value(1 if cap['btn0'] else 0)
cap_led1.value(1 if cap['btn1'] else 0)
cap_bar.value(int(cap['slider']))

เลือก widget ตาม "ใครเป็นคนเปลี่ยนค่านี้" ไม่ใช่ตามว่าอันไหนดูดีกว่า
while True:
t0 = time.ticks_ms()
ok = True
if running:
try:
ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
except OSError:
ok = False # คงค่าเดิมไว้ ไม่เขียนศูนย์ทับ
imu_chart.set_next(0, int(ax * 10))
...
if time.ticks_diff(time.ticks_ms(), last_text) >= UI_TEXT_MS:
last_text = time.ticks_ms()
...
head.text("รอบที่ {} | loop {} ms".format(
rounds, time.ticks_diff(time.ticks_ms(), t0)))
rounds += 1
for ev in ui.poll():
...
time.sleep_ms(200)
กฎเหล็กข้อ 4 มีผลเต็ม ๆ ตรงนี้: พอเรียก ui.* ครั้งแรก ระบบอ่านเซนเซอร์อัตโนมัติของเฟิร์มแวร์จะหยุด เราจึงต้องอ่านเองทุกตัวในลูป แบบ synchronous เรียกแล้วรอค่าตรงนั้น ทั้งสี่ตัวเรียงกันในรอบเดียว
motion() ให้หกแกนจากการอ่านครั้งเดียว เร็วกว่าเรียก acceleration() แล้ว gyroscope() แยกสองครั้ง เช่นเดียวกับ capsense.read() ที่คืนสองปุ่มและ slider ในครั้งเดียว — หนึ่งรอบลูป แตะเซนเซอร์ให้น้อยครั้งที่สุด
ui.poll() ต้องเรียกทุกลูป — ชุดบทเรียนนี้มีปุ่มเดินหน้า/หยุดภาพและ Spinbox ให้กดจริง แต่ต่อให้หน้าไหนไม่มีปุ่มก็ต้องเรียก เพราะมันคือจังหวะที่ CM55 ได้ระบายคิว ไม่เรียกแล้วจอจะซ่อน widget ราวสองวินาทีแล้วกลับมาใหม่วน ๆ · except ไม่เขียนศูนย์ทับค่าเดิม แค่ยกธง ok แล้วไฟค่าค้างเป็นคนบอก · t0 ต้นลูปแล้ววัดเวลาที่ใช้ไป คือเครื่องมือหลักตอน soak test
ทุกบรรทัดในลูปนี้จะถูกรันประมาณสามพันรอบระหว่างการทดสอบสิบนาที บรรทัดที่แพงจะโผล่ให้เห็นเอง
ดูเพิ่ม (1 นาที): Working principle of an accelerometer — Bosch Sensortec — 1:01 — ผู้ผลิต BMI270 บนบอร์ดเราแสดงให้เห็นว่ามวลพิสูจน์ในชิปขยับจริง ซึ่งคือที่มาของเส้นกราฟสามเส้นนี้
เซนเซอร์สี่ตัวไม่ได้วิ่งขนานกัน มันต่อคิวกันอยู่ในลูปเดียวของเรา — นี่คือเหตุผลที่ต้องอ่านให้น้อยครั้งที่สุด
mic ทั้งแปดชื่อไมโครโฟนบนบอร์ดใช้จาก Python ได้แล้ว โมดูล mic ฝังมากับเฟิร์มแวร์ ทั้ง Eva Kit, TESAIoT Dev Kit และ AI Kit import mic ได้เลย ไม่ต้องคัดลอกไฟล์ไปไว้บนบอร์ด และมีอยู่แปดชื่อพอดี
| เรียกอะไร | คืนอะไร | หมายเหตุ |
|---|---|---|
mic.start(sens=3, rate=16000, samples=256) |
True |
ต้องเรียกก่อนทุกอย่าง ตัวอื่นจะโยน OSError ถ้ายังไม่เปิด |
mic.stop() |
None |
ปิดแล้วคิวหยุดโต |
mic.read(n=None) |
list ของจำนวนเต็ม หักค่ากลางออกแล้ว ยาวเท่า samples (ตั้งต้น 256) |
ตัวเดียวที่ให้ รูปคลื่นดิบ · ไม่ทิ้งของค้าง อ่านของเก่าก่อน |
mic.stats(fresh=True) |
tuple สามค่า (rms, peak, dc) |
สามค่ามาจาก หน้าต่างเดียวกัน เป็นวิธีเดียวที่จะได้ชุดที่สอดคล้องกัน |
mic.rms() |
จำนวนเต็ม 0 ถึง 32768 | ความดังเฉลี่ยแบบยกกำลังสองก่อน |
mic.peak() |
จำนวนเต็ม 0 ถึง 32768 | ยอดสูงสุดในหน้าต่าง เห็นเสียงพุ่งสั้น ๆ ที่ rms() เฉลี่ยจนหาย |
mic.level() |
จำนวนเต็ม 0 ถึง 100 | สเกลอ็อกเทฟ ไม่ใช่เศษส่วนของสเกลเต็ม อ่านหัวข้อถัดไปก่อนใช้ |
mic.lag() |
จำนวนเต็ม มิลลิวินาที ที่ค้างอยู่ในคิว | ตัวเลขนี้ คือ ความหน่วงที่ตาเราเห็น ไม่ใช่ตัวประมาณ |
mic (ต่อ) — คิวเสียงยาว 625 ms และมันเสิร์ฟของเก่าก่อนไมค์ผลิตเสียงตลอดเวลา ไม่ว่าโปรแกรมเราจะอ่านหรือไม่ ตัวรับปลายทางคือวงแหวนที่จุได้ 624.9 ms พอดี (20,000 ไบต์ ที่ 16 kHz แบบสองไบต์ต่อตัวอย่าง) และมันจ่าย ตัวที่เก่าที่สุดก่อน
ผลคือ ลูปที่อ่านช้ากว่าที่ไมค์ผลิต จะตามหลังถาวร และเพดานของการตามหลังคือ 625 ms — สิ่งที่การ์ดแสดงจะเป็นห้องเมื่อครึ่งวินาทีที่แล้ว ไม่ใช่ห้องตอนนี้ ทางแก้มีอยู่แล้วในตัว: fresh=True ซึ่งเป็น ค่าตั้งต้น ของ stats() แปลว่า "ทิ้งของค้างทั้งหมด แล้วเอาเฉพาะหน้าต่างล่าสุด"
| อ่านแบบไหน | lag() ที่วัดได้จริงบนบอร์ด 14 ส.ค. 2026 |
|---|---|
stats(fresh=True) (ค่าตั้งต้น) |
0 ถึง 48 ms |
stats(fresh=False) — เก็บของค้างไว้ |
496 ถึง 624 ms |
read() ใช้ทางที่ ไม่ทิ้ง เสมอ อยากได้รูปคลื่นสด ๆ ต้องอ่านให้ทันเอง หรือเรียก stats() นำหน้าเพื่อเคลียร์คิวก่อน
level() เป็นสเกลอ็อกเทฟ ไม่ใช่เปอร์เซ็นต์ของสเกลเต็มไทย: ทุกครั้งที่ rms เพิ่มเป็นสองเท่า ตัวเลข level ขึ้นราว 9 หน่วย ไม่ใช่ขึ้นเป็นเท่าตัว สิบเอ็ดอ็อกเทฟถูกยืดลงบนช่วง 0 ถึง 100 พอดี
ตัวเลขจากบอร์ดนี้ ห้องเงียบ rms 30 ได้ level 7 · พูดปกติ rms ราว 1000 ได้ 54 · ตบมือหรือเปิดโทนใส่ไมค์ rms 19335 ได้ 92 — ถ้าใช้เศษส่วนของสเกลเต็มแบบตรง ๆ ทั้งห้องเงียบและเสียงพูดจะกลายเป็น 0 เท่ากันหมด นั่นคือเหตุผลที่มันเป็นอ็อกเทฟ
mic (ต่อ) — ราคาที่ต้องจ่าย และกับดักที่เจอบ่อยที่สุดmic.level() หนึ่งครั้งกินราว 32 ms วัดบนบอร์ด ในนั้น 16 ms คือตัวเสียงเอง (หน้าต่าง 256 ตัวอย่างที่ 16 kHz ก็คือ 16 ms ของเวลาจริง) ส่วนนั้น ลดไม่ได้ ที่เหลือคือค่าใช้จ่ายของการอ่าน — การ์ดเสียงที่ลูป 200 ms จึงจ่ายค่านี้ไปราวหนึ่งในหก ของงบเวลาทั้งรอบ
กับดัก: rms() peak() level() แต่ละตัว อ่านไมค์ใหม่หนึ่งครั้ง เรียก peak() แล้ว rms() ติดกันคือการอ่านสองหน้าต่างคนละช่วงเวลา และจ่ายค่า 32 ms ไปสองรอบ ต้องการทั้งคู่ให้เรียก stats() ครั้งเดียว แล้วแกะออกมาสามค่า
อีกสองข้อ: sens= รับ 1 ถึง 5 (ยิ่งมากยิ่งไวต่อเสียงเบา) ใส่ค่านอกช่วงมัน ตกกลับไปเป็น 3 เงียบ ๆ ไม่มี error ให้เห็น · read(n) ตัดให้สั้นได้อย่างเดียว ขอ 1000 จากบัฟเฟอร์ 256 จะได้ 256 ไม่ใช่ 1000 และมันไม่รอเก็บเพิ่มให้
ลองสามไฟล์ตามลำดับ: 02_mic_sound_level_meter.py (level()) · 03_mic_clap_trigger.py (peak() กับเกณฑ์ที่วัดจากห้องเอง) · 07_mic_window_stats.py (read() stats() lag() และการทดลองสลับ fresh ให้เห็นคิวโตกับตา)
ตัวเลขความดังที่อ่านมาช้ากว่าความจริงครึ่งวินาที ยังเป็นตัวเลขที่ถูก — แต่มันตอบคนละคำถามกับที่คนดูจอกำลังถาม
s08_dashboard.py ใน BENTO IDETEAM ให้เป็นของทีมเราถ้าจอค้างระหว่างทาง กด RESTART บนหน้า Playground แล้วเริ่มจับเวลาใหม่ตั้งแต่ศูนย์ — ห้ามนับต่อ
ห้ามเติมครบเจ็ดจุดแล้วค่อยรันทีเดียว ถ้าพังจะไม่รู้เลยว่าพังที่จุดไหนในเจ็ดจุด
การรันสิบนาทีไม่ใช่การนั่งรอเฉย ๆ เรากำลังเก็บข้อมูลอยู่ จดเลขรอบทุกสองนาที
ที่ cadence 200 ms ลูปควรเดินราว 5 รอบต่อวินาที = ประมาณ 600 รอบต่อสองนาที ถ้าจดได้ 590–600 ทุกช่วง แปลว่าคงที่ ถ้าช่วงหลังเหลือ 300 แปลว่าเริ่มช้าลงแล้ว
| อาการที่เห็น | เลขรอบ | loop ms | แปลว่า |
|---|---|---|---|
| ทุกอย่างนิ่งสนิท ภาพค้างที่เฟรมสุดท้าย | หยุด | หยุด | ค้างจริง — ลูปตายหรือ CM55 หยุดตอบ |
| ตัวเลขยังเดินแต่ช้าลงเรื่อย ๆ | เดินต่อ | โตขึ้น 3 → 40 | ช้า ไม่ใช่ค้าง — มีอะไรสะสมในลูป |
| จอนิ่ง แต่คอนโซลขึ้น Traceback | หยุด | หยุด | สคริปต์ตายที่ Python — อ่าน error ได้ตรง ๆ |
| widget หายไปแวบหนึ่งแล้วกลับมา | เดินต่อ | ปกติ | ลืม ui.poll() ในลูป |
| ค่าเซนเซอร์ค้างค่าเดิม แต่รอบยังเดิน | เดินต่อ | ปกติ | เซนเซอร์ไม่ตอบ ไม่ใช่จอมีปัญหา |
วิธีแยกที่เร็วที่สุด: หมุนลูกบิด แล้วดูสามที่พร้อมกัน — เลข Seg7, เลขรอบ, และ loop ms ถ้าเลขรอบเดินแต่ Seg7 ไม่ขยับ ปัญหาอยู่ฝั่งเซนเซอร์ ถ้าเลขรอบหยุด ปัญหาอยู่ฝั่งลูปหรือจอ
อีกอย่างที่ต้องดูคือ ความร้อนและการเสียบสาย — สาย USB ที่หลวมทำให้บอร์ดรีเซ็ตกลางทาง แล้วเราจะไปโทษโค้ดผิด ๆ
"ค้าง" กับ "ช้า" แก้คนละวิธีกันสิ้นเชิง เสียเวลาห้าวินาทีแยกให้ออกก่อน ประหยัดไปได้ครึ่งชั่วโมง