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

บทเรียน 1.2 — ข้อความแรกขึ้นจอ: โมดูล lcd กับ ui

AIoT รอบตัวเรา · ทัวร์บอร์ดครบทุกเมนู แล้วส่งข้อความแรกขึ้นจอ

โมดูล 1 — แอปพลิเคชันบนจอที่มีอยู่แล้ว

ต่อจากบทเรียน 1.1 — ทัวร์บอร์ด: เล่นของจริงก่อน

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

เรื่องที่เราให้ 70% ผู้เรียนเขียน 30%

70% — เฟิร์มแวร์ทำให้แล้ว 30% — งานของเรา

สิ่งที่เฟิร์มแวร์ทำให้แล้ว (70%)
อ่านเซนเซอร์ตลอดเวลา, วาดหน้าจอทุกเมนู, จัดการ IPC ระหว่างสองคอร์, ระบบไฟล์บนบอร์ด, รัน MicroPython, จัดการวิทยุ WiFi

สิ่งที่เป็นงานของเรา (30%)
ตัดสินใจว่า จะเอาข้อมูลอะไร มาแสดง ยังไง และ ส่งไปไหนต่อ

วันนี้ 30% ของเราเล็กมาก คือแค่ "พิมพ์อะไรลงจอ" แต่กลไกเบื้องหลังเหมือนกันทุกบทเรียน

เราไม่ได้เรียนเขียนไดรเวอร์ เราเรียน ออกแบบระบบ AIoT โดยยืนบนไดรเวอร์ที่มีอยู่แล้ว

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

สามโมดูลที่จะลงมือจริงก่อน — แล้วครึ่งหลังจะเปิดที่เหลือทั้งกล่อง

lcd ลิ้นชัก Console clear() · print() · console() ประวัติที่ไล่ลงมาเรื่อย ๆ ตอบว่า "ที่ผ่านมาเกิดอะไร" ui ป้ายบนหน้า Playground Label · Seg7 · Bar · Chart ค่าที่เขียนทับที่เดิมได้ ตอบว่า "ตอนนี้เป็นยังไง" time จังหวะและนาฬิกา sleep_ms · ticks_ms · ticks_diff คุมว่าคนดูจะอ่านทันไหม และวัดว่าเราช้าไปเท่าไร

สามตัวนี้คือของที่จะพิมพ์เองครบทุกบรรทัดในครึ่งแรก ส่วนครึ่งหลังของชุดบทเรียนจะเปิดอีกห้าโมดูล — gpio sensors dsp mic machine — และ widget อีกสิบกว่าชนิด ให้เห็นว่ากล่องนี้มีอะไรอยู่จริงบ้าง ก่อนจะไปลงลึกทีละตัวในบทเรียนต่อ ๆ ไป

print() ขึ้นที่คอนโซลฝั่งคอม · lcd.print() ลงลิ้นชักบนบอร์ด · ui.Label ขึ้นบนหน้าจอตรง ๆ — สามที่ คนละที่กัน

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

สามโมดูลที่จะลงมือจริงก่อน (ต่อ) — กฎข้อเดียวที่ต้องจำวันนี้

import lcd
import time
import ui
...
ui.screen()                    # ล้าง widget เดิมทิ้ง เริ่มจากจอเปล่าที่เรารู้แน่
time.sleep_ms(200)
...
say = ui.Label("กำลังพิมพ์ลงลิ้นชัก Console...", x=24, y=56,
               color=COL_ACCENT, value=24)
...
ui.poll()                      # เคาะหนึ่งครั้ง ป้ายถึงจะโผล่ทันที
...
lcd.clear()
lcd.print("สวัสดี บอร์ด PSoC Edge")

