กลับไปหน้ารวมไฟล์
multiple-switches-one-interrupt-2b53b6.md

พื้นหลังและที่มา

ในบทความนี้พี่จะมาเล่าให้ฟังถึงวิธีการตั้งค่าสวิตช์กี่ตัวก็ได้ ให้ลิงก์เข้ากับ Interrupt Service Routine (ISR) ตัวเดียวกัน วิธีนี้เป็นซอฟต์แวร์ล้วนๆ ไม่ต้องไปยุ่งกับสายไฟต่อสวิตช์เข้ากับขา interrupt ให้วุ่นวาย

บทความนี้พัฒนาต่อยอดมาจากงานเก่าที่พี่เคยเขียนไลบรารีสำหรับจัดการสวิตช์ทุกประเภทที่ต่อวงจรต่างกันออกไป นั่นคือไลบรารีชื่อ ez_switch_lib นั่นเอง

เจ้า ez_switch_lib นี่มันมีฟีเจอร์เด็ดๆ เยอะเลยนะ มาดูกันคร่าวๆ:

  • ตั้งค่าสวิตช์แบบไหนก็ได้ เช่น ปุ่มกด, สวิตช์โยก, อะไรก็ว่าไป
  • รองรับวงจรต่อสวิตช์แบบพื้นฐานที่เราใช้กันบ่อยๆ
  • ตั้งค่าสวิตช์หลายตัวพร้อมกันได้ แม้จะคนละประเภท ต่อวงจรไม่เหมือนกันก็ตาม
  • จัดการเรื่องเดานซ์ (debounce) ให้อัตโนมัติ และยังปรับแต่งได้ด้วย
  • สามารถเช็คสถานะของสวิตช์ได้ตลอดเวลา เช่น กำลังกดอยู่รึเปล่า, เปิดหรือปิดอยู่ เป็นต้น
  • สามารถลิงก์สวิตช์เข้ากับขาเอาต์พุตดิจิตอลอื่นได้อัตโนมัติ พอกดสวิตช์ ขาที่ลิงก์ไว้ก็จะทำงานตาม (เซ็ตหรือเคลียร์)

และฟีเจอร์สุดท้ายนี่แหละ ที่บทความนี้จะมาเจาะลึกและใช้เป็นพื้นฐานของเรา นั่นคือการลิงก์สวิตช์เข้ากับขาเอาต์พุตดิจิตอลนั่นเอง

ลิงก์สวิตช์เข้ากับ Interrupt

บทความนี้จะแสดงให้เห็นว่าเราสามารถลิงก์สวิตช์กี่ตัวก็ได้ (ไม่ว่าจะประเภทไหน ต่อวงจรยังไง) เข้ากับ interrupt ได้อย่างง่ายดาย โดยไม่ต้องเดินสายไฟเพิ่มเลยแม้แต่เส้นเดียว — ไม่มีสายต่อจากสวิตช์ไปหา interrupt เลยนะตัวนี้!

เพื่อให้ตัวอย่างง่ายขึ้น เราจะใช้ ISR ตัวเดียวจัดการสวิตช์ทั้งหมด แต่เราปรับเปลี่ยนได้ตามสบาย เช่นเดียวกัน เราสามารถใช้ ISR กี่ตัวก็ได้ และใช้ขา interrupt ภายนอกกี่ขาก็ได้ (แน่นอนว่าต้องไม่เกินขีดจำกัดของไมโครคอนโทรลเลอร์ที่เราใช้)

แล้วเราทำยังไงล่ะ?

อย่างที่บอกไป ไลบรารี ez_switch_lib มีฟีเจอร์พิเศษที่ให้เราลิงก์สวิตช์เข้ากับขาเอาต์พุตดิจิตอลได้ โดยที่เมื่อสวิตช์ถูกกด ขาเอาต์พุตที่ลิงก์ไว้จะถูกเซ็ต/เคลียร์อัตโนมัติ ฟังก์ชันนี้ชื่อว่า link_switch_to_output และมีแค่สามพารามิเตอร์:

  • ID ของสวิตช์ที่ต้องการลิงก์กับขาเอาต์พุตดิจิตอล
  • หมายเลขขา (pin number) ของขาเอาต์พุตดิจิตอลที่ต้องการลิงก์
  • สถานะเริ่มต้นของขาเอาต์พุตที่ลิงก์ไว้ (คือ LOW หรือ HIGH)

