คาบ 1 — AIoT รอบตัวเรา

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

คาถาประจำคาบ: เล่นของจริงให้เห็นภาพก่อน แล้วค่อยถามว่ามันทำงานยังไง

ดูของจริงก่อน — บอร์ดนี้ทำอะไรได้บ้าง

Eva Kit จอสัมผัส 4.3 นิ้ว IMU เอียง/เขย่า เข็มทิศ ปุ่มสัมผัส + ลูกบิด ไมโครโฟน WiFi + Bluetooth เทอร์มิสเตอร์วัดอุณหภูมิ LED + ปุ่มผู้ใช้ ช่องเสียบ SD card

วันนี้ยังไม่ต้องเขียนโปรแกรมอะไรยาว ๆ เราจะ เล่นก่อน และเราจะเริ่มเล่นตั้งแต่สไลด์ถัดไป ไม่ใช่ตอนกลางคาบ

บอร์ดที่อยู่ตรงหน้าน้อง ๆ ตอนนี้ ข้างในมีของครบทุกอย่างที่ระบบ AIoT จริงต้องมี — เซนเซอร์วัดการเคลื่อนไหว เข็มทิศ ปุ่มสัมผัส หน้าจอ ไมโครโฟน และวิทยุ WiFi

คอร์สนี้ใช้บอร์ดสองรุ่น — PSoC Edge Eval Kit (Eva Kit) และ TESAIoT Dev Kit — ภาพร่างข้างบนวาดจาก Eva Kit ส่วน Dev Kit มีของชุดเดียวกันนี้ครบ และเพิ่มเซนเซอร์อุณหภูมิ/ความชื้น ความกดอากาศ เรดาร์ ลูกบิดสี่ตัว และ RGB dot matrix เข้ามา โค้ดที่เขียนในคอร์สนี้รันได้ทั้งสองบอร์ด ที่ต่างกันจะบอกไว้ตรงจุด

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

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

เป้าหมายของคาบนี้

  1. เล่นเมนูหลักบนบอร์ดให้ครบ แล้วบอกได้ว่า เมนูไหนใช้เซนเซอร์ตัวไหน
  2. รู้จักสมองสองก้อนของบอร์ด (CM33 กับ CM55) ว่าใครทำหน้าที่อะไร
  3. ใช้โมดูล lcd กับ ui ส่งข้อความและตัวเลขของทีมขึ้นจอได้
  4. ต่อบอร์ดเข้ากับ BENTO IDE แล้ว รันไฟล์ตัวอย่างของคาบนี้ได้ครบ

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

คาบนี้วัดกันที่ "เล่นเป็น + อธิบายได้" ไม่ใช่ "เขียนโค้ดยาว" — เปิดบอร์ดได้เลยตั้งแต่สไลด์หน้า

รอบที่ 1 — เล่นหน้า Home ให้ทั่ว

Sensor Live IMU x +0.02 Comp 137° Touch -- Pot 48% เอียงบอร์ด → แถว IMU วิ่ง หมุนบอร์ด → เข็มทิศเปลี่ยน แตะปุ่มสัมผัส → Touch เปลี่ยน หมุนลูกบิด → Pot เปลี่ยน ยังไม่มีโค้ดของเรา แต่ค่าวิ่งอยู่แล้ว

เปิดบอร์ด รอจอติด แล้วอยู่ที่หน้า Home ก่อน อย่าเพิ่งกดเข้าเมนูไหน

ลองทีละอย่างแล้วสังเกตแผง Sensor Live ทางขวาของจอ:

  1. เอียงบอร์ดไปทางซ้าย-ขวา แล้วดูแถว IMU — ตัวเลขวิ่งตามไหม
  2. หมุนบอร์ดรอบตัวเอง แล้วดูแถว Comp (เข็มทิศ) — ค่าองศาเปลี่ยนไหม
  3. แตะแผ่นสัมผัส CapSense แล้วดูแถว Touch (Eva Kit: ปุ่มสัมผัสสองปุ่มด้านขวาของบอร์ด · Dev Kit: ดูที่บอร์ดของทีมว่าแผ่นสัมผัสอยู่ตรงไหน)
  4. ลากนิ้วบนแถบเลื่อน (Eva Kit: ใต้ปุ่มสัมผัส · Dev Kit: ถ้าบอร์ดของทีมมี) — ค่าเปลี่ยนจาก 0 ถึง 100 ไหม
  5. หมุนลูกบิด แล้วดูแถว Pot (Eva Kit: ลูกบิดสีน้ำเงินตัวเดียว · Dev Kit: VR1 — ลูกบิดตัวอื่นไม่ขึ้นแถวนี้)

ภาพถ่ายจอจริงของบอร์ด Eva Kit หน้า Home ในวินาทีที่เพิ่งเปิดเครื่อง — บันทึกโดยผู้สอน · ตัวอักษร "Welcome" ยังลากไม่จบ กำลังเขียนอยู่ตอนกดชัตเตอร์ · แผง Sensor Live อยู่ มุมขวาบน ไม่ใช่ซ้ายอย่างในภาพร่าง และตอนนี้ยังเป็นขีดทั้งสี่แถว เพราะเซนเซอร์ยังไม่ส่งค่ารอบแรกกลับมา — ทำตามห้าข้อทางซ้ายแล้วขีดจะกลายเป็นตัวเลข · บน Dev Kit แผงนี้มีหกแถว เพิ่ม Temp กับ Humid จากเซนเซอร์ที่ Eva Kit ไม่มี · มุมขวาบนสุดยังไม่มีไอคอน WiFi และไม่มีนาฬิกา เพราะคาบนี้ยังไม่ได้ต่อเน็ต

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

รอบที่ 2 — สามเมนูที่ต้องเล่นให้ครบ

Controls (Eva Kit) แตะจอ ไฟจริงติด Sensor Dashboard กราฟ + เข็มทิศ Smart Watch ปัดเปลี่ยน 6 หน้า

Controls (Eva Kit เท่านั้น) — แตะวงกลมสีบนจอ แล้วมอง หลอด LED จริงบนบอร์ด ว่าติดตาม
นี่คือครั้งแรกที่น้อง ๆ เห็นว่า "แตะกระจก" ทำให้ "ของจริงเปลี่ยนสถานะ" ได้ (คาบ 3 กับ 4 เราจะสร้างหน้านี้เอง) · Dev Kit ไม่มีการ์ด Controls — ทีมที่ถือ Dev Kit ให้ข้ามไปเมนูถัดไปก่อน แล้วดูไฟจริงติดจากโค้ดตอนรัน examples/s01/11_lights_and_a_button.py↗ ในครึ่งหลังของคาบแทน

Sensor Dashboard — เขย่าบอร์ดเบา ๆ แล้วดูกราฟทางซ้ายกระเพื่อม เข็มทิศทางขวาหมุนตามการหันบอร์ด
(คาบ 7 กับ 8 เราจะสร้างกราฟและเข็มทิศแบบนี้เอง)

Smart Watch — ปัดนิ้วซ้าย-ขวาเพื่อเปลี่ยนหน้า มีทั้งหน้านาฬิกา สุขภาพ เพลง อากาศ
ตัวนับก้าวคำนวณจากเซนเซอร์ความเร่งตัวเดียวกับที่เราจะใช้ในคาบ 6 · เวลาบนหน้าปัดยังไม่ตรง เพราะบอร์ดตั้งนาฬิกาจากอินเทอร์เน็ตเท่านั้น และยังไม่ได้ต่อ WiFi (คาบหน้าต่อแล้วจะตรงเอง)

แผงหน้าปัดรถยนต์จริง เกจกลม สวิตช์ และวิทยุอยู่ครบในหน้าเดียว — คือสิ่งที่เมนู Sensor Dashboard บนบอร์ดกำลังเลียนแบบ ระหว่างเล่นให้เทียบดูว่า พอย้ายทุกอย่างขึ้นจอเดียวแล้ว อะไรหายไป จากการหมุนลูกบิดจริงบ้าง (ภาพ: Eric Friedebach, Wikimedia Commons, CC BY 2.0)

เล่นแล้วจดลง worksheet ทันที — ความจำจากการเล่นหายเร็วกว่าที่คิด

รอบที่ 3 — เมนูที่ต้องรู้ว่ามีอยู่

Audio Player Eva Kit · No SD Card Wi-Fi Setting คาบ 2 ค่อยต่อ Playground ประตูของเราทุกคาบ TESAIoT Connect Eva Kit · คาบ 10-11

