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

บทเรียน 3.7 — ออกแบบ HMI: การ์ด ลำดับสายตา สี และงบ widget

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

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

คาถาประจำบทเรียน: ออกแบบบนกระดาษก่อน แล้วค่อยพิมพ์ — ข้อจำกัดคือส่วนหนึ่งของโจทย์

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

ดูของจริงก่อน — แดชบอร์ดเต็มรูปแบบที่บอร์ดทำได้

ผังของ lvgl_dashboards/12_dash_full_eva.py — ไม่ใช่หน้าเฟิร์มแวร์ 792 x 398 IMU · BMI270 Compass · BMM350 137 deg SE CapSense B0 --- B1 --- Potentiometer 48 % Gyro X +0.4 Y -1.2 deg/s สถานะโปรแกรม รอบที่ 1842 loop 3 ms

ผู้สอนเปิดเมนู Sensor Dashboard ของเฟิร์มแวร์บนบอร์ดหน้าห้อง แล้วปล่อยให้มันวิ่ง

หน้าจอเดียว การ์ดหลายใบ เซนเซอร์หลายตัวอัปเดตพร้อมกัน ไม่มีเมนูให้เข้า มองแล้วรู้สถานะทั้งระบบภายในสองวินาที — นั่นคือสิ่งที่โรงงานเรียกว่า HMI (Human-Machine Interface) หน้าจอที่คนยืนดูแล้วตัดสินใจได้ · วันนี้เราจะสร้างของแบบนี้เอง ด้วยงบ 32 widgets ที่ตั้งเอง (เพดานเฟิร์มแวร์ 64) และไฟล์ Python ไฟล์เดียว

ภาพจำลองข้างบนคือผังของ lvgl_dashboards/12_dash_full_eva.py (ตัวอย่าง MicroPython ที่มากับเฟิร์มแวร์ หัวไฟล์นับไว้ 23 widgets — ไม่ใช่ไฟล์ในหลักสูตรนี้) ไม่ใช่หน้า Sensor Dashboard ของเฟิร์มแวร์ — หน้าเฟิร์มแวร์บน Eva Kit มีการ์ด BMI270 · Controls (btn0/btn1/slider) · กราฟการเคลื่อนไหว · เข็มทิศ · จอยสติ๊ก และ ไม่มีการ์ดลูกบิด · บน TESAIoT Dev Kit หน้าเดียวกันมีการ์ดเพิ่มที่ Eva ไม่มี คือ DPS368 (ความดัน) SHT40 (อุณหภูมิ/ความชื้น) และเรดาร์ เพราะเฟิร์มแวร์ประกอบการ์ดตามชิปที่บอร์ดมี (page_dashboard.c ครอบด้วยธง BSP_HAS_*) ดูของจริงจากบอร์ดของทีม

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

ทำไม · คืออะไร · ทำยังไง — แผนที่ของชุดบทเรียนนี้

คำถาม คำตอบของชุดบทเรียนนี้ อยู่ช่วงไหน
Why ของทุกชิ้นก็ทำงานได้อยู่แล้วตั้งแต่บทเรียน 2.7–3.6 ทำไมต้องเอามารวมจอเดียว เพราะแผงควบคุมในโรงงานไม่ได้ออกแบบให้ "สวย" มันออกแบบให้ คนที่เหนื่อยและรีบ อ่านถูกในครั้งแรก · และของที่ทำงานได้ทีละชิ้น กับของที่ทำงานพร้อมกันทั้งจอภายใต้เพดาน 64 widgets เป็นคนละโจทย์กัน ครึ่งแรก · HMI · ลำดับสายตา · สีมีความหมาย
What มีอะไรให้ใช้บ้าง ของใหม่มีสองชนิดคือ ui.Panel กับ ui.Compass · บวกโมดูล mic ทั้งแปดชื่อ สำหรับการ์ดใบที่ห้าที่ทีมเลือกได้ และ sensors.bmm350 ทั้งห้าชื่อ ที่ป้อนเข็มทิศ สไลด์ bmm350 ห้าชื่อ + สไลด์ mic แปดชื่อ
How ประกอบยังไงให้ใช้งานได้จริง นับ widget บนกระดาษให้ครบก่อนพิมพ์ (จองไป 23 จากงบ 32 ที่ตั้งไว้เอง — เพดานเฟิร์มแวร์คือ 64) → คำนวณพิกัดสี่การ์ดเอง → อ่านเซนเซอร์ทุกตัวในลูปเดียวที่ cadence 200 ms → รันยาวสิบนาทีเพื่อพิสูจน์ หกไฟล์ตัวอย่าง + ไฟล์ฝึก + soak run

ปลายทางที่จับต้องได้ — แดชบอร์ดสี่การ์ดในจอเดียว IMU · เข็มทิศ · CapSense · ลูกบิด ที่รันต่อเนื่องสิบนาทีโดยไม่ค้างและไม่ crash

บทเรียน 3.4–3.6 เราทำกราฟหนึ่งใบให้ลื่น · ชุดบทเรียนนี้ต้องทำให้ของทั้งจอลื่นพร้อมกัน ภายใต้งบที่นับได้

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

เป้าหมายของชุดบทเรียนนี้

  1. ออกแบบผัง HMI บนกระดาษได้ก่อนเขียนโค้ด — วางการ์ด คำนวณพิกัด และ นับ widget ให้ครบก่อนพิมพ์
  2. อธิบายหลักการ HMI ที่ใช้ในแผงควบคุมจริงได้: การจัดกลุ่ม ลำดับสายตา และความหมายของสี
  3. ประกอบ ui.Panel สี่ใบเข้ากับ Chart, Compass, Bar, Arc, Seg7 ให้เป็นหน้าจอเดียวที่อ่านรู้เรื่อง
  4. ทดสอบความทนทานด้วยการรันต่อเนื่อง 10 นาที และแยกให้ออกว่า "ค้าง" กับ "ช้า" ต่างกันอย่างไร

ปลายทางของวันนี้: จอบอร์ดขึ้นแดชบอร์ดสี่การ์ดของทีมเรา รันยาวสิบนาทีโดยไม่มีอะไรสะดุด

ชุดบทเรียนนี้คือ capstone ครึ่งทาง — ไม่มีของใหม่มาก แต่ต้องเอาของเก่าทั้งหมดมาอยู่ร่วมจอเดียวกันให้ได้

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

ปลายทางของชุดบทเรียนนี้ — สี่การ์ดในจอเดียว

หน้าจอจาก BENTO Emulator ของเฉลย Mini-HMI Dashboard สี่การ์ด

หน้าจอจริงจากการรันโค้ดเฉลยบน BENTO Emulator ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — ไม่ใช่ภาพวาด ไม่ใช่ mock-up และไม่ใช่ภาพถ่ายจากบอร์ด
  • การ์ดสี่ใบ: ความเร่ง BMI270 (กราฟ) · เข็มทิศ BMM350 · แถบสัมผัส CapSense กับปุ่มทองแดงสองปุ่ม · ลูกบิด Potentiometer พร้อมเกณฑ์เป็นเปอร์เซ็นต์และไฟ "เกิน"
  • แต่ละใบคือ Panel หนึ่งใบที่ให้สีขอบต่างกัน ใช้แยกกลุ่มข้อมูลด้วยสายตาก่อนอ่านตัวหนังสือ
  • แถบหัวบอกชื่อทีม ตัวนับรอบกับ loop ไฟ "ค่าค้าง" และปุ่มสั่งงาน "เดินหน้า" / "หยุดภาพ" (ดูผังพิกัดข้างหน้า)

งบ widget มีจำกัด การจัดวางจึงเป็นการตัดสินใจ ไม่ใช่การตกแต่ง

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

ทบทวน — สามชิ้นส่วนที่เราสร้างไว้แล้ว