หลังจากประกาศ/กำหนดสวิตช์แล้ว เราก็สามารถเชื่อมโยงมันกับขาเอาต์พุตดิจิตอลโดยใช้ฟังก์ชันด้านบน นี่คือหัวใจของวิธีนี้ — สิ่งที่เราต้องทำต่อก็แค่กำหนด ISR โดยใช้ฟังก์ชัน attachInterrupt เพื่อจัดการเหตุการณ์จากสวิตช์บนขา interrupt ดิจิตอลภายนอกขาไหนก็ได้ที่เหมาะสม

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

มาดูตัวอย่างสเก็ตช์และสวิตช์ที่เราตั้งค่าไว้กันดีกว่า

ตัวอย่างสเก็ตช์จะใช้ขาดิจิตอลเจ็ดขา หกขาสำหรับสวิตช์ และหนึ่งขาสำหรับขา interrupt ร่วม กำหนดและต่อวงจรตามตารางด้านล่าง:

Switch Configurations

(ให้ต่อวงจรส่วนประกอบตามตารางด้านบนและแผนภาพ Schematic ด้านล่าง โดยใช้เบรดบอร์ด)

สิ่งสำคัญที่ต้องสังเกตคือ:

  • เราใช้ขาดิจิตอลหมายเลข 2 เป็นขาร่วมที่กำหนดให้กับ ISR ดูให้ดีนะว่าขานี้ไม่ได้ต่อสายไฟอะไรเข้าไปทางกายภาพเลย
  • 'circuit_C1' กับ 'circuit_C2' เป็นคำสงวนในไลบรารี ez_switch_lib โดยตัวแรกหมายถึงวงจรที่มีตัวต้านทานดึงลง (Pull-down Resistor) 10k โอห์มภายนอก ส่วนตัวหลังคือวงจรพื้นฐาน (ไม่มีองค์ประกอบอื่นนอกจากสวิตช์ตัวเอง)

เรากำหนดให้ขา Digital พิน 3-8 เชื่อมกับสวิตช์ดังนี้:

  • สวิตช์แบบ Toggle 3 ตัว ต่อกับตัวต้านทานดึงลง 10k โอห์ม - 'circuit_C1' (ดูแผนภาพด้านล่าง)
  • สวิตช์แบบปุ่มกด (Button) 3 ตัว ต่อตรงๆ ไม่มีตัวต้านทานเพิ่ม - 'circuit_C2' (ดูแผนภาพด้านล่าง)
  • สวิตช์ทุกตัวจะถูกเชื่อมโยงซอฟต์แวร์ให้ใช้ขา Digital พิน 2 ร่วมกัน (พินสำหรับ Interrupt Service Routine - ISR) โดยใช้ฟังก์ชัน link_switch_to_output หลังจากสร้างสวิตช์แต่ละตัวด้วยฟังก์ชัน add_switch แล้ว

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

ออกแบบสเก็ตช์ (Sketch Design)

