หลังไฟและปุ่ม: active-low กันเด้ง และลูปที่ไม่หยุด
โมดูล 2 — จากจอสู่ฮาร์ดแวร์ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เปิดดูสิ่งที่อยู่หลังขา ว่าทำไมไฟสั่ง 1 แล้วติดแต่ปุ่มกดแล้วได้ 0 ทำไมกดครั้งเดียวนับได้หลายครั้ง และทำไมลูปที่อ่านปุ่มไม่หลุดต้องเลิกใช้ sleep เป็นตัวจับเวลา
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- อธิบายวงจรไฟ active-high (ขาชิปสั่ง gate ของ n-MOSFET) และปุ่ม active-low (pull-up) แล้วทายค่า btn.value() กับ btn.is_pressed() ทั้งตอนกดและตอนปล่อยได้ถูก พร้อมคำนวณกระแสหลอดแดงบน Eva Kit จากสูตรตัวต้านทานอนุกรมได้ราว 5.9 mA
- ไล่ตรรกะกันเด้งด้วยตัวแปร raw, last_raw และ stable กับ DEBOUNCE_MS = 40 จนกดปุ่มสิบครั้งแล้วนับได้สิบพอดี และบอกได้ว่าทำไม count += 1 ต้องอยู่ใต้ if stable
- ให้ไฟวิ่งขยับทุก STEP_MS ด้วย time.ticks_ms() กับ time.ticks_diff() ในลูปเดียวกับที่ถามปุ่มทุก 5 ms โดยไม่มีการกดหลุด และอธิบายได้ว่าทำไม last_step = now ต้องอยู่ใน if และทำไมไม่ลบเวลาตรง ๆ
- ให้โปรแกรมจำเองว่าไฟดวงไหนติด (led_index) แทนการถาม value() หรือ duty() ของหลอด และเริ่มกับจบโปรแกรมด้วยการดับไฟทุกดวง
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ทบทวนจากบทเรียน 2.1 สามเรื่อง: gpio.led(n) สั่งไฟ btn.is_pressed() ตอบความหมาย ส่วน btn.value() ตอบระดับไฟฟ้าดิบ
และ duty() ตอบแค่ตัวเลขที่เราสั่งไป เตรียมบอร์ดหรือ Emulator ที่เปิดหน้า BENTO Playground ค้างไว้
และเปิดบันทึกการเรียนไว้จดตัวเลขจากการกดปุ่ม เพราะบทเรียนนี้ให้ผลการทดลองของทีมเป็นคนตอบหลายคำถาม
- อุปกรณ์: บอร์ด Eva Kit หรือ TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE
- เรียนมาก่อน: บทเรียน 2.1 — โมดูล gpio: LED ปุ่ม และบอร์ดที่บอกได้ว่ามีอะไร
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”รัน 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 เพื่อคืนบอร์ดให้ทีมรันรอบต่อไปได้
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”ไล่ตามลำดับนี้ ทายก่อนรันทุกครั้ง แล้วจดผลลงบันทึกการเรียน
05_debounce_count.pyทายก่อนว่ากดสิบครั้งเลขส้ม (นับดิบ) กับเลขเขียว (นับกันเด้ง) จะต่างกันไหม กดช้า ๆ สิบครั้ง แล้วกดรัว ๆ อีกสิบครั้ง จดส่วนต่างของทั้งสองรอบ ส่วนต่างเป็นศูนย์ไม่ได้แปลว่าปุ่มไม่เด้ง จากนั้นลดDEBOUNCE_MSลงทีละ 10 แล้วกดสิบครั้งเท่าเดิม จดค่าที่เลขเขียวเริ่มเกินจำนวนครั้งที่กดจริง ตัวเลขนี้คือคำตอบของทีมว่าปุ่มบนบอร์ดของคุณเด้งแค่ไหน02_led_blink.pyดูป้ายบนจอเทียบกับหลอดจริง และตัวนับรอบที่เดินขึ้นทีละหนึ่ง สังเกตว่าไฟล์นี้จับจังหวะด้วยsleep_msและปิดท้ายด้วยoff()เพราะถ้าจบด้วยtoggle()จำนวนคี่ไฟจะค้างติด ลองคิดว่าถ้าเพิ่มการอ่านปุ่มเข้าไปในลูปนี้ การกดสั้น ๆ จะหายไปช่วงไหน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 | ปุ่มเดียวคุมไฟทุกดวง ด้วยการจำสถานะเอง |
สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นด้วย:
- m02-ui-to-hardware/l03-led-button-lab/practice/s03_led_button.py — ไฟวิ่งทุกดวง + ปุ่มนับครั้งแบบกันเด้ง (ฉบับฝึกเติมโค้ด)
ภาพจอจาก BENTO Emulator ของตัวอย่างในบทนี้ (คลิกชื่อไฟล์เพื่อเปิดโค้ด)

02_led_blink.py ทำให้ไฟกะพริบเป็นจังหวะ แล้วนับรอบที่กะพริบไปแล้ว
04_button_active_low.py ปุ่มนี้ 0 คือกด ไม่ใช่ 1
05_debounce_count.py นับการกดให้ตรง ด้วยการรอให้ปุ่มนิ่งก่อน
06_button_picks_led.py ปุ่มเดียวคุมไฟทุกดวง ด้วยการจำสถานะเองเช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
คุณกดปุ่มผู้ใช้ค้างไว้ แล้วอ่านค่าทั้งสองแบบในรอบเดียวกัน ได้ผลอย่างไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 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
-
ในวงจรหลอด 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 ถี่ ๆ ไม่ใช่ลดแรงดัน
-
ทีมหนึ่งลบบรรทัด
if stable:ออก ให้count += 1ทำงานทุกครั้งที่ stable เปลี่ยน แล้วกดปุ่มช้า ๆ ห้าครั้ง ตัวนับน่าจะได้เท่าไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)- ก) ห้า เพราะกันเด้งยังทำงานอยู่
- ข) สิบ เพราะการปล่อยปุ่มก็เป็นการเปลี่ยนสถานะที่ผ่านการกันเด้งเหมือนกัน
- ค) สิบห้าถึงยี่สิบห้า เพราะปุ่มเด้งทีละ 3–5 ครั้ง
- ง) ศูนย์ เพราะ stable ไม่มีวันเปลี่ยน
เฉลย
ข — กันเด้งยังกรองการเด้งได้ แต่ทุกการกดมีสองขอบที่นิ่งแล้ว คือกดลงกับปล่อย ถ้าไม่มีบรรทัด if stable โปรแกรมจะนับทั้งสองขอบ เลขจึงกระโดดทีละสอง ซึ่งดูเหมือนปุ่มเด้งทั้งที่เป็นบั๊กของตรรกะเรา
-
ข้อใดถูกต้องเกี่ยวกับลูปที่ถามปุ่มทุก 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 จึงหลุดได้
-
โปรแกรมของทีมต้องพิมพ์ขึ้นจอว่าตอนนี้ไฟวิ่งอยู่ที่ดวงไหน วิธีใดเชื่อถือได้ที่สุด (เลือกหนึ่งข้อ · เป้าหมายข้อ 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)
- 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
ดูเฉลย
คำตอบ: B. btn.value() = 0 และ btn.is_pressed() = True
ปุ่มเป็น active-low ตอนปล่อย pull-up ดึงขาไว้ที่ 1 พอกด สวิตช์ต่อขาลงกราวด์จึงได้ 0 ส่วน is_pressed() กลับด้านให้แล้ว กดจึงได้ True
-
ในวงจรหลอด D3 สีแดงบน Eva Kit (ตัวต้านทาน 220 Ω) ข้อใดบรรยายเส้นทางของกระแสได้ถูกต้อง (เป้าหมายข้อ 1)
- ขาชิป P16.7 จ่ายกระแสราว 5.9 mA เข้าหลอดโดยตรง ตัวต้านทานมีไว้กันขาชิปพัง
- ขาชิปต่อที่ gate ของ n-MOSFET ซึ่งแทบไม่กินกระแส กระแสหลอดราว 5.9 mA มาจากราง 3.3 V ผ่านตัวต้านทานและหลอดลงกราวด์
- ขาชิปลดแรงดันที่หลอดลงเมื่อสั่ง brightness() ค่ากลาง หลอดจึงหรี่
- สั่ง 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 ถี่ ๆ ไม่ใช่ลดแรงดัน
-
ทีมหนึ่งลบบรรทัด `if stable:` ออก ให้ `count += 1` ทำงานทุกครั้งที่ stable เปลี่ยน แล้วกดปุ่มช้า ๆ ห้าครั้ง ตัวนับน่าจะได้เท่าไร (เป้าหมายข้อ 2)
- ห้า เพราะกันเด้งยังทำงานอยู่
- สิบ เพราะการปล่อยปุ่มก็เป็นการเปลี่ยนสถานะที่ผ่านการกันเด้งเหมือนกัน
- สิบห้าถึงยี่สิบห้า เพราะปุ่มเด้งทีละ 3–5 ครั้ง
- ศูนย์ เพราะ stable ไม่มีวันเปลี่ยน
ดูเฉลย
คำตอบ: B. สิบ เพราะการปล่อยปุ่มก็เป็นการเปลี่ยนสถานะที่ผ่านการกันเด้งเหมือนกัน
กันเด้งยังกรองการเด้งได้ แต่ทุกการกดมีสองขอบที่นิ่งแล้ว คือกดลงกับปล่อย ถ้าไม่มีบรรทัด if stable โปรแกรมจะนับทั้งสองขอบ เลขจึงกระโดดทีละสอง ซึ่งดูเหมือนปุ่มเด้งทั้งที่เป็นบั๊กของตรรกะเรา
-
ข้อใดถูกต้องเกี่ยวกับลูปที่ถามปุ่มทุก 5 ms และขยับไฟวิ่งทุก 150 ms เลือกทุกข้อที่ถูก (เป้าหมายข้อ 3)
- ถ้าใช้ sleep_ms(150) เป็นตัวรอไฟวิ่ง การกดปุ่มสั้น ๆ ระหว่างที่หลับจะหายไป
- ใช้ ticks_diff(now, last_step) แทนการลบตรง ๆ เพราะตัวนับ ticks วิ่งถึงเพดานแล้ววนกลับ
- ถ้าย้าย last_step = now ออกไปไว้นอก if ไฟจะไม่วิ่งเลย
- sleep_ms(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 จึงหลุดได้
-
โปรแกรมของทีมต้องพิมพ์ขึ้นจอว่าตอนนี้ไฟวิ่งอยู่ที่ดวงไหน วิธีใดเชื่อถือได้ที่สุด (เป้าหมายข้อ 4)
- วนถาม gpio.led(i).value() ทุกดวงแล้วเลือกดวงที่ได้ 1
- วนถาม gpio.led(i).duty() ทุกดวงแล้วเลือกดวงที่ได้ 100
- อ่านจากตัวแปร led_index ที่โปรแกรมอัปเดตเองทุกครั้งที่สั่งขยับ
- เรียก 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 Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA