| คำถาม | คำตอบของชุดบทเรียนนี้ | อยู่ช่วงไหน | |
|---|---|---|---|
| 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 เราทำกราฟหนึ่งใบให้ลื่น · ชุดบทเรียนนี้ต้องทำให้ของทั้งจอลื่นพร้อมกัน ภายใต้งบที่นับได้
ui.Panel สี่ใบเข้ากับ Chart, Compass, Bar, Arc, Seg7 ให้เป็นหน้าจอเดียวที่อ่านรู้เรื่องปลายทางของวันนี้: จอบอร์ดขึ้นแดชบอร์ดสี่การ์ดของทีมเรา รันยาวสิบนาทีโดยไม่มีอะไรสะดุด
ชุดบทเรียนนี้คือ capstone ครึ่งทาง — ไม่มีของใหม่มาก แต่ต้องเอาของเก่าทั้งหมดมาอยู่ร่วมจอเดียวกันให้ได้

งบ widget มีจำกัด การจัดวางจึงเป็นการตัดสินใจ ไม่ใช่การตกแต่ง
| จากบทเรียน | สิ่งที่ทำได้แล้ว | วันนี้กลายเป็น |
|---|---|---|
| บทเรียน 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()
ที่เหลือคืองานออกแบบ — จัดของที่มีอยู่แล้วให้อยู่ร่วมจอเดียวกันโดยไม่ทับกัน ไม่เกินงบ และยังอ่านออก
เนื้อหาใหม่น้อย แต่ความยากขึ้นชัด เพราะครั้งนี้ทุกชิ้นต้องทำงานพร้อมกัน

ดูเพิ่ม (7 นาที): Projected Capacitive Touch Technology - How It Works — นิ้ว "ขโมย" เส้นสนามไฟฟ้า คือค่าที่ capsense.read() คืนมา
แผงควบคุมในโรงงานไม่ได้ออกแบบให้ "สวย" มันออกแบบให้ คนที่เหนื่อยและรีบ อ่านถูกในครั้งแรก
หลักข้อแรกคือการจัดกลุ่ม: ค่าที่มาจากแหล่งเดียวกันหรือใช้ตัดสินใจเรื่องเดียวกัน ต้องอยู่ในกรอบเดียวกัน
ลองคิดถึงหน้าจอที่มีตัวเลขสิบห้าตัวเรียงกันเป็นแถวยาวโดยไม่มีกรอบอะไรเลย ต่อให้ตัวเลขถูกทุกตัว คนดูก็ต้องอ่านป้ายกำกับทีละตัวเพื่อหาว่าอันไหนคืออันที่ตัวเองต้องการ นั่นคือเวลาที่เสียไปทุกครั้งที่มีคนมองจอ
การ์ดหนึ่งใบตอบคำถามหนึ่งคำถาม: "การเคลื่อนไหวเป็นยังไง" · "หันหน้าไปทางไหน" · "มีใครแตะอยู่ไหม" · "ลูกบิดตั้งไว้เท่าไร"
ถ้าตอบไม่ได้ว่าการ์ดใบนี้ตอบคำถามอะไร แสดงว่ายังไม่ควรมีการ์ดใบนี้
บนหน้าจอเดียวกัน ของทุกชิ้นไม่ได้สำคัญเท่ากัน เราต้องตัดสินใจแทนคนดูว่าอะไรควรถูกเห็นก่อน
สามชั้นที่ใช้จริงในงาน HMI
ใน ui เรามีเครื่องมือคุมสามชั้นนี้อยู่แค่สองอย่าง: value= ของ Label (ขนาดฟอนต์ 14/16/20/24/28) และ color= เท่านั้น จึงต้องใช้ให้ตรงเป้า อย่าใส่ 28 ให้ทุกตัวเพราะ "ใหญ่แล้วดูดี" — ถ้าทุกอย่างเด่น แปลว่าไม่มีอะไรเด่น
ในโค้ดวันนี้ ตัวเลของศาของเข็มทิศได้ 28 px ส่วนชื่อการ์ดได้ 16 px และหน่วยได้สีเทา นั่นคือการตัดสินใจ ไม่ใช่ความบังเอิญ

ขนาดฟอนต์คือการประกาศว่า "ของชิ้นนี้สำคัญกว่าชิ้นนั้น" — ประกาศให้ตรงกับความจริง
ในแผงควบคุมจริง สีสามสีนี้ถูกจองไว้แล้ว และคนทั้งอุตสาหกรรมอ่านมันตรงกัน
| สี | ความหมาย | คนดูควรทำอะไร |
|---|---|---|
| เขียว | ปกติ อยู่ในเกณฑ์ | ไม่ต้องทำอะไร |
| เหลือง/อำพัน | เฝ้าดู เริ่มออกนอกช่วงที่คุ้นเคย | กลับมาดูอีกที |
| แดง | ต้องลงมือ | หยุด/แก้ เดี๋ยวนี้ |
กฎที่ตามมาคือ ห้ามใช้แดงกับของที่ปกติ ถ้าเราทำกรอบการ์ดเป็นสีแดงเพราะชอบสีแดง วันที่เกิดเรื่องจริงจะไม่มีใครสังเกตเห็น