Audio Player (Eva Kit เท่านั้น — Dev Kit ไม่มีการ์ดนี้) — เครื่องเล่นเพลงจริงจาก SD card
ถ้าไม่ได้เสียบ SD card หน้านี้จะขึ้น Now Playing: No SD Card และบรรทัด สีแดง SD Card failed: step 2 (InitCard) — นี่ไม่ใช่บอร์ดเสีย ปุ่มเล่นและแถบเสียงยังอยู่ครบ ขาดแค่รายชื่อไฟล์

Wi-Fi Setting — สแกนหาเครือข่าย เลือก แล้วใส่รหัส
คาบนี้ยังไม่ต้องต่อ (คาบ 2 จะต่อด้วยโค้ดของเราเอง) แต่ให้กดเข้าไปดูว่าหน้าตาเป็นยังไง — เมนูนี้มีทั้งสองบอร์ด และอยู่ในเกณฑ์ผ่านของคาบ

BENTO Playground — หน้าที่สำคัญที่สุดสำหรับเรา
ทุกครั้งที่เราส่งโค้ดจากคอมมาที่บอร์ด ผลลัพธ์จะมาโผล่ที่หน้านี้ ถ้าไม่เปิดหน้านี้ค้างไว้ จะงงว่าทำไมส่งโค้ดแล้วไม่เห็นอะไร

TESAIoT Connectivity (Eva Kit เท่านั้น — Dev Kit ไม่มีการ์ดนี้) — แดชบอร์ดสถานะการเชื่อมต่อกับแพลตฟอร์ม IoT
ปลายทางของคาบ 10-11 อยู่ตรงนี้ กดเข้าไปดูไว้ก่อนได้ว่าเราจะไปจบที่ไหน — ทีมที่ถือ Dev Kit จะได้เห็นปลายทางเดียวกันผ่านโค้ดของตัวเองในคาบ 10-11

เมนู Playground คือประตูที่เราจะเดินผ่านทุกคาบตั้งแต่วันนี้เป็นต้นไป

ปลายทางของคาบนี้ — จอที่เราจะได้ตอนปิดคาบ

หน้าจอจริงจากการรัน examples/s01/15_one_number_many_faces.py↗ บน BENTO Emulator ที่ 800x480 เท่าจอของทั้งสองบอร์ด — ไม่ใช่ภาพวาด ไม่ใช่ mock-up · ภาพนี้เก่า ถ่ายตอนไฟล์ยังพิมพ์ว่า "จาก 32" ปัจจุบันไฟล์พิมพ์ "จาก 64" แล้ว รอถ่ายใหม่
  • เลขตัวเดียว 76 ขับทุกอย่างบนจอพร้อมกัน — Arc, Compass, Seg7, Bar, Slider, Switch, Checkbox, Chart, Spinner
  • มุมล่างเขียนว่า ui.list() นับได้ 24 ตัว จาก 64 — บอร์ดนับ widget ของตัวเองได้ · 64 คือเพดานของเฟิร์มแวร์ ส่วนงบที่คอร์สตั้งให้ตัวเองคือ 32 ต่อหน้า (คาบ 4 อธิบายว่าทำไม)
  • ปุ่ม "แตะฟังเสียง" มีจริง กดแล้วดัง — บน Eva Kit ดังจากลำโพงบนบอร์ด (Dev Kit: ฟังที่บอร์ดของทีม)

ทั้งหมดนี้เขียนด้วย Python บนบอร์ด ไม่มี LVGL ไม่มี C สักบรรทัด — และวันนี้น้อง ๆ จะได้เขียนเอง

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

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

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

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

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

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

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

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 ขึ้นบนหน้าจอตรง ๆ — สามที่ คนละที่กัน

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

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 รอคาบ 4) · บรรทัดพวกนี้ตัดมาจาก examples/s01/01_first_line.py↗ ตรง ๆ — COL_ACCENT คือสีเน้น 0x4A9EFF ที่หัวไฟล์ประกาศไว้

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

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

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

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

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

ไฟล์ที่ 1 · examples/s01/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(...) — แก้ข้อความบนป้ายเดิม ไม่ใช่วางป้ายใหม่ทับ ป้ายหนึ่งใบที่เปลี่ยนค่าได้ อ่านง่ายกว่าป้ายสิบใบที่กองทับกัน

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

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

ไฟล์ที่ 2 · examples/s01/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

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

ภาพหน้าจอจริงจากบอร์ด Eva Kit ขณะรัน examples/s01/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 อีกหนึ่งแถว ทำนายก่อนรัน ว่าจะได้กี่ไบต์และจะเป็นเขียวหรือแดง แล้วรันเทียบกับที่ทำนาย

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

ไฟล์ที่ 4 · examples/s01/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 พิกเซลหรือเปล่า)

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

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

ภาพหน้าจอจริงจากบอร์ด Eva Kit ขณะรัน examples/s01/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 รันดูแล้วตอบว่าท่าไหนอ่านออก ท่าไหนกลายเป็นจอกระพริบ

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

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

ภาพหน้าจอจริงจากบอร์ด Eva Kit ขณะรัน examples/s01/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 != "": ท้ายฟังก์ชันคือบรรทัดที่คนลืมบ่อยที่สุด ลืมเมื่อไรท้ายประโยคหายทันที

ไฟล์ที่ 7 · examples/s01/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 ทิ้ง ทำนายก่อนรันว่าเลขช้าสะสมตอนจบจะออกมาราวเท่าไร แล้วรันเทียบ

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

ไฟล์ที่ 8 · examples/s01/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 ตรง ๆ — ของที่เรียนมาแล้วต้องกลับมาใช้ ไม่ใช่เรียนแล้วทิ้ง

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

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

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

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

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

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

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

ไฟล์ที่ 9 · examples/s01/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 อีกหนึ่งแถวที่คุณคิดว่ากฎของตัวเองน่าจะตก แล้วรันดูว่าตกจริงไหม ถ้าไม่ตก แปลว่ากฎแข็งกว่าที่คิด

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

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

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

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

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

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

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

ครึ่งหลัง — เปิดกล่องให้เห็นทั้งใบ

เก้าไฟล์ที่ผ่านมาใช้ lcd time ui สามตัว ซึ่งเป็นแค่สามในสิบเอ็ดโมดูลที่บอร์ดนี้มี

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

ไฟล์ เปิดโมดูล ชื่อที่จะได้เห็นทำงานจริง
examples/s01/10_board_knows_itself.py↗ gpio · machine gpio.board_info() gpio.led() gpio.button() — และพิสูจน์ว่า machine.PWM กับ machine.ADC ไม่มีอยู่จริงในพอร์ตนี้
examples/s01/11_lights_and_a_button.py↗ gpio gpio.num_leds() gpio.num_buttons() · เมธอดของหลอด on() off() toggle()
examples/s01/12_every_sense_at_once.py↗ sensors sensors.snapshot() คืนทุกเซนเซอร์ในครั้งเดียว (ทั้งสองบอร์ด) · และ init() scan() ที่ Eva Kit ปฏิเสธ พร้อมบอกเหตุผล ส่วน Dev Kit ยอมให้เรียก — ไฟล์พิมพ์คำตอบของบอร์ดตรงหน้า ไม่ได้เดาให้
examples/s01/13_raw_and_filtered.py↗ dsp dsp.EMA dsp.Median dsp.tilt — ค่าดิบกับค่ากรอง วาดทับกันบนกราฟเดียว
examples/s01/14_the_board_hears_you.py↗ mic mic.start() mic.level() mic.stats() mic.lag() — และหน้าจอ DotMatrix
examples/s01/15_one_number_many_faces.py↗ ui เต็มรูปแบบ เลขตัวเดียวขับ เก้า widget พร้อมกัน — Arc Bar Chart Seg7 Slider Switch Checkbox Spinner Compass · บวก ui.sfx() ui.tone()

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

ไฟล์ examples/s01/12_every_sense_at_once.py↗ กับ examples/s01/15_one_number_many_faces.py↗ คือสองไฟล์ที่ต้องได้เล่นแน่ ๆ ถ้าเวลาไม่พอ — ตัวแรกแสดงว่าบอร์ดรับรู้โลกได้กี่ทาง ตัวหลังแสดงว่าเลขหนึ่งตัวเล่าเรื่องได้กี่แบบ

