เครื่องเกมเก่งขึ้นด้วยการ partitioning — แบ่งงานให้ชิปที่ถนัด (Genesis: 68000 ทำเกม, Z80 ทำเสียง)
เชื่อมกับวันนี้: ระบบกระจายของน้อง (บอร์ดหนึ่ง ส่ง อีกบอร์ด เล่นเสียง/คุม LED) คือ partitioning ข้ามเครื่อง — แนวคิดเดียวกันในสเกลใหญ่ขึ้น

ที่มา: "Sega-Genesis-Mk2-Motherboard-01.jpg" — Evan-Amos, Public domain, Wikimedia Commons · เปิดฝาเครื่องดูจริง: ชิปคนละตัวรับงานคนละอย่าง (CPU เกม + ชิปเสียงแยกกัน) คือ partitioning ที่จับต้องได้
ที่มา: en.wikipedia.org/wiki/Sega_Genesis
cmd/ledรีโมตไม่ได้ต่อสายตรงไปหาไฟ มันแค่หย่อนคำสั่งเข้า topic กลาง แล้วใครฟัง topic นั้นก็ได้รับ
หัวใจคือ topic + payload: ตกลงชื่อ topic เดียวกัน (cmd/led) และรูปแบบข้อความเดียวกัน (on/off) แค่นี้สองบอร์ดก็คุยรู้เรื่อง
กด SW1 ที่บอร์ด A → คำสั่งเป็น "ก้อนข้อความสั้น ๆ" วิ่งเข้า broker แล้วถูกส่งต่อให้บอร์ด B ไฟจึงติด/ดับตามคำสั่ง

สังเกตว่าบอร์ด A กับ B ไม่ได้รู้จักกันเลย — ต่างฝ่ายต่างคุยกับ broker เท่านั้น นี่คือหัวใจของ pub/sub: ผู้ส่งกับผู้รับ แยกขาดจากกัน (decoupled) เพิ่ม-ลดบอร์ดปลายทางได้โดยฝั่งรีโมตไม่ต้องแก้อะไรเลย
04 Subscribe To MQTT Topic To Control Led Using ESP32 — Age of Robotics — บอร์ดปลายทาง subscribe topic แล้วสั่งไฟติด/ดับตามข้อความ ตรงกับรีโมตคุม LED ที่เราทำวันนี้
ต่อ WiFi → ต่อ broker → แล้ววนอ่าน ปุ่มเดียว (SW1) ใช้เป็น toggle ส่งคำสั่งเฉพาะ จังหวะเพิ่งกด (กันส่งรัวตอนกดค้าง)
sw = gpio.button(0) # บอร์ดนี้มีปุ่มเดียว = SW1
prev = sw.is_pressed()
state = False # สถานะไฟปลายทางที่เราจำไว้ (เริ่ม "ปิด")
while True:
now = sw.is_pressed()
# จับขอบขาลง (ปล่อย -> กด) แล้วสลับสถานะ on<->off
if now and not prev:
state = not state
cmd = "on" if state else "off"
mqtt.publish(TOPIC, cmd)
print("ส่งคำสั่ง ->", cmd)
prev = now
time.sleep_ms(20)
prev คือเทคนิค edge detection เดียวกับคาบปุ่ม FSM — จับ "เพิ่งกด" ไม่ใช่ "กำลังกดค้าง" · ปุ่มเดียวก็คุมได้ครบเพราะแต่ละครั้งที่กด เราสลับ state แล้วส่งค่าใหม่
ภาพนี้คือสัญญาณปุ่มจริง ๆ ทีละรอบลูป: ตอนกดค้าง now เป็น HIGH หลายรอบติด แต่ now and not prev เป็นจริง แค่รอบแรก (วงแดง) เท่านั้น — จึง publish ครั้งเดียวต่อการกดหนึ่งครั้ง ไม่ท่วม broker
ตัวแปร state มีแค่สองค่า (False/True) ทุกครั้งที่จับ ขอบขาลง (now and not prev) เราพลิกสถานะแล้วส่งคำสั่งที่ตรงกับสถานะใหม่

ทำไมต้องจับ "ขอบ" ไม่ใช่ "ระดับ"? ปุ่มหนึ่งครั้งที่นิ้วเรากดค้างไว้ จริง ๆ แล้วลูปวนผ่านหลายรอบ (รอบละ 20 ms) ถ้าเช็กแค่ "กดอยู่ไหม" มันจะส่ง on รัว ๆ ทุกรอบจนท่วม broker · เก็บค่ารอบก่อน (prev) ไว้เทียบ จึงรู้ว่า "นี่คือรอบแรกที่เพิ่งกด" แล้วส่งแค่ครั้งเดียว
subscribe หนึ่งครั้ง แล้ววน get_message() ซึ่ง ไม่บล็อก — ได้คำสั่งมาก็ลงมือทำ
mqtt.subscribe(TOPIC) # ขอฟังคำสั่งทั้งหมดที่เข้า topic นี้
while True:
msg = mqtt.get_message() # (topic, payload) bytes หรือ None
if msg is not None:
topic, data = msg
command = data.decode() # payload เป็น bytes ต้อง .decode() ก่อน
if command == "on":
led.on()
game.sfx("select") # เสียงตอบรับ "เปิดแล้ว"
elif command == "off":
led.off()
game.sfx("back") # เสียงตอบรับ "ปิดแล้ว"
time.sleep_ms(50)
อย่าลืม .decode() — payload ที่ได้มาเป็น bytes (เช่น b"on") เทียบกับ string ตรง ๆ ไม่ติด
โครงนี้คือหัวใจของ "ไม่บล็อก": ถ้า get_message() คืน None ก็แค่วนกลับไปทำงานอื่นต่อ ไม่ยืนค้างรอข้อความ — ได้ข้อความเมื่อไรค่อย .decode() แล้วสั่งงาน LED/เสียง
เสียงในคาบนี้ — ปลายทางใช้เสียง UI สั้น ๆ ตอบรับคำสั่ง: ได้
"on"เล่นselect, ได้"off"เล่นbackให้ผู้ใช้รู้ว่าคำสั่งถึงแล้ว (กดเล่นฟังได้จริง):
เวลาตั้งแต่นิ้วกดปุ่มฝั่ง A จนไฟติดฝั่ง B ไม่ใช่ศูนย์ มันคือผลรวมของหลายช่วงต่อกัน:
แต่ละพจน์มาจากโค้ดที่เราเพิ่งเขียนตรง ๆ: t_scan คือคาบลูปปุ่มฝั่ง sender ( จาก sleep_ms(20)), t_up/t_down คือเวลาวิ่งบน WiFi ขาขึ้น/ขาลง, t_broker คือเวลา broker ส่งต่อ, ส่วน t_act คือคาบ poll ของ get_message() ฝั่งปลายทาง ( จาก sleep_ms(50))

รวมแล้วราว ๆ — สายตาคนเรารับรู้ความหน่วงราว ขึ้นไป จึง "รู้สึก" เหมือนกดแล้วติดทันที · อยากให้ไวขึ้นอีก? ลด sleep_ms(50) ฝั่งปลายทาง พจน์ จะเล็กลง แต่แลกกับ CPU ที่วน poll ถี่ขึ้น
ใช้ 2 บอร์ด (คู่กันสนุกสุด): บอร์ดหนึ่งเป็นรีโมต อีกบอร์ดเป็นปลายทาง
ex_c1_actuator.py กด Program to Device → รอขึ้น "พร้อมรับคำสั่ง"ex_c1_sender.py กด Program to Device
ปุ่ม Program to Device มุมซ้ายบนของ BENTO IDE (ขั้นตอน 2–3) — กดแล้วเซฟเป็น main.py ลงบอร์ดและรันทันที · ที่มา: BENTO IDE (course asset)

มีบอร์ดเดียวก็เทสต์ได้: รัน actuator บอร์ดเดียว แล้วใช้แอป MQTT บนมือถือ publish
on/offเข้า topiccmd/ledดูไฟตอบสนอง
ทั้งสองไฟล์ตั้ง TOPIC = "cmd/led" ตรงกัน และ client_id ไม่ซ้ำกัน (remote-sender กับ remote-actuator)
โครงนี้เล็กมาก แต่เป็น แกนเดียวกับระบบ IoT จริง — เปลี่ยนแค่ payload กับปลายทาง
led.on() เป็นสั่งรีเลย์ → เปิด-ปิดปลั๊ก/หลอดไฟจริงจากมือถือ"stop" → เครื่องจักรทุกตัวที่ subscribe หยุดพร้อมกัน"on"/"off"/"blink"/"red" แล้วแตก if/elif เพิ่ม"on"/"off" หากันได้ทั้งคู่ (ต่างคนต่างเป็นทั้ง sender + actuator) → กลายเป็นปุ่มสั่งงานข้ามบอร์ดแบบสองทาง
ที่มา: "Emergency stop button" — Cjp24, CC BY-SA 3.0, Wikimedia Commons · ปุ่มฉุกเฉินในโรงงานคือ sender ตัวจริง — กดทีเดียว publish "stop" ให้เครื่องจักรทุกตัวหยุดพร้อมกัน

น้อง ๆ ลองคิดดูว่าอุปกรณ์รอบตัวที่ "กดที่นี่ ทำงานที่โน่น" มีอะไรบ้าง แล้วลองทำให้บอร์ดเราเป็นแบบนั้น
โปรเจกต์เล็ก ๆ นี้รวมรากฐานสี่ด้านเข้าด้วยกัน ทุกอย่างที่ใช้คือเครื่องมือจริงของวิศวกรฝังตัว ไม่ใช่ลูกเล่นของเล่น
prev) ไว้เทียบกับรอบนี้ คือเทคนิคเดียวกับ FSM ปุ่ม ใช้จับ "เพิ่งกด" บน input แทบทุกชนิด (สวิตช์ rotary encoder ฯลฯ)จากเกม "กดที่นี่ ไฟติดที่โน่น" น้องได้ครบทั้ง GPIO, การจัดการ input ใน Python, สถาปัตยกรรมระบบกระจาย และการออกแบบ feedback ที่ตอบสนองทันใจ — สี่เสาที่อยู่ในผลิตภัณฑ์ IoT จริงทุกตัว
fit-css