ภาพพื้นหลังปกบทเรียน

บทเรียน 3.9 — ลงมือทำ: Mini-HMI Dashboard และการทดสอบ 10 นาที

Mini-HMI Dashboard · ประกอบทุกอย่างที่เรียนมาให้เป็นหน้าจอเดียว ภายใต้งบ 32 widgets ที่ตั้งเอง

โมดูล 3 — แสดงผลเซนเซอร์บน HMI

ต่อจากบทเรียน 3.8 — ประกอบแดชบอร์ด: สี่การ์ดในลูปเดียว

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

MVP checkpoint — ผ่านชุดบทเรียนนี้เมื่อ

dashboard 4 การ์ดรันต่อเนื่อง 10 นาทีไม่ค้างไม่ crash

แปลเป็นสิ่งที่ตรวจได้จริง:

  • [ ] มีการ์ดครบสี่ใบบนจอเดียว ไม่ทับกัน ไม่ล้นขอบ 792×398
  • [ ] การ์ด IMU มีกราฟที่วิ่งตามการเขย่าบอร์ดจริง
  • [ ] เข็มทิศหมุนตามการหันบอร์ด และตัวอักษรทิศเปลี่ยนตามองศา
  • [ ] แตะปุ่ม CapSense แล้วไฟ ui.Led ดวงนั้นติด (ปล่อยแล้วหรี่ ไม่ใช่หาย) · เลื่อนนิ้วแล้ว Bar กับตัวเลข % ขยับ
  • [ ] หมุนลูกบิดแล้ว Arc และ Seg7 เปลี่ยนพร้อมกัน
  • [ ] งบ widget ที่นับได้ ไม่เกิน 32 ที่ตั้งไว้ (เพดานเฟิร์มแวร์ 64) และตัวเลขในหัวไฟล์ตรงกับของจริง
  • [ ] รันต่อเนื่อง 10 นาที เลขรอบเดินตลอด ไม่มี Traceback และ loop ms ไม่โตขึ้นเรื่อย ๆ
  • [ ] จดเลขรอบทุกสองนาทีลงบันทึกการเรียนครบหกช่อง

ห้าข้อแรกใช้เวลาสร้างหนึ่งชั่วโมง ข้อที่เจ็ดใช้เวลาสิบนาทีที่ห้ามลัด — และมันคือข้อที่แยกของเล่นออกจากของใช้งานจริง

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

กับดักที่เจอบ่อย

อาการ สาเหตุที่แท้จริง วิธีแก้
widget ตัวท้าย ๆ ไม่ขึ้นเลย เกินเพดาน 64 (มักเพราะไม่ได้ ui.screen() ก่อน) นับใหม่ + เรียก ui.screen() ต้นสคริปต์
การ์ดบังตัวหนังสือจนหาย สร้าง Panel หลัง Label สร้าง Panel ก่อน Label เสมอ
ขอบการ์ดไม่มีสี มุมไม่โค้ง สับสน kwarg ของ Panel min=สีขอบ max=รัศมี value=ความหนา
กราฟเป็นขั้นบันไดหยาบ ๆ ป้อน int(ax) ตรง ๆ คูณ 10 ก่อน แล้วตั้งแกน −150..150
จอกระพริบ ซ่อน widget ทุกสองวินาที ลืม ui.poll() ในลูป เติม ui.poll() ก่อน sleep_ms
OSError ที่บรรทัดแรกสุดของสคริปต์ เรียก sensors.init() — Eva Kit ปฏิเสธ (Dev Kit ผ่านเงียบ ๆ แต่ไม่จำเป็น) ลบบรรทัดนั้นทิ้ง ไม่ต้องมี init ทั้งสองบอร์ด
บรรทัดอ่านค่าแรกเงียบไปสิบกว่าวินาที เพิ่งรีเซ็ตบอร์ด คอร์จอยังไม่ตอบสายเซนเซอร์ รอให้จบ (บน Eva วัดได้ถึง 16 วินาที) ครั้งต่อไปไม่เกิน 1
เข็มทิศกระตุกกลับตอนผ่านทิศเหนือ ค่าองศาหลุดเกิน 360 heading = ... % 360.0
ค่าทุกอย่างค้าง แต่โปรแกรมไม่ error ยังใช้ sensors.auto() ร่วมกับ ui (เกิดได้เฉพาะ Dev Kit — บน Eva Kit auto() ขึ้น OSError ตั้งแต่บรรทัดนั้น) ลบทิ้ง แล้วอ่าน sync ในลูปแทน ทั้งสองบอร์ด
OSError ตอนเรียก sensors.scan() บน Eva Kit สแกน 112 แอดเดรสบนบัสที่ CM55 ถืออยู่ (Dev Kit ผ่าน) ห้ามใช้ในคอร์สนี้ทั้งสองบอร์ด เฟิร์มแวร์ Eva กันไว้ให้แล้ว
OSError ที่ bmi270.temperature() หรือ chip_id() บน Eva Kit ภาพสแกนของ CM55 ไม่ได้ขนสองค่านี้มา (Dev Kit ใช้ได้) ใช้ sensors.snapshot()['bmi270']['sequence'] ดูว่า IMU ยังมีชีวิตแทน — รันได้ทั้งสองบอร์ด

ส่วนใหญ่ของข้อเหล่านี้เงียบสนิท ไม่มี error message — ตาของเราคือเครื่องมือดีบักหลักในงาน UI

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ลงมือทำ — เติมช่องว่างในไฟล์ฝึก

เติมแล้วรัน — ควรเห็นการ์ดว่าง เติมทีละจุด แล้วรันทุกครั้ง — ค่าจะมาทีละการ์ด จุดสุดท้าย จุด 1 อ่านทิ้ง 1 ครั้ง จุด 2 Panel จุด 3 chart จุด 4 compass จุด 5 bar จุด 6 seg7 จุด 7 ui.poll() จงใจลองผิดหนึ่งรอบ: รันครึ่งนาทีก่อนเติมจุดที่ 7 แล้วจดว่าจอมีอาการอะไร นั่นคืออาการของการลืม ui.poll() ซึ่งจะเจอได้อีกในโปรเจกต์จริง

เปิด s08_dashboard.py มีช่องว่างให้เติม 7 จุด

ห้ามเรียก sensors.init() ทั้งสองบอร์ด — Eva Kit ได้ OSError เสมอ Dev Kit ผ่านแต่ไม่จำเป็น — จุดที่ 1 ที่ต้องเติมคือ การอ่านทิ้งหนึ่งครั้ง ไม่ใช่การเปิดเซนเซอร์

try:
    # เติม: sensors.bmi270.motion()
    pass
except OSError:
    pass
# เติม: imu_panel = ui.Panel(x=24, y=100, w=368, h=136, color=BG_CARD, min=COL_IMU, max=12, value=2)
pass
# เติม: imu_chart.set_next(sy, int(ay * 10))
pass
# เติม: compass.value(int(heading))
pass
# เติม: cap_bar.value(int(cap['slider']))
pass
# เติม: pot_seg7.text("{:.1f}".format(pct))
pass
# เติม: for ev in ui.poll():
pass

ลำดับที่แนะนำ: เติมจุดที่ 1–2 ก่อนแล้วรัน (ควรเห็นการ์ดครบสี่ใบ เพราะอีกสามใบเขียนไว้ให้แล้ว) · เติม 3–6 ทีละจุดแล้วรัน (ค่าจะมาทีละการ์ด) · เติมจุดที่ 7 เป็นอันสุดท้าย — จงใจลองผิดหนึ่งรอบ: ก่อนเติมจุดที่ 7 ให้รันดูสักครึ่งนาที แล้วจดว่าจอมีอาการอะไร นั่นคืออาการของการลืม ui.poll()

เติมทีละจุดแล้วรัน คือวิธีเดียวที่ทำให้รู้ว่าจุดไหนพัง เมื่อโค้ดยาวเกินสามสิบบรรทัด

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ตัวอย่างของบทเรียน 3.7–3.9 — สามไฟล์นี้คือชุดที่ทำให้การ์ดบน dashboard ยืนได้ตลอดสิบนาที

ต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 30 นาที

ลำดับ · เรื่อง · เวลา ไฟล์ ลงมือทำอะไร แล้วจะเข้าใจอะไร
1 · เข็มทิศที่อ่านออก · 10 นาที 06_compass_readout.py ประกอบการ์ด Compass ใบที่ 2 ของ dashboard ได้ครบทั้งเข็ม ตัวเลข และกราฟ · จะเห็นว่าเข็มทิศที่คนอ่านออกต้องมีทั้งเข็มและตัวเลของศาอยู่ด้วยกัน
2 · เกณฑ์สองระดับกันป้ายกระพริบ · 10 นาที 05_door_open_switch.py เขียนป้ายสถานะที่ยืนอยู่ได้จริงตลอด soak run 10 นาที ไม่สั่นตอนค่าคาบเส้น · จะเข้าใจว่าเกณฑ์สองระดับคือสิ่งที่ทำให้ป้ายสถานะไม่กระพริบ
3 · การ์ดระดับเสียง · 10 นาที 02_mic_sound_level_meter.py ประกอบการ์ดระดับเสียงจากไมโครโฟนได้ครบทั้งแถบ ตัวเลข และกราฟ · จะเห็นว่า mic.level() ยุบคลื่นเสียงทั้งชุดเหลือตัวเลขเดียวที่การ์ดใบหนึ่งแสดงได้

ไฟล์ที่ 1 ยืนยันบน Eva Kit แล้ว (heading() วัดจริงได้ 215.8 เมื่อ 14 ส.ค. 2026 · บน Dev Kit ยังไม่ได้รัน) · ไฟล์ที่ 2 ใช้ sensors.bmm350.magnetic() ซึ่ง ยังไม่มีใครรันบนบอร์ดจริงทั้ง Eva Kit และ Dev Kit ถ้าเปิดแล้วได้ OSError ให้ข้ามไปทำการ์ด IMU ก่อน แล้วแจ้งผู้สอน · ไฟล์ที่ 3 ยืนยันบน Eva Kit แล้วเช่นกัน (14 ส.ค. 2026 ห้องเงียบได้ rms 30 คือระดับ 7 · พูดปกติราว 1000 คือระดับ 54 · เปิดโทนใส่ไมค์ได้ 19335 คือระดับ 92)

ภาพถ่ายหน้า Playground ขณะรันมาตรวัดระดับเสียงจากไมโครโฟน

ภาพซ้ายคือไฟล์ที่ 3 กำลังรันอยู่บนบอร์ดจริงในหน้า BENTO Playground เส้นกราฟขยับตามเสียงในห้องระหว่างที่ถ่ายภาพ ระดับตอนนั้นคือ 9 และดังสุดที่เจอคือ 23

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ติดตรงไหน เปิดอันนี้ — และไฟล์ที่เหลือของชุดบทเรียน