กฎข้อเดียวที่ต้องจำวันนี้: สร้างหรือแก้ ui.* แล้วต้องเคาะ ui.poll() หนึ่งครั้ง ไม่งั้นจอจะนิ่งไปราวสองวินาทีจนกลไกกันเหนียวปลดล็อกเอง หลายทีมสรุปว่า "โค้ดพัง" ทั้งที่แค่ยังไม่ได้เคาะ (กติกาที่เหลือของ ui รอบทเรียน 2.4–2.6) · บรรทัดพวกนี้ตัดมาจาก 01_first_line.py ตรง ๆ — COL_ACCENT คือสีเน้น 0x4A9EFF ที่หัวไฟล์ประกาศไว้

value= บน ui.Label คือขนาดตัวอักษร ไม่ใช่ตัวเลขที่จะเอาไปแสดง — จุดนี้ทำคนสะดุดทุกรุ่น

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

วิธีรันบนบอร์ด

ภาพถ่ายหน้า BENTO Playground บนบอร์ดที่มีคอนโซลรอรับโค้ด

ภาพถ่ายจอจริงของบอร์ด Eva Kit หน้า BENTO Playground ที่เพิ่งเปิด — พื้นที่แสดงผลกลางจอยังดำสนิททั้งผืน ไม่มีข้อความสักบรรทัด · มุมขวาล่างคือปุ่มสี่เหลี่ยมมนสีเขียวรูปไอคอนรายการ ไม่มีตัวหนังสือกำกับ และมีจุดแดงเล็ก ๆ เกาะอยู่ที่มุมขวาบนของปุ่ม — นั่นคือขั้นที่ 6 ที่ต้องแตะ · มุมขวาบนของหน้ามีปุ่มกลมสองปุ่ม แดงรูปถังขยะ กับเขียวรูปสามเหลี่ยมเล่น · แถบบนสุดยังไม่มีไอคอน WiFi และไม่มีนาฬิกา เพราะชุดบทเรียนนี้ยังไม่ได้ต่อเน็ต
  1. บนจอบอร์ด แตะการ์ด BENTO Playground เปิดค้างไว้ (เฟิร์มแวร์เด้งมาหน้านี้ให้เองตอนสคริปต์เรียก lcd ครั้งแรก แต่เปิดรอไว้จะเห็นผลตั้งแต่วินาทีแรก)
  2. บนคอม เปิด BENTO IDE เชื่อมต่อบอร์ด (ดูไฟสถานะว่าเจอบอร์ดแล้ว)
  3. เปิดไฟล์ตัวอย่างของชุดบทเรียนนี้จาก โฟลเดอร์ examples/ ของบทเรียน 1.1–1.3 แล้วกด Program to Device
  4. หันไปมองจอบอร์ด ไม่ใช่จอคอม
  5. แตะปุ่มไอคอนสีเขียวที่มุมขวาล่างของหน้า Playground เพื่อเปิดลิ้นชัก Console — หน้านี้เปิดมาที่โหมด UI เป็นค่าเริ่มต้น ข้อความจาก lcd จะรออยู่จนกว่าจะกดสลับ สังเกตจุดแดงเล็ก ๆ ที่มุมปุ่มเมื่อมีข้อความรอ
  6. อยากเริ่มใหม่ ปุ่ม RESTART บนหน้า Playground รันสคริปต์ซ้ำได้เลย

พื้นที่วาดของเราคือ 792 x 398 พิกเซล และมุมขวาล่างราว 100x58 เป็นของปุ่ม Console ที่เฟิร์มแวร์จองไว้ — วาง widget ทับตรงนั้นแล้วจะกดเปิดลิ้นชักไม่ได้

ทีมละหนึ่งบอร์ด — สลับกันเป็นคนพิมพ์ทุกช่วง อย่าให้ใครนั่งดูอย่างเดียวทั้งบทเรียน

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

ไฟล์ที่ 1 · 01_first_line.py — หนึ่งบรรทัด สามปลายทาง

say = ui.Label("กำลังพิมพ์ลงลิ้นชัก Console...", x=24, y=56,
               color=COL_ACCENT, value=24)
ui.poll()

lcd.clear()
lcd.print("สวัสดี บอร์ด PSoC Edge")
lcd.print("หนึ่งบวกหนึ่งได้", 1 + 1)

