ข้ามไปยังเนื้อหา

หลังไฟและปุ่ม: active-low กันเด้ง และลูปที่ไม่หยุด

โมดูล 2 — จากจอสู่ฮาร์ดแวร์ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

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

เมื่อจบบทเรียนนี้ คุณจะ:

  1. อธิบายวงจรไฟ active-high (ขาชิปสั่ง gate ของ n-MOSFET) และปุ่ม active-low (pull-up) แล้วทายค่า btn.value() กับ btn.is_pressed() ทั้งตอนกดและตอนปล่อยได้ถูก พร้อมคำนวณกระแสหลอดแดงบน Eva Kit จากสูตรตัวต้านทานอนุกรมได้ราว 5.9 mA
  2. ไล่ตรรกะกันเด้งด้วยตัวแปร raw, last_raw และ stable กับ DEBOUNCE_MS = 40 จนกดปุ่มสิบครั้งแล้วนับได้สิบพอดี และบอกได้ว่าทำไม count += 1 ต้องอยู่ใต้ if stable
  3. ให้ไฟวิ่งขยับทุก STEP_MS ด้วย time.ticks_ms() กับ time.ticks_diff() ในลูปเดียวกับที่ถามปุ่มทุก 5 ms โดยไม่มีการกดหลุด และอธิบายได้ว่าทำไม last_step = now ต้องอยู่ใน if และทำไมไม่ลบเวลาตรง ๆ
  4. ให้โปรแกรมจำเองว่าไฟดวงไหนติด (led_index) แทนการถาม value() หรือ duty() ของหลอด และเริ่มกับจบโปรแกรมด้วยการดับไฟทุกดวง

ทบทวนจากบทเรียน 2.1 สามเรื่อง: gpio.led(n) สั่งไฟ btn.is_pressed() ตอบความหมาย ส่วน btn.value() ตอบระดับไฟฟ้าดิบ และ duty() ตอบแค่ตัวเลขที่เราสั่งไป เตรียมบอร์ดหรือ Emulator ที่เปิดหน้า BENTO Playground ค้างไว้ และเปิดบันทึกการเรียนไว้จดตัวเลขจากการกดปุ่ม เพราะบทเรียนนี้ให้ผลการทดลองของทีมเป็นคนตอบหลายคำถาม

รัน 04_button_active_low.py แล้วยังไม่ต้องกดอะไร เลขตัวใหญ่ทางซ้ายเป็น 1 คู่กับคำว่า ปล่อย จากนั้นกดปุ่มผู้ใช้ค้างไว้ เลขตกเป็น 0 คำเปลี่ยนเป็น กดอยู่ และเส้นกราฟตกลงพร้อมกัน ไฟสั่ง 1 แล้วติด แต่ปุ่มกดแล้วได้ 0 บทเรียนนี้เริ่มจากตอบให้ได้ว่าทำไมสองอย่างนี้กลับด้านกัน

ไฟ active-high ผ่าน MOSFET ขาชิปไม่ได้จ่ายกระแสให้หลอดเลย มันต่อไปที่ gate ของ n-MOSFET ซึ่งแทบไม่กินกระแส กระแสของหลอดมาจากราง 3.3 V ผ่านตัวต้านทาน ลงหลอด ลง drain แล้วลงกราวด์ สั่ง 1 ที่ gate คือเปิดประตู หลอดจึงติด บน Eva Kit (ขา P16.7/P16.6/P16.5) หลอด D3 แดงใช้ 220 Ω เมื่อแทนในสูตร $I_F = (3.3 - V_F)/R$ ด้วย $V_F \approx 2.0$ V (ค่าสมมติทั่วไปของ LED แดง) ได้ราว 5.9 mA ส่วน Dev Kit ดวง RGB อยู่ที่ P20.6/P20.5/P20.4 วงจรขับยังไม่ได้เปิดคู่มือตรวจ แต่ฝั่งโค้ดเหมือนกันทุกประการ และเพราะประตูเปิดปิดได้เร็วมาก brightness() จึงหรี่ไฟด้วยการกะพริบ gate ถี่ ๆ ไม่ใช่ลดแรงดัน