อาการที่เจอ ไฟล์ที่ตอบอาการนั้น
เกณฑ์ที่ตั้งไว้ใช้ได้ที่โต๊ะนี้ แต่ย้ายโต๊ะแล้วเตือนรัว 04_magnet_presence.py — วัดเส้นฐานของห้องเองตอนเริ่ม แล้วคิดเกณฑ์จากความผันผวนของห้องนั้น
กดปุ่มครั้งเดียว แต่ตัวนับบนการ์ดขึ้นหลายครั้ง 05_debounce_count.py — นับดิบกับนับกันเด้งวิ่งคู่กันให้เห็นความต่าง
อยากให้การ์ดใบหนึ่งแสดง "ระดับ" ที่ตัดสินแล้ว ไม่ใช่ตัวเลขดิบ 01_andon_severity_lamp.py — หนึ่งระดับคือไฟหนึ่งดวง ดับให้หมดก่อนจุดดวงใหม่เสมอ และเขียนจอเฉพาะตอนระดับเปลี่ยน
อยากให้การ์ดตอบตอนตบมือ แต่ตัวเลขความดังเฉลี่ยแทบไม่ขยับ 03_mic_clap_trigger.py — peak() เห็นเสียงพุ่งสั้น ๆ ที่ rms() เฉลี่ยจนหายไป และเกณฑ์ต้องวัดจากห้องที่บอร์ดอยู่ตอนนั้น
การ์ดเสียงขยับช้ากว่าที่พูดจริงราวครึ่งวินาที 07_mic_window_stats.py — lag() บอกเป็นตัวเลขว่าคิวค้างกี่ ms และปุ่มสลับ fresh ให้เห็นคิวโตจนเต็ม 625 กับตา · ไฟล์นี้ยังเป็นที่เดียวที่ใช้ read() กับ stats() สามค่าจากหน้าต่างเดียว
เข็มทิศบนการ์ดใบที่ 2 ชี้ผิดทิศ หรือสั่นทั้งที่วางบอร์ดนิ่ง 14_hard_iron_calibration.py — ล้างค่าชดเชยแล้วหมุนเลขแปด เฝ้าดู offset_x offset_y ลู่เข้าจนเป็นเส้นแบน คาลิเบรตเป็นตัวเลขที่ดูได้ ไม่ใช่พิธีกรรมที่ทำแล้วหวังผล · แต่ cal_reset() กับ cal_status() ยังไม่มีใครรันบนบอร์ดจริง ทั้ง Eva Kit และ Dev Kit ถ้าเปิดแล้วได้ OSError ให้แจ้งผู้สอนแล้วกลับไปทำการ์ดใบอื่นก่อน
อยากได้ปุ่มสั่งงานเพิ่ม แต่ไม่อยากจ่ายงบ widget ให้ปุ่มบนจอ 05_short_long_press.py — ปุ่มผู้ใช้ที่เฟิร์มแวร์เปิดให้คือ gpio.button(0) ตัวเดียว (.name() คืน "USER Button 1" ทั้งสองบอร์ด) และไม่กินงบเลย · บน Dev Kit ห้ามโยกสวิตช์บนฐาน พวกนั้นคือสวิตช์ไฟ ไม่ใช่ปุ่ม · แตะสั้นกับกดค้างเป็นคนละคำสั่ง จับเวลาตอนกดลง แล้วตัดสินตอนปล่อย ไม่ใช่ตอนกด

ชุดบทเรียนนี้มีตัวอย่างเจ็ดไฟล์ สามไฟล์เป็นเรื่องไมโครโฟน ซึ่งตอนนี้ใช้จาก Python ได้แล้วผ่านโมดูล mic ที่ฝังมากับเฟิร์มแวร์ ทั้งบนบอร์ดและในอีมูเลเตอร์ ดังนั้น การ์ดระดับเสียงวางในผังสี่การ์ดได้แล้ว ทีมที่อยากได้การ์ดที่ไม่ซ้ำกับใครเลือกใบนี้ได้ — สามไฟล์นั้นแบ่งกันคนละหน้าที่: 02 ใช้ level() · 03 ใช้ peak() · 07 ใช้ read() stats() lag() ครบทั้งสามชื่อที่เหลือ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เชื่อมโยงรากฐาน — วันนี้เราแตะอะไรไปบ้าง

ระบบสมองกลฝังตัว จองทรัพยากรแบบคงที่ ทำงานภายใต้งบที่นับได้ soak run ทดสอบความทน อ่านอุปกรณ์แบบ sync ในลูปเดียว Python และ CS try/except กันลูปตาย เก็บ handle ไว้สั่งทีหลัง ticks_diff กันเวลาล้น แปลงค่าต่อเนื่องเป็นหมวด องศา 137 ไปเป็น SE การออกแบบระบบ จัดกลุ่มตามคำถามที่มันตอบ ลำดับความสำคัญทางสายตา สีเป็นภาษา ไม่ใช่การตกแต่ง เลือก widget ตามผู้เปลี่ยนค่า ออกแบบใต้ข้อจำกัดที่แก้ไม่ได้

ฝั่งระบบสมองกลฝังตัว
การจองทรัพยากรแบบคงที่ (ตาราง handle 64 ช่อง) และเหตุผลว่าทำไมระบบที่ต้องทำงานยาวถึงเลือกทางนี้ · การทำงานภายใต้งบที่นับได้ · การทดสอบความทนทานด้วย soak run · การอ่านอุปกรณ์แบบ synchronous ในลูปเดียว

ฝั่ง Python และวิทยาการคอมพิวเตอร์
try/except กันลูปตายเพราะเซนเซอร์ตัวเดียว · การเก็บ handle ของอ็อบเจกต์ไว้ในตัวแปรเพื่อสั่งงานทีหลัง · time.ticks_ms() / ticks_diff() กับการวัดเวลาแบบไม่ล้น · การแปลงค่าต่อเนื่องเป็นหมวด (องศา → ชื่อทิศ)

ฝั่งการออกแบบระบบ
การจัดกลุ่มข้อมูลตามคำถามที่มันตอบ · ลำดับความสำคัญทางสายตา · การใช้สีเป็นภาษาแทนการตกแต่ง · การเลือก widget ตามว่าใครเป็นคนเปลี่ยนค่า · การออกแบบภายใต้ข้อจำกัดที่แก้ไม่ได้

