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

บทเรียน 2.3 — ลงมือทำ: ไฟวิ่งกับปุ่ม แล้วส่งขึ้น broker

สั่งฮาร์ดแวร์ด้วยโค้ดของเราเอง · LED ทุกดวงบนบอร์ด ปุ่มหนึ่งปุ่ม และลูปที่ไม่มีวันหยุด

โมดูล 2 — จากจอสู่ฮาร์ดแวร์

ต่อจากบทเรียน 2.2 — หลังไฟและปุ่ม: active-low กันเด้ง และลูปที่ไม่หยุด

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

MVP checkpoint — ผ่านชุดบทเรียนนี้เมื่อ

LED chaser ครบทุกดวงของบอร์ด ปรับจังหวะได้ + กดปุ่มผู้ใช้ (เรียกด้วยชื่อจาก .name()) นับครั้งแสดงบน LCD

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

  • [ ] ไฟวิ่งไล่กันเป็นวงครบทุกดวงที่ gpio.num_leds() บอก ไม่มีดวงไหนค้างติดหรือข้ามดวง (Dev Kit: ดวง LED1/LED2 อยู่บนโมดูล ถ้ามองไม่เห็นให้ดูดวง RGB สามดวงเป็นหลัก แล้วจดไว้ว่าเห็นกี่ดวง)
  • [ ] แก้ค่า STEP_MS แล้วรันใหม่ จังหวะไฟเปลี่ยนตามจริง (สาธิตอย่างน้อยสองค่า)
  • [ ] กดปุ่มผู้ใช้ (gpio.button(0)) สิบครั้ง ตัวเลขบนจอขึ้นสิบพอดี
  • [ ] อธิบายให้ผู้สอนฟังได้ว่าถ้าเอาโค้ดกันเด้งออก จะเกิดอะไรขึ้นและเพราะอะไร
  • [ ] จบโปรแกรมแล้วไฟทุกดวงดับ พร้อมบรรทัดสรุปสีเขียวบนจอ
  • [ ] ถ่ายรูปหรือคลิปสั้นแนบในบันทึกการเรียน

ข้อที่ยากที่สุดคือข้อสี่ — ทำได้ไม่ยาก แต่ อธิบายได้ ต้องเข้าใจจริง

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

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

อาการ สาเหตุที่แท้จริง วิธีแก้
กดหนึ่งครั้ง เลขขึ้น 3-5 ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน ตั้ง DEBOUNCE_MS = 40 แล้วลองใหม่
กดหนึ่งครั้ง เลขขึ้นทีละ 2 ลืม if stable: จึงนับทั้งตอนกดและตอนปล่อย นับเฉพาะขอบขาเข้า
กดแล้วเลขไม่ขึ้นเลย ใช้ sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ ใช้ ticks_diff คุมจังหวะ หลับแค่ 5 ms
ไฟไม่วิ่ง ค้างดวงเดียว last_step = now อยู่นอก if ย้ายเข้าไปในบล็อก if
ไฟติดพร้อมกันสองดวง จุดดวงใหม่ก่อนดับดวงเดิม เรียง off → เลื่อน index → on
(Eva) ไฟดวงที่ 3 เป็นสีน้ำเงิน ทั้งที่ชื่อ RGB_RED ชื่อในตารางร่วมของเฟิร์มแวร์ ตกทอดมาจากบอร์ดรุ่นอื่น เชื่อสายตา ตั้งค่าคงที่ของทีมเอง
(Dev Kit) สั่ง led(0)/led(1) แล้วมองไม่เห็นอะไรติด LED1/LED2 อยู่บนโมดูล — มองเห็นบนบอร์ดที่ประกอบแล้วหรือไม่ ยังไม่ได้วัด ดูดวง RGB (ดัชนี 2-4) เป็นหลัก แล้วจดผลที่เห็นลงบันทึกการเรียน
กดปุ่มอื่นบนบอร์ดแล้วไม่มีอะไรเกิดขึ้น Python เข้าถึงได้ปุ่มเดียวคือ index 0 ใช้ปุ่มที่ gpio.button(0).name() บอก — บน Dev Kit ห้ามโยกสวิตช์ใดบนฐานที่บทเรียนไม่ได้สั่ง หลายตัวคือสวิตช์ตัดไฟเลี้ยง · ปุ่มกดสองปุ่มของฐานใช้ผ่านโมดูล buttons เท่านั้น
จอไม่ขึ้นตัวเลข แต่ไฟวิ่งปกติ ไม่ได้เปิดหน้า Playground ไว้ แตะการ์ด Playground แล้ว RESTART
duty() ตอบไม่ตรงกับหลอดที่เห็น toggle() ไม่ได้ไปแก้ตัวเลขของ duty() อ่าน duty() ว่า "สั่งอะไรไปล่าสุด" เท่านั้น
AttributeError ตอนเรียก machine.PWM PWM ADC SPI ไม่มีในพอร์ตนี้ · Timer มีโค้ดแต่เฟิร์มแวร์ชุดนี้ไม่ได้เปิดให้ หรี่ไฟด้วย gpio.led().brightness() (ดวง RGB ค้างระดับได้) หรือ hold(pct, ms) แทน
เรียก hold(80, 3000) แล้วปุ่มกดไม่ติดสามวินาที hold() บล็อกจนครบ ms ระหว่างนั้นไม่มีใครอ่านปุ่ม แบ่งเป็น hold() สั้น ๆ หลายครั้งในลูป

สังเกตว่ากับดักสามข้อแรกคือ ตรรกะเวลา ไม่ใช่ไวยากรณ์ — นี่คือลักษณะเฉพาะของงานฝังตัว

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

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

เปิด s03_led_button.py มีช่องว่างให้เติม 6 จุด

for i in range(NUM_LEDS):
    # เติม: gpio.led(i).off()
    pass

    # เติม: led_index = (led_index + 1) % NUM_LEDS
    pass

    # เติม: raw = btn.is_pressed()
    pass

        # เติม: last_change = now
        pass

            # เติม: count += 1
            pass

# เติม: lcd.print("<span class=ok>จบรอบทดสอบ กดปุ่มทั้งหมด " + str(count) + " ครั้ง</span>")
pass
หกจุด แบ่งเป็นสามกลุ่ม — เติมแล้วรันทีละกลุ่ม อย่ารวดเดียว กลุ่มไฟ · จุด 1-2 ดับให้หมด + เลื่อน index รันแล้วต้องเห็นไฟวิ่ง กลุ่มปุ่ม · จุด 3-5 อ่านดิบ + จับเวลา + นับ ระวังการเยื้องสามชั้น กลุ่มปิดท้าย · จุด 6 บรรทัดสรุปสีเขียว ใส่ str(count) ให้ถูก

ระวังการเยื้องบรรทัด — สามจุดกลางอยู่คนละชั้นกัน ถ้าเยื้องผิด Python จะเอาโค้ดไปไว้ผิดเงื่อนไข · ลำดับที่แนะนำ: เติมจุด 1-2 ให้ไฟวิ่งก่อน แล้วค่อยจุด 3-5 เรื่องปุ่ม สุดท้ายจุด 6