ข้อมูลไหลไปทางไหน — ตั้งแต่คีย์บอร์ดถึงจอ

BENTO IDE เราพิมพ์โค้ด CM33 MicroPython รัน lcd.print() IPC กล่องจดหมาย CM55 วาดตัวอักษรจริง จอ 4.3" เราเห็นผล โค้ดหนึ่งบรรทัดของเรา เดินทางผ่านห้าจุดกว่าจะเป็นตัวอักษรบนจอ ถ้าไม่เห็นข้อความ ให้ไล่หาว่าขาดตอนที่จุดไหน — เกือบทุกครั้งคือ "ยังไม่ได้เปิดหน้า Playground"

เข้าใจเส้นทางนี้แล้ว การดีบักจะเลิกเป็นการเดา — และเข้าใจด้วยว่าทำไม ui.poll() ถึงจำเป็น มันคือจังหวะที่เราเปิดกล่องจดหมายให้อีกฝั่งหยิบของไป

เข้าใจฮาร์ดแวร์ · ทำไมบอร์ดนี้ต้องมีสมองสองก้อน

ในชิปตัวเดียวกันมีซีพียูสองตัวที่ทำงานคนละแบบ

CM33 — สมองฝั่งงานระบบ รัน MicroPython (โค้ดที่เราเขียน) คุม WiFi · MQTT · ไฟล์ อ่านเซนเซอร์บางตัว เป็นคอร์ที่รับคำสั่งจากเรา CM55 — สมองฝั่งงานหนัก วาดทุกอย่างบนจอ อ่านแผ่นสัมผัส/ลูกบิดให้ Home Eva: เจ้าของบัส อ่าน IMU ด้วย เป็นคอร์ที่ไม่เคยคุยกับเราตรง ๆ คำสั่ง ค่าที่วัดได้ IPC — กล่องจดหมายระหว่างสองคอร์

ใครอ่านเซนเซอร์ตัวไหน ต่างกันระหว่างสองบอร์ด: บน Eva Kit CM55 เป็นเจ้าของบัสเซนเซอร์ทั้งหมด (IMU แผ่นสัมผัส ลูกบิด) Python จึงต้องขอค่าผ่าน IPC และ sensors.init() ถูกปฏิเสธ · บน Dev Kit CM33 อ่าน IMU/เข็มทิศเองได้ (sensors.init() ทำงาน) ส่วนแผ่นสัมผัสกับลูกบิดยังอ่านผ่าน CM55 เหมือนกัน — sensors.snapshot() ซ่อนความต่างนี้ให้ คืน dict รูปเดียวกันทั้งสองบอร์ด

โค้ด Python ของเราอยู่ฝั่ง CM33 เสมอ อยากให้อะไรขึ้นจอ ต้อง ฝากข้อความข้ามไปให้ CM55 วาด

AIoT คืออะไร — ตัดสินใจใกล้จุดเกิดเหตุ

IoT = อุปกรณ์มีเซนเซอร์ + ต่อเน็ต ส่งข้อมูลขึ้นระบบกลาง
AIoT = ย้าย การตัดสินใจ ลงมาไว้ที่ตัวอุปกรณ์เอง ไม่ต้องรอถามคลาวด์ทุกครั้ง

แบบ IoT — ส่งทุกอย่างขึ้นคลาวด์แล้วรอคำตอบ เซนเซอร์ ไม่มีสมองในตัว คลาวด์ ตัดสินใจแทน · ตอบช้า จุดแดง = ข้อมูลดิบหนึ่งชุด ส่งทุกวินาที แบบ AIoT — ตัดสินใจตรงจุดเกิดเหตุ เซนเซอร์ + สมอง รู้ผลทันทีในตัวเอง คลาวด์ รับแต่สรุปที่สำคัญ จุดเขียว = ส่งเฉพาะตอนที่ผิดปกติ

ตัวอย่างที่จับต้องได้: เครื่องจักรตัวหนึ่งสั่นผิดปกติ

  • แบบ IoT: ส่งค่าความสั่นทุกวินาทีขึ้นคลาวด์ ให้คลาวด์ตัดสิน — เปลืองเน็ต ช้า และถ้าเน็ตหลุดคือตาบอด
  • แบบ AIoT: บอร์ดตัดสินเองที่หน้างานภายในเสี้ยววินาที ส่งขึ้นคลาวด์เฉพาะตอน "ผิดปกติ" — ประหยัด เร็ว และเน็ตหลุดก็ยังทำงาน

เทอร์โมสตัทสัมผัสในบ้านจริงตัวนี้ทำครบวงในกล่องเดียว — วัดอุณหภูมิ ตัดสินว่าร้อนไปหรือเย็นไป แล้วสั่งคอมเพรสเซอร์ทำงาน ลองชี้ให้ได้ว่าสามหน้าที่นั้นอยู่ตรงไหนของกล่องนี้

และนี่คือรูปร่างของไฟล์ที่ 8 กับ 9 ที่เพิ่งรันไปเป๊ะ ๆ — วัดค่า ตัดระดับ แล้วรายงาน

ภาพ: Flarn2006, Wikimedia Commons, CC BY-SA 3.0

ใช้จริงที่ไหน — สามอุตสาหกรรมที่ของแบบนี้ทำงานอยู่จริง

โรงงาน ความสั่นเปลี่ยน ก่อนพังจริง 2-3 สัปดาห์ อาคาร นับคนจริงในห้อง แล้วสั่งแอร์ตามนั้น สุขภาพ แยก "นั่งลงเร็ว" ออกจาก "ล้ม" ให้ได้

โรงงาน — Predictive Maintenance
มอเตอร์ปั๊มน้ำมีเซนเซอร์ความสั่นติดอยู่ ระบบบนตัวมันรู้ว่า "เสียงสั่นปกติ" หน้าตาแบบไหน พอลูกปืนเริ่มสึก รูปแบบการสั่นเปลี่ยนก่อนพัง 2-3 สัปดาห์ ระบบแจ้งซ่อมล่วงหน้า แทนที่จะรอสายพานหยุดกลางกะ

อาคาร — Smart Building
เซนเซอร์ในห้องประชุมนับคนจากความเคลื่อนไหวและเสียง แล้วสั่งแอร์ให้แรงตามจำนวนคนจริง ไม่ใช่ตั้งไว้ 22 องศาทั้งวัน ค่าไฟลดได้ 20-30% โดยไม่มีใครต้องกดสวิตช์

สุขภาพ — Fall Detection
อุปกรณ์ติดตัวผู้สูงอายุ แยกให้ออกระหว่าง "นั่งลงเร็ว" กับ "ล้ม" — สองอย่างนี้กราฟความเร่งคล้ายกันมาก ค่าเดียวที่จุดเดียวแยกไม่ออก ต้องดูรูปร่างของสัญญาณทั้งช่วง

ภาพ: MicroSYST GmbH, Wikimedia Commons, CC BY-SA 4.0 — เสาไฟสถานะบนสายการผลิตจริง "แจ้งสถานะ" ในโรงงานคือไฟไม่กี่ดวงที่อ่านได้จากอีกฝั่งโรงงาน ไม่ใช่หน้าจอสวย ๆ

ภาพ: RandomKatze, Wikimedia Commons, CC0 1.0 — ป้ายวัดระดับเสียงในสนามกีฬาจริง เซนเซอร์ตัวเดียวถูกติดตั้งถาวรเพื่อเฝ้าค่าเดียว ไม่ใช่การทดลองบนโต๊ะ

ภาพ: NASA / Helen Arase Vargas, Wikimedia Commons, สาธารณสมบัติ — เครื่อง actigraphy ที่นักบินอวกาศใส่จริง เล็กได้ขนาดนี้เพราะประมวลผลอยู่ในตัวมันเอง

ทั้งสามเคสนี้ ตัวเซนเซอร์กับตัวตัดสินใจอยู่ที่เดียวกัน — นั่นแหละ AIoT

บอร์ดของเรา: มีอะไรอยู่บ้าง — Eva Kit กับ Dev Kit ต่างกันตรงไหน

ภาพ: KIT_PSE84_EVAL PSOC™ Edge E84 Evaluation Kit guide, Infineon 002-39007 Rev.*B, รูปที่ 2 (หน้า 9) — ใช้เพื่อการเรียนการสอน · ภาพนี้คือ Eva Kit — ทีมที่ถือ TESAIoT Dev Kit ให้ดูของจริงบนโต๊ะประกอบตาราง (ภาพ Dev Kit จะเพิ่มเมื่อถ่ายจริง)
ของบนบอร์ด มันวัด/ทำอะไร เจอในเมนูไหน · ใช้เองคาบไหน
BMI270 ความเร่ง 3 แกน + การหมุน 3 แกน (ทั้งสองบอร์ด) Home, Dashboard, Smart Watch · เราใช้เองคาบ 6-7
BMM350 สนามแม่เหล็กโลก → ทิศเหนือ (ทั้งสองบอร์ด) Dashboard (เข็มทิศ) · คาบ 8
CapSense แผ่นสัมผัส — Eva: ปุ่มสัมผัส 2 ปุ่ม + แถบเลื่อน (5 อิเล็กโทรด อ่านเป็นค่าเดียว 0–100) · Dev Kit: ดูตำแหน่งที่บอร์ดของทีม Home (Eva: Controls ด้วย) · คาบ 5
Potentiometer ลูกบิดหมุน → ค่าอนาล็อก — Eva: ลูกบิดสีน้ำเงิน 1 ตัว · Dev Kit: VR1–VR4 (sensors.pot อ่าน VR1) Home (Eva: Controls ด้วย) · คาบ 5
LED + ปุ่มผู้ใช้ LED — Eva 3 ดวง · Dev Kit 5 ดวง (ถาม gpio.num_leds() อย่าจำเลข) · ปุ่มที่ Python ใช้ได้ 1 ปุ่ม เรียกด้วยชื่อจาก gpio.button(0).name() ไม่ใช่ป้ายบนแผ่นวงจร Eva: Controls · คาบ 3
ไมโครโฟน PDM เสียงรอบตัว ยังไม่มีเมนูของตัวเอง · คาบ 8 ใช้ผ่านโมดูล mic
WiFi/BT (Eva: CYW55513IUBG) Wi-Fi + Bluetooth ใช้วิทยุร่วมกัน (Eva: Wi-Fi 6 2.4/5 GHz + Bluetooth 5.4) Wi-Fi Setting · คาบหน้าเราต่อเอง
จอสัมผัส 4.3" หน้าต่างของทุกอย่าง — พื้นที่วาด 792×398 เท่ากันทั้งสองบอร์ด ทุกเมนู · วันนี้เลย
เฉพาะ Dev Kit SHT40 (อุณหภูมิ/ความชื้น) · DPS368 (ความกดอากาศ) · เรดาร์ · RGB dot matrix · CAN · ปุ่มเสริมสองปุ่ม (import buttons) Home (แถว Temp / Humid) · คาบ 9–12 ใช้ SHT40 เมื่อบอร์ดมี

จำตารางนี้ไว้ — คอลัมน์ขวาคือแผนที่ของทั้งเทอม เราจะไล่หยิบของในตารางนี้มาสั่งงานเองทีละตัว · ตัวเลขที่ต่างกันระหว่างสองบอร์ด (จำนวน LED, จำนวนลูกบิด) ให้ถามบอร์ดด้วยโค้ดเสมอ ไม่ต้องจำ

ก่อนเริ่มคาบ — ผู้สอนตรวจให้ครบ

บอร์ด + สายไฟ จอติด เห็นหน้า Home BENTO IDE ติดตั้งบนคอมทุกกลุ่ม แถว Touch ตอบไหม ทั้งสองบอร์ด: flash ชิปก่อน SD card (Eva) ถ้าจะเล่น Audio Player ชิป CapSense ที่ยังไม่ถูก flash ทำให้แถว Touch ขึ้น -- ตลอดกาล ไม่ว่าบอร์ดไหน ผู้เรียนจะเข้าใจว่าบอร์ดเสีย ทั้งที่เป็นเรื่องการเตรียมของ ตรวจทุกบอร์ดก่อนเปิดคาบ อย่าปล่อยให้ไปเจอกลางคาบ

สี่อย่างนี้เป็นงานของผู้สอน ไม่ใช่ของผู้เรียน ตรวจให้ครบก่อนเริ่ม แล้วคาบจะเดินได้ตลอดสามชั่วโมงโดยไม่สะดุด · ข้อที่สามตรวจเหมือนกันทั้งสองบอร์ด: แตะแผ่นสัมผัสแล้วแถว Touch บนหน้า Home ต้องเปลี่ยน — ถ้าไม่เปลี่ยน สาเหตุที่ต้องสงสัยก่อนคือชิป CapSense ยังไม่ถูก flash ซึ่งเกิดได้กับทั้งสองบอร์ด เพราะแผ่นสัมผัสของทั้งคู่ต่อกับชิป PSoC 4000T แยกต่างหาก ที่ต้องโปรแกรมคนละรอบกับตัวบอร์ดหลัก · ข้อที่สี่มีเฉพาะ Eva Kit เพราะ Dev Kit ไม่มีการ์ด Audio Player

ข้อที่พลาดบ่อยที่สุดคือชิป CapSense — มันเป็นชิปแยกที่ต้องโปรแกรมต่างหากจากตัวบอร์ดหลัก ทั้งสองบอร์ดเหมือนกัน

MVP checkpoint — ผ่านคาบนี้เมื่อ

ทีมสาธิตการใช้ 5 เมนูพร้อมอธิบายว่าเมนูใดใช้เซนเซอร์ใด + รัน lcd.print() ข้อความของทีมขึ้นจอสำเร็จ

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

  • [ ] เล่นครบ 5 เมนู: Home, Sensor Dashboard, Smart Watch, Wi-Fi Setting, BENTO Playground (ห้าเมนูนี้มีทั้งบน Eva Kit และ Dev Kit — Controls มีเฉพาะ Eva Kit จึงไม่อยู่ในเกณฑ์)
  • [ ] ตารางสำรวจเมนูในใบงาน (ข้อ 4.1) กรอกครบ 5 แถวแรก (แถว Controls กรอกเฉพาะทีมที่ถือ Eva Kit)
  • [ ] ชี้ได้ว่าเมนูไหนใช้เซนเซอร์ตัวไหน (ตอบปากเปล่ากับผู้สอนได้)
  • [ ] จอบอร์ดขึ้นหัวเรื่อง <h2> ของคาบนี้
  • [ ] จอบอร์ดขึ้นชื่อทีมและชื่อสมาชิกครบทุกคน ไล่ทีละคน
  • [ ] จอบอร์ดขึ้นบรรทัดสีเขียวปิดท้าย
  • [ ] examples/s01/09_your_level_rule.py↗ ผ่านครบหกแถว (เขียวหมด)
  • [ ] ถ่ายรูปหน้าจอบอร์ดแนบใน worksheet

ไม่มีข้อไหนต้องใช้โค้ดเกินสามสิบบรรทัด — คาบนี้วัดความเข้าใจ ไม่ใช่ปริมาณ

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

อาการ สาเหตุที่แท้จริง วิธีแก้
อยู่หน้า Playground แล้ว แต่พื้นที่แสดงผลว่าง มีจุดแดงเล็ก ๆ ที่ปุ่มไอคอนมุมขวาล่าง หน้านี้เปิดมาที่โหมด UI ข้อความ lcd ถูกพักไว้ แตะปุ่มไอคอนสีเขียวมุมขวาล่าง เพื่อสลับเป็นโหมดข้อความ
สร้าง ui.Label แล้วจอนิ่งไปราวสองวินาที ยังไม่ได้เคาะ ui.poll() เติม ui.poll() หนึ่งครั้งหลังสร้าง/แก้ widget
ป้ายบนจอขึ้นคำว่า Label ที่เราไม่ได้พิมพ์ สร้าง ui.Label ด้วยข้อความว่าง ตั้งข้อความเริ่มต้นให้มันเสมอ
ส่งโค้ดแล้วเงียบสนิท ไม่มีแม้แต่จุดแดง โค้ดยังไม่ถึงบอร์ด หรือสคริปต์ error ก่อนถึงบรรทัด lcd ดู error ที่คอนโซล IDE แล้วส่งใหม่
ข้อความไปโผล่ที่คอนโซลคอมแทน ใช้ print() แทน lcd.print() เปลี่ยนเป็น lcd.print()
ตัวอักษรโตผิดปกติเฉพาะบรรทัดนั้น ลืมปิดแท็ก </h2> ตรวจว่าแท็กเปิด-ปิดครบคู่
ข้อความยาวถูกตัดหาย แถวถัดไปต่อท้ายมาเลย เกิน 127 ไบต์/ครั้ง (ไทย ≈ 42 ตัวอักษร) — ตัดแล้ว \n หายด้วย แบ่งเป็นหลาย lcd.print() หรือใช้ท่าของไฟล์ 06
เรียก seg.value(12) แล้วได้ 12 ไม่ใช่ 12.0 .value() ส่งได้แต่จำนวนเต็ม ใช้ seg.text("12.0")
หน้า Audio Player ว่างเปล่า (Eva Kit) บอร์ดไม่ได้เสียบ SD card ไม่ใช่ความผิดพลาด ข้ามไปเมนูอื่น
หาการ์ด Controls / Audio Player / TESAIoT Connect ไม่เจอ ถือ Dev Kit อยู่ — build ของคอร์สไม่มีสามการ์ดนี้ ไม่ใช่ความผิดพลาด เกณฑ์ผ่านใช้เฉพาะเมนูที่มีทั้งสองบอร์ด
แตะการ์ดแล้วจอค้างแวบหนึ่ง บางหน้าใช้เวลาสร้างครั้งแรก รอสองวินาที อย่ารัวแตะซ้ำ

เกือบทุกข้อในตารางนี้ ไม่ใช่ความผิดของโค้ด แต่เป็นความไม่รู้ลำดับขั้นตอน

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

s01_hello_lcd.py pass ท่า 1 — ล้างจอ + หัวเรื่อง (2 จุด) pass ท่า 2 — ทักทายจากทีม (1 จุด) pass ท่า 3 — ในลูป เยื้องเข้าไป (2 จุด) ท่า 4 — ปิดท้ายสีเขียว แยกสองบรรทัด (2 จุด) รวม 7 จุด เติมทีละจุด แล้วรันทุกครั้ง

เปิด practise_codes/s01_hello_lcd.py↗ มีช่องว่างให้เติม 7 จุด (ท่าที่ 4 แยกเป็นสองบรรทัดเพราะเรื่อง 127 ไบต์)

# เติม: lcd.clear()
pass

# เติม: lcd.console("<h2>AIoT in Action - คาบ 1</h2>")
pass

for i in range(len(MEMBERS)):
    # เติม: lcd.print("สมาชิกคนที่", i + 1, ":", MEMBERS[i])
    pass
    # เติม: time.sleep_ms(800)
    pass

ทำสามขั้น: หนึ่ง แก้ข้อมูลทีมสามบรรทัดบนสุด สอง เติมทีละจุดแล้วส่งขึ้นบอร์ดทุกครั้ง สาม ครบแล้วลองเพิ่มบรรทัดของตัวเอง

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

เฉลย s01_hello_lcd.py↗ — ส่วนที่หนึ่ง

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

# s01_hello_lcd.py - ข้อความแรกของทีม ขึ้นจอบอร์ด
import lcd
import time
import ui

TEAM_NAME = "BentoBuilders"
MEMBERS = ["สมชาย", "สมหญิง", "สมศรี"]
MOTTO = "เล่นของจริงก่อน แล้วค่อยแกะ"
ส่วนที่ 1 — ประกาศ import + ข้อมูลทีม คนมาแก้ทีหลังหาเจอทันที ส่วนที่ 2 — สั่งจอ ส่วนที่ 3 — ลูป + ปิดท้าย

import lcd ต้องมาก่อนใช้งานเสมอ ส่วน import time เอาไว้ใช้ sleep_ms() ในท่าที่ 3 และ import ui เอาไว้วางป้ายลงหน้าจอในท่าที่ 5

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

การวางค่าที่ต้องแก้บ่อยไว้บนสุด เป็นมารยาทที่ดีต่อคนอ่านคนถัดไป (ซึ่งมักคือตัวเราเองในอีกสองสัปดาห์)

เฉลย — ส่วนที่สอง: หัวเรื่องและคำทักทาย

lcd.clear()
lcd.console("<h2>AIoT in Action - คาบ 1</h2>")
lcd.console("<span class=muted>ทดสอบจอครั้งแรกของทีม " + TEAM_NAME + "</span>")

lcd.print("สวัสดีจากทีม", TEAM_NAME)
lcd.print("คำขวัญของเรา:", MOTTO)
print("ส่งข้อความทักทายขึ้นจอบอร์ดแล้ว ดูที่จอ ไม่ใช่ที่หน้าคอม")
มีแท็กมาเกี่ยว → ใช้ + "<span>ทีม " + NAME + "</span>" ชื่อไปอยู่กลางแท็กพอดี ไม่มีช่องว่างแทรก ไม่มีแท็ก → ใช้จุลภาค lcd.print("สวัสดีจากทีม", NAME) ระบบเติมช่องว่างให้เอง ไม่ต้องต่อเอง

สังเกตความต่างสองแบบของการประกอบข้อความ: บรรทัด <span class=muted> ใช้ + เพราะต้องแทรกชื่อทีม ไว้กลางแท็ก ส่วน lcd.print("สวัสดีจากทีม", TEAM_NAME) ใช้จุลภาคเพราะไม่มีแท็กมาเกี่ยว

ถ้าใช้จุลภาคกับแท็ก จะได้ <span class=muted> ทีม </span> ที่มีช่องว่างเกินติดขอบแท็ก

ทำไมต้อง lcd.clear() ก่อนเสมอ — ลิ้นชักอาจมีข้อความจากโปรแกรมของกลุ่มก่อนหน้าค้างอยู่ การเริ่มจากสถานะที่เรารู้แน่นอน เป็นนิสัยของงาน embedded จริง ไม่ใช่แค่ความเรียบร้อย

เลือกวิธีต่อสตริงตามว่า "มีแท็กมาเกี่ยวไหม" ไม่ใช่ตามความเคยชิน

เฉลย — ส่วนที่สาม: ลูปและการปิดท้าย

for i in range(len(MEMBERS)):
    lcd.print("สมาชิกคนที่", i + 1, ":", MEMBERS[i])
    time.sleep_ms(800)

TAIL = "ทีม " + TEAM_NAME + " พร้อมลุยคาบต่อไป"
lcd.print("<span class=ok>ขึ้นจอสำเร็จ</span>")
lcd.print("<span class=ok>" + TAIL + "</span>")
หน่วงอยู่ในลูป (ถูก) for i in ...: lcd.print(...) time.sleep_ms(800) ชื่อขึ้น ทีละคน หน่วงนอกลูป (ผิด) for i in ...: lcd.print(...) time.sleep_ms(800) ขึ้นพรึ่บ เดียวหมด

range(len(MEMBERS)) ให้ตัวเลข 0, 1, 2 ตามจำนวนสมาชิกจริง — ถ้าทีมมีสองคน ลูปจะวนสองรอบเอง ไม่ต้องแก้อะไร ข้อมูลเป็นตัวขับการแสดงผล

การหน่วง 800 ms อยู่ ในลูป ไม่ใช่นอกลูป ถ้าย้ายออกไปข้างนอก ชื่อทุกคนจะขึ้นพร้อมกันแล้วค่อยหน่วงครั้งเดียว — ลองย้ายดูแล้วสังเกตความต่าง จะเข้าใจเรื่อง indent ในภาษา Python ทันที

บรรทัดปิดท้ายแยกเป็นสอง lcd.print() ตั้งแต่ต้น เพราะรวมกันแล้วเสี่ยงเกิน 127 ไบต์ถ้าชื่อทีมยาว ประโยคปิดท้ายถูกยกออกมาเก็บไว้ในตัวแปร TAIL เพราะท่าที่ 5 ต้องเอาความยาวของมันไปวัด

ใน Python การเยื้องบรรทัดคือความหมาย ไม่ใช่แค่ความสวยงาม

เฉลย — ส่วนที่สี่: ป้ายทีมบนหน้าจอ ที่ lcd ทำแทนไม่ได้

ui.screen()
time.sleep_ms(200)
COL_TEXT, COL_DIM = 0xE8EAED, 0x9AA3AF
COL_CARD, COL_OK, COL_RUN = 0x171B22, 0x30A46C, 0x4A9EFF
BYTE_LIMIT = 127
ui.Panel(x=16, y=8, w=760, h=372, color=COL_CARD, min=COL_DIM, max=12, value=1)
ui.Label("ป้ายประจำทีม", x=32, y=16, color=COL_DIM, value=20)
ui.Label(TEAM_NAME, x=32, y=40, color=COL_TEXT, value=28)
ui.Label(MOTTO, x=32, y=84, color=COL_DIM, value=20)
tbl = ui.Table(x=32, y=116, w=340, h=252, cols=2)
tbl.col_width(0, 100)
tbl.col_width(1, 230)
tbl.add_row("ลำดับ", "ชื่อสมาชิก")
ui.Label("สถานะการเขียนตาราง", x=400, y=116, color=COL_DIM, value=20)
led_run = ui.Led(x=408, y=144, w=48, h=48, color=COL_RUN, value=1)
ui.Label("กำลังเขียน", x=452, y=148, color=COL_DIM, value=20)
led_done = ui.Led(x=408, y=188, w=48, h=48, color=COL_OK, value=0)
ui.Label("เขียนครบแล้ว", x=452, y=192, color=COL_DIM, value=20)
for i in range(len(MEMBERS)):
    tbl.add_row(str(i + 1), MEMBERS[i])
    ui.poll()
    time.sleep_ms(500)
led_run.value(0)
led_done.value(1)
lcd.print() → ลิ้นชัก Console ต้องกดเปิดลิ้นชักเองถึงจะเห็น ตอบว่า "ที่ผ่านมาเกิดอะไรขึ้นบ้าง" ui.* → หน้า Playground เห็นทันที และค้างอยู่หลังโปรแกรมจบ ตอบว่า "ตอนนี้เป็นยังไง"

ลูปนี้เดินรายชื่อ ชุดเดียวกัน กับท่าที่ 3 แต่ปลายทางคนละที่ — ท่าที่ 3 พิมพ์เข้าลิ้นชัก ท่านี้เขียนลงตารางบนจอ ข้อมูลชุดเดียวไปได้สองที่พร้อมกัน และนั่นคือสิ่งที่คาบนี้อยากให้เห็น · ui.Table จัดคอลัมน์ให้เอง ถ้าเรียง ui.Label เองต้องนับพิกเซลทุกแถว พอชื่อยาวไม่เท่ากันคอลัมน์ที่สองจะเยื้องจนอ่านไม่ออก แถวหนึ่งสูงราว 62 พิกเซล หัวตารางบวกสมาชิกสามคนจึงต้องการ h=252

ไฟสองดวงบอกสถานะแทนการเปลี่ยนสีตัวอักษร เหตุผลอยู่ที่ การทดสอบขาวดำ — ถ่ายรูปจอแล้วแปลงเป็นเกรย์สเกล ไฟที่ติดกับไฟที่หรี่ยังแยกออกด้วยความสว่าง ส่วนตัวหนังสือสีเขียวกับสีเทากลายเป็นสีเดียวกัน · led_run.value(0) ทำให้ไฟ หรี่ ไม่ใช่หาย โดยตั้งใจ ไฟแผงควบคุมที่หายไปตอนดับ ทำให้คนดูแยกไม่ออกว่าดับจริงหรือจอเสีย

เฉลย — ส่วนที่ห้า: มาตรวัดไบต์ ตัวเลขที่มีพิสัยกำกับ

used = len(TAIL.encode())
ui.Label("ความยาวบรรทัดปิดท้าย จากเพดาน 127", x=400, y=240, color=COL_DIM, value=20)
ui.Bar(x=408, y=272, w=332, h=16, color=COL_OK, min=0, max=BYTE_LIMIT, value=used)
ui.Scale(x=408, y=288, w=332, h=44, color=COL_TEXT, min=0, max=BYTE_LIMIT)
ui.Label(str(used) + " ไบต์", x=408, y=336, color=COL_TEXT, value=20)
ui.poll()

เลข 72 ลอย ๆ ไม่บอกอะไรเลย เลข 72 ที่มีไม้บรรทัด 0–127 อยู่ใต้มันบอกทันทีว่าเหลือที่อีกเกินครึ่ง นี่คือกฎเดียวกับที่หน้าจอโรงงานใช้ — ค่าที่วัดได้ต้องมาพร้อมพิสัยหรือเกณฑ์ของมัน

ui.Scale ไม่รับ .value() มันคือไม้บรรทัด ตัวที่ขยับคือ ui.Bar ที่เราวางทับไว้ข้างบน (แบบวงกลมมีเข็มจริงผ่าน prop — คาบ 6 สอน) ลองเรียก .value(50) ใส่ Scale ดูก็ได้ แล้วจะเห็นว่าไม่มีอะไรเกิดขึ้น

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

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

ท่า 1 — ช่องทางถึงจอใช้ได้ ท่า 2 — ข้อความคงที่ผ่าน ท่า 3 — ตรรกะลูปผ่าน ท่า 4 — ประกาศว่าสำเร็จ ท่า 5 — ป้ายที่คนอ่านได้เอง แต่ละขั้นยืนยันขั้นก่อนหน้า — พังตรงไหนรู้ทันที

ท่า 1 ล้างจอ + หัวเรื่อง มาก่อน เพราะต้องพิสูจน์ให้ได้ก่อนว่า "ช่องทางสื่อสารกับจอใช้ได้จริง" ถ้าท่านี้ไม่ขึ้น ท่าที่เหลือไม่มีประโยชน์ที่จะเขียนต่อ

ท่า 2 ข้อความคงที่ มาก่อนลูป เพราะการพิมพ์ค่าตายตัวง่ายกว่า และแยกได้ว่าปัญหาอยู่ที่ lcd หรืออยู่ที่ตรรกะของเรา

ท่า 3 ลูป มาหลังจากพิสูจน์สองข้อบนแล้ว ตอนนี้ถ้าพัง เรารู้แน่ว่าพังที่ลูป ไม่ใช่ที่จอ

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

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

นี่คือวิธีคิดแบบ ไล่จากง่ายไปยาก แล้วให้แต่ละขั้นยืนยันขั้นก่อนหน้า ซึ่งเป็นวิธีดีบักงาน embedded มาตรฐาน

ถ้าเขียนรวดเดียวสี่สิบบรรทัดแล้วรัน พอมันเงียบ น้อง ๆ จะไม่รู้เลยว่าต้องเริ่มหาจากตรงไหน

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

ฝั่งสมองกลฝังตัว หลายคอร์ · IPC เฟิร์มแวร์ซ่อนความยุ่งยาก ฝั่ง Python import · list · for · def การเยื้องบรรทัดคือความหมาย ฝั่งออกแบบระบบ เริ่มจากสถานะที่รู้แน่ แยกข้อมูลออกจากตรรกะ

ฝั่งระบบสมองกลฝังตัว
สถาปัตยกรรมหลายคอร์และการแบ่งงานตามความถนัด · การสื่อสารระหว่างคอร์ผ่าน IPC · แนวคิดว่าเฟิร์มแวร์คือชั้นที่ซ่อนความยุ่งยากของฮาร์ดแวร์ไว้ให้เรา

ฝั่ง Python และวิทยาการคอมพิวเตอร์
import โมดูล · ตัวแปร list และ tuple · ลูป for กับ range() · การเขียนฟังก์ชันด้วย def · การเข้ารหัสข้อความเป็นไบต์ · นาฬิกาที่วนกลับกับ ticks_diff()

ฝั่งการออกแบบระบบ
การเริ่มจากสถานะที่รู้แน่ · การรายงานสถานะเมื่อจบงาน · การแยกข้อมูลออกจากตรรกะ · การแบ่งหน้าที่ว่าจอตอบ "ตอนนี้" ลิ้นชักตอบ "ที่ผ่านมา"

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

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

เล่น เข้าใจ ส่งโค้ด ขึ้นจอ คาบหน้า: บอร์ดคุยกับโลกด้วยโค้ดเรา แล้ววงจรนี้จะยาวขึ้นทีละคาบจนถึงแพลตฟอร์ม

วันนี้เราได้:
เล่นบอร์ดครบทุกเมนูหลักและรู้ว่าแต่ละเมนูกินข้อมูลจากเซนเซอร์ตัวไหน · รู้จักสมองสองก้อนและเส้นทางที่ข้อความเดินทางไปถึงจอ · ใช้ lcd ui และ time ประกอบเป็นจอสถานะหนึ่งใบได้ · เขียนกฎตัดระดับเองแล้วให้บอร์ดตรวจจนผ่าน

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

คาบหน้า: เราจะพาบอร์ดออกอินเทอร์เน็ต ด้วยโค้ดของเราเอง ไม่ใช่กดผ่านเมนู — wifi.connect() ให้ค่าอะไรกลับมา wifi.ip() คืออะไร แล้วทำไม "ต่อติดแล้ว" กับ "ยังต่ออยู่" ถึงเป็นคนละคำถาม · แล้วเลขที่วันนี้จบอยู่บนจอบอร์ด จะออกไปโผล่บนหน้าเว็บ (สไลด์ถัดไป)

เก็บโค้ดของวันนี้ไว้ให้ดี คาบต่อ ๆ ไปเราจะต่อยอดจากไฟล์เดิมเรื่อย ๆ — โดยเฉพาะโครงของไฟล์ 08

คาบหน้า — เลขบนจอนี้จะออกจากบอร์ด

บอร์ดของทีม sensors.snapshot() วันนี้จบที่จอนี้ broker สาธารณะ broker.hivemq.com ตู้ไปรษณีย์กลาง หน้าเว็บ บนโน้ตบุ๊กของเรา ไม่ต้องติดตั้งอะไร พอร์ต 1883 เบราว์เซอร์ คาบ 2: ค่าเดียวกับที่เห็นวันนี้ เดินทางออกจากโต๊ะ

วันนี้ตัวเลขทุกตัวจบที่จอบอร์ด examples/s01/12_every_sense_at_once.py↗ อ่านลูกบิด แผ่นสัมผัส และ IMU ได้ในคำสั่งเดียว แต่คนที่เห็นมีแค่คนที่ยืนอยู่หน้าบอร์ด

คาบหน้าเราเอาเลขชุดเดียวกันนี้ส่งออกไปที่ broker สาธารณะ broker.hivemq.com แล้วเปิดหน้าเว็บ examples/web/my_first_reader.html บนโน้ตบุ๊กของเราเองอ่านกลับมา ใช้แค่เบราว์เซอร์ ไม่ต้องติดตั้งอะไรเพิ่ม

ของที่ติดมือไปคาบหน้า: sensors.snapshot() จากไฟล์ 12 คือแหล่งตัวเลข · โครงจอสถานะของไฟล์ 08 คือหน้าตาของบันได WiFi → broker → ส่ง ที่ต้องเห็นบนจอทีละขั้น · ชื่อทีม team01 ถึง team19 ที่ผู้สอนแจก ให้จดลงใบงาน

Emulator ใน ide.tesaiot.dev มีโมดูล mqtt ที่ต่อ broker สาธารณะตัวจริงผ่าน WebSocket ได้ เลขจาก Emulator จึงขึ้นหน้าเว็บได้เหมือนเลขจากบอร์ด

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

วันนี้ · คาบ 1-2 เล่นของที่มีอยู่ แล้วส่งค่าออกไปถึงคนอื่น คาบ 3-5 สั่งฮาร์ดแวร์เอง ไฟ ปุ่ม จอสัมผัส คาบ 6-8 อ่านเซนเซอร์ วาดเป็นแดชบอร์ด คาบ 9-12 ส่งขึ้นแพลตฟอร์ม แล้วสร้างของจริง เราเดินย้อนศร: เห็นของสำเร็จก่อน แล้วค่อยไล่แกะจนสร้างเองได้

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

สิบคาบข้างหน้า — ทีละคาบ ได้อะไรกลับบ้าง

คาบ เรื่อง จบคาบแล้วทำอะไรได้
3 ควบคุมฮาร์ดแวร์ — LED ปุ่ม จอ สั่งไฟติดดับ อ่านปุ่มโดยไม่โดนสัญญาณเด้งหลอก
4 สร้าง Touch UI คุมฮาร์ดแวร์ แตะปุ่มบนจอแล้วไฟบนบอร์ดติดจริง
5 อนาล็อก + สัมผัส + กรองสัญญาณ หมุนลูกบิดคุมค่า และทำให้เลขที่สั่นนิ่งลงได้
6 IMU กับมุมเอียง ทำเครื่องวัดระดับดิจิทัลที่เอียงตามบอร์ดจริง
7 กราฟเรียลไทม์หลายเส้น วาดสัญญาณที่วิ่งอยู่ และรู้ว่าสุ่มช้าไปแล้วภาพหลอกยังไง
8 Mini-HMI ประกอบทุกอย่าง หน้าจอเดียวที่รวมทุกเซนเซอร์ และไม่ตายเมื่อตัวใดตัวหนึ่งเงียบ
9 WiFi และเครือข่าย จอสถานะเครือข่ายที่บอกได้ว่าหลุดตอนไหน และกลับมาเมื่อไร
10 MQTT — ส่งค่าและรับคำสั่ง ค่าจากโต๊ะเราขึ้นจอคนอื่น และคำสั่งจากคนอื่นสั่งบอร์ดเราได้
11 MQTTs เข้ารหัส สู่แพลตฟอร์ม ส่งข้อมูลแบบที่คนกลางดักอ่านไม่ได้ ขึ้นแพลตฟอร์มจริง
12 Capstone — สร้างของจริงของทีม ผลิตภัณฑ์ AIoT ย่อมหนึ่งชิ้นที่ทีมออกแบบเอง ตั้งแต่เซนเซอร์ถึงหน้าจอถึงคลาวด์

เส้นเรื่องคือเส้นเดียว — คาบ 3 ถึง 8 ทำให้บอร์ดรับรู้และแสดงผลได้ครบ · คาบ 9 ถึง 11 พาออกไปหาโลก · คาบ 12 คือวันที่ทีมเอาทุกชิ้นมาประกอบเป็นของตัวเอง

รายละเอียดเต็ม พร้อมภาพหน้าจอของแต่ละคาบ อยู่ที่ slides/roadmap.html — เปิดดูล่วงหน้าได้เลย

ดูเพิ่มเติมนอกเวลา — ของจริงที่อยู่ในบอร์ดเรา

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

ไมโครโฟน MEMS ดิจิทัลหน้าตาเป็นอย่างไร
InvenSense (TDK) · 2 นาที 16 วินาที · อังกฤษ

บอร์ดเรามีไมโครโฟนแบบนี้ (Eva Kit มีสองตัว · Dev Kit ดูที่บอร์ดของทีม) คลิปนี้เปิดให้เห็นว่าข้างในมีอะไรและมันส่งอะไรออกมา — คาบ 8 เราจะอ่านค่าจากมันด้วยโมดูล mic

ข้างในเซนเซอร์วัดการเคลื่อนไหวมีอะไรอยู่จริง ๆ
Breaking Taps · ความยาว: ยังไม่ยืนยัน · อังกฤษ

แกะฝาชิป IMU แล้วส่องด้วยกล้องจุลทรรศน์อิเล็กตรอน เห็นมวลกับซี่หวีที่ขยับได้จริง — ตอบข้อสงสัยที่เกือบทุกคนมีว่า "มันวัดการเอียงได้ยังไงในเมื่อไม่มีอะไรหมุน"

BMI270 ตัวเดียวกับที่อยู่บนบอร์ดเรา
SparkFun Electronics · ความยาว: ยังไม่ยืนยัน · อังกฤษ

เบอร์ชิปตรงกับ U5 ในตารางเมื่อครู่เป๊ะ ๆ คลิปนี้ต่อสายจริงแล้วอ่านค่าออกมา — เห็นว่าสิ่งที่แถว IMU บนหน้า Home แสดงอยู่ มาจากชิ้นส่วนที่ซื้อแยกได้ ไม่ใช่ของวิเศษเฉพาะบอร์ดนี้

อ่านต่อสำหรับคนอยากรู้ลึก

คลิปพวกนี้ไม่ได้อยู่ในเกณฑ์ผ่าน แต่คนที่ดูจะเข้าใจคาบหลัง ๆ ได้เร็วกว่าเพื่อน

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

1 · จอต้อนรับ หน้าที่ในทีม + สี 2 · นับถอยหลัง 5 → 1 แล้วจบสีเขียว 3 · วัดกับดัก 127 ไบต์ ไทยได้กี่ตัวกันแน่ 4 · แผนที่เมนู เมนูที่หกของทีมเรา

ข้อ 1 · จอต้อนรับของทีม
ทำหน้าจอต้อนรับที่มีหัวเรื่อง ชื่อทีม คำขวัญ และรายชื่อสมาชิกพร้อม "หน้าที่ในทีม" ของแต่ละคน ใช้ระดับสีให้ตรงความหมาย ไม่ใช่เลือกตามชอบ

