ใครที่เล่น Micro-controller บ่อยๆ สิ่งที่ขาดไม่ได้เลยคืออุปกรณ์ input พวกปุ่มต่างๆ แต่โดยทั่วไปมักจะให้ติดปุ่มเข้ากับ Pin IO ปุ่มละ Pin หรือพวก key pad metrix ที่ยุ่งยากในการใช้งานใช้ pin เยอะเหมือนกัน ซึ่งทำให้ Pin ใช้งานหมดหรือไม่พอ ยิ่งพวกที่มี Pin น้อยๆอย่าง ESP8266 อะไรพวกนี้ยิ่งไม่ต้องพูดถึง วันนี้เราจะมาดูวิธีง่ายๆ เรียกว่า ขาเดียวร้อยปุ่ม หรือจะกี่ร้อยปุ่มก็ทำได้
เรามาดูว่าปกติใน Aruduino มักใช้อุปกรณ์พวกนี้ เมื่อต้องการปุ่มเยอะๆ
แต่พวกนี้มันจะใข้ pin IO แบบทวีคูณตามจำนวนปุ่มเลย หรือเอาจำนวนปุ่มหาร2 เช่นในรูป 16 ปุ่มต้องใช้ 8 pin เวลาต่อก็แบบนี้
ซึ่งผมมองว่ามันไม่เหลือ Pin ให้เราใช้งานอื่นๆเลยหากเราใช้ทำโครงการซับซ้อน วันนี้เราจะมาแบนมันซะแล้ว
แล้วเอาพระเอกเรามาแทน (แม้ว่าหน้าตาจะขี้เหร่กว่า) แบบไม่มีใครเหมือน ไม่มีขายต้องทำเอง
มีแค่สามเส้น Vcc, GND, และสัญญาณ จะสิบร้อย พันก็ 3 pin
หลักการง่ายๆแลยเราจะใช้การแบ่ง Voltage เป็นช่วงๆ และตรวจจับระดับแรงดันขาเข้าของ Analog IO ทำให้เรารู้ว่ากดอะไรโดยที่ Analog IO ของ Arduino นี่มีเยอะและมักไม่ค่อยได้ใช้
พูดแค่นี้บางคนร้องอ๋อไปทำเองต่อได้แล้ว หลักการมันเหมือนเส้นผมบังภูเขา แปลกแต่จริงไม่มีคนทำขาย เพราะอะไร? คงเพราะ Software และความเข้าใจหลักการมันที่แสนง่าย แต่ซ่อนด้วยความยากนิดๆ (งงหน่อยๆ แบบเส้นผมบังภูเขาแต่อ่านกระทู้นี้แล้วหมูเลย) คนซื้อเอาไปใช้เมื่อใช้ไม่ได้ดังใจมันก็เคลม คนขายขี้เกียจตอบเพราะเวลาใช้ไม่ได้คนซื้อจะบอกว่าของห่วยแทนที่จะโทษตัวเอง
เราต้องมาดูกันต่อแบบเบสิกๆสำหรับคนที่ยังไม่อินในหลักการ
หลักการแบ่ง Voltage
เมื่อต่อ Resistor 2 ตัวเข้าด้วยกันแรงดันตกคร่อม Vo จะหาได้จาก
Vo= (R2xVcc)/(R1+R2)
หากเราต่อ R เข้าไปเยอะๆเราก็จะได้ Vo แต่ละจุดที่ไม่ซ้ำกัน เป็น V1,V2,V3,…Vn
หากต่อ Switch ปุ่มเข้าไปเราก็จะได้สัญญาณที่จะส่งไปที่ Analog Pin ได้
แต่การต่อแบบนี้ไม่ดีแน่เพราะ 2 สาเหตุคือ มันกินไฟตลอดเวลา และ หากไม่มีการกดปุ่มใดเลยขา Analog จะลอยให้ค่าออกมามั่วไปหมด
เราย้าย R ตัวสุดท้ายมาด้าน Switch ได้ตามรูป
จะเห็นได้ว่า มันไม่ใช้ไฟเลยเมื่อไม่กดสวิทช์และ Analog Pin จะลง GND ตลอดแบบ Pulldown
(หลักการแบบนี้เหมือนว่าจะใช้ในปุ่มใน LCD shield)
R1,R2…Rn ก็เลือกได้ตามความเหมาะสม ปกติผมใช้ 100k และ Rbase=800k-1M เพื่อประหยัดไฟ หาก R พวกนี้ไม่เหมาะสมการแบ่งช่วง Voltage จะน้อยหรือมากเกินในแต่ละช่วงทำให้เกิดปัญหา ได้
(บางทีเราใส่ Capacitor คร่อม Analog Pin และ GND เพื่อลดสัญญาณ noise ตอนกดและหน้าสัมผัสเกิด spark แต่ไม่จำเป็นหรอก software แทน)
Rbase มากกว่าตัวอื่นเพื่อยกระดับ level ด้านต่ำให้สูงขึ้นและทำให้ช่วงแบ่งกว้างขึ้นไปด้วยทำให้ง่ายต่อการวัด (บางทีผมก็เปลี่ยน R1 ด้วยเพื่อให้ช่วง Voltage เปลี่ยนด้วยเหตุผลเดียวกัน แต่ด้านบน แต่ไม่ค่อยจำเป็น)
จะเห็นว่าหลักการมันง่ายแสนง่ายและทำก็ง่ายแสนง่าย แต่ช้าก่อน
หากเรากดปุ่มใดๆสัญญาณมันจะออกมาคล้ายๆเป็นแบบนี้ (แค่ให้เห็นภาพครับจริงๆมันจะชันและกระเพื่อมมาก) จากเริ่มกดปุ่ม เพราะว่า Voltage มันจาก 0 มา จุดใดๆมันย่อมต้องผ่านค่าอื่นมาก่อน หาก Microcontroller เร็วมากๆย่อมจับค่าได้ทุกค่า มันก็จะเหมือนว่าเรากดมาทุกปุ่ม ดังนั้นใน Software เราต้องเช็คค่าสุดท้ายครับ
ในทางปฏิบัติเราไม่ต้องมาสนใจหรอกว่าค่าที่ได้มันกี่โวล์ทเราเอาค่าที่เข้า Analog Pin ก็พอแล้ว
ทดลองดู ตัวอย่าง สมมุติว่าเราต่อไปที่ Arduino Analog Pin A1
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(analogRead(A1));
delay(200);
}
จะเห็นว่ากดแล้วค่าก็เปลี่ยนไปลองกดแช่
กดปุ่มดูทีละปุ่มแล้วจดค่ามามันอาจกระเพื่อมเล็กน้อยช่างมันเอาคร่าวๆ เช่นสมมุติว่า เรากดได้ดังนี้ (จากสวิทช์ล่างขึ้นบน)
SW1 =530
SW2= 585
SW3 = 640
SW4 =715
ทีนี้เอาค่ากลางของแต่ละช่วงมา เช่น
SW1และSW2 = ((585-530)/2) = 27.5 เอาเลขกลมๆก็ 27
นั่นประมาณว่าหากค่าที่กดลงมา อยู่ในช่วง 530-27 ถึง 530+27 ความน่าจะเป็นคือกด Sw1
หลักการเดียวกันทำในทุกช่วง
SW1 = 503 ถึง 557
SW2 = 557 ถึง 612
SW3 = 612 ถึง 677
SW4 = 677 ถึง 752 (ประมาณจากค่าก่อนหน้า)
อย่าลืมว่าช่วงแต่ละช่วงมันไม่เท่ากันนะต้องหาไปทีละช่วง
นั่นคือหากค่าไม่อยู่ช่วงนี้ก็ไม่ได้กดอะไรเลย ค่าเป็น NONE
ตัวอย่างโปรแกรมอ่านค่าที่ใช้งานได้ดี ที่จะพิมพ์ค่า Analog ที่อ่านได้กับ Key ที่กด
#define btnOne 1 // 1
#define btnTwo 2 // 2
#define btnThree 3 // 3
#define btnFour 4 // 4
#define btnNONE 5 //Nothing
byte AnalogPin = 1;
boolean BounceAllow = LOW; // Allow repeat key
int MultiSW; // Value of analog data to identify swithc selector
int MultiSW2; // 2nd read, Value of analog data to identify switch selector for compare
byte OldxKey;//
int x;//different value between each progressive time
int Key;
int ReadKey()
{
do
{
MultiSW = int(analogRead(AnalogPin)); // read the value from switch
delay(50);
MultiSW2 = int(analogRead(AnalogPin));// 2nd read, check stability of value
x = abs(MultiSW2 - MultiSW);
}
while (x > 10); // x= different between 2 values, prevent read data during up and down voltage
if (MultiSW > 752) return btnNONE;
if (MultiSW < 503) return btnNONE;
if (MultiSW < 557) return btnOne;
if (MultiSW < 612) return btnTwo;
if (MultiSW < 677) return btnThree;
if (MultiSW < 752) return btnFour;
return btnNONE; // when all others fail, return this...
}
int read_MultiSW()
{
int TempKey;
TempKey = ReadKey();
if (BounceAllow == HIGH)
{
return TempKey;
}
else
{
if ((TempKey != btnNONE) && (OldxKey == btnNONE)) //-------prevent bouncing
{
OldxKey = TempKey;
return TempKey;
}
else
{
if (TempKey == btnNONE) OldxKey = TempKey;
return btnNONE;
}
}
return btnNONE;
}
void setup() {
Serial.begin(9600);
}
void loop() {
Key = read_MultiSW();
Serial.print(analogRead(AnalogPin));
Serial.print(" ");
Serial.println(Key);
//delay(200);
}
ต้องตั้งค่าตามกรอบ สีน้ำเงินกับ สีแดง ใหม่นะครับตามที่ออกแบบหรือวัดได้
AnalogPin คือ Pin ที่จะเอามาต่อกับ อุปกรณ์นี้
BounceAllow หากเป็น HIGH จะส่งค่าซ้ำๆตลอดเวลาที่กดปุ่ม หากเป็น LOW จะส่งค่าเดียวแล้วกลับเป็น NONE จะส่งอีกหากปล่อยแล้วกดปุ่มใหม่
ในกรอบสีแดงคือค่าช่วงที่เราหามาจากข้างต้น
หมายเหคุ
1 ในทางปฏิบัติ แทบ ทุก MCU ค่า Analog จะแปรตามค่า Vcc ทำให้การวัดค่าที่ได้ไม่ตรงกับตอนโปรแกรมดังนั้นการออกแบบให้ค่า R เพื่อมีช่วงกว้างของแต่ละระดับกว้างๆจึงสำคัญ และควรให้ค่า Vcc นั้นเท่ากับค่า Vcc ที่ใช้ตอนโปรแกรม นี่เป็นข้อยุ่งยากสำหรับบางคน
2 ในปัญหาข้อ 1 อาจหาทางแก้ไขโดย ใช้ Zener diode คร่อม และอาจใช้ External Ref เพื่อแก้ปัญหา ซึ่งฝากเป็นการบ้านหากใครจะเอาไปพัฒนาต่อ ส่วนผมไม่ได้มีปัญหากับมันมากนัก เพราะผมใช้ปกติแค่ 5-7 ปุ่มเอง
3 จากปัญหาในข้อ 1 อาจทำให้เกิดปัญหารวนคือกดปุ่มนึงได้ผลอีกแบบนึง ต้องพยายามให้การจ่ายไฟผ่าน Regulator และค่อนข้างนิ่งนะครับ จะใช้ไฟเลี้ยงวงจรแบบขึ้นๆลงๆเอาแน่เอานอนใข้ไม่ได้ครับ
4 หาก Port เหลือเพียบไม่แนะนำให้ใช้ครับ ต่อไปตรงๆ หรือ Scan แบบปกติดีกว่าครับเพราะเสถียรกว่า
หวังว่าคงได้ประโยชน์จากบทความนี้บ้างนะครับ
ขาเดียวร้อยปุ่ม DIY Keypad สำหรับ MCU, Arduino, ESP8266,
เรามาดูว่าปกติใน Aruduino มักใช้อุปกรณ์พวกนี้ เมื่อต้องการปุ่มเยอะๆ
แต่พวกนี้มันจะใข้ pin IO แบบทวีคูณตามจำนวนปุ่มเลย หรือเอาจำนวนปุ่มหาร2 เช่นในรูป 16 ปุ่มต้องใช้ 8 pin เวลาต่อก็แบบนี้
ซึ่งผมมองว่ามันไม่เหลือ Pin ให้เราใช้งานอื่นๆเลยหากเราใช้ทำโครงการซับซ้อน วันนี้เราจะมาแบนมันซะแล้ว
แล้วเอาพระเอกเรามาแทน (แม้ว่าหน้าตาจะขี้เหร่กว่า) แบบไม่มีใครเหมือน ไม่มีขายต้องทำเอง
มีแค่สามเส้น Vcc, GND, และสัญญาณ จะสิบร้อย พันก็ 3 pin
หลักการง่ายๆแลยเราจะใช้การแบ่ง Voltage เป็นช่วงๆ และตรวจจับระดับแรงดันขาเข้าของ Analog IO ทำให้เรารู้ว่ากดอะไรโดยที่ Analog IO ของ Arduino นี่มีเยอะและมักไม่ค่อยได้ใช้
พูดแค่นี้บางคนร้องอ๋อไปทำเองต่อได้แล้ว หลักการมันเหมือนเส้นผมบังภูเขา แปลกแต่จริงไม่มีคนทำขาย เพราะอะไร? คงเพราะ Software และความเข้าใจหลักการมันที่แสนง่าย แต่ซ่อนด้วยความยากนิดๆ (งงหน่อยๆ แบบเส้นผมบังภูเขาแต่อ่านกระทู้นี้แล้วหมูเลย) คนซื้อเอาไปใช้เมื่อใช้ไม่ได้ดังใจมันก็เคลม คนขายขี้เกียจตอบเพราะเวลาใช้ไม่ได้คนซื้อจะบอกว่าของห่วยแทนที่จะโทษตัวเอง
เราต้องมาดูกันต่อแบบเบสิกๆสำหรับคนที่ยังไม่อินในหลักการ
หลักการแบ่ง Voltage
เมื่อต่อ Resistor 2 ตัวเข้าด้วยกันแรงดันตกคร่อม Vo จะหาได้จาก
Vo= (R2xVcc)/(R1+R2)
หากเราต่อ R เข้าไปเยอะๆเราก็จะได้ Vo แต่ละจุดที่ไม่ซ้ำกัน เป็น V1,V2,V3,…Vn
หากต่อ Switch ปุ่มเข้าไปเราก็จะได้สัญญาณที่จะส่งไปที่ Analog Pin ได้
แต่การต่อแบบนี้ไม่ดีแน่เพราะ 2 สาเหตุคือ มันกินไฟตลอดเวลา และ หากไม่มีการกดปุ่มใดเลยขา Analog จะลอยให้ค่าออกมามั่วไปหมด
เราย้าย R ตัวสุดท้ายมาด้าน Switch ได้ตามรูป
จะเห็นได้ว่า มันไม่ใช้ไฟเลยเมื่อไม่กดสวิทช์และ Analog Pin จะลง GND ตลอดแบบ Pulldown
(หลักการแบบนี้เหมือนว่าจะใช้ในปุ่มใน LCD shield)
R1,R2…Rn ก็เลือกได้ตามความเหมาะสม ปกติผมใช้ 100k และ Rbase=800k-1M เพื่อประหยัดไฟ หาก R พวกนี้ไม่เหมาะสมการแบ่งช่วง Voltage จะน้อยหรือมากเกินในแต่ละช่วงทำให้เกิดปัญหา ได้
(บางทีเราใส่ Capacitor คร่อม Analog Pin และ GND เพื่อลดสัญญาณ noise ตอนกดและหน้าสัมผัสเกิด spark แต่ไม่จำเป็นหรอก software แทน)
Rbase มากกว่าตัวอื่นเพื่อยกระดับ level ด้านต่ำให้สูงขึ้นและทำให้ช่วงแบ่งกว้างขึ้นไปด้วยทำให้ง่ายต่อการวัด (บางทีผมก็เปลี่ยน R1 ด้วยเพื่อให้ช่วง Voltage เปลี่ยนด้วยเหตุผลเดียวกัน แต่ด้านบน แต่ไม่ค่อยจำเป็น)
จะเห็นว่าหลักการมันง่ายแสนง่ายและทำก็ง่ายแสนง่าย แต่ช้าก่อน
หากเรากดปุ่มใดๆสัญญาณมันจะออกมาคล้ายๆเป็นแบบนี้ (แค่ให้เห็นภาพครับจริงๆมันจะชันและกระเพื่อมมาก) จากเริ่มกดปุ่ม เพราะว่า Voltage มันจาก 0 มา จุดใดๆมันย่อมต้องผ่านค่าอื่นมาก่อน หาก Microcontroller เร็วมากๆย่อมจับค่าได้ทุกค่า มันก็จะเหมือนว่าเรากดมาทุกปุ่ม ดังนั้นใน Software เราต้องเช็คค่าสุดท้ายครับ
ในทางปฏิบัติเราไม่ต้องมาสนใจหรอกว่าค่าที่ได้มันกี่โวล์ทเราเอาค่าที่เข้า Analog Pin ก็พอแล้ว
ทดลองดู ตัวอย่าง สมมุติว่าเราต่อไปที่ Arduino Analog Pin A1
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println(analogRead(A1));
delay(200);
}
จะเห็นว่ากดแล้วค่าก็เปลี่ยนไปลองกดแช่
กดปุ่มดูทีละปุ่มแล้วจดค่ามามันอาจกระเพื่อมเล็กน้อยช่างมันเอาคร่าวๆ เช่นสมมุติว่า เรากดได้ดังนี้ (จากสวิทช์ล่างขึ้นบน)
SW1 =530
SW2= 585
SW3 = 640
SW4 =715
ทีนี้เอาค่ากลางของแต่ละช่วงมา เช่น
SW1และSW2 = ((585-530)/2) = 27.5 เอาเลขกลมๆก็ 27
นั่นประมาณว่าหากค่าที่กดลงมา อยู่ในช่วง 530-27 ถึง 530+27 ความน่าจะเป็นคือกด Sw1
หลักการเดียวกันทำในทุกช่วง
SW1 = 503 ถึง 557
SW2 = 557 ถึง 612
SW3 = 612 ถึง 677
SW4 = 677 ถึง 752 (ประมาณจากค่าก่อนหน้า)
อย่าลืมว่าช่วงแต่ละช่วงมันไม่เท่ากันนะต้องหาไปทีละช่วง
นั่นคือหากค่าไม่อยู่ช่วงนี้ก็ไม่ได้กดอะไรเลย ค่าเป็น NONE
ตัวอย่างโปรแกรมอ่านค่าที่ใช้งานได้ดี ที่จะพิมพ์ค่า Analog ที่อ่านได้กับ Key ที่กด
#define btnOne 1 // 1
#define btnTwo 2 // 2
#define btnThree 3 // 3
#define btnFour 4 // 4
#define btnNONE 5 //Nothing
byte AnalogPin = 1;
boolean BounceAllow = LOW; // Allow repeat key
int MultiSW; // Value of analog data to identify swithc selector
int MultiSW2; // 2nd read, Value of analog data to identify switch selector for compare
byte OldxKey;//
int x;//different value between each progressive time
int Key;
int ReadKey()
{
do
{
MultiSW = int(analogRead(AnalogPin)); // read the value from switch
delay(50);
MultiSW2 = int(analogRead(AnalogPin));// 2nd read, check stability of value
x = abs(MultiSW2 - MultiSW);
}
while (x > 10); // x= different between 2 values, prevent read data during up and down voltage
if (MultiSW > 752) return btnNONE;
if (MultiSW < 503) return btnNONE;
if (MultiSW < 557) return btnOne;
if (MultiSW < 612) return btnTwo;
if (MultiSW < 677) return btnThree;
if (MultiSW < 752) return btnFour;
return btnNONE; // when all others fail, return this...
}
int read_MultiSW()
{
int TempKey;
TempKey = ReadKey();
if (BounceAllow == HIGH)
{
return TempKey;
}
else
{
if ((TempKey != btnNONE) && (OldxKey == btnNONE)) //-------prevent bouncing
{
OldxKey = TempKey;
return TempKey;
}
else
{
if (TempKey == btnNONE) OldxKey = TempKey;
return btnNONE;
}
}
return btnNONE;
}
void setup() {
Serial.begin(9600);
}
void loop() {
Key = read_MultiSW();
Serial.print(analogRead(AnalogPin));
Serial.print(" ");
Serial.println(Key);
//delay(200);
}
ต้องตั้งค่าตามกรอบ สีน้ำเงินกับ สีแดง ใหม่นะครับตามที่ออกแบบหรือวัดได้
AnalogPin คือ Pin ที่จะเอามาต่อกับ อุปกรณ์นี้
BounceAllow หากเป็น HIGH จะส่งค่าซ้ำๆตลอดเวลาที่กดปุ่ม หากเป็น LOW จะส่งค่าเดียวแล้วกลับเป็น NONE จะส่งอีกหากปล่อยแล้วกดปุ่มใหม่
ในกรอบสีแดงคือค่าช่วงที่เราหามาจากข้างต้น
หมายเหคุ
1 ในทางปฏิบัติ แทบ ทุก MCU ค่า Analog จะแปรตามค่า Vcc ทำให้การวัดค่าที่ได้ไม่ตรงกับตอนโปรแกรมดังนั้นการออกแบบให้ค่า R เพื่อมีช่วงกว้างของแต่ละระดับกว้างๆจึงสำคัญ และควรให้ค่า Vcc นั้นเท่ากับค่า Vcc ที่ใช้ตอนโปรแกรม นี่เป็นข้อยุ่งยากสำหรับบางคน
2 ในปัญหาข้อ 1 อาจหาทางแก้ไขโดย ใช้ Zener diode คร่อม และอาจใช้ External Ref เพื่อแก้ปัญหา ซึ่งฝากเป็นการบ้านหากใครจะเอาไปพัฒนาต่อ ส่วนผมไม่ได้มีปัญหากับมันมากนัก เพราะผมใช้ปกติแค่ 5-7 ปุ่มเอง
3 จากปัญหาในข้อ 1 อาจทำให้เกิดปัญหารวนคือกดปุ่มนึงได้ผลอีกแบบนึง ต้องพยายามให้การจ่ายไฟผ่าน Regulator และค่อนข้างนิ่งนะครับ จะใช้ไฟเลี้ยงวงจรแบบขึ้นๆลงๆเอาแน่เอานอนใข้ไม่ได้ครับ
4 หาก Port เหลือเพียบไม่แนะนำให้ใช้ครับ ต่อไปตรงๆ หรือ Scan แบบปกติดีกว่าครับเพราะเสถียรกว่า
หวังว่าคงได้ประโยชน์จากบทความนี้บ้างนะครับ