สีที่เหลือ (ม่วง · เขียวน้ำทะเล · ม่วงกล้วยไม้ · ฟ้า — จานสีเส้นข้อมูลของหลักสูตร) ใช้เพื่อ แยกแหล่งข้อมูล ไม่ใช่บอกสถานะ — พื้นการ์ดกับตัวหนังสือยืมจาก 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และอย่าสุ่มเลขสีเองเด็ดขาด
สัญชาตญาณบอกว่ายิ่งอัปเดตถี่ยิ่งดี ในงาน HMI มันไม่จริง
ตัวเลขที่เปลี่ยนทุก 20 ms คือตัวเลขที่ มนุษย์อ่านไม่ทัน สายตาเห็นเป็นเลขเบลอ ๆ ที่กระพริบ ส่วนกราฟที่เลื่อนเร็วเกินก็ดูไม่ออกว่าแนวโน้มขึ้นหรือลง
ที่ 200 ms คนอ่านตัวเลขทันพอดี (ห้าครั้งต่อวินาที) กราฟเลื่อนแบบเห็นทิศทาง และ CM55 มีเวลาวาดครบทุกเฟรม
อีกด้านหนึ่ง — ด้านของเครื่อง ลูปที่เร็วเกินจะส่งงานให้ CM55 ถี่กว่าที่มันวาดทัน คิวเต็ม แล้วเฟรมจะหายไปเงียบ ๆ ไม่มี error ให้เห็น หน้าจอแค่ "รู้สึกกระตุก" ซึ่งเป็นอาการที่ดีบักยากที่สุด
จำง่าย ๆ: แดชบอร์ดหนัก 200 ms · หน้าเบา ๆ ที่มีไม่กี่ widget อย่างต่ำ 50 ms
การจำกัดความถี่ไม่ใช่การยอมแพ้เรื่องประสิทธิภาพ มันคือการออกแบบให้ตรงกับความเร็วของสายตาคน


ui ไม่มีระบบ layout อัตโนมัติแบบเว็บ (ไม่มี grid ไม่มี flex) ทุก widget ต้องบอกพิกัดเอง เราจึงต้องบวกลบเลขบนกระดาษก่อน
เลขที่ต้องตรวจให้ตรงเสมอ: x + w ต้องไม่เกิน 792 และ y + h ต้องไม่เกิน 398 (การ์ด 2: 408+360 = 768 เหลือขอบขวา 24 px · การ์ดแถวล่าง: 252+136 = 388 เหลือขอบล่าง 10 px)
วางของทับกันแล้วจอไม่ error มันแค่วาดทับ — เลขที่ผิดจะเงียบจนกว่าเราจะมองเห็นด้วยตา
ui.Panel — การ์ดหนึ่งใบ กับพารามิเตอร์ที่ชื่อไม่ตรงความหมาย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 ตัวไหนแปลว่าอะไร ให้เปิดตารางนี้ อย่าเดาจากชื่อ
เฟิร์มแวร์รับได้ 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 ตัวไว้ให้ทีมต่อยอด — ใครจะเพิ่มการ์ดที่ห้า ต้องกลับมาแก้ตารางนี้ก่อน
ตารางนี้อยู่ในหัวไฟล์โค้ดด้วย เพราะคนที่กลับมาแก้ในอีกสองสัปดาห์คือตัวเราเอง
ตัวเลข 64 ไม่ได้มาจากความสวยงามของเลขยกกำลังสอง มันมาจากข้อจำกัดจริงของการคุยข้ามคอร์ — และเท่ากันทั้ง Eva Kit และ Dev Kit เพราะสองบอร์ดใช้ ipc_ui ชุดเดียวกัน
โค้ด Python ของเราอยู่บน CM33 ส่วน widget จริง ๆ ถูกสร้างและวาดโดย LVGL บน CM55 สองฝั่งนี้คุยกันผ่าน IPC ซึ่งมีพื้นที่หน่วยความจำร่วมขนาดคงที่ ทุก widget ที่สร้างจะกิน "ช่อง" ในตารางอ้างอิงฝั่ง CM55 หนึ่งช่อง และตารางนั้นถูกจองขนาดไว้ตายตัวตั้งแต่ตอนคอมไพล์ — จองแบบไม่ต้องขอหน่วยความจำเพิ่มตอนรัน เพราะการขอหน่วยความจำระหว่างวาดจอคือทางลัดสู่การค้าง
นี่คือแบบแผนที่เจอได้ทั่วไปในงาน embedded: จองล่วงหน้าในขนาดที่รู้แน่ ดีกว่ายืดหยุ่นแล้วเสี่ยงพังตอนทำงาน ระบบที่ต้องทำงานยาว ๆ โดยไม่มีคนดูแล เลือกทางแรกเสมอ
เชื่อมกับวันนี้: ตอนทีมนั่งเถียงกันว่าจะตัด Label ตัวไหนออกเพื่อให้พองบ 32 ที่ตั้งไว้ — นั่นคือการทำงานภายใต้ข้อจำกัดของหน่วยความจำจริง แบบเดียวกับที่วิศวกรออกแบบ HMI ในเครื่องจักรทำอยู่ทุกวัน และเป็นเหตุผลที่ MVP ของวันนี้วัดกันที่ "รันสิบนาทีไม่ค้าง" ไม่ใช่ "มีของบนจอเยอะที่สุด"