ข้อ 2 · นับถอยหลัง
ดัดแปลง examples/s01/05_clear_and_refresh.py↗ ให้นับถอยหลังจากเวลาที่ทีมตั้งเอง แล้วจบด้วยข้อความสีเขียว ลองปรับ TICK_MS แล้วสังเกตความรู้สึกที่ต่างกัน

ข้อ 3 · สำรวจกับดัก 127 ไบต์
จงใจพิมพ์ข้อความไทยยาวเกิน 42 ตัวอักษรในครั้งเดียว วัดดูว่าตัดที่ตัวอักษรที่เท่าไร แล้วอธิบายว่าทำไมภาษาไทยกับภาษาอังกฤษได้จำนวนตัวอักษรไม่เท่ากัน

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

เขียนคำตอบลง worksheet ข้อ 8 แล้วเอามาเล่าให้เพื่อนฟังต้นคาบหน้า

หน้าจอของทุกไฟล์ในคาบนี้ (1/5)

01 บรรทัดแรกที่ขึ้นจอบอร์ด · 02 ทำให้บรรทัดที่ต้องรีบอ่าน เด่นออกมาจากบรรทัดอื่น · 03 ข้อความยาวเกิน 127 ไบต์ จะถูกตัดหายเงียบ ๆ
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

หน้าจอของทุกไฟล์ในคาบนี้ (2/5)

04 ลิ้นชัก Console กับการ์ดสรุปที่ไม่ต้องเปิดลิ้นชัก · 05 อัปเดตซ้ำที่เดิม ต่างจากไล่พิมพ์ลงมา · 06 ฟังก์ชันช่วยพิมพ์ที่ไม่มีวันโดนตัดเงียบ
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

หน้าจอของทุกไฟล์ในคาบนี้ (3/5)

07 ลูปที่สั่ง sleep เท่าเดิมทุกรอบ ไม่ได้เดินตรงเวลา · 08 จอสถานะหนึ่งใบ ที่สามโมดูลแบ่งงานกันทำ · 09 ไฟล์นี้รันได้ แต่ยังตอบผิดทุกข้อ งานของคุณคือทำให้มันถูก
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

หน้าจอของทุกไฟล์ในคาบนี้ (4/5)

10 ถามบอร์ดว่าตัวเองมีอะไร แทนที่จะเปิดคู่มือหา · 11 หลอดไฟกับปุ่มจริง สั่งได้จาก Python บรรทัดเดียว (ภาพเก่า ถ่ายตอนไฟล์ยังเรียกปุ่มว่า "BTN0" — ปัจจุบันไฟล์พิมพ์ชื่อจาก .name() คือ "USER Button 1" รอถ่ายใหม่) · 12 คำสั่งเดียว ได้ทุกเซนเซอร์พร้อมกัน
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

หน้าจอของทุกไฟล์ในคาบนี้ (5/5)

13 เส้นดิบที่สั่น กับเส้นเดียวกันที่นิ่ง อยู่บนกราฟใบเดียว · 14 พูดใส่บอร์ด แล้วดูมันขยับตาม · 15 ตัวเลขตัวเดียว กับสิบวิธีที่จอเล่ามันออกมา
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

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

เอกสารของผู้ผลิต

งานวิจัยที่กำหนดรูปร่างของคาบนี้

  • Carroll, J. M. et al. (1987–1990) — งานทดลองแบบมีกลุ่มควบคุมเรื่องคู่มือแบบ minimalist: คู่มือที่ให้ผู้เรียนลงมือทำงานจริงตั้งแต่หน้าแรก ๆ ใช้เวลาเรียนน้อยกว่าและทำงานสำเร็จได้มากกว่าคู่มือที่ให้อ่านทฤษฎีก่อน · นี่คือเหตุผลที่คาบนี้ให้จับบอร์ดตั้งแต่สไลด์ที่สี่ ไม่ใช่ตอนกลางคาบ
  • Mayer, R. E. — หลักการ pre-training: การบอก ชื่อและหน้าที่ ของชิ้นส่วนสั้น ๆ ก่อนลงมือ ช่วยการเรียนอย่างมีนัยสำคัญ · สไลด์ "สามโมดูลที่วันนี้ต้องรู้จัก" ยาวแค่หน้าเดียวด้วยเหตุผลนี้
  • ผลสำรวจนักพัฒนามืออาชีพ 80 คนเรื่องตัวอย่างโค้ด — ข้อร้องเรียนอันดับหนึ่งคือตัวอย่างไม่ช่วยให้คิดออกว่าจะประกอบชิ้นส่วนเข้าด้วยกันอย่างไร · นี่คือเหตุผลที่ examples/s01/08_status_screen.py↗ ได้พื้นที่สองสไลด์

หมายเหตุเรื่องภาพ

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

ภาพถ่ายและภาพเคลื่อนไหวที่นำมาประกอบ มาจาก Wikimedia Commons และหน่วยงานรัฐ ภายใต้สัญญาอนุญาต CC0 · CC BY · CC BY-SA หรือสาธารณสมบัติ ระบุผู้สร้างและสัญญาอนุญาตไว้ใต้ภาพทุกภาพ

ภาพหน้าจอในเด็คนี้มาจากสามแหล่ง และคำบรรยายใต้ภาพระบุไว้ทุกใบว่าใบไหนมาจากไหน — ภาพถ่ายจากบอร์ด Eva Kit จริง บันทึกโดยผู้สอน · ภาพจาก BENTO Emulator ที่ 800×480 เท่าจอของทั้งสองบอร์ด · และ ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด แต่ค่าจากเซนเซอร์ WiFi และไมโครโฟนเป็นค่าแทนบนเครื่องโฮสต์ — ภาพจากตัวจำลองแสดงหน้าจอที่ตัวอย่างสร้าง ไม่ใช่ผลการวัดของบอร์ด

ข้อเท็จจริงเกี่ยวกับพฤติกรรมของ lcd ui เมนู และเฟิร์มแวร์ ตรวจสอบจากซอร์สโค้ดของโปรเจกต์ KIT_PSE84_EVAL_EPC2-MicroPython-BentoClaw (Eva Kit) TESAIoT_KIT_PSE84_AI-Micropython-BentoClaw (Dev Kit) และ BENTO-TESAIoT-libraries โดยตรง

ทุกตัวเลขบนสไลด์นี้สืบกลับไปที่เอกสารต้นทางหรือซอร์สโค้ดได้ ถ้าเจอที่ไม่ตรง บอกผู้สอนได้เลย

fit-css

VIDEO-SLOT: คลิป 60-90 วินาที ถ่ายจอบอร์ดจริง (ถ่ายทั้ง Eva Kit และ Dev Kit ถ้าทำได้) — ไล่จาก Home ที่ค่าเซนเซอร์วิ่ง → (เฉพาะ Eva Kit: แตะ Controls แล้วแตะวงกลมให้ LED บนบอร์ดติด — Dev Kit ไม่มีการ์ดนี้ ให้รัน examples/s01/11 แทน) → Sensor Dashboard กราฟเลื่อน → Smart Watch ปัดเปลี่ยนหน้า → ปิดท้ายที่ BENTO Playground ที่ยังว่างเปล่า พร้อมคำบรรยายว่าหน้านี้คือหน้าที่โค้ดของเราจะมาโผล่

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

หัวใจของ AIoT คือ ตัดสินใจใกล้จุดเกิดเหตุ ส่งขึ้นคลาวด์เฉพาะสิ่งที่มีความหมาย

VIDEO-SLOT: คลิป 20-30 วินาที ถ่ายจอบอร์ดตอนผ่าน MVP — หัวเรื่องขึ้น ชื่อสมาชิกไล่ทีละคน แล้วปิดด้วยบรรทัดสีเขียวสองบรรทัด ใช้เป็นตัวอย่าง "หน้าตาของคำว่าผ่าน" ให้ทุกทีมเทียบ

สีทั้งห้าตัวบนสุดคือจานสีของหลักสูตร (SPEC §S7.13) บทบาทละหนึ่งค่า — ห้ามหยิบสีสถานะมาแต่งจอ

ผู้สอน: ท่าที่ 5 นี้ในไฟล์ฝึกเขียนมาให้ครบแล้ว ไม่มีช่องว่าง ให้ทีมอ่านให้จบก่อนรัน แล้วเทียบว่าของที่ ui วางกับของที่ lcd พิมพ์ไปคนละที่กันอย่างไร

☰ สารบัญ