บทเรียน 2.7–2.9 · Arc Seg7 Bar pot และ CapSense บทเรียน 3.1–3.3 · motion() 6 แกน อ่านทีเดียวได้ครบ บทเรียน 3.4–3.6 · Chart หลายเส้น cadence 200 ms หน้าจอเดียว · การ์ดสี่ใบ · 23 widgets IMU + Chart ของเดิมจากบทเรียน 3.4–3.6 Compass ของใหม่วันนี้ CapSense ของเดิมจากบทเรียน 2.7–2.9 Pot ของเดิมจากบทเรียน 2.7–2.9
จากบทเรียน สิ่งที่ทำได้แล้ว วันนี้กลายเป็น
บทเรียน 2.7–2.9 pot → ui.Arc + ui.Seg7, CapSense slider → ui.Bar การ์ดสองใบล่าง
บทเรียน 3.1–3.3 sensors.bmi270.motion() อ่าน 6 แกนใน lock เดียว แหล่งข้อมูลของกราฟ
บทเรียน 3.4–3.6 ui.Chart หลาย series + cadence 200 ms การ์ด IMU ซ้ายบน

ของใหม่วันนี้มีสองอย่าง: ui.Panel เป็นกรอบการ์ด · ui.Compass กินค่าจาก bmm350.heading()

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

เนื้อหาใหม่น้อย แต่ความยากขึ้นชัด เพราะครั้งนี้ทุกชิ้นต้องทำงานพร้อมกัน

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

ทบทวน (ต่อ) — ของสองอย่างที่ต้องเห็นตอนมันเคลื่อน: สนามแม่เหล็ก และนิ้วบนกระจก

ภาพเคลื่อนไหวปรากฏการณ์ Hall สี่กรณี เมื่อสนามแม่เหล็กเปลี่ยนทิศ แรงดันที่วัดได้เปลี่ยนตาม

ที่มา: Peo / Hike395 / Wikimedia Commons — CC BY-SA 3.0 · สนามแม่เหล็กที่เปลี่ยนทิศแล้วแรงดันที่ได้เปลี่ยนตาม — ต้องเห็นทั้งสี่กรณีสลับกันถึงจะเข้าใจว่าทำไมค่าสามแกนถึงพลิกเครื่องหมายเมื่อหมุนบอร์ด

ดูเพิ่ม (7 นาที): Projected Capacitive Touch Technology - How It Works — นิ้ว "ขโมย" เส้นสนามไฟฟ้า คือค่าที่ capsense.read() คืนมา

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

HMI คืออะไร และทำไมต้องจัดของเป็นการ์ด

ตัวเลข 15 ตัว ไม่มีกรอบ 12.4 0.98 137 48 3.1 9.81 -1.2 0 62 1.64 204 0.4 17 -- 2.99 ต้องไล่อ่านป้ายทีละตัว กว่าจะเจอค่าที่ต้องการ การ์ดสี่ใบ หนึ่งใบหนึ่งคำถาม การเคลื่อนไหว เป็นยังไง หันหน้า ไปทางไหน มีใครแตะ อยู่ไหม ลูกบิดตั้งไว้ เท่าไร

แผงควบคุมในโรงงานไม่ได้ออกแบบให้ "สวย" มันออกแบบให้ คนที่เหนื่อยและรีบ อ่านถูกในครั้งแรก

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

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

การ์ดหนึ่งใบตอบคำถามหนึ่งคำถาม: "การเคลื่อนไหวเป็นยังไง" · "หันหน้าไปทางไหน" · "มีใครแตะอยู่ไหม" · "ลูกบิดตั้งไว้เท่าไร"

ถ้าตอบไม่ได้ว่าการ์ดใบนี้ตอบคำถามอะไร แสดงว่ายังไม่ควรมีการ์ดใบนี้

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

ลำดับสายตา — อะไรต้องอ่านออกจากอีกฝั่งห้อง

Compass - BMM350 137 deg SE อัปเดตล่าสุด 0.2 วินาทีที่แล้ว ชั้น 3 · ชื่อการ์ด — font 16 ชั้น 1 · ค่าหลัก — font 28 อ่านจาก 3 เมตร ชั้น 2 · ค่าประกอบ — font 24 อ่านตอนเดินเข้ามา ชั้น 3 · รายละเอียด — font 14-16

บนหน้าจอเดียวกัน ของทุกชิ้นไม่ได้สำคัญเท่ากัน เราต้องตัดสินใจแทนคนดูว่าอะไรควรถูกเห็นก่อน