ข้อสุดท้ายคือของที่ใช้ได้แม้เปลี่ยนภาษา เปลี่ยนบอร์ด และเปลี่ยนอาชีพ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

งานทำเอง 30% + สรุปบทเรียน

วันนี้ · บทเรียน 3.7–3.9 IMU Compass CapSense Pot ต่อยอด ชุดบทเรียนถัดไป · บทเรียน 4.1–4.3 บนหน้าจอใบเดิม IMU Compass CapSense SSID · IP ping

วันนี้เราได้:
ออกแบบผัง HMI บนกระดาษก่อนเขียนโค้ด · ประกอบ ui.Panel สี่ใบเข้ากับ Chart, Compass, Bar, Arc, Seg7 · ทำงานภายใต้งบ 32 widgets ที่ตั้งเอง (เพดานเฟิร์มแวร์ 64) โดยรู้ว่าทุกตัวไปอยู่ไหน · อ่านเซนเซอร์สี่ตัวแบบ sync ในลูปเดียวที่ 200 ms · ทดสอบความทนทานสิบนาทีและแยกอาการค้างออกจากอาการช้า

การบ้านของทีม: เลือกทำ 1 ข้อจากสี่ข้อในสไลด์ถัดไป จดลงบันทึกการเรียน

ชุดบทเรียนถัดไป: เราจะต่อบอร์ดเข้า WiFi แล้วเอา SSID, IP และค่า ping ขึ้นมาแสดงบนแดชบอร์ดที่สร้างวันนี้ — MVP ของบทเรียน 4.1–4.3 ระบุไว้ชัดว่าให้ ต่อยอดจากหน้าจอของบทเรียน 3.7–3.9 ไม่ใช่เริ่มใหม่

เก็บไฟล์วันนี้ให้ดีและอย่าลบ บทเรียน 4.1–5.3 จะงอกออกจากไฟล์นี้ทั้งหมด

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย s08_dashboard.py — ส่วนที่หนึ่ง: หัวไฟล์กับงบ

# งบ widget ของไฟล์นี้ = 32 # หัวเรื่อง + ไฟค่าค้าง + สถานะ = 4 # การ์ด IMU = 4 # การ์ดเข็มทิศ = 5 # การ์ดสัมผัส (ไฟ 2 + Bar) = 8 # การ์ด pot + เกณฑ์ 4 ชิ้น = 9 # ปุ่มสั่งงานในแถบหัว = 2 เอกสารอยู่บรรทัดบนโค้ด ใช้ไป 32 64 เอกสารที่อยู่ไกลจากโค้ดจะเก่าเสมอ เอกสารที่อยู่บรรทัดบนโค้ด มีโอกาสถูกแก้ตาม

อ่านให้เข้าใจ แล้วพิมพ์เอง อย่าคัดลอกวาง

# งบ widget ของไฟล์นี้ = 32   (เพดานของเฟิร์มแวร์คือ 64 ห้ามเกิน)
#   หัวเรื่อง 1 + ไฟค่าค้าง 1 + ป้ายค่าค้าง 1 + แถบสถานะ 1              =  4
#   การ์ด IMU      Panel + หัวข้อ + Chart + Label ค่า                  =  4
#   การ์ดเข็มทิศ   Panel + หัวข้อ + Compass + Label องศา + Label ทิศ   =  5
#   การ์ดสัมผัส    Panel + หัวข้อ + ไฟ 2 ดวง + ป้าย 2 + Bar + %       =  8
#   การ์ด pot      Panel + หัวข้อ + Arc + Seg7 + โวลต์ + เกณฑ์ 4 ชิ้น  =  9
#   ปุ่มสั่งงานในแถบหัว                                              =  2

import ui
import sensors
import time

TEAM = "BentoBuilders"
UI_TEXT_MS = 1000    # ตัวเลขที่คนต้องอ่าน เขียนใหม่ไม่เกินวินาทีละครั้ง
STALE_MS = 3000      # อ่านไม่ได้ติดกันเกินเท่านี้ ถือว่าเลขบนจอเป็นของเก่า

ui.screen()
time.sleep_ms(200)

บล็อกนับ widget อยู่ในหัวไฟล์ ไม่ใช่ในสมุด เพราะโค้ดกับงบต้องเดินทางไปด้วยกัน คนที่เปิดไฟล์นี้ในอีกสองสัปดาห์ (ซึ่งมักคือตัวเราเอง) จะเห็นทันทีว่าเหลืองบเท่าไรก่อนจะเผลอเพิ่ม Label ตัวที่ 65

การนำเข้าโมดูลสามตัวนี้ครบพอดีสำหรับงานวันนี้ — ไม่มี math เพราะเราไม่ได้คำนวณอะไรที่ต้องใช้

เอกสารที่อยู่ไกลจากโค้ดจะเก่าเสมอ เอกสารที่อยู่บรรทัดบนโค้ดมีโอกาสถูกแก้ตาม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สอง: การ์ดสี่ใบ

แนวนอน (แถวบน) — บวกให้ครบ 792 การ์ดซ้าย w=368 การ์ดขวา w=360 24 16 24 24 + 368 + 16 + 360 + 24 = 792 แถวล่าง: 24 + 320 + 16 + 408 + 24 = 792 แนวตั้ง — บวกให้ครบ 398 แถบหัว 92 แถวบน 136 16 แถวล่าง 136 92 + 8 + 136 + 16 + 136 + 10 = 398
imu_panel = ui.Panel(x=24, y=100, w=368, h=136,
                     color=BG_CARD, min=COL_IMU, max=12, value=2)
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 ฟ้า
...
comp_panel = ui.Panel(x=408, y=100, w=360, h=136,
                      color=BG_CARD, min=COL_COMP, max=12, value=2)
compass = ui.Compass(x=424, y=132, w=96, h=96, color=COL_COMP)
...
cap_panel = ui.Panel(x=24, y=252, w=320, h=136,
                     color=BG_CARD, min=COL_TOUCH, max=12, value=2)