print("บรรทัดนี้อยู่บนคอม ไม่ได้อยู่บนจอบอร์ด")
1 · ป้ายบนหน้า Playground ui.Label + ui.poll() เห็นทันที ไม่ต้องกดอะไร 2 · ลิ้นชัก Console lcd.print() รออยู่จนกว่าจะแตะปุ่มมุมขวาล่าง 3 · คอนโซลฝั่งคอม print() ไม่ได้อยู่บนบอร์ดเลย โปรแกรมเดียว พูดสามช่อง ที่อยู่คนละที่กัน — ตามหาให้เจอครบทั้งสาม

lcd.print() รับหลายค่าคั่นด้วยจุลภาคเหมือน print() ทุกประการ และเติมช่องว่างให้เอง ตัวเลขส่งได้เลยไม่ต้องแปลงก่อน

ท้ายไฟล์มี say.text(...) — แก้ข้อความบนป้ายเดิม ไม่ใช่วางป้ายใหม่ทับ ป้ายหนึ่งใบที่เปลี่ยนค่าได้ อ่านง่ายกว่าป้ายสิบใบที่กองทับกัน

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

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

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

ไฟล์ที่ 2 · 02_markup_tags.py — สีคือระดับ ไม่ใช่ของตกแต่ง

<span class=muted> รุ่นเฟิร์มแวร์ 1.7.0 <span class=info> เริ่มรอบตรวจใหม่ <span class=ok> ต่อเน็ตสำเร็จ <span class=warn> แบตเตอรี่เหลือ 12% <span class=error> อ่านเซนเซอร์ไม่ได้ เรียงจากเรื่องที่ไม่ต้องสนใจ ไปหาเรื่องที่ต้องลุกจากเก้าอี้ ไฟล์นี้พิมพ์เนื้อความชุดเดียวกันสามรอบ ชุดที่ 1 ติดระดับครบ · ชุดที่ 2 ไม่ติดเลย ชุดที่ 3 ข่าวดีที่ทาสีแดง เปิดลิ้นชักแล้วเทียบสามชุดจากบนลงล่าง
for cls, text in REPORT:
    # แท็กเปิดกับแท็กปิดต้องอยู่ในการเรียกครั้งเดียวกันเสมอ
    lcd.print("<span class=" + cls + ">" + text + "</span>")

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

ฝั่งจอแกะแท็กทีละบรรทัด จบบรรทัดแล้วสถานะเริ่มใหม่หมด ลืมปิด </h2> ผลกระทบจำกัดอยู่ในบรรทัดนั้นบรรทัดเดียว

กับดัก: พิมพ์ชื่อคลาสผิด เช่น class=okay ไม่มี error ให้จับสักตัว บรรทัดนั้นแค่ออกมาเป็นสีปกติ — เจอบรรทัดที่ไม่มีสี ให้สงสัยชื่อคลาสก่อน

ตาคุณ: ใน REPORT มีบรรทัดหนึ่งติดระดับไว้ผิด หาให้เจอแล้วแก้ จากนั้นเพิ่มข่าวของทีมเองอีกหนึ่งบรรทัดพร้อมเลือกระดับให้มัน

เกณฑ์ตัดสินง่าย ๆ: บรรทัดนี้ทำให้คนที่เดินผ่านต้องลุกจากเก้าอี้ไหม ถ้าไม่ ก็ไม่ใช่ error

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

ไฟล์ที่ 3 · 03_byte_limit.py — เพดาน 127 ไบต์

ภาพถ่ายจอบอร์ด Eva Kit ขณะรัน 03_byte_limit.py ข้อความยาวถูกตัดที่เพดาน 127 ไบต์

ภาพหน้าจอจริงจากบอร์ด Eva Kit ขณะรัน 03_byte_limit.py — บันทึกโดยผู้สอน
nbytes = len(text.encode())      # นับไบต์ ไม่ใช่นับตัวอักษร
seg.text(str(nbytes))

if nbytes > LIMIT:
    seg.color(COL_BAD)
else:
    seg.color(COL_OK)