สามชั้นที่ใช้จริงในงาน HMI

  1. ชั้นที่ต้องอ่านจากระยะสองสามเมตร — ตัวเลขหลักของการ์ด ใช้ฟอนต์ 24–28 สีขาวสว่างบนพื้นเข้ม
  2. ชั้นที่อ่านตอนเดินเข้ามาใกล้ — ค่ารายละเอียด กราฟ แถบ ใช้ฟอนต์ปกติ
  3. ชั้นที่อ่านเฉพาะตอนหา — ชื่อการ์ด หน่วย ป้ายกำกับ ใช้สีจางลง

ใน ui เรามีเครื่องมือคุมสามชั้นนี้อยู่แค่สองอย่าง: value= ของ Label (ขนาดฟอนต์ 14/16/20/24/28) และ color= เท่านั้น จึงต้องใช้ให้ตรงเป้า อย่าใส่ 28 ให้ทุกตัวเพราะ "ใหญ่แล้วดูดี" — ถ้าทุกอย่างเด่น แปลว่าไม่มีอะไรเด่น

ในโค้ดวันนี้ ตัวเลของศาของเข็มทิศได้ 28 px ส่วนชื่อการ์ดได้ 16 px และหน่วยได้สีเทา นั่นคือการตัดสินใจ ไม่ใช่ความบังเอิญ

แผงเฟดเดอร์ของมิกเซอร์เสียงที่ตำแหน่งปุ่มเลื่อนบอกค่าหลายค่าพร้อมกัน

ภาพ: hanmaili / Wikimedia Commons — CC0 1.0 · แผงเฟดเดอร์ของมิกเซอร์จริง — ค่าหลายสิบค่าที่อ่านได้ในสายตาเดียวเพราะตำแหน่งเรียงกัน ไม่ใช่เพราะตัวเลขใหญ่

ขนาดฟอนต์คือการประกาศว่า "ของชิ้นนี้สำคัญกว่าชิ้นนั้น" — ประกาศให้ตรงกับความจริง

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

สีมีความหมาย ไม่ใช่ของตกแต่ง

เขียว · ปกติ 9.8 อยู่ในเกณฑ์ ไม่ต้องทำอะไร เหลือง · เฝ้าดู 11.4 เริ่มออกนอกช่วงที่คุ้น กลับมาดูอีกที แดง · ต้องลงมือ 14.9 เกินเกณฑ์แล้ว หยุดหรือแก้ เดี๋ยวนี้ ห้ามใช้แดงกับของที่ปกติ — วันที่เกิดเรื่องจริงจะไม่มีใครสังเกตเห็น

ในแผงควบคุมจริง สีสามสีนี้ถูกจองไว้แล้ว และคนทั้งอุตสาหกรรมอ่านมันตรงกัน

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

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

เสาไฟสถานะสามสี แดง เหลือง เขียว บนเครื่องจักร

ภาพ: User:Mattes / Wikimedia Commons — สาธารณสมบัติ · เสาไฟสถานะจริงบนเครื่องจักร แดง-เหลือง-เขียวเท่านั้น — ยืนยันว่าชุดสีที่อ่านออกจากระยะไกลมีไม่กี่สี และความหมายถูกล็อกไว้แล้ว
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

จานสีของเฉลย — สีสถานะสามสี กับสีประจำเซนเซอร์สี่สี

สีที่เหลือ (ม่วง · เขียวน้ำทะเล · ม่วงกล้วยไม้ · ฟ้า — จานสีเส้นข้อมูลของหลักสูตร) ใช้เพื่อ แยกแหล่งข้อมูล ไม่ใช่บอกสถานะ — พื้นการ์ดกับตัวหนังสือยืมจาก ui_theme.py ในชุดตัวอย่างที่มากับเฟิร์มแวร์ หน้าจอของเราจะดูเป็นระบบเดียวกับเมนูอื่น · บรรทัดจริงจาก s08_dashboard.py:

BG_CARD   = 0x171B22   # พื้นการ์ด เทาเข้มอมน้ำเงิน
COL_WHITE = 0xE8EAED   # ตัวเลขพระเอก
COL_GRAY  = 0x9AA3AF   # ข้อความประกอบ / สถานะเงียบ เทาอ่อน
COL_IMU   = 0x8E7BFF   # BMI270 - เส้นที่ 2 ของจานสีเส้นข้อมูล สีม่วง
COL_COMP  = 0x2FB6A8   # BMM350 - เส้นที่ 3 ของจานสีเส้นข้อมูล สีเขียวน้ำทะเล
COL_TOUCH = 0xC77DFF   # CapSense - เส้นที่ 4 ของจานสีเส้นข้อมูล สีม่วงกล้วยไม้
COL_POT   = 0x4A9EFF   # Potentiometer - เส้นที่ 1 ของจานสีเส้นข้อมูล สีฟ้า
COL_STAT  = 0x30A46C   # เขียว "ปกติ"
COL_WARN  = 0xF5A623   # เหลือง "ค่าเชื่อไม่ได้"
COL_ALERT = 0xE5484D   # แดง "ต้องลงมือ"

ให้คัดค่าสีที่ใช้จริงมาวางไว้ต้นไฟล์ของเราเอง (อย่างที่เห็นข้างบน) แทนการ import — ชื่อในไฟล์ต้นทางไม่ตรงกับของเราทุกตัว เช่นสีเตือนของเราชื่อ COL_ALERT ส่วนต้นทางเรียกว่า COL_AX และอย่าสุ่มเลขสีเองเด็ดขาด

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

อัตราอัปเดต กับ ความอ่านออก — สองอย่างนี้ตีกัน

อัปเดตทุก 20 ms · 50 ครั้งต่อวินาที 47.2 51.8 44.6 49.1 ตาคนอ่านไม่ทัน อัปเดตทุก 200 ms · 5 ครั้งต่อวินาที 47.2 48.1 48.6 48.4 อ่านทัน เห็นทิศทาง ฝั่งเครื่อง — คิววาดของ CM55 ลูปเร็วเกิน คิวเต็ม เฟรมหายเงียบ ๆ ไม่มี error ที่ 200 ms คิวว่างเสมอ

สัญชาตญาณบอกว่ายิ่งอัปเดตถี่ยิ่งดี ในงาน HMI มันไม่จริง

ตัวเลขที่เปลี่ยนทุก 20 ms คือตัวเลขที่ มนุษย์อ่านไม่ทัน สายตาเห็นเป็นเลขเบลอ ๆ ที่กระพริบ ส่วนกราฟที่เลื่อนเร็วเกินก็ดูไม่ออกว่าแนวโน้มขึ้นหรือลง

ที่ 200 ms คนอ่านตัวเลขทันพอดี (ห้าครั้งต่อวินาที) กราฟเลื่อนแบบเห็นทิศทาง และ CM55 มีเวลาวาดครบทุกเฟรม

อีกด้านหนึ่ง — ด้านของเครื่อง ลูปที่เร็วเกินจะส่งงานให้ CM55 ถี่กว่าที่มันวาดทัน คิวเต็ม แล้วเฟรมจะหายไปเงียบ ๆ ไม่มี error ให้เห็น หน้าจอแค่ "รู้สึกกระตุก" ซึ่งเป็นอาการที่ดีบักยากที่สุด

จำง่าย ๆ: แดชบอร์ดหนัก 200 ms · หน้าเบา ๆ ที่มีไม่กี่ widget อย่างต่ำ 50 ms

การจำกัดความถี่ไม่ใช่การยอมแพ้เรื่องประสิทธิภาพ มันคือการออกแบบให้ตรงกับความเร็วของสายตาคน

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 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 · หน้าต่างเฉลี่ยที่เลื่อนไปบนข้อมูลจริง — ให้เห็นว่าการทำให้อ่านออกคือการยอมช้าลง ไม่ใช่การได้ของฟรี
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เข้าใจฮาร์ดแวร์ · จอกว้าง 792 สูง 398 และเลขพิกัดที่เราต้องคำนวณเอง

ui ไม่มีระบบ layout อัตโนมัติแบบเว็บ (ไม่มี grid ไม่มี flex) ทุก widget ต้องบอกพิกัดเอง เราจึงต้องบวกลบเลขบนกระดาษก่อน

แถบหัว y=4..92 · ชื่อทีม ไฟค่าค้าง ปุ่มสั่งงานสูง 88 รอบที่ N | loop ms y=52 · ขอบซ้าย 24 · ช่องไฟระหว่างการ์ด 16 การ์ด 1 · IMU + Chart x=24 y=100 w=368 h=136 Chart x=40 y=136 w=336 h=56 4 widgets การ์ด 2 · Compass x=408 y=100 w=360 h=136 Compass x=424 y=132 w=96 5 widgets การ์ด 3 · CapSense x=24 y=252 w=320 h=136 Bar x=40 y=348 w=288 h=16 8 widgets การ์ด 4 · Pot + เกณฑ์ x=360 y=252 w=408 h=136 Arc w=88 · Seg7 w=112 · Spinbox w=88 9 widgets

เลขที่ต้องตรวจให้ตรงเสมอ: x + w ต้องไม่เกิน 792 และ y + h ต้องไม่เกิน 398 (การ์ด 2: 408+360 = 768 เหลือขอบขวา 24 px · การ์ดแถวล่าง: 252+136 = 388 เหลือขอบล่าง 10 px)

วางของทับกันแล้วจอไม่ error มันแค่วาดทับ — เลขที่ผิดจะเงียบจนกว่าเราจะมองเห็นด้วยตา

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

ui.Panel — การ์ดหนึ่งใบ กับพารามิเตอร์ที่ชื่อไม่ตรงความหมาย

ui.Panel min = สีขอบ (ไม่ใช่ค่าต่ำสุด) color = สีพื้นของการ์ด max = รัศมีมุมโค้ง value = ความหนาขอบ ชื่อ kwarg เดียวกัน เปลี่ยนความหมายไปตามชนิดของ widget

ui.Panel ใช้ kwargs ชุดเดียวกับ widget อื่น แต่ ความหมายไม่เหมือนใคร จุดนี้พลาดกันทุกปี

kwarg สำหรับ Panel แปลว่า ค่าที่เราใช้
color สีพื้นของการ์ด BG_CARD = 0x171B22
min สีขอบ (ไม่ใช่ค่าต่ำสุด) สีประจำเซนเซอร์ของการ์ดนั้น
max รัศมีมุมโค้ง เป็นพิกเซล 12
value ความหนาเส้นขอบ เป็นพิกเซล 2
imu_panel = ui.Panel(x=24, y=100, w=368, h=136,
                     color=BG_CARD, min=COL_IMU, max=12, value=2)

เทียบกับ ui.Slider ที่ min/max/value เป็นตัวเลขค่าจริง ๆ และ ui.Label ที่ value คือขนาดฟอนต์ — ชื่อ kwarg เดียวกันเปลี่ยนความหมายไปตามชนิด widget

ลำดับการสร้างสำคัญ: สร้าง Panel ก่อน แล้วค่อยสร้าง Label/Chart ทับลงไป ของที่สร้างทีหลังอยู่ชั้นบน ถ้าสลับลำดับ การ์ดจะบังตัวหนังสือจนหาย

เวลาสงสัยว่า kwarg ตัวไหนแปลว่าอะไร ให้เปิดตารางนี้ อย่าเดาจากชื่อ

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

งบ widget — นับบนกระดาษให้ครบก่อนพิมพ์โค้ดบรรทัดแรก

งบ 32 ช่องที่เราตั้งเอง (เพดานเฟิร์มแวร์ 64) — จองไป 23 เหลือ 9 หัวเรื่อง + สถานะ 3 IMU 4 Compass 5 CapSense 6 Pot 5 ว่าง 9 ช่อง — เผื่อไว้ให้ทีมต่อยอด รวม 23 / 32

เฟิร์มแวร์รับได้ 64 widgets ต่อหนึ่งหน้าจอ (UI_MAX_WIDGETS ใน ipc_ui_protocol.h เท่ากันทั้งสองบอร์ด) ตัวที่ 65 จะไม่ขึ้น และมันไม่แจ้งเตือนอะไรเลย · ส่วน 32 คืองบที่ชุดบทเรียนนี้ตั้งให้ตัวเอง เพื่อเหลือที่ไว้ให้บทเรียน 4.1–5.3 ต่อยอดบนหน้าจอใบเดิม