cap_led0 = ui.Led(x=40, y=288, w=48, h=48, color=COL_TOUCH, value=0)
cap_bar = ui.Bar(x=40, y=348, w=288, h=16, min=0, max=100, value=0, color=COL_TOUCH)
...
pot_panel = ui.Panel(x=360, y=252, w=408, h=136,
                     color=BG_CARD, min=COL_POT, max=12, value=2)
pot_arc = ui.Arc(x=376, y=288, w=88, h=88, min=0, max=100, value=0)
spin_thr = ui.Spinbox(x=600, y=288, w=88, h=88, color=COL_WHITE,
                      min=0, max=100, value=80)
led_alarm = ui.Led(x=704, y=288, w=48, h=48, color=COL_ALERT, value=0)

สูตรเดียวกันทั้งหน้า: ขอบซ้าย 24 · ช่องไฟระหว่างการ์ด 16 · การ์ดสูง 136 ทั้งสองแถว · ปุ่มสั่งงานอยู่ใน แถบหัว (สูง 88 px) ไม่ใช่แถบล่าง เพราะการ์ดสองแถวกินจนหมด 398 แล้ว · ของที่เปลี่ยนจากรุ่นก่อน — ป้าย B0 ON ที่เปลี่ยนสีถูกแทนด้วย ui.Led สองดวง (ป้ายที่บอกสถานะด้วยสีอย่างเดียว ไม่ผ่านการทดสอบขาวดำ) · เกณฑ์เตือน spin_thr + led_alarm อยู่ ในการ์ดลูกบิด เพราะเป็นเกณฑ์ของลูกบิด — กลุ่มนี้เคยอยู่ที่ y=404–612 บนจอสูง 398 คือนอกจอทั้งแถบ กดไม่ได้และไม่มีใครเห็นว่าหายไป · imu_chart กับ compass ไม่ใช้ COL_ALERT อีกแล้ว สีเตือนต้องใช้กับการเตือนเท่านั้น

ถ้าต้องขยับการ์ดใบหนึ่ง ต้องขยับเลขทั้งแถว — จดสูตรไว้ในบันทึกการเรียน จะแก้ได้เร็วกว่าเดาทีละตัว

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สาม: ลูปหลัก

try:
    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 ok:
                t_good = time.ticks_ms()
        stale = time.ticks_diff(time.ticks_ms(), t_good) >= STALE_MS
        if stale != stale_shown:        # เขียนเฉพาะตอนเปลี่ยน
            stale_shown = stale
            led_stale.value(1 if stale else 0)
        if time.ticks_diff(time.ticks_ms(), last_text) >= UI_TEXT_MS:
            last_text = time.ticks_ms()
            pot_seg7.text("{:.1f}".format(pct))
            head.text("รอบที่ {} | loop {} ms".format(
                rounds, time.ticks_diff(time.ticks_ms(), t0)))
        rounds += 1
        for ev in ui.poll():
            ...                         # ปุ่มเดินหน้า/หยุดภาพ และ Spinbox เกณฑ์
        time.sleep_ms(200)
except KeyboardInterrupt:
    ui.clear()
try ครอบทีละตัว — ที่เฉลยใช้ BMM350 ไม่ตอบ คงค่าเดิม จุดไฟค่าค้าง BMI270 ปกติ กราฟยังวิ่ง CapSense ปกติ Bar ยังขยับ Pot ปกติ Seg7 ยังเปลี่ยน try ครอบทั้งลูป — สิ่งที่ห้ามทำ BMM350 ไม่ตอบ โดดออกจากลูป ไม่ได้อัปเดต ไม่ได้อัปเดต ไม่ได้อัปเดต การเลือกขอบเขตของ try คือการตัดสินใจว่า "อะไรพังได้ โดยที่ระบบยังใช้งานได้อยู่"
try/except ครอบการอ่านเซนเซอร์ ทีละตัว ไม่ใช่ครอบทั้งลูป — เข็มทิศตัวเดียวมีปัญหา อีกสามการ์ดยังต้องทำงานต่อ · ticks_diff() แทนการลบตรง ๆ เพราะ ticks_ms() วนกลับเป็นศูนย์ในวันที่รันยาว · except ไม่เขียนศูนย์ทับค่าเดิม — ศูนย์คือตัวเลขที่หน้าตาเหมือนค่าที่วัดมาจริง รุ่นนี้คงค่าล่าสุดไว้แล้วจุดไฟ "ค่าค้าง" ตอบคำถามที่แดชบอร์ดทุกใบต้องตอบ: เลขที่เห็นอยู่ตอนนี้ ใช่ค่าปัจจุบันไหม · ตัวเลขขยับไม่เกินวินาทีละครั้ง ทั้งที่ลูปเดินทุก 200 ms — กราฟ แถบ เข็ม และไฟขยับที่ 200 ms ได้ เพราะตาอ่านรูปทรง แต่ตัวเลขที่กระพริบห้าครั้งต่อวินาทีไม่มีใครอ่านทัน

การเลือกขอบเขตของ try คือการตัดสินใจว่า "อะไรพังได้โดยที่ระบบยังใช้งานได้อยู่"

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย · ทำไมเรียงหกท่าแบบนี้ ไม่ใช่สุ่มเรียง

ท่า 1 · ราก จอ สี เซนเซอร์ ท่า 2 · ใบที่ยากสุด Panel + Chart + series ท่า 3 · ทำซ้ำ การ์ดเข็มทิศ ท่า 4 · ทำซ้ำ สัมผัส + ลูกบิด ท่า 5-6 · คำสั่ง + ลูป ทุกอย่างมาเจอกัน สร้างของนิ่งให้ครบก่อน แล้วค่อยใส่ของที่เคลื่อนไหว ของนิ่งพังจะเห็นทันทีจากตา ของเคลื่อนไหวพังต้องนั่งดูสักพัก

