import gpio
import time
# ขอใช้ปุ่มดวงแรก (index 0 = USER BTN1 บนบอร์ด) และไฟ LED 2 ดวงไว้บอกสถานะ
btn = gpio.button(0)
led_a = gpio.led(1) # LED2 (เขียว) — เลี่ยง LED1 (แดง) ที่ระบบใช้
led_b = gpio.led(2) # RGB_RED — คู่กับ led_a เข้ารหัส 3 สถานะด้วย 2 ดวง
# นิยาม state ของ FSM ให้อ่านง่าย แทนที่จะใช้ตัวเลขลอย ๆ
GREEN, YELLOW, RED = 0, 1, 2
state = GREEN
gpio.button(0) คือปุ่ม USER BTN1 บนบอร์ด ส่วน gpio.led(1) / gpio.led(2) คือ LED สองดวง (เลี่ยง LED1 แดงที่ระบบใช้)
ตรงนี้เรายังไม่ทำอะไร แค่ "จองอุปกรณ์" ไว้ใช้ และตั้งชื่อ state ให้คนอ่านเข้าใจ
def show(s):
# สะท้อน state ปัจจุบันออก LED ตามตารางด้านบน
a_on, b_on = LED_PATTERN[s]
led_a.on() if a_on else led_a.off()
led_b.on() if b_on else led_b.off()
print("STATE =", NAME[s])
เราแยกเรื่อง "แสดงผล" ออกมาเป็นฟังก์ชันเดียว — ที่ไหนอยากอัปเดต LED ก็เรียก show(state)
นี่คือนิสัยที่ดีของโปรแกรมเมอร์: logic การเปลี่ยน state กับ การแสดงผล แยกกันคนละที่
พอถึงเกมจริงshow()จะกลายเป็นการวาดภาพบนจอ แต่โครงคิดเหมือนกันเป๊ะ
# ตัวแปรสำหรับ debounce: จำสถานะปุ่มรอบก่อน เพื่อจับ "ขอบขาลง" (กดลงครั้งเดียว)
prev_pressed = btn.is_pressed()
show(state)
print("กดปุ่ม USER BTN1 เพื่อเปลี่ยนไฟจราจร (Ctrl+C เพื่อหยุด)")
while True:
pressed = btn.is_pressed()
# debounce แบบง่าย: นับเป็น "กด" เฉพาะตอนที่เพิ่งเปลี่ยนจากปล่อย -> กด
# (เป็น edge detection กันปุ่มเด้งซ้ำ ๆ จากการกดค้างหนึ่งครั้ง)
if pressed and not prev_pressed:
state = (state + 1) % 3 # เลื่อน FSM ไปสถานะถัดไปแบบวนรอบ
show(state)
time.sleep_ms(50) # หน่วงสั้น ๆ ให้สัญญาณปุ่มนิ่งก่อนอ่านต่อ
prev_pressed = pressed
time.sleep_ms(10) # คุมจังหวะ loop ~100 รอบ/วินาที ไม่ให้ busy เกินจำเป็น
หัวใจอยู่ที่บรรทัด if pressed and not prev_pressed: — แปลเป็นภาษาคนว่า
"ตอนนี้กดอยู่ และ รอบที่แล้วยังไม่กด" นั่นแหละคือ จังหวะกดลงพอดี
prev_pressed ก็เป็น True แล้ว เงื่อนไขเลยไม่จริง → ไม่นับซ้ำprev_pressed = pressed คือการ "จำไว้ใช้รอบหน้า" ห้ามลืมจุดที่มักสับสน — "rising edge" ของอะไร? ทางไฟฟ้า ปุ่มเป็น active-low: กดแล้วขาถูกดึงลง LOW (0V) ซึ่งคือ falling edge. แต่ btn.is_pressed() กลับขั้วให้แล้ว มันคืน True เมื่อกด ดังนั้น pressed and not prev_pressed (s02_button_fsm.py:50) ทำงานบนค่าตรรกะ (True = กด) ไม่ใช่บนระดับแรงดันไฟ — "rising edge ของสัญญาณตรรกะ" (False → True) จึงตรงกับ "falling edge ทางไฟฟ้า" (HIGH → LOW) พอดี เราเลยคิดเป็น "ปล่อย → กด" ได้ตรง ๆ โดยไม่ต้องพะวงว่าไฟจริงเป็น 0 หรือ 1
ลองไล่ทั้ง 4 ความเป็นไปได้ของ (prev, ตอนนี้) — มีแค่แถวเดียวที่เงื่อนไขเป็นจริง:

ลองคิดดูว่าถ้าเราลบ
prev_pressedทิ้ง แล้วเช็กแค่if pressed:จะเกิดอะไร?
คำตอบ: กดทีเดียวไฟจะวิ่งรัวจนกว่าจะปล่อยมือ — นี่คือเหตุผลที่ต้อง debounce
โค้ด s02_button_fsm.py ทั้งไฟล์เดินตามผังนี้ — ตั้งค่าครั้งเดียว แล้ววน "อ่าน → เช็กขอบ → เปลี่ยน state → จำ" ไม่รู้จบ:
สัญกรณ์ผังงาน (ใช้เหมือนกันทุกคาบ): สี่เหลี่ยมมน = จุดเริ่ม/จบ · สี่เหลี่ยม = ทำงาน (process) · ข้าวหลามตัด = ตัดสินใจ (มีป้าย Yes/No) · สีเขียว = อ่าน input / แสดงผล · เส้นประ = เส้นวนกลับ
ดูสัญญาณเดียวกันแต่เทียบตัวนับสองตัวแบบสด ๆ: ฝั่งซ้าย (แดง) อ่านดิบ if pressed: นับทุก step ที่เป็น LOW, ฝั่งขวา (เขียว) pressed and not prev_pressed: + หน่วง

time.sleep_ms(50) ทำงาน เรา "ปิดหู" ข้ามช่วงเด้งทั้งหมดนี่คือภาพเดียวกับโค้ดในสไลด์ที่แล้ว แค่เห็นมันเดินทีละ step จะเข้าใจ
prev_pressedทันที
ขั้นตอนวันนี้ ไม่ต้องพิมพ์ exec(open(...)) ใด ๆ ทั้งสิ้น:
practise_codes/s02_button_fsm.py↗ ใน BENTO IDEกดปุ่ม USER BTN1 เพื่อเปลี่ยนไฟจราจรSTATE = ใน console
หน้าจอ BENTO IDE จริงเป็นแบบนี้ — ① คือปุ่ม Program to Device ที่แถบเครื่องมือ (ข้อ 2) กดแล้วดูที่ ② ช่อง console จะขึ้น Saved /main.py แล้ว Restarting device... (ข้อ 3):