วิธีทำงานที่ถูกคือเขียนตารางนี้ก่อน แล้วรวมเลขให้เห็นก่อนแตะคีย์บอร์ด

การ์ด widget ที่ใช้ จำนวน
หัวเรื่อง + แถบสถานะ Label ชื่อทีม, Label รอบ, Label loop ms 3
1 · IMU Panel, Label หัวข้อ, Chart, Label ค่า 3 แกน 4
2 · Compass Panel, Label หัวข้อ, Compass, Label องศา, Label ทิศ 5
3 · CapSense Panel, Label หัวข้อ, Label ปุ่ม B0, Label ปุ่ม B1, Bar, Label % 6
4 · Pot Panel, Label หัวข้อ, Arc, Seg7, Label โวลต์ 5
รวม 23 / 32

สังเกตว่าอะไรไม่นับ: series ของ Chart ไม่ใช่ widget (add_series() สามครั้งยังเป็น Chart ตัวเดียว) ส่วน Panel นับ ทุกใบ การ์ดสี่ใบจึงกินไปแล้ว 4 ตัวก่อนจะแสดงค่าอะไรเลย

เหลืองบ 9 ตัวไว้ให้ทีมต่อยอด — ใครจะเพิ่มการ์ดที่ห้า ต้องกลับมาแก้ตารางนี้ก่อน

ตารางนี้อยู่ในหัวไฟล์โค้ดด้วย เพราะคนที่กลับมาแก้ในอีกสองสัปดาห์คือตัวเราเอง

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

เกร็ด: ทำไมเพดานถึงเป็น 64 พอดี

CM33 MicroPython ui.Label(...) ตาราง handle 64 ช่อง (วาดเฉพาะ 32 ช่องที่เป็นงบของชุดบทเรียนนี้) ขนาดคงที่ ไม่ขอหน่วยความจำเพิ่มตอนรัน CM55 + LVGL วาดของจริงบนจอ อ่านจากตารางนี้ widget ตัวที่ 65 ไม่มีช่องให้ลง — หายเงียบ ๆ ไม่มี error จองล่วงหน้าในขนาดที่รู้แน่ ดีกว่ายืดหยุ่นแล้วพังตอนทำงาน

ตัวเลข 64 ไม่ได้มาจากความสวยงามของเลขยกกำลังสอง มันมาจากข้อจำกัดจริงของการคุยข้ามคอร์ — และเท่ากันทั้ง Eva Kit และ Dev Kit เพราะสองบอร์ดใช้ ipc_ui ชุดเดียวกัน

โค้ด Python ของเราอยู่บน CM33 ส่วน widget จริง ๆ ถูกสร้างและวาดโดย LVGL บน CM55 สองฝั่งนี้คุยกันผ่าน IPC ซึ่งมีพื้นที่หน่วยความจำร่วมขนาดคงที่ ทุก widget ที่สร้างจะกิน "ช่อง" ในตารางอ้างอิงฝั่ง CM55 หนึ่งช่อง และตารางนั้นถูกจองขนาดไว้ตายตัวตั้งแต่ตอนคอมไพล์ — จองแบบไม่ต้องขอหน่วยความจำเพิ่มตอนรัน เพราะการขอหน่วยความจำระหว่างวาดจอคือทางลัดสู่การค้าง

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

เชื่อมกับวันนี้: ตอนทีมนั่งเถียงกันว่าจะตัด Label ตัวไหนออกเพื่อให้พองบ 32 ที่ตั้งไว้ — นั่นคือการทำงานภายใต้ข้อจำกัดของหน่วยความจำจริง แบบเดียวกับที่วิศวกรออกแบบ HMI ในเครื่องจักรทำอยู่ทุกวัน และเป็นเหตุผลที่ MVP ของวันนี้วัดกันที่ "รันสิบนาทีไม่ค้าง" ไม่ใช่ "มีของบนจอเยอะที่สุด"

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