ท่า 1 เตรียมจอ ชุดสี เซนเซอร์ มาก่อน เพราะทั้งสามอย่างคือรากที่ท่าอื่นยืนอยู่บน ถ้า ui.screen() ไม่ทำงาน หรือเซนเซอร์ยังไม่ตื่น ที่เหลือไม่มีประโยชน์จะเขียนต่อ

ท่า 2 การ์ด IMU มาเป็นใบแรกเพราะมันซับซ้อนที่สุด (Panel + Chart + series สามเส้น) ถ้าใบยากที่สุดผ่านแล้ว ใบที่เหลือคือการทำซ้ำแบบเดียวกัน — ทำของยากตอนที่ยังมีแรงและยังมีเวลาเหลือ

ท่า 3–4 การ์ดที่เหลือ เรียงตามผังจากซ้ายไปขวา บนลงล่าง ตรงกับที่ตาคนอ่าน ทำให้เวลากลับมาแก้ หาตำแหน่งในไฟล์ได้จากตำแหน่งบนจอ

ท่า 5 แถบคำสั่ง มาก่อนลูป เพราะปุ่มกับ Spinbox ต้องมีตัวตนอยู่ก่อน ลูปถึงจะอ้าง btn_run.id() ได้ และเพราะมันคือส่วนที่ทำให้แดชบอร์ดใบนี้ เป็นแผงควบคุม ไม่ใช่โปสเตอร์ที่ตัวเลขขยับได้ แดชบอร์ดที่ผู้ใช้แตะอะไรไม่ได้เลย ตอบได้แค่ "ตอนนี้เป็นยังไง" แต่ตอบไม่ได้ว่า "แล้วจะให้ทำอะไรต่อ"

ท่า 6 ลูปหลัก มาสุดท้ายเสมอ เพราะลูปคือที่ที่ทุกอย่างมาเจอกัน ถ้าเขียนลูปก่อนสร้าง widget โปรแกรมจะพังที่ชื่อตัวแปรที่ยังไม่มี

หลักการเดียวกับที่ใช้มาตั้งแต่บทเรียน 1.1–1.3: สร้างของนิ่งให้ครบก่อน แล้วค่อยใส่ของที่เคลื่อนไหว ของนิ่งพังจะเห็นทันทีจากตา ของเคลื่อนไหวพังต้องนั่งดูสักพักถึงจะรู้

ลำดับที่ดีคือลำดับที่ทำให้ "รู้ว่าพังตรงไหน" ได้เร็วที่สุด ไม่ใช่ลำดับที่พิมพ์แล้วลื่นที่สุด

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เชื่อมจุดให้เห็นภาพ — วันนี้อยู่ตรงไหนของเส้นทาง

บทเรียน 2.1–2.9 คุมฮาร์ดแวร์ทีละชิ้น ปุ่ม ไฟ ลูกบิด สัมผัส บทเรียน 3.1–3.6 แปลงข้อมูลดิบเป็นความหมาย มุมเอียง · กราฟเวลาจริง วันนี้ · บทเรียน 3.7–3.9 รวมทุกชิ้นเป็น HMI เดียว ภายใต้งบและ cadence จริง บทเรียน 4.1–5.3 ต่อเน็ต ส่งขึ้นแพลตฟอร์ม บนหน้าจอใบเดิมนี้ บทเรียน 3.7–3.9 คือจุดที่ของทุกชิ้นมาบรรจบ แล้วกลายเป็นฐานของอีกสี่ชุดบทเรียนข้างหน้า

คำถามคิดต่อ: ถ้าต้องเพิ่มการ์ดสถานะเครือข่ายในชุดบทเรียนถัดไป จะตัดอะไรออกหรือใช้งบที่เหลือ · การ์ดใบไหนที่คนดูจริงจะมองบ่อยที่สุด และมันควรอยู่ตำแหน่งนั้นไหม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ใช้จริงที่ไหน — HMI สี่แบบในสนามจริง

โรงงาน · แผงข้างเครื่องจักร การ์ดความสั่น · อุณหภูมิ · รอบมอเตอร์ · สถานะ ต้องอ่านออกจากระยะ 3 เมตร ขณะใส่ถุงมือ ตรงกับวันนี้: ลำดับสายตา + สีสามระดับ อาคาร · ห้องควบคุมระบบ การ์ดต่อชั้น: แอร์ ไฟ คุณภาพอากาศ คนในพื้นที่ เปิดค้างไว้ 24 ชั่วโมง ห้ามค้างห้ามหน่วง ตรงกับวันนี้: soak test 10 นาทีคือฉบับย่อ ยานพาหนะ · แผงหน้าปัด เข็มทิศ ความเร็ว มุมเอียง อยู่บนจอเดียว คนขับมองได้ครั้งละไม่เกินครึ่งวินาที ตรงกับวันนี้: เข็มทิศ + ตัวเลขใหญ่ 28 px เกษตร · ตู้ควบคุมโรงเรือน การ์ดความชื้น น้ำ พัดลม พร้อมปุ่มสัมผัส ทำงานคนเดียวกลางแดด ไม่มีคีย์บอร์ด ตรงกับวันนี้: Bar อ่านอย่างเดียว vs Slider ที่ลากได้

นักสำรวจใช้เครื่องวัดสนามแม่เหล็กสำรวจพื้นที่ภาคสนาม

ภาพ: Axel Hindemith / Wikimedia Commons — CC BY-SA 3.0 · การสำรวจสนามแม่เหล็กในภาคสนามจริง — งานที่ใช้ค่าจากเข็มทิศเป็นข้อมูลหลัก ไม่ใช่ของประดับหน้าจอ

เซนเซอร์ที่การ์ดสองใบล่างต้องใช้ — Eva Kit ไม่มีบนบอร์ด ยกมาเทียบให้เห็นว่ามันวัดยังไง · TESAIoT Dev Kit มีทั้งสองตัว (DPS368 ความดัน · SHT40 อุณหภูมิ/ความชื้น เรียกผ่าน sensors.dps368 / sensors.sht40) ทีม Dev Kit จึงทำการ์ดพวกนี้ด้วยค่าจริงได้

Barometric pressure sensor: working principle — Bosch Sensortec — 0:53 · Understanding Temperature Sensor Technology — RealPars — 8:59 (ช่วง thermistor เริ่ม 5:26)

ทั้งสี่แบบใช้หลักการเดียวกับที่เราทำวันนี้ทุกข้อ ต่างกันแค่ว่าเบื้องหลังการ์ดเป็นเซนเซอร์อะไร

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ต่อยอด — คิดต่อเอง (เลือกทำ 1 ข้อ)

ข้อ 1 · การ์ดใบที่ห้า: สถานะระบบ เวลารันสะสม · จำนวนรอบ · loop ms สูงสุด ต้องบอกด้วยว่าใช้งบที่เหลือหรือตัดอะไรออก ข้อ 2 · โซนสีตามเกณฑ์ เขียว ต่ำกว่า 11 · เหลือง 11-12 · แดง เกิน 12 เขียนด้วยว่าทำไมถึงเลือกเกณฑ์นี้ ข้อ 3 · สลับหน้าด้วย show() / hide() ภาพรวม กับ IMU เต็มจอ — ซ่อนแทนการลบ ประหยัดงบ widget อย่างไรเมื่อเทียบกับสร้างใหม่ ข้อ 4 · รายงานผล soak run 10 นาทีสองรอบ ที่ 200 ms และที่ 80 ms สรุปครึ่งหน้าว่าทีมจะเลือก cadence เท่าไร

ข้อ 1 · การ์ดใบที่ห้า: สถานะระบบ
เพิ่มการ์ดที่รายงานสุขภาพของโปรแกรมเอง — เวลาที่รันมาแล้ว (นาที), จำนวนรอบ, และ loop ms สูงสุดที่เคยเจอ ต้องอยู่ในงบ 32 ที่ตั้งไว้ให้ได้ (ไม่ใช่ขยับไปหาเพดาน 64) บอกมาด้วยว่าตัดอะไรออกหรือใช้งบที่เหลือ

ข้อ 2 · โซนสีตามเกณฑ์
ทำให้ค่าบนการ์ดเปลี่ยนสีเองตามเกณฑ์ที่ทีมตั้ง เช่น ขนาดความเร่งรวมเกิน 12 m/s² ให้ตัวเลขเป็นแดง อยู่ระหว่าง 11–12 เป็นเหลือง ต่ำกว่านั้นเป็นเขียว เขียนในบันทึกการเรียนด้วยว่าทำไมถึงเลือกเกณฑ์นี้

ข้อ 3 · สลับหน้าด้วย .show() / .hide()
สลับหน้าเราทำเองได้ (จริง ๆ ui.Tabview ก็มี — บทเรียน 2.4–2.6 ใช้ไปแล้ว — แต่ท่าซ่อน/โชว์การ์ดเบากว่าเมื่อการ์ดเดิมอยู่ครบ) — เพิ่มปุ่มสองปุ่มสลับระหว่างหน้า "ภาพรวม" กับหน้า "IMU เต็มจอ" โดยซ่อนการ์ดที่ไม่ได้ใช้แทนการลบทิ้ง วิธีนี้ประหยัดงบ widget อย่างไรเมื่อเทียบกับการสร้างใหม่ทุกครั้ง

ข้อ 4 · รายงานผล soak run
รันยาว 10 นาทีสองรอบ รอบแรกที่ sleep_ms(200) รอบสองที่ sleep_ms(80) จดเลขรอบทุกสองนาทีทั้งสองรอบ แล้วเขียนสรุปครึ่งหน้าว่าอะไรเปลี่ยนไปบ้าง และทีมจะเลือก cadence เท่าไรถ้าต้องส่งงานจริง

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

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (1/2)

หน้าจอของ 02_mic_sound_level_meter.py: เครื่องวัดระดับเสียงในห้อง หน้าจอของ 03_mic_clap_trigger.py: ตบมือแล้วไฟสลับ หน้าจอของ 04_magnet_presence.py: ตรวจว่ามีแม่เหล็กอยู่ใกล้หรือไม่

02 เครื่องวัดระดับเสียงในห้อง · 03 ตบมือแล้วไฟสลับ · 04 ตรวจว่ามีแม่เหล็กอยู่ใกล้หรือไม่
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (2/2)

หน้าจอของ 05_door_open_switch.py: สวิตช์แม่เหล็กบอกว่าประตูเปิดหรือปิด หน้าจอของ 06_compass_readout.py: เข็มทิศที่ใช้งานได้จริง พร้อมตัวเลของศา หน้าจอของ 07_mic_window_stats.py: รูปคลื่นดิบ สามค่าจากหน้าต่างเดียว และคิวที่ค้างอยู่

05 สวิตช์แม่เหล็กบอกว่าประตูเปิดหรือปิด · 06 เข็มทิศที่ใช้งานได้จริง พร้อมตัวเลของศา · 07 รูปคลื่นดิบ สามค่าจากหน้าต่างเดียว และคิวที่ค้างอยู่
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

อ้างอิงและเครดิต

เอกสารบอร์ดและเซนเซอร์ (แหล่งปฐมภูมิ)

  • KIT_PSE84_EVAL Kit guide, Infineon 002-39007 Rev.*B — §3.2.2.13 (หน้า 87) · §3.2.2.14 (หน้า 88)
  • BMM350 datasheet, BST-BMM350-DS001-27 rev 1.27 — Bosch Sensortec — bst-bmm350-ds001.pdf บน bosch-sensortec.com
  • DPS368 datasheet — Infineon (เซนเซอร์ที่ ไม่มี บน Eva Kit · มี บน TESAIoT Dev Kit)
  • Practical Electronics for Inventors, 4th ed. — §2.31 Decibels · §6.2.1 Thermistors · §6.4.8 Pressure

ซอฟต์แวร์

วิดีโอที่ตรวจแล้วว่าเปิดได้

  • RLQGZl0lpjQ Working principle of an accelerometer — Bosch Sensortec
  • 6BS6aQBaMhU Projected Capacitive Touch Technology - How It Works — Zytronic Displays
  • 0bw5UJQxkhA Barometric pressure sensor: working principle — Bosch Sensortec
  • J2ulD-bPTS4 Understanding Temperature Sensor Technology — RealPars

เพดาน 64 widgets · ความหมาย kwarg ของ ui.Panel · ขนาดจอ 792x398 — มาจากซอร์สเฟิร์มแวร์เอง ดู path ในสไลด์ผู้สอน

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

อ้างอิงและเครดิต (ต่อ) — ภาพประกอบ

ภาพประกอบ

  • สนามแม่เหล็กโลกแบบไดโพล: NASA/USGS / Wikimedia Commons — สาธารณสมบัติ
  • กุหลาบทิศ (wind rose): Brosen / Wikimedia Commons — CC BY 2.5
  • แผนที่ค่าเบี่ยงเบนแม่เหล็กโลก 2025: NOAA NCEI / BGS, World Magnetic Model 2025.0 — สาธารณสมบัติ
  • ปรากฏการณ์ฮอลล์สี่กรณี: Peo / Hike395 / Wikimedia Commons — CC BY-SA 3.0
  • แกนลำตัวกับการติดตั้ง accelerometer: Rodriguez V.H. et al., Sensors 22(20):7690 (2022) — CC BY 4.0
  • แรงโน้มถ่วงในลิฟต์: Vkidambi / Wikimedia Commons — CC BY-SA 4.0
  • สัญญาณสั่นสะเทือนดิบจากตลับลูกปืน: Sehri M. et al., Data in Brief (2023) — CC BY 4.0
  • ความเร่งจากการเดิน แขนเทียบข้อเท้า: Kisiel M. et al., Sensors 26(3):876 (2026) — CC BY 4.0
  • หน้าต่างเฉลี่ยแบบเลื่อน: b_sliding_window_smoothing_animation.gif / Wikimedia Commons — CC0 1.0
  • สัญญาณเด้งของหน้าสัมผัส 250 ไมโครวินาที: Arctanx / Wikimedia Commons — สาธารณสมบัติ
  • เสาไฟสถานะเครื่องจักร: User:Mattes / Wikimedia Commons — สาธารณสมบัติ
  • แผงเฟดเดอร์ของมิกเซอร์: hanmaili / Wikimedia Commons — CC0 1.0
  • การสำรวจสนามแม่เหล็กภาคสนาม: Axel Hindemith / Wikimedia Commons — CC BY-SA 3.0
  • ไดอะแกรมอื่นทุกภาพวาดขึ้นใหม่สำหรับหลักสูตรนี้ ไม่ได้คัดลอกจากเอกสารผู้ผลิต
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

การ์ดที่มีชื่อกำกับมาในตัว

หน้าจอของ 08_win_titled_card.py: การ์ดที่มีชื่อกำกับมาในตัว และหัวเรื่องที่แก้ไม่ได้

ภาพจากตัวจำลอง Eva Kit (KIT_PSE84_EVAL_EPC2-MicroPython-BentoClaw/sim) ซึ่งรัน ipc_ui.c กับ ui_widget_mgr.c ตัวจริงเดียวกับบอร์ด บนพื้นที่วาด 800x398 · แสดงหน้าจอที่ตัวอย่างสร้าง — หน้าจอของ 08_win_titled_card.py · ค่าที่เห็นในภาพมาจากเซนเซอร์จำลองบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

ui.Win คือกรอบที่มีแถบหัวเรื่องมาให้แล้ว .content() คืนกล่องข้างในที่เราเอา widget ไปใส่ด้วย parent=

แฮนเดิล หัวเรื่อง ราคาที่ซ่อนอยู่
ui.Win + .content() 2 เฟิร์มแวร์จัดให้ แถบหัวกินความสูงราว 60 px จากกรอบ
ui.Panel + ui.Label 2 เราจัดเอง ไม่มี แต่ต้องนับพิกัดเอง

ราคาเท่ากัน ต่างกันที่ ใครเป็นคนจัดตำแหน่งหัวเรื่อง — และที่ Win หักความสูงไปโดยไม่บอก

text= ใช้ได้ตอนสร้างเท่านั้น ในภาพสั่งเปลี่ยนหัวเรื่องไปแล้ว 18 ครั้ง หัวยังเหมือนเดิม

ตั้งความสูงเท่าการ์ดธรรมดาแล้วเนื้อในจะถูกตัด พร้อมแถบเลื่อนโผล่มาเงียบ ๆ — เผื่อความสูงให้แถบหัวเสมอ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

โน้ตผู้สอน: อ่านเสริมนอกเวลา — เรื่องนี้อยู่นอกเกณฑ์ผ่านของบทเรียน 3.7–3.9 เพราะเป็นเอกสารอ้างอิงของตารางงบ widget ในบันทึกการเรียน มากกว่าจะเป็นบทเรียน: 06_layout_budget.py นับ widget ที่สร้างไปแล้วและจับ RuntimeError ตอนชนเพดาน 64 เปิดตอนงบเริ่มตึงแล้วอยากรู้ว่าเหลือเท่าไรจริง ๆ