สเก็ตช์นี้มี 6 ส่วนหลัก จัดไปวัยรุ่น:

  • ประกาศใช้ไลบรารี ez_switch_lib - ก็แค่ประกาศ include ไฟล์หัวไลบรารี (#include "ez_switch_lib.h") ก่อนจะรันได้ น้องต้องคัดลอกไฟล์ไลบรารี (.cpp, .h และ .txt) ไปไว้ในโฟลเดอร์ไลบรารีของ Arduino ในโฟลเดอร์ชื่อ 'ez_switch_lib' นะ เช่น ../Arduino/libraries/ez_switch_lib (หาดาวน์โหลดไลบรารีได้จากบทความเกี่ยวกับ ez_switch_lib)
  • สร้างอินสแตนซ์ของ ez_switch_lib - สร้างและตั้งอินสแตนซ์ของไลบรารี โดยกำหนดขนาดให้เท่ากับจำนวนสวิตช์ที่เราจะใช้ (ในตัวอย่างคือ 6 ตัว) ตัวอย่างสเก็ตช์ตั้งชื่ออินสแตนซ์ว่า 'my_switches' เราจะใช้ชื่อนี้เป็น prefix เรียกฟังก์ชันต่างๆ ของไลบรารี เช่น 'my_switches.add_switch(..)' เป็นต้น
  • กำหนดข้อมูลการตั้งค่าสวิตช์ - ข้อมูลที่ใช้นิยามการตั้งค่าสวิตช์ของเรา หัวใจของสเก็ตช์อยู่ที่ข้อมูลนี้ ซึ่งเก็บอยู่ในอาร์เรย์สองมิติชื่อ 'my_switch_data' แต่ละแถวจะเก็บข้อมูลของสวิตช์แต่ละตัว แบบนี้:
column[0] - หมายถึงประเภทของสวิตช์ ('button_switch' หรือ ' toggle_switch') (อีกครั้งนะ คำว่า 'button_switch' กับ ' toggle_switch' เป็นคำสงวนของไลบรารี ใช้นิยามประเภทสวิตช์)
column[1] - นี่คือขา Digital พินที่กำหนดให้สวิตช์ตัวนั้น
column[2] - หมายถึงรูปแบบการต่อวงจรของสวิตช์ (ใช้คำสงวน 'circuit_C1' หรือ 'circuit_C2')
  • ฟังก์ชัน setup - น้องจะเห็นว่าในส่วนนี้แหละที่เราประกาศสวิตช์แต่ละตัวให้ไลบรารีรู้จัก โดยใช้ฟังก์ชัน add_switch และกำหนดขา Interrupt ร่วมให้แต่ละสวิตช์ด้วยฟังก์ชัน link_switch_to_output
  • การประมวลผลในลูปหลัก (main loop) ดูเผินๆ ลูปหลักอาจดูเหมือนไม่ทำอะไรเลย มันแค่วนอ่านสถานะของสวิตช์แต่ละตัวด้วยฟังก์ชัน read_switch อย่างต่อเนื่อง เพื่อหาการเปลี่ยนแปลงสถานะ นี่เป็นเพราะเมธอดนี้ขับเคลื่อนด้วยซอฟต์แวร์ล้วนๆ เลยต้องคอยตรวจสอบสถานะตลอดเวลา พอสถานะเปลี่ยน จะเกิดสองสิ่งขึ้น:
1. ขาที่เชื่อมโยงกับสวิตช์ (ซึ่งก็คือขา interrupt ร่วม) จะถูกดึงให้เป็น HIGH (RISING) อัตโนมัติ ทำให้ ISR ทำงานและไปจัดการกับเหตุการณ์ที่เกิดจากสวิตช์ตัวนั้น และ
2. หลังจาก ISR ทำงานเสร็จสิ้นแล้ว ฟังก์ชัน read_switch จะคืนค่ากลับมาเป็น 'switched' (ซึ่งเป็นคำสงวนของไลบรารี ez_switch_lib ด้วย) จากนั้นเราก็สามารถเอาไปประมวลผลต่อในลูปหลักได้อีกทีนึง ถ้าต้องการ - ได้ประโยชน์สองต่อแบบจัดเต็ม!
  • interrupt service routine (ISR) - เป็น ISR ที่เขียนง่ายๆ ออกแบบมาให้รู้จักประเภทของสวิตช์และคุณลักษณะเฉพาะของมัน โดยใช้ตัวแปรต่างๆ ที่มีอยู่ใน ez_switch_lib น้องจะเห็นว่า ISR ตัวนี้ไม่ได้ทำอะไรมาก แค่รายงานไปที่ Serial Monitor ว่าสวิตช์ตัวไหนถูกกดเท่านั้น ปกติเราไม่แนะนำให้ใช้ Serial.print() ใน ISR นะจ๊ะ ที่ใช้ในที่นี้ก็เพื่อให้เห็นภาพชัดๆ ว่าโค้ดมันทำงานถูกต้องเท่านั้นเอง น้องสามารถเพิ่มโค้ดของตัวเองลงไปในตำแหน่ง 'hooks' เหล่านี้ตามที่โปรเจคของน้องต้องการได้เลย

โอเค แล้วมันทำงานยังไงล่ะ?

ไลบรารี ez_switch_lib ช่วยให้เราไม่ต้องกังวลเรื่องสวิตช์เด้ง (switch bounce) หรือวิธีการต่อสายสวิตช์เลย มันยังให้ข้อมูลสถานะที่มีประโยชน์มากๆ เกี่ยวกับสวิตช์ของเราด้วย เช่น ประเภทสวิตช์, สวิตช์กำลังเปลี่ยนสถานะหรือเปล่า, สวิตช์แบบ Toggle ปัจจุบันเป็น On หรือ Off เป็นต้น แต่ที่สำคัญที่สุดคือ มันทำให้เราสามารถเชื่อมโยงสวิตช์เข้ากับขา Digital Output อีกขาหนึ่งได้อัตโนมัติ ซึ่งเราจะใช้ขานี้เป็นตัวเรียก ISR เมื่อสวิตช์ตัวนั้นๆ ถูกกด

ทีนี้ ในตัวอย่างโค้ดของเรา สวิตช์ทุกตัวที่ประกาศไว้จะเรียก ISR ตัวเดียวกันหมด คำถามคือ แล้ว ISR จะรู้ได้ไงว่าสวิตช์ตัวไหนถูกกด? อีกแล้ว ไลบรารี ez_switch_lib ก็จัดการเรื่องนี้ให้ด้วย มันจะเก็บ ID ของสวิตช์ตัวล่าสุดที่ถูกกดไว้ คุณสมบัตินี้แหละที่ทำให้ ISR สามารถไปประมวลผลสวิตช์ตัวที่ถูกต้องได้ ตัวแปรของไลบรารีตัวนี้ชื่อ 'last_switched_id'

ด้วยข้อมูลนี้ ISR ก็สามารถไปจัดการกับความต้องการเฉพาะของสวิตช์ตัวที่ถูกกดได้

เข้าใจดีมั้ย? ใช่แล้ว ถ้าน้องเข้าใจว่าสวิตช์แบบปุ่มกด (Button) กับแบบสลับ (Toggle) มันมีคุณสมบัติเฉพาะตัวที่ ISR ต้องจัดการต่างกันด้วย กล่าวคือ:

สวิตช์แบบปุ่มกด (Button) - วงจรการทำงานหนึ่งครั้งจะต้องผ่านจาก off -> on แล้วกลับไป off อีกครั้ง ถึงจะเรียกว่าการกดครั้งหนึ่งสมบูรณ์
สวิตช์แบบสลับ (Toggle) - สวิตช์แบบ Toggle มีวงจรการทำงานสองแบบ - มันสามารถเปลี่ยนจาก off เป็น on หรือจาก on เป็น off ก็ได้

อีกครั้ง ไลบรารี ez_switch_lib ก็รองรับคุณสมบัติเฉพาะของสวิตช์เหล่านี้ด้วย ทำให้ผู้ใช้ (และ ISR) สามารถประมวลผลแต่ละประเภทของเหตุการณ์ได้อย่างเหมาะสม

เมื่อน้องดูโค้ด ISR จะเห็นเลยว่ามันจัดการกับสถานการณ์ข้างต้นได้อย่างไร ง่ายมั้ยล่ะ

และอย่าลืมว่า ในขณะที่ ISR จะประมวลผลการกดสวิตช์ มันยังมีจุดที่เราสามารถเพิ่มการประมวลผลต่อได้อีก ถ้าน้องดูในลูปหลัก จะเห็นว่าเราตรวจสอบ (poll) และอ่านค่าจากสวิตช์แต่ละตัวโดยตรงและตลอดเวลา การตรวจสอบแบบนี้แหละที่ทำให้ ISR ของสวิตช์ถูกเรียกทำงาน อย่างไรก็ตาม ทุกครั้งที่ ISR ของสวิตช์ถูกเรียก สวิตช์ตัวนั้นก็จะถือว่าถูกกด ('switched') ด้วยเช่นกัน คำตอบจากฟังก์ชัน read_switch ในลูปการตรวจสอบจะได้ค่าเป็น 'switched' ถ้าสวิตช์ถูกกด มันจึงเป็นโอกาสเพิ่มเติมให้น้องสามารถใส่โค้ดประมวลผลสวิตช์เพิ่มลงไปในลูปหลักได้ ถ้าจำเป็น หรือในกรณีที่สวิตช์ไม่ได้เชื่อมโยงกับขา Output สำหรับเรียก ISR

ใครบอกว่าเรากินเค้กแล้วจะเก็บเค้กไว้ไม่ได้วะ?!!

Interrupt Resource Optimization

โปรเจคนี้จะโชว์เทคนิคเจ๋งๆ ในการสู้กับข้อจำกัดของฮาร์ดแวร์ให้ดู Arduino มาตรฐานอย่าง Uno น่ะ มันมีขา Interrupt แค่ 2 ขา (D2 กับ D3) เท่านั้น โปรเจคนี้เลยจะสอนวิธีใช้ขา Interrupt แค่ขาเดียว แต่มอนิเตอร์ปุ่มกดได้เป็นสิบๆ ปุ่มเลย

  • Software "OR" Logic: ไลบรารี ez_switch_lib นี่มันสร้าง Logic แบบ "OR" ขึ้นมาในซอฟต์แวร์แทนฮาร์ดแวร์น่ะ ปุ่มทุกปุ่มจะต่อกับขาดิจิตอลของตัวเองเพื่อให้เช็คสถานะได้ แต่สัญญาณ Output ของมันจะถูกส่งไปที่ขา Interrupt ขาเดียวกันผ่านฟังก์ชัน link_switch_to_output พอมีปุ่มไหนถูกกด มันก็จะดันขา Interrupt ร่วมนั้นให้เป็น HIGH ทันที ทำให้ ISR ทำงาน
  • Wake-on-Press: วิธีนี้เหมาะสุดๆ สำหรับงานที่ต้องการประหยัดพลังงาน พอมีปุ่มถูกกด Interrupt ก็จะทำงาน ซึ่งสามารถใช้ปลุก Arduino จากโหมด Sleep ได้เลย ประหยัดแบตรอจนกว่าผู้ใช้จะกดปุ่ม

Software Handling

  • ISR Logic: ต้องเขียน Interrupt Service Routine (ISR) ให้เล็กและเร็วที่สุด ในตัวอย่างโค้ด มันแค่ใช้ตัวแปร last_switched_id ระบุว่าปุ่มไหนเป็นคนกด แล้วก็ตั้งค่าตัวแปร Flag ไว้ จากนั้นใน Loop หลัก หลังจาก ISR ทำงานเสร็จ ก็ค่อยไปเช็คปุ่มที่ระบุไว้หรือทำงานอื่นๆ ที่ไม่เร่งด่วนตาม Flag นั้น รับรองว่าได้การตอบสนองที่ไวและประหยัดพลังงานแน่นอน

สุดท้ายนี้

หวังว่าน้องๆ จะได้ไอเดียเจ๋งๆ ไปใช้ในโปรเจคของตัวเองบ้างนะ ตัวพี่แนะนำให้ไปศึกษารายละเอียดของไลบรารี ez_switch_lib ให้ลึกซึ้งขึ้นอีกนิด เพื่อจะได้เห็นศักยภาพทั้งหมดของมัน

ข้อมูล Frontmatter ดั้งเดิม

title: "หลายสวิตช์ แต่ Interrupt เดียว งานง่ายแต่หล่อ!"
description: "มาใช้ Interrupt เพียงตัวเดียวจัดการสวิตช์กี่ตัวก็ได้ ไม่ว่าจะเป็นปุ่มกดหรือสวิตช์แบบไหน ต่อวงจรยังไงก็ได้หมด จัดไปแบบตึงๆ!"
author: "ronbentley1"
category: ""
tags:
  - "button"
  - "switches"
  - "interrupts"
  - "isr"
  - "toggle"
views: 3778
likes: 0
price: 99
difficulty: "Intermediate"
components:
  - "1x Solderless Breadboard Half Size"
  - "1x Arduino UNO"
  - "3x Tactile Switch, Top Actuated"
  - "8x Jumper wires (generic)"
  - "3x Resistor 10k ohm"
  - "3x Toggle Switch, Toggle"
tools: []
apps:
  - "1x Arduino IDE"
downloadableFiles: []
documentationLinks: []
passwordHash: "cfa829c6f89a68bd0d32475899c5a4642769d6ded40384de9b7531a7a29f54f1"
encryptedPayload: "U2FsdGVkX1+uYQjTS/xuS7RIfTnJOIi3upsZPvrad1VkT3XWwNR7bhJSUsSYwnqh40HnrQdXMd9dpURmCEyJWsNbEcUHpV3lLdN02BPXj8E="
seoDescription: "Learn to process multiple Switches, Buttons, or Toggles using a single Interrupt on Arduino for any wiring scheme."
videoLinks: []
heroImage: "https://cdn.jsdelivr.net/gh/bigboxthailand/arduino-assets@main/images/projects/multiple-switches-one-interrupt-2b53b6_cover.jpg"
lang: "en"