len() ของสตริงนับ ตัวอักษร ส่วน len() ของ bytes นับ ไบต์ สองค่านี้ไม่เท่ากันเมื่อเป็นภาษาไทย เพราะไทยหนึ่งตัวกิน 3 ไบต์

เพดานที่ต้องจำมีสามตัว: lcd.print() ส่งได้ 127 ไบต์ ต่อครั้ง · ui.Label(...) ตอนสร้างพาได้ 126 ไบต์ · .text() พาได้ 126 ไบต์ เท่ากัน

ไทยจึงได้ราว 42 ตัวอักษร ต่อครั้ง เกินแล้วถูกตัดทิ้ง เงียบ ๆ ไม่มี error และ \n ท้ายบรรทัดหายไปด้วย บรรทัดถัดไปจึงมาต่อท้ายกันเละ

ui.Seg7 รับได้ทั้ง .text() และ .value() — แต่ .value(198) ขึ้น 198 เท่านั้น ถ้าอยากได้ 198.0 หรือ 0198 ต้องส่งเป็นข้อความ

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

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

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

ไฟล์ที่ 4 · 04_console_drawer.py — บทสรุปที่ไม่ต้องเปิดลิ้นชัก

CHECKS = [
    ("จอแสดงผล", True),
    ("ปุ่มบนบอร์ด", True),
    ("หลอด LED", True),
    ("การ์ด SD", False),
]
...
for name, ok in CHECKS:
    if ok:
        passed = passed + 1
        ...
        lcd.print(name, "<span class=ok>ผ่าน</span>")
    else:
        ...
        lcd.print(name, "<span class=error>ไม่ผ่าน</span>")
    seg.text(str(passed) + "-" + str(TOTAL))
    bar.value(passed)
    ...
    ui.poll()

คนหน้างานไม่ได้อยากอ่านรายงานสิบบรรทัด เขาอยากรู้บรรทัดเดียวว่า ผ่านกี่ข้อจากกี่ข้อ แล้วค่อยเจาะเฉพาะข้อที่ตก · lcd.console() ใช้กับสิ่งที่ "จัดหน้า" เช่นหัวเรื่องกับเส้นคั่น ส่วน lcd.print() ใช้กับ "ข้อมูล" — ทั้งคู่ลงลิ้นชักเดียวกัน

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

ผ่านแล้ว 3-4 การ์ดสรุปที่เห็นได้โดยไม่ต้องกดอะไร ลิ้นชัก Console จอแสดงผล ผ่าน ปุ่มบนบอร์ด ผ่าน หลอด LED ผ่าน การ์ด SD ไม่ผ่าน การ์ดกับลิ้นชักต้องเล่าเรื่องเดียวกันเสมอ

ตาคุณ: แก้ CHECKS ให้ผ่านครบทุกข้อ ดูว่าการ์ดข้างบนเปลี่ยนไปยังไง แล้วเพิ่มรายการที่ห้า สังเกตว่าต้องแก้อะไรบ้างนอกจาก CHECKS (ใบ้: y ของแถวสุดท้ายยังอยู่ในจอ 398 พิกเซลหรือเปล่า)

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

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

ไฟล์ที่ 5 · 05_clear_and_refresh.py — เขียนทับที่เดิม

ภาพถ่ายจอบอร์ด Eva Kit ขณะรัน 05_clear_and_refresh.py ที่เขียนค่าทับที่เดิม

ภาพหน้าจอจริงจากบอร์ด Eva Kit ขณะรัน 05_clear_and_refresh.py — บันทึกโดยผู้สอน
for left in range(TOTAL, -1, -1):
    seg.text(str(left))          # ท่า widget: ทับค่าเดิม
    arc.value(left)

    lcd.clear()                  # ท่าลิ้นชัก: ล้างแล้ววาดใหม่ทั้งหน้า
    lcd.console("<h2>นับถอยหลัง</h2>")
    lcd.print("เหลืออีก", left, "วินาที")

    ui.poll()
    time.sleep_ms(TICK_MS)

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

ท่า widget ถูกกว่ามาก เพราะส่งข้ามคอร์ไปแค่ค่าใหม่ ไม่ได้ส่งทั้งหน้า

สร้าง widget ไว้นอกลูปเสมอ ถ้าย้ายสามบรรทัดนั้นเข้าไปในลูป จะได้ widget ใหม่ทุกวินาที

ห้ามสร้าง ui.Label ด้วยข้อความว่าง ระบบวาดจะเติมคำว่า Label ให้เอง แล้วคำนั้นค้างบนจอจนกว่าจะมีการเขียนทับครั้งแรก

ตาคุณ: ตั้ง TICK_MS = 150 แล้วขยาย TOTAL เป็น 20 รันดูแล้วตอบว่าท่าไหนอ่านออก ท่าไหนกลายเป็นจอกระพริบ

โปรแกรมที่ล้างจอเป็นสิ่งสุดท้ายก่อนจบ จะทิ้งจอว่างให้คนงงว่าเกิดอะไรขึ้น — ไฟล์นี้จึงปล่อยค่าสุดท้ายค้างไว้

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

ไฟล์ที่ 6 · 06_safe_print.py — ย้ายกฎเข้าไปอยู่ในฟังก์ชัน

ภาพถ่ายจอบอร์ด Eva Kit ขณะรัน 06_safe_print.py ที่ตัดข้อความยาวก่อนพิมพ์

ภาพหน้าจอจริงจากบอร์ด Eva Kit ขณะรัน 06_safe_print.py — บันทึกโดยผู้สอน
for ch in text:
    size = len(ch.encode())      # ไทย 3 · อังกฤษ 1

    # เช็ก "ก่อนใส่" ไม่ใช่ "หลังใส่"
    if used + size > SAFE_BYTES:
        flush(chunk)
        chunk = ""
        used = 0

    chunk = chunk + ch
    used = used + size

if chunk != "":                  # ก้อนสุดท้ายต้องส่งด้วย
    flush(chunk)

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

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

ไฟล์นี้เผื่อไว้ที่ SAFE_BYTES = 120 ต่ำกว่าเพดานจริง 127 อยู่ 7 ไบต์ เป็นระยะปลอดภัย

ข้อจำกัดที่ต้องรู้: ฟังก์ชันนี้ตัดที่ตัวอักษร ไม่ใช่ตัดที่คำ และมันไม่รู้จักแท็ก — ห้ามส่งข้อความที่มีแท็กเข้ามา เพราะแท็กอาจถูกตัดครึ่งกลางคัน

ตาคุณ: ลด SAFE_BYTES เหลือ 40 แล้วรันใหม่ จำนวนก้อนจะเพิ่มขึ้นแต่ตัวข้อความยังครบ ตอบว่าทำไมการแบ่งถี่ขึ้นถึงไม่ทำให้ตัวอักษรหายสักตัว

บรรทัด if chunk != "": ท้ายฟังก์ชันคือบรรทัดที่คนลืมบ่อยที่สุด ลืมเมื่อไรท้ายประโยคหายทันที

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

ไฟล์ที่ 7 · 07_ticks_and_beat.py — ลูปที่สั่ง sleep เท่าเดิม ไม่ได้เดินตรงเวลา

เส้นส้ม = คาบที่ขอไว้ 200 ms ท่าที่ 1 — หลับเท่าเดิมทุกรอบ เส้นเขียวลอยเหนือเส้นส้ม ช้าสะสมโตขึ้นเรื่อย ๆ ท่าที่ 2 — หักเวลางานออกก่อนหลับ เส้นเขียวทรุดลงมาทาบเส้นส้ม ช้าสะสมหยุดโต งานต่อรอบเท่าเดิมทุกอย่าง เปลี่ยนแค่วิธีคิดเวลาหลับ
work = time.ticks_diff(time.ticks_ms(), t_work)   # งานรอบนี้กินไปกี่ ms
left = TARGET_MS - work                           # เหลือให้หลับเท่าไร
if left > 0:
    time.sleep_ms(left)

sleep_ms() แปลว่า "หลับอย่างน้อยเท่านี้" ไม่ได้แปลว่า "รอบละเท่านี้" งานที่ทำก่อนหลับกินเวลาของมันเอง คาบจริงจึงยาวกว่าที่ขอเสมอ และส่วนเกินนั้น สะสมทุกรอบ

ห้ามลบเวลาสองค่าด้วยเครื่องหมายลบธรรมดา นาฬิกานี้นับขึ้นแล้ววนกลับ ticks_diff() รู้เรื่องการวน ส่วน t2 - t1 ไม่รู้ แล้วจะได้เลขติดลบมหาศาล

ui.Chart รับเฉพาะจำนวนเต็ม ช่วงแกนตั้งกำหนดตอนสร้างแล้วเปลี่ยนทีหลังไม่ได้ และมันเกิดมาพร้อมเส้นที่ 0 อยู่แล้ว add_series() จึงคืนเลข 1 เป็นเส้นแรกที่เราเพิ่ม — เก็บเลขที่มันคืนมาไว้ในตัวแปร อย่าเดาเอง

ตาคุณ: ตั้ง SWITCH_AT = 41 เพื่อปิดท่าที่ 2 ทิ้ง ทำนายก่อนรันว่าเลขช้าสะสมตอนจบจะออกมาราวเท่าไร แล้วรันเทียบ

นี่คือไฟล์แรกของชุดที่ไม่ได้แค่ให้ดู แต่ให้ ตั้งสมมติฐานแล้วรันพิสูจน์ — จดตัวเลขที่ทำนายไว้ก่อนกดรันเสมอ

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

ไฟล์ที่ 8 · 08_status_screen.py — จอบอกตอนนี้ ลิ้นชักบอกที่ผ่านมา

งานของจอ · ทำทุกรอบ ตอบว่า "ตอนนี้เป็นยังไง" Seg7 · Label · Bar · Chart จอไม่สะสมอะไรไว้ จึงเขียนทับได้ ถี่เท่าไรก็ได้ ไม่มีอะไรรก งานของลิ้นชัก · เฉพาะตอนเปลี่ยน ตอบว่า "ที่ผ่านมาเกิดอะไร" lcd.print เมื่อระดับเปลี่ยนเท่านั้น ลิ้นชักสะสม จึงต้องเลือกว่าจะเล่าอะไร ประวัติที่ไม่มีใครอ่านไหว = ไม่มีประวัติ คำถามต่างกัน โมดูลต่างกัน จังหวะการเขียนต่างกัน
    # --- งานของจอ: ทำทุกรอบ ---
    seg.text(str(value))
    seg.color(color)
    level_lbl.text(name)
    level_lbl.color(color)
    bar.value(value)
    chart.set_next(s_value, value)
    ...
    # --- งานของลิ้นชัก: ทำเฉพาะตอนมีเรื่องให้เล่า ---
    if lv != last_level:
        changes = changes + 1
        last_level = lv
        lcd.print("<span class=" + cls + ">" + str(elapsed) + " ms  " +
                  name + "  ค่า " + str(value) + "</span>")

ไฟล์นี้ไม่มีคำสั่งใหม่เลยสักตัว ทุกอย่างในนี้เคยผ่านตามาแล้วในไฟล์ 01 ถึง 07 ของใหม่คือ "จะเอามันมาต่อกันยังไง" ซึ่งเป็นคำถามที่ไฟล์เดี่ยว ๆ ไม่เคยตอบ

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

last_level = -1 แปลว่า "ยังไม่เคยรู้ระดับมาก่อน" รอบแรกจึงนับเป็นการเปลี่ยนเสมอ ถ้าตั้งต้นเป็น 0 ประวัติจะไม่มีบรรทัดแรกบอกว่าเริ่มที่ระดับไหน

ลูปในไฟล์นี้ใช้ท่าที่ 2 ของไฟล์ 07 ตรง ๆ — ของที่เรียนมาแล้วต้องกลับมาใช้ ไม่ใช่เรียนแล้วทิ้ง

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

ทำไมไฟล์ที่ 8 เป็นไฟล์ที่สำคัญที่สุดในชุด

lcd ui time จอสถานะหนึ่งใบ ที่คนหน้างานใช้ได้จริง บทเรียน 2.7–2.9 เสียบค่าจากลูกบิดเข้ามาแทน บทเรียน 3.1–3.3 เสียบค่าจากเซนเซอร์เอียง บทเรียน 3.7–3.9 กลายเป็นแดชบอร์ดสี่การ์ด โครงเดิม เปลี่ยนแค่ต้นทางของตัวเลข

มีงานวิจัยที่ไปสัมภาษณ์นักพัฒนามืออาชีพ 80 คนว่า ตัวอย่างโค้ดที่หาเจอบนเน็ตทำให้หงุดหงิดตรงไหนที่สุด คำตอบอันดับหนึ่งไม่ใช่ "โค้ดผิด" และไม่ใช่ "อธิบายน้อยไป" แต่คือ ตัวอย่างพวกนั้นไม่ช่วยให้คิดออกว่าจะเอาชิ้นส่วนมาต่อกันยังไง

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

reading_at() ในไฟล์นั้นเป็นค่าอ่านจำลอง บทเรียน 2.7–2.9 เป็นต้นไปจะถอดฟังก์ชันนี้ทิ้งแล้วเสียบค่าจากเซนเซอร์จริงเข้ามาแทน ที่ยังจำลองไว้ก่อน เพราะชุดบทเรียนนี้เรากำลังเรียนเรื่องการรายงานผล ไม่ใช่การวัด

ตาคุณ: ย้ายบล็อก lcd.print() ออกจาก if ให้มันยิงทุกรอบ แล้วรันใหม่ เปิดลิ้นชักดู แล้วตอบว่าประวัติแบบไหนที่คนเดินมาดูหน้างานใช้งานได้จริงกว่ากัน (ใบ้: ลองหาคำตอบจากลิ้นชักว่า "ค่าขึ้นถึงระดับต้องรีบดูตอนวินาทีที่เท่าไร")

ถ้าจะเลือกอ่านซ้ำนอกเวลาแค่ไฟล์เดียวจากทั้งเก้าไฟล์ ให้เลือกไฟล์นี้

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

ไฟล์ที่ 9 · 09_your_level_rule.py — รันได้ แต่ยังตอบผิดทุกข้อ

ผลตรวจกฎของคุณ — หกแถว หกค่า 12 ควรเป็น ปกติ ได้ ปกติ 49 ควรเป็น ปกติ ได้ ปกติ 50 ควรเป็น เริ่มสูง ได้ ปกติ 79 ควรเป็น เริ่มสูง ได้ ปกติ 80 ควรเป็น ต้องรีบดู ได้ ปกติ 92 ควรเป็น ต้องรีบดู ได้ ปกติ รันครั้งแรกได้เขียว 2 แดง 4 — นั่นถูกแล้ว บอร์ดเป็นคนตรวจให้ ไม่ต้องรอผู้สอนเดินมาถึงโต๊ะ
CASES = (
    (12, "ปกติ"),
    (49, "ปกติ"),
    (50, "เริ่มสูง"),
    (79, "เริ่มสูง"),
    (80, "ต้องรีบดู"),
    (92, "ต้องรีบดู"),
)
...
# ----- เติมส่วนนี้เอง (งานของคุณ) -----
def level_name(v):
    ...                    # docstring ในไฟล์บอกโจทย์และใบ้วิธีเรียง if ไว้แล้ว
    return "ปกติ"          # ตอนนี้ตอบ "ปกติ" ทุกค่า จึงผ่านแค่สองข้อแรก
# ----- จบส่วนที่ต้องเติม -----

ไฟล์นี้เป็นคู่ฝึกของไฟล์ 08 ในนั้นกฎตัดระดับเขียนไว้ให้แล้ว ส่วนในนี้ยังว่าง และลอกจาก 08 มาตรง ๆ ไม่ได้ เพราะโจทย์คนละเจ้าใช้เส้นคนละที่

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