ปุ่ม active-low ตอนไม่กด ตัวต้านทาน pull-up ดึงขาไว้ที่ไฟบวก ค่าจึงเป็น 1 พอกด สวิตช์ต่อขาลงกราวด์ ค่าจึงเป็น 0 แบบนี้ขาไม่เคยลอย btn.value() คืนระดับดิบนี้ ส่วน btn.is_pressed() กลับด้านให้แล้ว (กด = True) ใช้ value() เพื่อเข้าใจวงจร และใช้ is_pressed() เขียนตรรกะ

polling และการเด้ง โปรแกรมไม่มีทางรู้เองว่ามีคนกด สิ่งที่ทำได้คือถามซ้ำให้ถี่พอ ทุกรอบมีสามจังหวะ อ่าน → ตัดสิน → สั่ง นิ้วกดเร็วสุดราว 50–80 ms ถามทุก 5–20 ms จึงไม่พลาด แต่ถามทุก 300 ms การกดจะหลุดหายเงียบ ๆ และ time.sleep_ms(5) คือการคืนซีพียูให้จอ เซนเซอร์ และ WiFi ปัญหาถัดมาคือหน้าสัมผัสโลหะเด้งแยกแล้วชนใหม่อยู่ราว 1–20 ms ลูปที่ถามทุก 5 ms เห็นครบทุกจังหวะ ถ้านับทุกครั้งที่ค่าเปลี่ยน เลขจะกระโดดทีละ 3–5 ทั้งที่กดครั้งเดียว บน Eva Kit ที่ว่างสำหรับตัวต้านทาน และตัวเก็บประจุกันเด้งถูกระบุว่าไม่ได้ลงอุปกรณ์จริง (DNI) บอร์ดนี้จึงพึ่ง pull-up ในชิปกับโค้ดของเราล้วน ๆ

กันเด้งด้วยซอฟต์แวร์ คือกฎข้อเดียว: อย่าเชื่อค่าที่เพิ่งเปลี่ยน รอให้นิ่งครบ DEBOUNCE_MS ก่อน ตัวแปรสามตัวต้องแยกให้ออก raw ค่าดิบรอบนี้ · last_raw ค่าดิบรอบก่อน · stable ค่าที่ยอมเชื่อแล้ว ถ้า raw ขยับ ให้จับเวลาใหม่ ถ้านิ่งครบเวลาและต่างจาก stable จึงยอมรับ แล้วนับเฉพาะใต้ if stable: คือนับตอนกดลง ไม่ใช่ตอนปล่อย 40 ms ใช้ได้ดีกับปุ่มทั่วไป น้อยกว่า 10 ยังเด้งหลุด มากกว่า 200 ปุ่มจะรู้สึกหน่วง

นาฬิกาสองแบบในลูปเดียว sleep_ms(150) หยุดโปรแกรมทั้งตัว ระหว่างนั้นกดปุ่มเท่าไรก็ไม่มีใครเห็น ส่วน ticks_ms() คือมิลลิวินาทีนับจากบอร์ดบูต ใช้ตอบว่า “ถึงเวลาหรือยัง” โดยไม่ต้องหยุด และต้องเทียบด้วย ticks_diff(now, last_step) เพราะตัวเลขนี้วิ่งถึงเพดานแล้ววนกลับ ถ้าลบเองจะได้ค่าติดลบมหาศาลตอนวน ลูปของเราจึงหลับสั้น ๆ 5 ms ทุกรอบ แล้วให้ ticks_diff ตัดสินว่าถึงคิวขยับไฟหรือยัง ขยับด้วยลำดับ ดับดวงเดิม → led_index = (led_index + 1) % NUM_LEDS → จุดดวงใหม่ และ last_step = now อยู่ใน if เท่านั้น สุดท้ายโปรแกรมต้อง จำสถานะที่ตัวเองสั่งไว้ (led_index) เพราะ value() ตอบระดับขา ณ วินาทีที่ถาม (หลัง hold() ได้ 0) และ duty() ตอบแค่เลขที่สั่งล่าสุด เริ่มด้วยการดับไฟทุกดวง และจบด้วยการดับไฟทุกดวงเสมอ ลูปวิ่งตาม RUN_MS แทน while True เพื่อคืนบอร์ดให้ทีมรันรอบต่อไปได้