เติมทีละกลุ่มแล้วรัน — พอไฟวิ่งได้แล้วปุ่มพัง เรารู้ทันทีว่าปัญหาไม่ได้อยู่ที่ไฟ

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

ตัวอย่างของบทเรียน 2.1–2.3 — สามไฟล์แรกคือชุดที่กันไม่ให้ MVP พังทั้งวัน

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

ลำดับ · เรื่อง · เวลา ไฟล์ ลงมือทำอะไร แล้วจะเข้าใจอะไร
1 · ถามบอร์ดก่อนว่ามีอะไร · 10 นาที 01_board_info.py รันแล้วจดว่ามีไฟกี่ดวง ปุ่มกี่ปุ่ม ชื่อไหนคู่กับดัชนีอะไร · จะไม่ตกหลุมชื่อปุ่มกับชื่อสีที่ไม่ตรงกับความจริง เพราะเขียนจากค่าที่ถามมา
2 · ปุ่มนี้ 0 คือกด · 10 นาที 04_button_active_low.py กดปุ่มผู้ใช้ค้างไว้แล้วดูเลขกับเส้นกราฟตกลงพร้อมกัน · จะรู้ว่า value() คืนแรงดันดิบ ส่วน is_pressed() คืนความหมาย และ if btn.value(): ทำงานกลับด้านหมด
3 · นับให้ตรงด้วยการรอให้นิ่ง · 15 นาที 05_debounce_count.py กดสิบครั้งแล้วเทียบเลขนับดิบกับเลขนับกันเด้ง · จะอธิบายได้ว่าถ้าถอดโค้ดกันเด้งออกจะเกิดอะไร ซึ่งเป็นข้อที่ตกกันมากที่สุดของ MVP

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

อาการที่เจอ ไฟล์ที่ตอบอาการนั้น
ไฟยังกะพริบไม่เป็นจังหวะ หรือจบโปรแกรมแล้วไฟค้างติดโดยไม่ตั้งใจ 02_led_blink.py — กะพริบจริงพร้อมนับรอบขึ้นจอ toggle() ขึ้นกับค่าเดิมเสมอ ส่วน on() กับ off() ไม่สนใจค่าเดิม จึงต้องปิดท้ายด้วย off()
สั่งหรี่ไฟแล้วเงยหน้าไปมองหลอด ไม่เห็นความต่างสักระดับ 03_led_brightness.py — ไฟล์เลือกดวง RGB ให้ แล้วสั่ง brightness(40) ครั้งเดียวให้มองหลอดเองว่าค้างหรือวูบ (ดวงที่มีเส้น PWM ค้าง ดวงอื่นวูบ 12 ms แล้วดับ) จากนั้นเทียบสองระดับด้วย led.hold(pct, ms) ที่ค้างได้ทุกดวง และไฟล์นี้ยังพิสูจน์ให้เห็นว่า duty() ตอบเลขที่เราสั่ง ไม่ได้ไปวัดหลอด
ปุ่มเดียวต้องคุมไฟหลายดวง แต่จำไม่ได้ว่าตอนนี้ดวงไหนติด 06_button_picks_led.py — กดปุ่มเดิมซ้ำ ๆ แล้วดูดัชนีเลื่อนไปทีละดวง สถานะที่โปรแกรมจำไว้เองคือของที่เชื่อได้ที่สุด

อ่านเสริมนอกเวลา — เรื่องนี้อยู่นอกเกณฑ์ผ่านของบทเรียน 2.1–2.3 แต่คือรูปร่างที่ลูปวันนี้ไปโผล่ในโรงงานจริง: 01_andon_severity_lamp.py เสาไฟ andon หนึ่งระดับความรุนแรงคือไฟหนึ่งดวง และต้องดับทุกดวงก่อนจุดดวงใหม่เสมอ ไม่งั้นคนที่มองจากอีกฝั่งโรงงานจะอ่านระดับผิด

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

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

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

ฝั่งระบบสมองกลฝังตัว
GPIO เอาต์พุตกับอินพุต · แนวคิด active-high / active-low และตัวต้านทาน pull-up · polling loop กับการเลือกคาบเวลาสุ่มตัวอย่าง · contact bounce และการกันเด้ง · ค่าที่อ่านกลับจากขา บอกระดับของขา ไม่ได้บอกความตั้งใจของโปรแกรม

ฝั่ง Python และวิทยาการคอมพิวเตอร์
while ที่มีเงื่อนไขจบ · ตัวดำเนินการมอดุโล % สำหรับการวนเป็นวง · state machine ขนาดเล็กด้วยตัวแปรสามตัว (last_raw, stable, count) · edge detection เทียบกับ level detection

ฝั่งการออกแบบระบบ
แยกค่าที่ปรับได้ไว้บนสุดของไฟล์ · ให้โปรแกรมจำสถานะของตัวเอง ไม่ฝากไว้กับอุปกรณ์ · รายงานเมื่อมีเหตุการณ์ ไม่ใช่รายงานตามเวลา · เก็บกวาดสถานะก่อนจบโปรแกรม

ฮาร์ดแวร์ active-high / active-low pull-up · contact bounce วิทยาการคอมพิวเตอร์ state machine 3 ตัวแปร edge เทียบกับ level · % การออกแบบ จำสถานะของตัวเอง รายงานตามเหตุการณ์ สามรากที่ชุดบทเรียนนี้ปักลงไป และจะโผล่อีกทุกบทเรียนที่เหลือ

% ที่ใช้วนไฟทุกดวงวันนี้ คือตัวเดียวกับที่ใช้วน buffer ข้อมูลเซนเซอร์ในบทเรียน 3.4–3.6

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

ปุ่มกับไฟขึ้น broker — ภาพทั้งวง

บอร์ดของทีม (07_button_to_broker.py) ปุ่ม ทุก 5 ms กันเด้ง นิ่ง 40 ms ตัวแปร presses, leds publish event, telemetry LED จริง gpio.led(n) ตรวจ n แล้วจด leds[n] get_message หยิบทุกรอบ broker broker.hivemq.com บอร์ด: 1883 เว็บ: wss 8884 ไม่เก็บใบล่าสุด หน้าเว็บ telemetry event ส่ง cmd แถวบนคือของที่ขึ้น แถวล่างคือของที่ลง ทั้งสองแถววิ่งอยู่ในลูปเดียวกัน
topic ทาง ส่งเมื่อไร JSON ตัวอย่าง
bento-aiot/team03/event บอร์ด → เว็บ ทันทีที่กด (ผ่านกันเด้งแล้ว) {"ev":"press","presses":4,"id":"team03","n":17}
bento-aiot/team03/telemetry บอร์ด → เว็บ ทุก 2 วินาที {"presses":4,"btn":0,"leds":[1,0,0],"az":9.79,"id":"team03","n":18}
bento-aiot/team03/cmd เว็บ → บอร์ด ตอนมีคนกดบนหน้าเว็บ {"cmd":"led","n":0,"on":1} · {"cmd":"beep"}

team03 คือตัวอย่าง ใช้รหัส TEAM ของคุณ · n คือเลขใบที่เดินต่อกันทุกใบ อีกฝั่งเห็นเลขกระโดดก็รู้ว่ามีใบหาย · ตัวเลขในตารางเป็นตัวอย่างรูปร่าง ไม่ใช่ค่าที่วัดมา

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

แกะ 07_button_to_broker.py — สามงานในลูปเดียว

while time.ticks_diff(time.ticks_ms(), t0) < RUN_MS:
    now = time.ticks_ms()
    raw = btn.is_pressed()
    # งานที่ 1: กันเด้งแบบไฟล์ 05
    if raw != last_raw:
        last_raw = raw; last_change = now
    elif raw != stable and time.ticks_diff(now, last_change) >= DEBOUNCE_MS:
        stable = raw
        if stable:                               # ส่งเฉพาะขอบ "เริ่มกด"
            presses = presses + 1
            if not send(TOPIC_EVENT, {"ev": "press", "presses": presses}):
                lost = True; break
    # งานที่ 2: telemetry ตามนาฬิกา ไม่ใช่ตาม sleep
    if time.ticks_diff(now, last_state) >= STATE_MS:
        last_state = now
        state = {"presses": presses, "btn": 1 if stable else 0, "leds": leds}
        if not send(TOPIC_STATE, state):
            lost = True; break
    # งานที่ 3: หยิบคำสั่งทุกรอบ กล่องรับมีช่องเดียว
    msg = mqtt.get_message()
    if msg is not None:
        handle(msg[1])
    if not mqtt.is_connected():
        lost = True; break
    ui.poll()
    time.sleep_ms(POLL_MS)
  • โครงนี้คือลูปเดิมของวันนี้ ที่เพิ่มมามีแค่ send() สองจุดกับ get_message() หนึ่งจุด · ; ใช้บีบบรรทัดบนสไลด์เท่านั้น
  • leds มาจากตัวแปร handle() สั่ง gpio.led(i).on() แล้วจด leds[i] ทันที ไม่มีบรรทัดไหนถามขา
  • send() ดักสองทาง: คืน False = ใบนั้นหาย สายยังอยู่ · โยน OSError = สายหลุด ออกจากลูป ดับไฟ บอกบนจอ
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

สี่เรื่องของเฟิร์มแวร์ตัวนี้ — โค้ดเราจึงหน้าตาแบบนี้

สิ่งที่เฟิร์มแวร์ทำ ที่มาในซอร์ส ถ้าไม่รู้จะเจออะไร 07 รับมืออย่างไร
publish() ส่งแบบ retain = false เสมอ แม้ docstring จะเขียนว่ารับ retain= modmqtt.c:316 เปิดหน้าเว็บทีหลังแล้วจอว่าง เพราะ broker ไม่ได้เก็บใบล่าสุดไว้ให้ใคร ส่ง telemetry ซ้ำทุก 2 วินาที หน้าเว็บที่เปิดช้าที่สุดก็ตามทันภายใน 2 วินาที
get_message() เป็นกล่องช่องเดียว ใบใหม่ทับใบเก่า modmqtt.c:80-98 ตอนรับ · :385-400 ตอนหยิบ กดปุ่มบนหน้าเว็บรัว ๆ สามครั้ง บอร์ดเห็นใบเดียว หยิบทุกรอบลูป รอบละ 5 ms ไม่หลับยาวที่ไหนเลย
publish() ตอนสายหลุดโยน OSError ไม่ได้คืน False modmqtt.c:292-294 โปรแกรมพังกลางบทเรียนพร้อม traceback send() ดัก OSError แล้วคืน False ให้ลูปหยุดอย่างสุภาพ
ไม่ต่อใหม่ให้เอง และ clean_session เป็นจริงเสมอ (broker ไม่จำว่าเราเคยขอฟังอะไร) modmqtt.c:256 · ไม่มีโค้ดต่อใหม่ในโมดูล สายหลุดแล้วบอร์ดเงียบ คนดูคิดว่าไม่มีใครกดปุ่ม ถาม is_connected() ทุกรอบ หลุดเมื่อไรขึ้นแดงบนจอ แล้วให้รันใหม่

อีกหนึ่งกฎที่ทั้งห้องต้องรู้: client_id ซ้ำกันไม่ได้ บน broker ตัวเดียวกัน ใครต่อทีหลังจะเตะคนก่อนหลุด (ทดลองแล้วบน broker สาธารณะทั้งสองตัว) ไฟล์ 07 ใช้ "bento-aiot-" + TEAM จึงรันได้บอร์ดเดียวต่อทีม ส่วนหน้าเว็บสุ่มชื่อ web-... เอง เปิดกี่แท็บก็ไม่เตะบอร์ด

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

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

เปิดหน้าเว็บของทีม — my_first_reader.html

ไม่ต้องติดตั้งอะไรเลย ใช้แค่เบราว์เซอร์ (มือถือก็ได้)

  1. เปิดลิงก์นี้ เปลี่ยน team05 ท้ายลิงก์เป็นรหัสของคุณ ไม่ต้องบันทึกหรือแก้ไฟล์ · หน้านี้บนเว็บ AIC รับเฉพาะรหัสรูป teamNN ถ้าใช้รหัสของตัวเองอย่าง nok4821 ให้ดาวน์โหลด my_first_reader.html ของรีโพนี้ไปเปิดในเบราว์เซอร์แทน

https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/my_first_reader.html?team=team05

// ทางเลือก: ดาวน์โหลด shared/web/my_first_reader.html ของรีโพนี้ไปเปิดเอง แล้วแก้บรรทัดนี้แทนการต่อท้ายลิงก์
let TEAM = "teamXX";          // รหัสเดียวกับ TEAM ในไฟล์ Python เช่น "nok4821" (เรียนเป็นกลุ่มใช้เลขที่ผู้จัดแจก เช่น "team05")
  1. บรรทัดสถานะบนหน้าเว็บต้องขึ้น "ต่อแล้ว (bento-aiot/team05)" ตามรหัสของคุณ
  2. รัน 07_button_to_broker.py บนบอร์ด ภายใน 2 วินาทีกล่องค่าจะขึ้น presses btn leds (และ az ถ้าอ่าน IMU ได้)
  3. กดปุ่มบนบอร์ด ช่อง "เหตุการณ์ล่าสุด" เปลี่ยนทันที · กด "เปิดไฟ LED 0" บนหน้าเว็บ leds ใบถัดไปเปลี่ยนตาม (บน Dev Kit ดวง 0 อยู่บนโมดูล อาจมองไม่เห็นหลอด แต่ leds บนหน้าเว็บยังเปลี่ยนให้เห็น · อยากสั่งดวง 2 ซึ่งเป็นดวง RGB มีทั้งสองบอร์ด ให้ใช้ไฟล์ที่ดาวน์โหลดแล้วแก้ปุ่มเป็น n: 2)

หน้าเว็บของทั้งห้องคือ mqtt_dashboard.html ผู้สอนฉายขึ้นจอ ทุกทีมเป็นการ์ดหนึ่งใบ https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/mqtt_dashboard.html

สมาชิกที่นั่ง Emulator — Emulator ต่อ broker จริงได้แล้ว รันไฟล์ 07 บน Emulator ด้วย TEAM ของทีมก็ขึ้นหน้าเว็บเดียวกัน (Emulator เติม -emu ท้าย client_id จึงไม่เตะบอร์ดจริง) หรือเปิดหน้าเว็บนี้ดูและสั่งบอร์ดของทีมจากที่นั่งตัวเองก็ได้

ต่อไม่ติด: เปลี่ยนทั้งสองฝั่งไปตัวสำรอง test.mosquitto.org (บอร์ด 1883 · เว็บ wss://test.mosquitto.org:8081/mqtt) · เครือข่ายขององค์กรยังไม่เคยทดสอบ อาจปิดพอร์ตใดพอร์ตหนึ่งไว้ · ห้าม subscribe # บน broker สาธารณะ ฟังเฉพาะ bento-aiot/<ทีม>/#

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

งานประยุกต์บทเรียน 1.4–1.6 + 2.1–2.3 (ไม่บังคับ) — ส่งของของทีมขึ้น broker แล้วให้หน้าเว็บแสดง

โจทย์ เลือกค่าหนึ่งตัวที่ทีมอยากให้คนนอกห้องเห็น แล้วทำให้มันไปถึงหน้าเว็บ

  • เลือกของ ค่าจากเซนเซอร์ (az มีให้แล้ว ลองแกนอื่นหรือเซนเซอร์อื่น) หรือสถานะ GPIO (ปุ่มค้างนานแค่ไหน · ดวงไหนติดอยู่ · กดยาวหรือกดสั้น)
  • ส่ง เพิ่ม key สั้น ตัวเล็ก ลงใน state ของไฟล์ 07 ไปกับ telemetry ทุก 2 วินาที ถ้าเป็นเหตุการณ์ที่เกิดทีเดียว ส่งเป็น event แทน
  • อ่าน หน้าเว็บวาดกล่องให้ทุก key อยู่แล้ว งานของทีมคือแก้ my_first_reader.html ให้ตอบสนองค่านั้น เช่น เปลี่ยนสีเมื่อเกินเกณฑ์ หรือเพิ่มปุ่มสั่งไฟดวงอื่น
  • กลับทาง (ไม่บังคับ) เพิ่มคำสั่งใหม่ใน handle() แล้วเพิ่มปุ่มบนหน้าเว็บที่ส่งมัน

ทำครบเมื่อ (การบ้านหรือทำเมื่อเสร็จก่อนเวลา ไม่อยู่ในเกณฑ์ผ่าน)

  • [ ] telemetry ของทีมขึ้นทุก 2 วินาที มี id n และค่าที่ทีมเลือกอย่างน้อยหนึ่งตัว
  • [ ] กดปุ่มบนบอร์ดสิบครั้ง หน้าเว็บเห็น presses เพิ่มสิบพอดี
  • [ ] สั่งไฟดวง 2 จากหน้าเว็บแล้วไฟบนบอร์ดติด และ leds ใน telemetry ใบถัดไปเปลี่ยนตาม
  • [ ] หน้าเว็บของทีมตอบสนองค่าที่เลือกอย่างน้อยหนึ่งอย่าง (แก้เอง ไม่ใช่แค่แสดงดิบ)
  • [ ] อธิบายได้ว่าทำไมต้องกันเด้งก่อน publish และทำไมส่ง leds จากตัวแปร ไม่ใช่จากขา

จดลงบันทึกการเรียน · ถ้าเจอพฤติกรรมที่ไม่ตรงกับสไลด์ จดไว้แล้วบอกผู้สอน นั่นคือผลการวัด ไม่ใช่ความผิดของทีม

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

เกมกดเร็วทั้งห้อง — จับเวลาที่ไหนถึงยุติธรรม

บอร์ด: 08_class_race.py (แก้ WiFi กับ TEAM จาก teamXX เหมือนไฟล์ 07 · เปิดเกมไว้ 45 นาที) · จอหน้าห้อง: class_game.html ผู้สอนเปิดลิงก์นี้ฉายขึ้นจอ https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/class_game.html

  1. ผู้สอนกด เริ่มรอบ คำสั่งใบเดียวไปถึงทุกบอร์ดทาง bento-aiot/all/cmd
  2. บอร์ดแต่ละทีมสุ่มรอเอง 1.5 ถึง 4 วินาที จอขึ้น "เตรียม... อย่าเพิ่งกด" พร้อมเสียงต่ำ
  3. ไฟทุกดวงติด จอขึ้น "กดเลย" กดปุ่มให้เร็วที่สุด บอร์ดจับเวลาจากไฟติดถึงนิ้วกด แล้วส่งผลขึ้น event ของทีม
  4. กดก่อนไฟติด หรือเร็วกว่า 100 ms = ออกตัวก่อน (เกณฑ์ออกตัวของกรีฑาโลก เป็นกติกา ไม่ใช่กฎธรรมชาติ) · ไฟติดแล้วไม่กดใน 3 วินาที = พลาดรอบ

เปลี่ยนคนนั่งบอร์ดทุกรอบ เล่นหกรอบ ทีมสี่คนได้นั่งบอร์ดครบทุกคนภายในสี่รอบแรก · ระหว่างรอบ ทั้งห้องช่วยกันกดให้ตัวนับรวมบนจอหน้าห้องถึง 300

ทายก่อน — ทีมที่ผลขึ้นจอหน้าห้องก่อน คือทีมที่เร็วที่สุด ☐ ใช่ ☐ ไม่ใช่ เพราะ ________ (เขียนลงบันทึกการเรียน ก่อนรอบแรก)

ระหว่างรอบ ใครส่ง led หรือ beep เข้าบอร์ดเพื่อแกล้งทีมอื่นก็ไม่ได้ผล ไฟล์ 08 ปฏิเสธเองอยู่แล้ว ลองหาดูว่าบรรทัดไหน · คนที่นั่ง Emulator กดปุ่มตอนว่างเพื่อเล่นรอบซ้อมได้ แต่ Emulator ไม่ได้ยินคำสั่งจากหน้าห้อง (ไฟล์นี้ยังไม่เคยลองบน Emulator)

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

ทำไมผลที่มาถึงก่อน ไม่ใช่ผลที่เร็วที่สุด

ผู้สอนกดเริ่มรอบพร้อมกัน แต่สองทีมรอไม่เท่ากัน (ตัวเลขสมมติ) ทีม A สุ่มรอ 3.6 วินาที 180 ms ถึงที่ 2 ทีม B สุ่มรอ 1.9 วินาที 290 ms ถึงที่ 1 ไฟติดถึงนิ้วกด (บอร์ดวัดเฉพาะช่วงนี้) ข้อความเดินทางไปถึงจอหน้าห้อง

จอหน้าห้องมีสองคอลัมน์ เวลาบนบอร์ด กับ มาถึงลำดับ และจอเรียงอันดับตามเวลาบนบอร์ด ไม่ใช่ตามลำดับที่มาถึง

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

กันเด้ง 40 ms ทำให้บอร์ด "เชื่อ" ช้ากว่านิ้ว 40 ms ไฟล์ 08 จึงจดเวลาตอนขาเริ่มเปลี่ยน แล้วใช้เวลานั้นเมื่อกันเด้งยืนยันแล้ว ลองข้อ 2 ในบล็อก "ตาคุณ" ท้ายไฟล์แล้ววัดเอง

หน้าเว็บไม่นับผลที่มาถึงเร็วเกินกว่าที่เป็นไปได้ (เร็วกว่าเวลารอขั้นต่ำบวกเวลาที่อ้าง) แต่คนที่ปลอมผลแล้วรอนานพอ หน้าเว็บจับไม่ได้ เพราะใครก็ส่งเข้าหัวข้อของทีมไหนก็ได้บน broker สาธารณะ บทเรียน 4.4–4.6 กับ 4.7–4.9 เราจะปิดช่องนี้

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

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

วันนี้เราได้:
สั่ง LED จริงด้วยโค้ดของเราเองเป็นครั้งแรก · อ่านปุ่มจริงด้วย polling และเข้าใจว่าทำไมต้องกันเด้ง · แยกออกระหว่าง sleep_ms กับ ticks_diff และรู้ว่าเมื่อไรควรใช้อันไหน · เห็นกับดักชื่อของเฟิร์มแวร์กับตาตัวเอง และรู้ว่าทำไมปุ่มถึงชื่อ USER Button 1 · (Eva Kit) เห็น IPC ทำงานผ่านหน้า Controls โดยไม่ต้องเขียนโค้ดเพิ่ม

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

ชุดบทเรียนถัดไป: ปุ่มจริงหนึ่งปุ่มมันน้อยไป เราจะสร้างปุ่มบนจอสัมผัสเองด้วย ui.Button และ ui.Switch แล้วเอามาคุม LED สามสี (แดง เขียว น้ำเงิน) ของบอร์ด — พร้อมกฎเหล็กห้าข้อของการใช้ ui · และปุ่มบนจอพวกนั้นจะ publish ขึ้น bento-aiot/<ทีม>/event แบบเดียวกับปุ่มจริงวันนี้ ส่วนคำสั่งจากหน้าเว็บจะขยับ widget บนจอได้

วันนี้ · อินพุตหนึ่งตัว ปุ่มจริง 1 ปุ่ม → LED ทุกดวง บทเรียน 2.4–2.6 · อินพุตบนจอ ui.Button ×3 + Switch → LED สามสี โครงลูปไม่เปลี่ยน เปลี่ยนแค่ว่าอินพุตมาจากไหน

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

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

เฉลย s03_led_button.py — ส่วนที่หนึ่ง: ค่าคงที่

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

import gpio
import lcd
import time
import ui

STEP_MS = 150          # จังหวะไฟวิ่ง ปรับตรงนี้เพื่อเปลี่ยนความเร็ว
DEBOUNCE_MS = 40       # เวลาที่ปุ่มต้องนิ่งก่อนเราจะเชื่อ
POLL_MS = 5            # ความถี่ที่ลูปถามปุ่ม
RUN_MS = 30000         # อายุของโปรแกรมรอบนี้

NUM_LEDS = gpio.num_leds()
btn = gpio.button(0)
ค่าคงที่สี่ตัว = หน้าปัดของโปรแกรม ปรับได้โดยไม่ต้องอ่านตรรกะข้างล่างเลย STEP_MS = 150 ความเร็วไฟวิ่ง DEBOUNCE_MS = 40 ความอดทนก่อนเชื่อ POLL_MS = 5 ความถี่ที่ถามปุ่ม RUN_MS = 30000 อายุของโปรแกรม

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

NUM_LEDS = gpio.num_leds() ดีกว่าเขียน 3 ตรง ๆ เพราะย้ายไป Dev Kit ที่มีไฟห้าดวง โค้ดนี้วิ่งครบทุกดวงเองโดยไม่ต้องแก้ — ในห้องนี้มีทั้งสองบอร์ด ไฟล์เดียวกันจึงต้องถูกทั้งคู่

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

เฉลย — ส่วนที่สอง (1/2): รายงาน ดับไฟ แล้วเตรียมตัวแปร

ตั้งต้น — รายงานสิ่งที่รู้ ดับไฟให้หมด ประกาศตัวแปรสถานะ แล้วล้างจอ (ตัดจาก s03_led_button.py ตรง ๆ)

info = gpio.board_info()
lcd.clear()
lcd.console("<h2>AIoT in Action - ชุด 3</h2>")
lcd.print("บอร์ด:", info["name"], "| LED:", info["leds"], "| ปุ่ม:", info["buttons"])
...
lcd.print("<span class=muted>ปุ่มผู้ใช้มีตัวเดียว ดัชนี 0</span>")
lcd.print("<span class=muted>โค้ดเรียกมันว่า " + btn.name() + "</span>")
...
for i in range(NUM_LEDS):
    gpio.led(i).off()
...
led_index = 0          # ตอนนี้ไฟดวงไหนกำลังติด
count = 0              # จำนวนครั้งที่กดปุ่ม
raw = False            # ค่าดิบของปุ่มรอบนี้
last_raw = False       # ค่าดิบของปุ่มรอบก่อน
stable = False         # ค่าปุ่มที่ผ่านการกันเด้งแล้ว
...
COL_TEXT, COL_DIM = 0xE8EAED, 0x9AA3AF
COL_CARD, COL_OK, COL_RUN = 0x171B22, 0x30A46C, 0x4A9EFF
UI_MS = 100            # จอถูกอัปเดตทุก 100 ms ไม่ใช่ทุกรอบลูป
...
ui.screen()
time.sleep_ms(200)
ui.Label("แผงคุมไฟวิ่ง - ชุด 3", x=24, y=8, color=COL_TEXT, value=28)
lbl_status = ui.Label("ไฟวิ่งกำลังเดิน", x=360, y=12, color=COL_DIM, value=20)

สองบรรทัด lcd.print ของชื่อปุ่มจงใจแยกกัน — รวมเป็นบรรทัดเดียวยาว 163 ไบต์ เกินเพดาน 127 แล้วถูกตัดทิ้งเงียบ ๆ · ชื่อปุ่มเอาจาก btn.name() ไม่ใช่จากป้ายบนแผ่นวงจร ซึ่งบอร์ดสองรุ่นพิมพ์ต่างกัน

led_index ต้องจำเอง เพราะ gpio.led(n).value() ตอบระดับของขา ณ วินาทีที่ถาม ไม่ใช่สิ่งที่เราสั่ง · ห้าสีบนสุดคือจานสีของหลักสูตร · UI_MS = 100 คือจังหวะเขียนจอ ไม่ใช่จังหวะลูป — ลูปถามปุ่มทุก 5 ms แต่จอไม่ต้องรู้ทุกครั้ง

ค่าที่ต้องปรับบ่อยอยู่บนสุด ตรรกะอยู่ล่าง — เกณฑ์ "ปรับจังหวะได้" จึงเหลือแค่แก้ STEP_MS ตัวเดียวในส่วนที่หนึ่ง

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

เฉลย — ส่วนที่สอง (2/2): แผงบนจอ กล่องยืนยันที่ซ่อนไว้ และนาฬิกาสี่ตัว

PITCH = min(128, 408 // NUM_LEDS)
LED_W = 48 if PITCH >= 128 else 36
led_ui = []
for i in range(NUM_LEDS):
    x = 40 + i * PITCH
    led_ui.append(ui.Led(x=x, y=104, w=LED_W, h=LED_W, color=COL_OK, value=0))
...
btn_run = ui.Button("เดินไฟวิ่ง", x=40, y=268, w=176, h=88, color=0x30A46C, value=20)
btn_stop = ui.Button("หยุดไฟวิ่ง", x=248, y=268, w=176, h=88, color=0x3A4150, value=20)
...
box = ui.MsgBox("ยืนยันหยุด\nไฟทุกดวงจะดับทันที",
                x=48, y=96, w=496, h=160, color=COL_CARD)
box.hide()
...
btn_yes = ui.Button("ยืนยัน", x=568, y=96, w=152, h=88, color=0x3A4150, value=20)
btn_no = ui.Button("ยกเลิก", x=568, y=216, w=152, h=88, color=0x3A4150, value=20)
btn_yes.hide()
btn_no.hide()
...
t0 = time.ticks_ms()
last_step = t0
last_change = t0
last_ui = t0
chase_on = True        # ไฟวิ่งเดินอยู่ไหม - ปุ่มบนจอเป็นคนเปลี่ยนค่านี้
asking = False         # กำลังรอคำตอบจากกล่องยืนยันอยู่ไหม
นาฬิกาสี่ตัว ตั้งต้นจาก ticks_ms() ครั้งเดียว t0 = time.ticks_ms() t0 · อายุโปรแกรม last_step · ไฟวิ่ง last_change · ปุ่ม last_ui · จอ

เวลาสี่ตัวตั้งต้นจาก ticks_ms() ค่าเดียวกัน ถ้าเรียกแยกกัน รอบแรกจะเพี้ยนไปไม่กี่มิลลิวินาที · แผงบนจอสร้าง ครั้งเดียวก่อนเข้าลูป — งบ widget ของคอร์สคือ 32 ต่อหน้า (เพดานเฟิร์มแวร์ 64) และการสร้างซ้ำทุกรอบคือการยิง IPC ทิ้งเปล่า ๆ 200 ครั้งต่อวินาที · PITCH คิดจาก NUM_LEDS — 3 ดวงได้ 128 · 5 ดวงได้ 81 ไฟล์เดียวกันวางพอดีทั้งสองบอร์ด

btn_run กับ btn_stop เป็น คนละปุ่ม โดยตั้งใจ (สูง 88 px ตามระยะนิ้วจริง) ปุ่มสลับปุ่มเดียวบอกไม่ได้ว่าตอนนี้อยู่สถานะไหน · กล่องยืนยันกับปุ่มคำตอบสองปุ่มถูก สร้างแล้วซ่อนไว้ ตั้งแต่ต้น — การสร้างของตอนคนกำลังรอคำตอบ คือการเพิ่มความหน่วงในจังหวะที่แย่ที่สุด · จุดเริ่มเวลาที่ตรงกัน ทำให้ตอนดีบักไม่ต้องสงสัยว่าความคลาดเคลื่อนมาจากไหน

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

เฉลย — ส่วนที่สาม (1/2): ลูปหลักทั้งก้อน

while time.ticks_diff(time.ticks_ms(), t0) < RUN_MS:
    now = time.ticks_ms()

    if chase_on and time.ticks_diff(now, last_step) >= STEP_MS:
        gpio.led(led_index).off()
        led_index = (led_index + 1) % NUM_LEDS
        gpio.led(led_index).on()
        last_step = now
        for k in range(NUM_LEDS):
            led_ui[k].value(1 if k == led_index else 0)

    raw = btn.is_pressed()
    if raw != last_raw:
        last_raw = raw
        last_change = now
    elif raw != stable and time.ticks_diff(now, last_change) >= DEBOUNCE_MS:
        stable = raw
        if stable:
            count += 1
            lcd.print("กดครั้งที่", count)

    if time.ticks_diff(now, last_ui) >= UI_MS:
        last_ui = now
        led_btn.value(1 if stable else 0)
        left_s = (RUN_MS - time.ticks_diff(now, t0)) // 1000
        if left_s != last_sec:
            last_sec = left_s
            seg_count.text(str(count))
            lbl_left.text(str(left_s) + " วิ")
            bar_left.value(left_s)
        for ev in ui.poll():
            ...        # ปุ่มไหนถูกกด แล้วทำอะไรต่อ - อยู่ในไฟล์เฉลยเต็ม

    time.sleep_ms(POLL_MS)

now = time.ticks_ms() เรียกครั้งเดียวต่อรอบ แล้วใช้ร่วมกันทั้งสามงาน — ถ้าเรียกใหม่ทุกจุด เวลาของงานไฟกับงานปุ่มจะไม่ใช่เวลาเดียวกัน

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

เฉลย — ส่วนที่สาม (2/2): สี่จังหวะในหนึ่งรอบ

งานที่สามคือ จอ และมันเดินที่ 100 ms ไม่ใช่ 5 ms — ui.poll() ทุกรอบลูปคือการยิง IPC ข้ามคอร์ 200 ครั้งต่อวินาที เพื่อรอนิ้วที่มาถึงวินาทีละครั้ง

if left_s != last_sec: คือกฎ ตัวเลขที่คนต้องอ่าน เขียนใหม่ไม่เกินวินาทีละครั้ง ตัวเลขที่กระพริบสิบครั้งต่อวินาทีอ่านไม่ได้ และไม่มีใครได้ประโยชน์จากมัน ส่วนแถบกับไฟขยับเร็วกว่านั้นได้ เพราะตาอ่านรูปทรงไม่ได้อ่านหลัก

time.sleep_ms(POLL_MS) อยู่ท้ายสุดของลูปเสมอ เป็นจุดเดียวที่โปรแกรมยอมพัก

หนึ่งรอบลูป = สี่จังหวะ วนซ้ำจนครบ RUN_MS now = ticks_ms() อ่านนาฬิกาครั้งเดียว ถึงคิวไฟไหม ครบ 150 ms → ขยับ ปุ่มขยับไหม นิ่งครบ 40 ms → นับ พัก 5 ms คืนซีพียูให้งานอื่น พูดออกมาดัง ๆ ว่า "ถึงเวลาขยับไฟไหม แล้วปุ่มขยับไหม แล้วพัก" — ถ้าพูดได้ แปลว่าเข้าใจแล้ว

อ่านลูปนี้จากบนลงล่างแล้วพูดออกมาดัง ๆ ว่า "ถึงเวลาขยับไฟไหม แล้วปุ่มขยับไหม แล้วพัก" — ถ้าพูดได้ แปลว่าเข้าใจแล้ว

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

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

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

ท่า 2 ดับให้หมด มาก่อนลูป เพื่อให้ทุกการรันเริ่มจากจุดเดียวกัน ผลการทดลองถึงจะเทียบกันได้

ท่า 3 ไฟวิ่ง มาก่อนปุ่ม เพราะไฟวิ่งเป็นงานที่ ไม่ต้องพึ่งอินพุตจากใคร ถ้าไฟวิ่งได้ แปลว่าโครงลูปกับนาฬิกาถูกแล้ว

ท่า 4 ปุ่ม มาทีหลัง ตอนนี้ถ้าพัง เรารู้แน่ว่าพังที่เรื่องปุ่มหรือการกันเด้ง ไม่ใช่ที่โครงลูป

ท่า 5 ปิดท้าย มาสุดท้าย เพราะมันคือคำแถลงว่าทุกอย่างข้างบนผ่านหมดแล้ว

ไล่จากสิ่งที่ยืนได้ด้วยตัวเอง ไปหาสิ่งที่ต้องพึ่งของอื่น 1 2 3 4 5 ถามบอร์ด ดับให้หมด ไฟวิ่ง ปุ่ม + กันเด้ง ปิดท้าย ไม่พึ่งใคร พึ่งท่า 1 พึ่งนาฬิกา พึ่งโครงลูป พึ่งทุกอย่าง เทคนิคนี้ชื่อ bring-up ทีละชั้น — วิศวกรฮาร์ดแวร์ใช้ตอนบอร์ดใหม่มาถึงโต๊ะ

หลักการเดียวกับบทเรียน 1.1–1.3: ไล่จากสิ่งที่ยืนได้ด้วยตัวเอง ไปหาสิ่งที่ต้องพึ่งของอื่น แล้วให้แต่ละขั้นยืนยันขั้นก่อนหน้า

เทคนิคนี้ชื่อว่า bring-up ทีละชั้น วิศวกรฮาร์ดแวร์ใช้ตอนบอร์ดใหม่มาถึงโต๊ะ และมันใช้ได้กับซอฟต์แวร์เหมือนกัน

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

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

บทเรียน 1.1–1.6 · ผ่านมาแล้ว เล่นของที่มีอยู่ พิมพ์ข้อความขึ้นจอ วันนี้ · บทเรียน 2.1–2.3 ลูปแรกที่สั่งของจริง ไฟ ปุ่ม เวลา บทเรียน 2.4–3.9 อินพุตเยอะขึ้น จอสัมผัส เซนเซอร์ กราฟ บทเรียน 4.1–5.3 ส่งขึ้นเครือข่าย แล้วสร้างของจริง โครงลูป อ่าน-ตัดสิน-สั่ง ของวันนี้ ไม่เปลี่ยนอีกเลยจนจบคอร์ส

คำถามคิดต่อ: ถ้าเปลี่ยนจาก "อ่านปุ่ม" เป็น "อ่านความเร่งจาก IMU" ลูปนี้ต้องแก้กี่บรรทัด · การกันเด้งของปุ่ม กับการกรองสัญญาณเซนเซอร์ที่จะเจอบทเรียน 2.7–2.9 เหมือนกันตรงไหน · ถ้าต้องคุมของ 20 ชิ้นพร้อมกัน โครงแบบนี้ยังไหวไหม

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

ใช้จริงที่ไหน — ลูปแบบนี้ทำงานอยู่ตรงไหนบ้าง

โรงงาน · ปุ่มหยุดฉุกเฉิน อ่านปุ่มถี่ ๆ กันเด้งเข้ม แล้วตัดไฟทันที พลาดหนึ่งครั้งคือคนบาดเจ็บ ไม่ใช่แค่ตัวเลขเพี้ยน เหมือนของเรา: polling + debounce + edge detection ยานยนต์ · ไฟเลี้ยวและไฟสถานะ ไฟกะพริบตามจังหวะที่กำหนดเป๊ะ ขณะที่ยังต้องอ่านสวิตช์อื่นได้ตลอด เหมือนของเรา: ticks_diff แทน sleep ยาว เครื่องมือแพทย์ · แผงสถานะ ไฟไล่วนบอกว่าเครื่องยังทำงาน ไม่ได้ค้าง ไฟหยุดวิ่งเมื่อไร คนไข้กับพยาบาลรู้ทันที เหมือนของเรา: chaser คือสัญญาณว่าลูปยังมีชีวิต เครื่องใช้ในบ้าน · ปุ่มกดสั้น-กดยาว ปุ่มเดียวทำสองหน้าที่ วัดจากเวลาที่กดค้าง ลดจำนวนปุ่ม ลดต้นทุน ลดรูบนฝาเครื่อง เหมือนของเรา: ticks_diff ระหว่างขอบเข้า-ขอบออก

ทั้งสี่ช่องใช้โครงโค้ดเดียวกับที่เราเพิ่งเขียน ต่างกันที่ค่าเวลาและสิ่งที่ต่อปลายสาย

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

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

ทั้งสี่ข้อต่อยอดจากลูปเดิม — ไม่มีข้อไหนต้องเขียนโครงใหม่ เลือกทำหนึ่งข้อ แล้วจดลงบันทึกการเรียน 1 · ปุ่มเปลี่ยนความเร็ว 400 → 200 → 100 ms 2 · รูปแบบไฟของทีม ไป-กลับ หรือกะพริบพร้อม 3 · กดสั้น กดยาว เส้นแบ่งที่ 800 ms 4 · ทดลองค่ากันเด้ง 0 · 5 · 40 · 200 ms

ข้อ 1 · ปุ่มเปลี่ยนความเร็ว — ให้การกดปุ่มวนเปลี่ยนจังหวะไฟวิ่งระหว่าง 400, 200 และ 100 ms โดยตัวนับยังทำงานถูกต้อง · ใบ้: เก็บความเร็วเป็น list แล้วใช้ % แบบเดียวกับที่ใช้วนไฟ

ข้อ 2 · รูปแบบไฟของทีม — ออกแบบลำดับไฟเป็น list ที่สร้างจาก gpio.num_leds() เช่น ไป-กลับ 0..N-1..1 (Eva 3 ดวงได้ 0,1,2,1 · Dev Kit 5 ดวงได้ 0,1,2,3,4,3,2,1) หรือกะพริบพร้อมกันสลับกับวิ่ง แล้วบันทึกว่าทีมเลือกแบบไหนเพราะอะไร

ข้อ 3 · แยกกดสั้นกับกดยาว — วัดเวลาที่กดค้างด้วย ticks_diff ระหว่างขอบขาเข้ากับขาออก เกิน 800 ms ถือเป็น "กดยาว" แล้วสั่งงานคนละอย่าง

ข้อ 4 · ทดลองค่ากันเด้ง — ตั้ง DEBOUNCE_MS เป็น 0, 5, 40 และ 200 อย่างละรอบ กดค่าละสิบครั้ง จดตัวเลขจริงเป็นตาราง แล้วสรุปว่าค่าไหนเหมาะกับปุ่มบนบอร์ดนี้

อยากหรี่ไฟด้วย ให้ต่อยอดจาก 03_led_brightness.py — brightness(pct) ค่ากลางค้างระดับได้บนดวง RGB (มีเส้น PWM ทั้งสองบอร์ด) และไม่บล็อก ส่วนดวงที่ไม่มีเส้นนั้นได้พัลส์ราว 12 ms แล้วดับ ท่าที่ค้างได้ทุกดวงคือ led.hold(pct, ms) ซึ่งย้ำพัลส์จนครบเวลาที่สั่ง (ms ไม่ใส่ได้ ปริยาย 500) แต่จำไว้ว่ามันบล็อกจนครบ ms และจบด้วยหลอดดับ — ถ้าเพิ่งสั่ง brightness() ค่ากลางบนดวง PWM ต้องเรียก off() คั่นก่อน hold() ไม่งั้นขายังถูก PWM ถืออยู่ hold() ไม่เห็นผลและหลอดไม่ดับตอนจบ · ทีมที่ทำข้อนี้ต้องแบ่งเวลาให้ตัวนับยังเดินถูก · และอย่าเผลอใช้ duty() เป็นตัวตรวจ มันคืนเลขที่สั่งไป ไม่ได้วัดหลอด

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

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (1/2)

หน้าจอของ 01_board_info.py: ถามบอร์ดก่อนว่ามีอะไรให้เล่นบ้าง หน้าจอของ 02_led_blink.py: ทำให้ไฟกะพริบเป็นจังหวะ แล้วนับรอบที่กะพริบไปแล้ว หน้าจอของ 03_led_brightness.py: หรี่ไฟค้างไว้ให้นานพอที่ตาจะเทียบสองระดับได้

01 ถามบอร์ดก่อนว่ามีอะไรให้เล่นบ้าง · 02 ทำให้ไฟกะพริบเป็นจังหวะ แล้วนับรอบที่กะพริบไปแล้ว · 03 หรี่ไฟค้างไว้ให้นานพอที่ตาจะเทียบสองระดับได้ · ภาพ 03 คือจอตอนจบรอบที่ให้เทียบ 10 กับ 90 ระหว่างรัน ไฟล์ให้มองหลอดตอน brightness(40) ก่อน
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (2/2)

หน้าจอของ 04_button_active_low.py: ปุ่มนี้ 0 คือกด ไม่ใช่ 1 หน้าจอของ 05_debounce_count.py: นับการกดให้ตรง ด้วยการรอให้ปุ่มนิ่งก่อน หน้าจอของ 06_button_picks_led.py: ปุ่มเดียวคุมไฟทุกดวง ด้วยการจำสถานะเอง

**04** ปุ่มนี้ 0 คือกด ไม่ใช่ 1 · **05** นับการกดให้ตรง ด้วยการรอให้ปุ่มนิ่งก่อน · **06** ปุ่มเดียวคุมไฟทุกดวง ด้วยการจำสถานะเอง · ชื่อปุ่มบนจอมาจาก `btn.name()` (= "USER Button 1")
ภาพจากตัวจำลอง bento\_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดง**หน้าจอที่ตัวอย่างสร้าง** ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
**07** ปุ่มกับไฟขึ้น broker และ **08** เกมกดเร็วทั้งห้อง ยังไม่มีภาพหน้าจอ ไฟล์ทั้งสองเขียนขึ้นคืนก่อนสอน
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

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

เอกสารของผู้ผลิตและตำรา — KIT_PSE84_EVAL PSOC™ Edge E84 Evaluation Kit guide (Eva Kit), Infineon 002-39007 Rev.*B: §3.2.2.15 User LEDs (หน้า 88–89, รูปที่ 78 ให้ค่า 220/200/2.4 kΩ) · §3.2.2.16 Reset and user buttons (หน้า 89–90, รูปที่ 80 แสดงว่า R411/R408 กับ C230/C229 เป็น DNI คือไม่ได้ลงอุปกรณ์) · Practical Electronics for Inventors, 4th ed. §2.12, §5.3.4, §13.5.1

ปุ่มเด้งและ pull-up — A Guide to Debouncing, Jack Ganssle (วัดสวิตช์จริง 16 ตัว bounce แย่สุด 157 ms, แนะช่วง 20–50 ms) https://www.ganssle.com/debouncing.htm · Pico Technology https://www.picotech.com/library/articles/blog/what-is-switch-bounce-how-to-implement-debounce · Pull-up Resistors, SparkFun (CC BY-SA 4.0) · ภาษาไทย IoT Engineering Education, KMUTNB (CC BY-SA 4.0) https://iot-kmutnb.github.io/blogs/arduino/arduino_avr_io_follower/

วิดีโอที่ตรวจแล้ว (ความยาวยังไม่ยืนยันทั้งหมวด) — AddOhms #15 pull-up wxjerCHCEMg · DigiKey เลือกตัวต้านทานให้ LED UWx2BEx7xyI · KruNarut (ไทย) U9JYM1VdF5U · DigiKey switch bounce IvU8m_30iK0

เครดิตภาพ วงจร LED และภาพบอร์ด: KIT_PSE84_EVAL user guide รูปที่ 78 (หน้า 89) และรูปที่ 2 (หน้า 9) — ใช้เพื่อการเรียนการสอน · Wikimedia Commons: Tomoldbury (สาธารณสมบัติ) · Super Rad! (CC0 1.0) · ไดอะแกรมที่เหลือวาดขึ้นใหม่สำหรับหลักสูตรนี้ โดยอ้างค่าจากคู่มือบอร์ด
ข้อเท็จจริงเรื่อง `gpio` (ชื่อ RGB_RED บน Eva และ "USER Button 1" กับเหตุผลของมัน, ตาราง PWM ของดวง RGB, `led(2).value()` อ่านกลับได้จริงผ่าน `Cy_GPIO_Read()` แต่หลัง `hold()` ขาถูกทิ้งไว้ต่ำ, `gpio.button(1)` โยน ValueError) ตรวจจากซอร์ส `modgpio.c` โดยตรง
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0