CASES เลือกไว้ให้มีค่าที่อยู่ ตรงเส้นพอดี (49, 50, 79, 80) เพราะนั่นคือจุดที่กฎผิดกันบ่อยที่สุด >= กับ > ต่างกันแค่ตัวเดียว

กับดักที่ต้องระวัง: ลำดับของ if สำคัญกว่าที่คิด ถ้าเช็กเงื่อนไข 50 ก่อน 80 ค่า 92 จะตกลงช่องกลางแล้วไม่มีวันไปถึงช่องบนเลย และไม่มี error ให้จับสักตัว

ตาคุณ (หลังผ่านครบหกข้อ): เพิ่มลงใน CASES อีกหนึ่งแถวที่คุณคิดว่ากฎของตัวเองน่าจะตก แล้วรันดูว่าตกจริงไหม ถ้าไม่ตก แปลว่ากฎแข็งกว่าที่คิด

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

เก้าไฟล์เรียงแบบนี้เพราะอะไร

01-06 · เราทำให้ดูจนจบ อ่านแล้วรัน แล้วแก้ตามที่ท้ายไฟล์ชวน 07 · มีการทดลอง ทำนายก่อน แล้วรันพิสูจน์ 08 · ประกอบ ไม่มีคำสั่งใหม่ 09 · คุณเขียนเอง บอร์ดตรวจให้ ความช่วยเหลือลดลงทีละขั้น จนขั้นสุดท้ายไม่มีเหลือ ไฟล์แต่ละไฟล์เพิ่มของใหม่ไม่เกินสามอย่าง — ที่เหลือคือของเดิมที่เอามาใช้ซ้ำ

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

ลำดับ · เวลา ไฟล์ ลงมือทำอะไร แล้วจะเข้าใจอะไร
1 · 10 นาที 01_first_line.py ตามหาข้อความให้เจอทั้งสามปลายทาง · จะรู้ว่าจอเงียบมักแปลว่าเรายืนผิดหน้า ไม่ใช่โค้ดพัง
2 · 10 นาที 02_markup_tags.py เทียบรายงานสามชุดในลิ้นชัก · จะเข้าใจว่าสีคือระดับความสำคัญ ไม่ใช่ของตกแต่ง
3 · 15 นาที 03_byte_limit.py พิมพ์ชื่อไทยเข้าไปแล้วดูว่าตัวเลขไบต์เปลี่ยนเป็นแดงตอนไหน · จะรู้ว่าจอนับไบต์ ไม่ได้นับตัวอักษร
4 · 15 นาที 08_status_screen.py ประกอบสามโมดูลเป็นจอเดียว · ไฟล์ที่ควรอ่านซ้ำที่สุดในชุด
5 · 20 นาที 09_your_level_rule.py เขียนกฎเอง แล้วให้บอร์ดตรวจจนหกแถวเขียวหมด

ติดตรงไหน เปิดอันนี้

อาการที่เจอ ไฟล์ที่ตอบอาการนั้น
ข้อความของทีมขาดหายท้ายบรรทัด ทั้งที่ไม่มี error ให้จับสักตัว 06_safe_print.py
รายงานยาวจนคนดูตอบไม่ได้ว่าตกลงผ่านหรือไม่ผ่าน 04_console_drawer.py
ค่าที่เปลี่ยนทุกวินาทีไหลลงจนเต็มจอ อ่านไม่ทัน 05_clear_and_refresh.py
ลูปเดินช้ากว่าที่สั่งไว้ และยิ่งนานยิ่งเพี้ยน 07_ticks_and_beat.py

ทุกไฟล์จบด้วยบล็อก ตาคุณ — อ่านแล้วรันแล้วยังไม่จบ ต้องแก้แล้วรันซ้ำถึงจะจบ

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

ไฟล์นี้ไม่มีเฉลยแยกให้ เพราะจอบนบอร์ดคือเฉลย — แก้แล้วรันใหม่ได้เรื่อย ๆ จนหกแถวเขียวหมด