ที่มา: ภาพหน้าจอ BENTO IDE (course asset) · ในภาพเป็นอีกตัวอย่างหนึ่ง แต่ปุ่มและลำดับ console เหมือนกันทุกแล็บ — สำหรับคาบนี้ console จะขึ้น กดปุ่ม USER BTN1 เพื่อเปลี่ยนไฟจราจร
สิ่งที่ควรเห็น:
time.sleep_ms(50) อาจถูกลบ หรือ debounce ไม่ครบ ลองเช็กบรรทัด prev_pressed = pressedshow(state) หลังเปลี่ยน stateshow() ว่าจับคู่ a_on / b_on ถูกดวงไหม
น้อง ๆ จำไว้ว่าการ debug คือการ "ตั้งสมมติฐานทีละข้อแล้วพิสูจน์" ไม่ใช่เดามั่ว
ไฟจราจรเล็ก ๆ ที่กดปุ่มแล้วเปลี่ยนสี ซ่อนรากฐานที่เราจะใช้ตลอดคอร์ส:
ฝั่ง Embedded / ฮาร์ดแวร์
ฝั่ง Python / อัลกอริทึม
if pressed and not prev_pressed คือ edge detection%) — เลขคณิตที่ทำให้ค่าวนเป็นวง โดยไม่ต้องเขียน if เช็กขอบเองLED_PATTERN แมป state → เอาต์พุต ทำให้เพิ่ม state ใหม่ได้ง่ายฝั่ง FSM (รากฐานของ game loop)
ดูคลิป (เสริม) — อธิบาย bounce บนออสซิลโลสโคปจริง แล้วโชว์ทั้ง debounce ทางฮาร์ดแวร์และซอฟต์แวร์ ตรงกับที่เราทำวันนี้:
What is Switch Bounce and How to Debounce – ATM | Digi-Key Electronics — DigiKey
วันนี้เราได้แล้ว:
gpio.button(0).is_pressed()pressed and not prev_pressed) + หน่วงสั้น ๆ(state + 1) % 3 แล้วสะท้อนออก LED ผ่าน show()การบ้าน — เติมช่องในฉบับฝึก:
เปิด practise_codes/s02_button_fsm.py↗ หาคำว่า "เติม:" 4 จุด แล้วเขียนเองให้ครบ
(เปิด/ปิด led_a, เปิด/ปิด led_b, อ่าน prev_pressed ตอนเริ่ม, และสูตรวน state)
ลองด้วยตัวเองให้สุดก่อนนะ — เดี๋ยวเฉลยพร้อมกันในห้องเรียน
อยากต่อยอด: เพิ่ม state ที่ 4 (ไฟกะพริบ) ดูว่าต้องแก้ตรงไหนบ้าง — เกมเต็มอยู่ในfull_games/
s02_button_fsm.py — อ่านให้เข้าใจแล้วพิมพ์เองเฉลยนี้ไม่ได้มีไว้ให้ copy-paste ส่ง คะแนนของคาบนี้อยู่ที่ใบงานกับการอธิบายด้วยคำพูดของน้องเอง ไม่ใช่ที่โค้ดตรงกับเฉลย วิธีใช้ให้ได้ของจริงคือ อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์ใหม่ด้วยมือตัวเอง — ช่วงที่นิ้วพิมพ์เองนี่แหละที่สมองจำ pattern ได้ ต่อไปเราจะแกะทีละส่วน เพราะทุกบรรทัดมีเหตุผล ไม่ได้เขียนสุ่ม ๆ
ส่วนแรก — จองอุปกรณ์ กับตั้งชื่อ state:
import gpio
import time
# ขอใช้ปุ่มดวงแรก (index 0 = USER BTN1 บนบอร์ด) และไฟ LED 2 ดวงไว้บอกสถานะ
# ใช้ LED2 (เขียว) + RGB_RED — เลี่ยง LED1 (แดง, index 0) ที่ระบบหลักมักใช้เป็นไฟ status
btn = gpio.button(0)
led_a = gpio.led(1) # LED2 (เขียว) — ใช้คู่กับ led_b เข้ารหัส 3 สถานะด้วย 2 ดวง
led_b = gpio.led(2) # RGB_RED
# นิยาม state ของ FSM ให้อ่านง่าย แทนที่จะใช้ตัวเลขลอย ๆ
GREEN, YELLOW, RED = 0, 1, 2
state = GREEN
import (:9-10) บอกว่าวันนี้เราใช้ของสองอย่าง: gpio อ่านปุ่ม/สั่งไฟ กับ time คุมจังหวะลูป — ตรงกับสองในสี่ท่อนของคาถาประจำคาบ "อ่าน input" กับ "กรองให้นิ่ง"btn = gpio.button(0) กับ led_a/led_b (:14-16) เราเก็บ object ลงตัวแปร ครั้งเดียว แล้วใช้ซ้ำทั้งไฟล์ ไม่เรียก gpio.button(0) ใหม่ทุกรอบลูป — ถ้าเรียกซ้ำในลูปที่วิ่ง ~100 Hz คือการจองอุปกรณ์เดิมพันครั้งเปล่า ๆGREEN, YELLOW, RED = 0, 1, 2 (:19) แทนที่จะเขียนเลข 0/1/2 ลอย ๆ? เพราะพอโค้ดยาวขึ้น if state == 2 อ่านไม่ออกว่าหมายถึงไฟอะไร แต่ if state == RED อ่านออกทันที นี่คือ named constant — ต่อจากสไลด์ "โครงคิดของ FSM ไฟจราจร" ที่เรานิยาม 3 สถานะไว้state = GREEN (:20) — ทุก FSM ต้องมี "สถานะตั้งต้น" ที่รู้แน่ ไม่ใช่ปล่อยให้ตัวแปรลอยจำหลักนี้ไว้: อะไรที่ใช้ซ้ำทั้งโปรแกรม (ปุ่ม ไฟ ชื่อสถานะ) ตั้งชื่อเก็บไว้บนหัวไฟล์ก่อน แล้วโค้ดข้างล่างจะอ่านเหมือนภาษาคน
# ตารางว่าแต่ละ state ควรเปิด LED ดวงไหนบ้าง (led_a, led_b)
# เขียว = ดวง A, เหลือง = ทั้งคู่, แดง = ดวง B (เลือกให้แยกกันได้ชัด)
LED_PATTERN = {
GREEN: (True, False),
YELLOW: (True, True),
RED: (False, True),
}
NAME = {GREEN: "GREEN", YELLOW: "YELLOW", RED: "RED"}
def show(s):
# สะท้อน state ปัจจุบันออก LED ตามตารางด้านบน
a_on, b_on = LED_PATTERN[s]
led_a.on() if a_on else led_a.off()
led_b.on() if b_on else led_b.off()
print("STATE =", NAME[s])
LED_PATTERN (:24-28) คือ dictionary ที่แมป state → คู่ on/off ของไฟสองดวง — นี่คือตารางเดียวกับสไลด์ "เข้ารหัส 3 สถานะด้วย LED 2 ดวง" เป๊ะ ๆ แค่ย้ายจากตารางบนสไลด์มาเป็นข้อมูลในโค้ด เขียน 3 สถานะด้วยไฟ 2 ดวงโดยเลือกลายไฟให้ไม่ซ้ำกันif state == GREEN: ... elif ...? เพราะถ้าวันหน้าจะเพิ่มไฟกะพริบเป็นสถานะที่ 4 เราแค่เติมอีกหนึ่งแถวในตาราง ไม่ต้องแก้ show() เลย — ข้อมูลขับพฤติกรรม ไม่ใช่ if ซ้อน if ยาว ๆshow(s) (:32-37) รวบเรื่อง "สะท้อน state ออกฮาร์ดแวร์" ไว้ที่เดียว บรรทัด led_a.on() if a_on else led_a.off() คือ ternary — อ่านว่า "ถ้า a_on ให้ติด ไม่งั้นดับ" ตรงกับท่อนสุดท้ายของคาถา "แสดงผล"print("STATE =", NAME[s]) มี NAME (:29) ไว้แปลงเลข state กลับเป็นคำ — ตอน debug เราอยากเห็น STATE = RED ไม่ใช่ STATE = 2 ที่ต้องมานั่งแปลเองนิสัยสำคัญที่ซ่อนอยู่: logic การเปลี่ยน state อยู่คนละที่กับ การแสดงผล พอถึงเกมจริง
show()จะกลายเป็นการวาดฉากลงจอ แต่โครงคิด "state อยู่ในตัวแปร ส่วนการวาดแยกออกมา" เหมือนเดิมทุกอย่าง
# ตัวแปรสำหรับ debounce: จำสถานะปุ่มรอบก่อน เพื่อจับ "ขอบขาลง" (กดลงครั้งเดียว)
prev_pressed = btn.is_pressed()
show(state)
print("กดปุ่ม USER BTN1 เพื่อเปลี่ยนไฟจราจร (Ctrl+C เพื่อหยุด)")
while True:
pressed = btn.is_pressed()
# debounce แบบง่าย: นับเป็น "กด" เฉพาะตอนที่เพิ่งเปลี่ยนจากปล่อย -> กด
# (เป็น edge detection กันปุ่มเด้งซ้ำ ๆ จากการกดค้างหนึ่งครั้ง)
if pressed and not prev_pressed:
state = (state + 1) % 3 # เลื่อน FSM ไปสถานะถัดไปแบบวนรอบ
show(state)
time.sleep_ms(50) # หน่วงสั้น ๆ ให้สัญญาณปุ่มนิ่งก่อนอ่านต่อ
prev_pressed = pressed
time.sleep_ms(10) # คุมจังหวะ loop ~100 รอบ/วินาที ไม่ให้ busy เกินจำเป็น
prev_pressed = btn.is_pressed() (:41) และ show(state) (:42) ครั้งหนึ่งก่อน — ตั้ง "ความจำรอบก่อน" กับปักไฟตั้งต้นให้ตรงกับ state = GREEN ถ้าลืมบรรทัดนี้ รอบแรกสุดจะเทียบกับค่าขยะif pressed and not prev_pressed: (:50) — "ตอนนี้กด และ รอบที่แล้วยังไม่กด" คือ edge detection ที่เราแกะกันไปแล้วในสไลด์ "อ่านลูปนี้ให้ขาด" กดค้างไว้ก็นับครั้งเดียว เพราะรอบถัดไป prev_pressed เป็น True แล้วstate = (state + 1) % 3 (:51) คือมอดุโลจากสไลด์ "โครงคิดของ FSM ไฟจราจร" — พอถึง RED (2) กดอีกที (2+1)%3 ได้ 0 วนกลับ GREEN เองโดยไม่ต้องเขียน if เช็กขอบtime.sleep_ms(50) หลังเปลี่ยน state (:53) คือการ "ปิดหู" ข้ามช่วง bounce ที่เราคำนวณไว้ในสไลด์ "อ่านกราฟ bounce ให้ออก" () — ส่วน prev_pressed = pressed (:55) กับ sleep_ms(10) (:56) คือ "จำไว้ใช้รอบหน้า" และคุมจังหวะลูปไม่ให้ busy — ตรงกับกล่องสุดท้ายในผังงาน "โปรแกรมทั้งตัว"ลองลบ
prev_pressedทิ้งแล้วเช็กแค่if pressed:ในหัวดูสิ — กดทีเดียวไฟจะวิ่งรัวจนกว่าจะปล่อยมือ นั่นคือเหตุผลทั้งหมดว่าทำไมสองบรรทัดprev_pressedถึงห้ามหาย
โค้ดทั้งไฟล์ไม่ได้เขียนเรียงมั่ว แต่ละส่วนรับผิดชอบท่อนหนึ่งของคาถา "อ่าน input → กรองให้นิ่ง → เปลี่ยน state → แสดงผล" แล้วต่อกันเป็นวงเดียว:
| ส่วน | บรรทัด | รับผิดชอบท่อนไหนของคาถา | รากฐานที่เอามาใช้ |
|---|---|---|---|
| จองอุปกรณ์ + ชื่อ state | :14-20 |
เตรียมของก่อนเข้าลูป | named constant + GPIO input/output |
LED_PATTERN + NAME |
:24-29 |
แสดงผล (ข้อมูลลายไฟ) | dictionary แมป state → เอาต์พุต |
show(s) |
:32-37 |
แสดงผล (ลงมือสั่งไฟ) | แยก display ออกจาก logic |
| อ่านปุ่ม + edge detect | :46-50 |
อ่าน input → กรองให้นิ่ง | is_pressed() + pressed and not prev |
(state + 1) % 3 + sleep_ms(50) |
:51-53 |
เปลี่ยน state + debounce | modulo วนรอบ + หน่วงข้าม bounce |
prev = pressed + sleep_ms(10) |
:55-56 |
จำไว้ใช้รอบหน้า + คุมจังหวะ | edge detection ต้องมีความจำ |
LED_PATTERN เก็บ "จะให้ไฟเป็นยังไง" ส่วน show() เก็บ "ลงมือสั่งยังไง" — แก้ลายไฟไม่ต้องแตะลูป แก้จังหวะลูปไม่ต้องแตะลายไฟถ้าจับได้ว่าสี่ส่วนนี้คือสี่ท่อนของคาถาที่เราท่องกันทั้งคาบ น้องจะเขียนเกมได้โดยไม่ต้องจำโค้ด — จำแค่ว่าแต่ละท่อนอยู่ตรงไหน แล้วเติมเนื้อเข้าไป
อยากให้หยุดคิดสักครู่ก่อนปิดคาบ ไม่ใช่เรื่องโค้ด แต่เรื่อง "มองให้ทะลุ" ว่าไฟจราจรที่กดปุ่มแล้วเปลี่ยนสี จริง ๆ แล้วมันคือชิ้นส่วนของอะไร
ที่มา — วันนี้ต่อยอดมาจากอะไร คาบที่แล้วเราสั่งไฟ LED ได้อย่างเดียว โปรแกรมพูดฝ่ายเดียว บอร์ดทำตามอย่างเดียว ไม่มีทางรู้เลยว่าคนกำลังทำอะไรอยู่ วันนี้เราเติมครึ่งที่หายไป คือ การรับ input ผ่าน gpio.button(0).is_pressed() ลองย้อนไปดูสไลด์ "เป้าหมายของคาบนี้" ที่บอกว่า 3 เรื่องต่อกันเป็นวงเดียว — พอบอร์ดอ่านปุ่มได้ วงจรการสื่อสารสองทางจึงครบ จากนั้น FSM (สไลด์ "โครงคิดของ FSM ไฟจราจร") ก็เข้ามาเป็น "ความจำ" ให้โปรแกรม ว่าตอนนี้อยู่สถานะไหน — เหมือน flip-flop ในคาบแรก แค่คราวนี้ความจำอยู่ในตัวแปร state ไม่ใช่ในสายไฟ
ที่ไป — ปุ่มนี้จะโตเป็นอะไร ในสไลด์ "เชื่อมโยงรากฐาน" อาจารย์ทิ้งท้ายไว้ว่า FSM 3 สีวันนี้คือต้นแบบของ MENU → PLAYING → GAME OVER ในเกมจริง ลองสังเกต: โครงลูปวันนี้คือ "อ่านปุ่ม → ถ้ากดขอบขาลง → เปลี่ยน state → วาดผล" ถ้าเปลี่ยน "ไฟจราจร 3 สี" เป็น "หน้าจอเกม 3 ฉาก" และเปลี่ยน "กดเลื่อนสี" เป็น "กด Start เริ่มเกม / ตายแล้วไป Game Over" โค้ดแทบไม่ต่างเลย — ปุ่มที่เราเพิ่งอ่านได้วันนี้ คือปุ่มควบคุมเกมของทั้งคอร์ส
ลองตอบคำถามพวกนี้ในใจ นี่แหละคือการเชื่อมจุดด้วยตัวเอง:
prev_pressed and not ... จากสไลด์ "อ่านลูปนี้ให้ขาด" ได้ไหม ถ้าวันหน้าปุ่มนี้คือปุ่ม "กระโดด" ในเกม เราจะยังใช้ edge detection ตัวเดิมกันกระโดดรัวไหม?(state + 1) % 3 พรุ่งนี้ถ้ามี 4 ฉาก เราจะเปลี่ยนเป็น % 4 แล้วมันก็วนได้เองเลยไหม?LED_PATTERN กลายเป็น "ฉากที่จะวาด")ถ้าตอบสามคำถามข้างบนได้ว่า "อ๋อ มันคือเรื่องเดียวกัน" — นั่นคือการหยั่งรู้ที่อาจารย์อยากให้เกิด: ไฟจราจรเล็ก ๆ วันนี้คือ state machine ของทั้งเกม เราแค่ยังไม่ได้เปลี่ยนไฟเป็นฉาก
เทคนิควันนี้ไม่ใช่แค่แบบฝึกหัด มันคือสิ่งที่เฟิร์มแวร์จริงใช้กันทุกวัน:
pressed and not prev_pressed (สไลด์ "อ่านลูปนี้ให้ขาด")เห็นไหมว่าสามเรื่องเล็ก ๆ วันนี้ — กรองปุ่มให้นิ่ง จับจังหวะกดลง จำสถานะแล้วเลื่อน — ไม่มีเรื่องไหนเป็นของสมมติเลย ทุกเครื่องที่มีปุ่มให้กดล้วนทำแบบนี้อยู่ข้างใน
เวอร์ชันหลักของคาบนี้ใช้ LED 2 ดวงเข้ารหัส 3 สถานะ (เหลือง = ติดทั้งคู่) — เป็นวิธีที่ฉลาดเมื่อมีดวงไฟน้อยกว่าจำนวนสถานะ ลองดูอีกแบบที่ ติดทีละดวง เหมือนไฟจราจรจริง แล้วสังเกตว่าทั้งสองแบบใช้ FSM เดียวกันเป๊ะ:
import gpio, time
btn = gpio.button(0)
lamp = {0: gpio.led(1), # GREEN -> LED2
1: gpio.led(0), # YELLOW -> LED1 (คราวนี้ได้ใช้)
2: gpio.led(2)} # RED -> RGB_RED
NAME = {0: "GREEN", 1: "YELLOW", 2: "RED"}
def show(s):
for i, led in lamp.items():
led.on() if i == s else led.off() # ติดดวงที่ตรงสถานะ ดับที่เหลือ
print("STATE =", NAME[s])
state, prev = 0, btn.is_pressed()
show(state)
while True:
pressed = btn.is_pressed()
if pressed and not prev: # edge detection เดิม
state = (state + 1) % 3 # FSM เดิม
show(state)
time.sleep_ms(50)
prev = pressed
time.sleep_ms(10)
จุดที่อยากให้สังเกต: แกน FSM ไม่เปลี่ยนเลยสักบรรทัด (
edge detection+(state+1)%3เหมือนเดิม) เปลี่ยนแค่show()— นี่คือหลัก "แยกตรรกะ (state) ออกจากการแสดงผล (display)" เปลี่ยนหน้าตาได้โดยไม่แตะสมองของเกม เดี๋ยวบล็อกเกมจริงจะใช้หลักนี้ตลอด · โค้ดเต็มอยู่ที่solution_codes/s02_traffic_v2.py(อ่านเสริม ไม่คิดคะแนน)
ลองเอาโจทย์พวกนี้ไปคิดต่อ ไม่มีคำตอบเดียวตายตัว ทุกข้อโยงกลับไปหาโปรเจกต์จริงได้:
prev_pressed? (ใบ้: ต้องจำ เวลาที่เริ่มกด ด้วย) นี่คือหลักเดียวกับปุ่ม power ของมือถือgpio.button(1) ให้ปุ่มหนึ่งเลื่อนไปข้างหน้า อีกปุ่มถอยหลัง สูตร (state + 1) % 3 จะกลายเป็นอะไร แล้วถอยหลังจาก GREEN (0) ต้องวนไปที่ RED (2) ยังไงไม่ให้ติดลบ?เลือกมาสักข้อ แล้วเขียนลงใบงานว่า "ถ้าเป็นเรา จะออกแบบยังไง" ไม่ต้องมีคำตอบถูก ขอแค่คิดต่อจากโค้ดที่พิมพ์เองวันนี้ — ตรงนั้นแหละคือจุดที่น้องเริ่มเป็นวิศวกร ไม่ใช่แค่คนพิมพ์ตามเฉลย
fit-css