ไล่ตามลำดับนี้ ทายก่อนรันทุกครั้ง แล้วจดผลลงบันทึกการเรียน

  1. 05_debounce_count.py ทายก่อนว่ากดสิบครั้งเลขส้ม (นับดิบ) กับเลขเขียว (นับกันเด้ง) จะต่างกันไหม กดช้า ๆ สิบครั้ง แล้วกดรัว ๆ อีกสิบครั้ง จดส่วนต่างของทั้งสองรอบ ส่วนต่างเป็นศูนย์ไม่ได้แปลว่าปุ่มไม่เด้ง จากนั้นลด DEBOUNCE_MS ลงทีละ 10 แล้วกดสิบครั้งเท่าเดิม จดค่าที่เลขเขียวเริ่มเกินจำนวนครั้งที่กดจริง ตัวเลขนี้คือคำตอบของทีมว่าปุ่มบนบอร์ดของคุณเด้งแค่ไหน
  2. 02_led_blink.py ดูป้ายบนจอเทียบกับหลอดจริง และตัวนับรอบที่เดินขึ้นทีละหนึ่ง สังเกตว่าไฟล์นี้จับจังหวะด้วย sleep_ms และปิดท้ายด้วย off() เพราะถ้าจบด้วย toggle() จำนวนคี่ไฟจะค้างติด ลองคิดว่าถ้าเพิ่มการอ่านปุ่มเข้าไปในลูปนี้ การกดสั้น ๆ จะหายไปช่วงไหน
  3. 06_button_picks_led.py ปุ่มเดียวเลื่อนไฟทีละดวงด้วย current = (current + 1) % N กดจนวนครบรอบ แล้วเทียบแถวบนจอกับหลอดจริง ทุกอย่างบนจออ่านจากตัวแปร current ไม่ได้ถามหลอด ลองเอา if stable: ออกสักครั้ง แล้วดูว่าไฟขยับทีละกี่ดวงต่อการกดหนึ่งครั้ง
ไฟล์ ไฟล์นี้สอน
examples/02_led_blink.py ทำให้ไฟกะพริบเป็นจังหวะ แล้วนับรอบที่กะพริบไปแล้ว
examples/04_button_active_low.py ปุ่มนี้ 0 คือกด ไม่ใช่ 1
examples/05_debounce_count.py นับการกดให้ตรง ด้วยการรอให้ปุ่มนิ่งก่อน
examples/06_button_picks_led.py ปุ่มเดียวคุมไฟทุกดวง ด้วยการจำสถานะเอง

สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นด้วย:

ภาพจอจาก BENTO Emulator ของตัวอย่างในบทนี้ (คลิกชื่อไฟล์เพื่อเปิดโค้ด)

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

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. คุณกดปุ่มผู้ใช้ค้างไว้ แล้วอ่านค่าทั้งสองแบบในรอบเดียวกัน ได้ผลอย่างไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) btn.value() = 1 และ btn.is_pressed() = True
    • ข) btn.value() = 0 และ btn.is_pressed() = True
    • ค) btn.value() = 0 และ btn.is_pressed() = False
    • ง) btn.value() = 1 และ btn.is_pressed() = False
    เฉลย

    ข — ปุ่มเป็น active-low ตอนปล่อย pull-up ดึงขาไว้ที่ 1 พอกด สวิตช์ต่อขาลงกราวด์จึงได้ 0 ส่วน is_pressed() กลับด้านให้แล้ว กดจึงได้ True

  2. ในวงจรหลอด D3 สีแดงบน Eva Kit (ตัวต้านทาน 220 Ω) ข้อใดบรรยายเส้นทางของกระแสได้ถูกต้อง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ขาชิป P16.7 จ่ายกระแสราว 5.9 mA เข้าหลอดโดยตรง ตัวต้านทานมีไว้กันขาชิปพัง
    • ข) ขาชิปต่อที่ gate ของ n-MOSFET ซึ่งแทบไม่กินกระแส กระแสหลอดราว 5.9 mA มาจากราง 3.3 V ผ่านตัวต้านทานและหลอดลงกราวด์
    • ค) ขาชิปลดแรงดันที่หลอดลงเมื่อสั่ง brightness() ค่ากลาง หลอดจึงหรี่
    • ง) สั่ง 0 ที่ขาชิปแล้วหลอดติด เพราะหลอดเป็น active-low เหมือนปุ่ม
    เฉลย

    ข — ขาชิปสั่ง “ประตู” ไม่ได้สั่ง “น้ำ” กระแสหลอดเดินจากราง 3.3 V และแทนสูตร (3.3 - 2.0)/220 ได้ราว 5.9 mA สั่ง 1 ที่ gate หลอดจึงติด ส่วนการหรี่ทำด้วยการกะพริบ gate ถี่ ๆ ไม่ใช่ลดแรงดัน

  3. ทีมหนึ่งลบบรรทัด if stable: ออก ให้ count += 1 ทำงานทุกครั้งที่ stable เปลี่ยน แล้วกดปุ่มช้า ๆ ห้าครั้ง ตัวนับน่าจะได้เท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) ห้า เพราะกันเด้งยังทำงานอยู่
    • ข) สิบ เพราะการปล่อยปุ่มก็เป็นการเปลี่ยนสถานะที่ผ่านการกันเด้งเหมือนกัน
    • ค) สิบห้าถึงยี่สิบห้า เพราะปุ่มเด้งทีละ 3–5 ครั้ง
    • ง) ศูนย์ เพราะ stable ไม่มีวันเปลี่ยน
    เฉลย

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

  4. ข้อใดถูกต้องเกี่ยวกับลูปที่ถามปุ่มทุก 5 ms และขยับไฟวิ่งทุก 150 ms เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 3)

    • ก) ถ้าใช้ sleep_ms(150) เป็นตัวรอไฟวิ่ง การกดปุ่มสั้น ๆ ระหว่างที่หลับจะหายไป
    • ข) ใช้ ticks_diff(now, last_step) แทนการลบตรง ๆ เพราะตัวนับ ticks วิ่งถึงเพดานแล้ววนกลับ
    • ค) ถ้าย้าย last_step = now ออกไปไว้นอก if ไฟจะไม่วิ่งเลย
    • ง) sleep_ms(5) ในลูปเป็นแค่การถ่วงเวลา ตัดออกแล้วทุกอย่างทำงานดีขึ้น
    • จ) ถามปุ่มทุก 300 ms ก็พอ เพราะนิ้วกดค้างนานกว่านั้นเสมอ
    เฉลย

    ก, ข, ค — sleep หยุดโปรแกรมทั้งตัวจึงไม่มีใครเห็นปุ่ม ticks_diff จัดการตอนตัวนับวนกลับให้ และ last_step ต้องจดเฉพาะตอนที่ขยับจริง ส่วน sleep_ms(5) คือการคืนซีพียูให้จอและ WiFi และนิ้วกดค้างราว 50–80 ms ถามทุก 300 ms จึงหลุดได้

  5. โปรแกรมของทีมต้องพิมพ์ขึ้นจอว่าตอนนี้ไฟวิ่งอยู่ที่ดวงไหน วิธีใดเชื่อถือได้ที่สุด (เลือกหนึ่งข้อ · เป้าหมายข้อ 4)

    • ก) วนถาม gpio.led(i).value() ทุกดวงแล้วเลือกดวงที่ได้ 1
    • ข) วนถาม gpio.led(i).duty() ทุกดวงแล้วเลือกดวงที่ได้ 100
    • ค) อ่านจากตัวแปร led_index ที่โปรแกรมอัปเดตเองทุกครั้งที่สั่งขยับ
    • ง) เรียก gpio.board_info() แล้วอ่านช่อง led_names
    เฉลย

    ค — value() ตอบระดับขา ณ วินาทีที่ถาม และได้ 0 หลัง hold() ทั้งที่เพิ่งเห็นหลอดสว่าง ส่วน duty() ตอบแค่เลขที่สั่งล่าสุดและ toggle() ไม่แก้มัน โปรแกรมจึงควรจำสถานะที่ตัวเองสั่งไว้ ซึ่งย้ายไปคุมวาล์วหรือรีเลย์ที่อ่านกลับไม่ได้ก็ยังใช้ได้

บทเรียน 2.3 เอาทั้งหมดนี้มารวมเป็นงานของทีม: ไฟวิ่งที่ปรับจังหวะได้ ปุ่มที่นับตรง และส่งผลขึ้น broker ลองรอบพิเศษบน Eva Kit ด้วย คือเปิดการ์ด Controls ค้างไว้แทน Playground แล้วดูวงกลมบนจอวิ่งตามไฟของเรา

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

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

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. คุณกดปุ่มผู้ใช้ค้างไว้ แล้วอ่านค่าทั้งสองแบบในรอบเดียวกัน ได้ผลอย่างไร (เป้าหมายข้อ 1)

    1. btn.value() = 1 และ btn.is_pressed() = True
    2. btn.value() = 0 และ btn.is_pressed() = True
    3. btn.value() = 0 และ btn.is_pressed() = False
    4. btn.value() = 1 และ btn.is_pressed() = False
    ดูเฉลย

    คำตอบ: B. btn.value() = 0 และ btn.is_pressed() = True

    ปุ่มเป็น active-low ตอนปล่อย pull-up ดึงขาไว้ที่ 1 พอกด สวิตช์ต่อขาลงกราวด์จึงได้ 0 ส่วน is_pressed() กลับด้านให้แล้ว กดจึงได้ True

  2. ในวงจรหลอด D3 สีแดงบน Eva Kit (ตัวต้านทาน 220 Ω) ข้อใดบรรยายเส้นทางของกระแสได้ถูกต้อง (เป้าหมายข้อ 1)

    1. ขาชิป P16.7 จ่ายกระแสราว 5.9 mA เข้าหลอดโดยตรง ตัวต้านทานมีไว้กันขาชิปพัง
    2. ขาชิปต่อที่ gate ของ n-MOSFET ซึ่งแทบไม่กินกระแส กระแสหลอดราว 5.9 mA มาจากราง 3.3 V ผ่านตัวต้านทานและหลอดลงกราวด์
    3. ขาชิปลดแรงดันที่หลอดลงเมื่อสั่ง brightness() ค่ากลาง หลอดจึงหรี่
    4. สั่ง 0 ที่ขาชิปแล้วหลอดติด เพราะหลอดเป็น active-low เหมือนปุ่ม
    ดูเฉลย

    คำตอบ: B. ขาชิปต่อที่ gate ของ n-MOSFET ซึ่งแทบไม่กินกระแส กระแสหลอดราว 5.9 mA มาจากราง 3.3 V ผ่านตัวต้านทานและหลอดลงกราวด์

    ขาชิปสั่ง "ประตู" ไม่ได้สั่ง "น้ำ" กระแสหลอดเดินจากราง 3.3 V และแทนสูตร (3.3 - 2.0)/220 ได้ราว 5.9 mA สั่ง 1 ที่ gate หลอดจึงติด ส่วนการหรี่ทำด้วยการกะพริบ gate ถี่ ๆ ไม่ใช่ลดแรงดัน

  3. ทีมหนึ่งลบบรรทัด `if stable:` ออก ให้ `count += 1` ทำงานทุกครั้งที่ stable เปลี่ยน แล้วกดปุ่มช้า ๆ ห้าครั้ง ตัวนับน่าจะได้เท่าไร (เป้าหมายข้อ 2)

    1. ห้า เพราะกันเด้งยังทำงานอยู่
    2. สิบ เพราะการปล่อยปุ่มก็เป็นการเปลี่ยนสถานะที่ผ่านการกันเด้งเหมือนกัน
    3. สิบห้าถึงยี่สิบห้า เพราะปุ่มเด้งทีละ 3–5 ครั้ง
    4. ศูนย์ เพราะ stable ไม่มีวันเปลี่ยน
    ดูเฉลย

    คำตอบ: B. สิบ เพราะการปล่อยปุ่มก็เป็นการเปลี่ยนสถานะที่ผ่านการกันเด้งเหมือนกัน

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

  4. ข้อใดถูกต้องเกี่ยวกับลูปที่ถามปุ่มทุก 5 ms และขยับไฟวิ่งทุก 150 ms เลือกทุกข้อที่ถูก (เป้าหมายข้อ 3)

    1. ถ้าใช้ sleep_ms(150) เป็นตัวรอไฟวิ่ง การกดปุ่มสั้น ๆ ระหว่างที่หลับจะหายไป
    2. ใช้ ticks_diff(now, last_step) แทนการลบตรง ๆ เพราะตัวนับ ticks วิ่งถึงเพดานแล้ววนกลับ
    3. ถ้าย้าย last_step = now ออกไปไว้นอก if ไฟจะไม่วิ่งเลย
    4. sleep_ms(5) ในลูปเป็นแค่การถ่วงเวลา ตัดออกแล้วทุกอย่างทำงานดีขึ้น
    5. ถามปุ่มทุก 300 ms ก็พอ เพราะนิ้วกดค้างนานกว่านั้นเสมอ
    ดูเฉลย

    คำตอบ: A. ถ้าใช้ sleep_ms(150) เป็นตัวรอไฟวิ่ง การกดปุ่มสั้น ๆ ระหว่างที่หลับจะหายไป · B. ใช้ ticks_diff(now, last_step) แทนการลบตรง ๆ เพราะตัวนับ ticks วิ่งถึงเพดานแล้ววนกลับ · C. ถ้าย้าย last_step = now ออกไปไว้นอก if ไฟจะไม่วิ่งเลย

    sleep หยุดโปรแกรมทั้งตัวจึงไม่มีใครเห็นปุ่ม ticks_diff จัดการตอนตัวนับวนกลับให้ และ last_step ต้องจดเฉพาะตอนที่ขยับจริง ส่วน sleep_ms(5) คือการคืนซีพียูให้จอและ WiFi และนิ้วกดค้างราว 50–80 ms ถามทุก 300 ms จึงหลุดได้

  5. โปรแกรมของทีมต้องพิมพ์ขึ้นจอว่าตอนนี้ไฟวิ่งอยู่ที่ดวงไหน วิธีใดเชื่อถือได้ที่สุด (เป้าหมายข้อ 4)

    1. วนถาม gpio.led(i).value() ทุกดวงแล้วเลือกดวงที่ได้ 1
    2. วนถาม gpio.led(i).duty() ทุกดวงแล้วเลือกดวงที่ได้ 100
    3. อ่านจากตัวแปร led_index ที่โปรแกรมอัปเดตเองทุกครั้งที่สั่งขยับ
    4. เรียก gpio.board_info() แล้วอ่านช่อง led_names
    ดูเฉลย

    คำตอบ: C. อ่านจากตัวแปร led_index ที่โปรแกรมอัปเดตเองทุกครั้งที่สั่งขยับ

    value() ตอบระดับขา ณ วินาทีที่ถาม และได้ 0 หลัง hold() ทั้งที่เพิ่งเห็นหลอดสว่าง ส่วน duty() ตอบแค่เลขที่สั่งล่าสุดและ toggle() ไม่แก้มัน โปรแกรมจึงควรจำสถานะที่ตัวเองสั่งไว้ ซึ่งย้ายไปคุมวาล์วหรือรีเลย์ที่อ่านกลับไม่ได้ก็ยังใช้ได้

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"หลังไฟและปุ่ม: active-low กันเด้ง และลูปที่ไม่หยุด" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Behind LEDs and buttons: active-low, debouncing and the endless loop" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/aiot-micropython/m02-ui-to-hardware/l02-active-low-debounce/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-03.html (slides 13–27)

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA