ในบทความนี้ผมตั้งใจว่าจะเขียนถึงการอ่านค่าจากสวิตซ์
หรือปุ่มกด เพราะสวิตซ์เป็นอีกหนึ่งอุปกรณ์สำคัญในงานไมโครคอนโทรลเลอร์
ที่เป็นเหมือกับ user
interface อีกประเภทหนึ่งที่ทำให้เราสามารถควบคุมหรือสั่งงานไปยังไมโครคอนโทรลเลอร์อีกทางหนึ่ง
โดยสวิตซ์เป็นอีกหนึ่งอุปกรณ์พื้นฐานที่ผู้เริ่มต้นเรียนไมโครคอนโทรลเลอร์จะต้องใช้เป็นประจำต่อจาก
LED, และ USART เรามาลองดูกันซิว่าจะเขียนโปรแกรมเพื่ออ่านค่าจากมันได้อย่างไรบ้าง
Experiment
Shield
ก่อนจะเริ่มเขียนโปรแกรมอ่านค่าจากสวิตซ์
ผมอยากจะแนะนำ Experiment Shield ที่ผมออกแบบขึ้นมาเพื่อใช้เริ่มต้นเรียนคู่กับ
PIC Get Start 8 บอร์ด โดยผมได้เอาแบบอย่างมาจากที่นี่ครับ Demo Shield แล้วปรับแก้เล็กน้อยพร้อมกับเพิ่มจุดเชื่อมต่อจอ
LCD 16x2 หรือ 20x4 เข้าไป เพื่อให้เกิดประโยชน์มากยิ่งขึ้น
โดยในส่วนของสวิตซ์จะมีอยู่สองตัวซึ่งต่ออยู่กับขา RB0 และ RB1
ซึ่งสวิตซ์ทั้งสองตัวนี้จะไม่มีตัวต้านทานต่อพูลอัพไว้
เพราะตั้งใจจะใช้ weak-pull up ในตัวไมโครคอนโทรลเลอร์
ซึ่งความสามารถนี้จะมีอยู่บน PORTB ของ PIC16F1938 นี้ นอกจากนี้ยังมี
LED อีกสี่ดวงซึ่งจากในรูปที่ 1 จะเห็นว่าต่ออยู่กับ PORTB ขา RB<2:5>
ซึ่งเราจะใช้ในการแสดงผลลัพธ์ของการกดสวิตซ์
เริ่มต้นสร้างโปรเจ็ค
ในการเขียนโปรแกรมเราจะสร้างโปรเจ็คใหม่ใน
MPLABX ก่อนแล้วใช้ MCC ในการ config และ
generate code เริ่มต้นให้กับเรา
โดยสามารถดูได้จากวิดีโอด้านล่างนี้ครับ
สำหรับคนที่เพิ่งมาเริ่มต้นกับบทความนี้สามารถดูวิธีการสร้างโปรเจ็คกับ MPLABX
และ MCC รวมถึงการใช้งานโปรแกรม ds30loaderGUI
ได้จากบทความก่อนหน้านี้ครับ
จากวิดีโอจะเห็นว่าเราได้ทำการเขียนโค้ดเพิ่มเติมเพื่อให้สามารถอ่านค่าจากสวิตซ์ได้ดังนี้
LED4_Toggle();
__delay_ms(400);
if(S1_GetValue()==LOW){
__delay_ms(20); //debounce
while(S1_GetValue()==LOW); //wait for S1 released
__delay_ms(20); //debounce
LED1_Toggle();
}
if(S2_GetValue()==LOW){
__delay_ms(20); //debounce
while(S2_GetValue()==LOW); //wait for S1 released
__delay_ms(20); //debounce
LED2_Toggle();
}
สองบรรทัดแรกการสั่งให้
LED4 กระพริบทุกๆ 400 ms จากนั้นจึงมาอ่านค่าจาก สวิตซ์
S1 โดยเมื่อยังไม่ได้กดสวิตซ์ S1 จะมีค่าเป็น logic
1 หรือ HIGH เพราะมี internal
weak-pull up resistor ต่ออยู่กับ Vdd
แต่เมื่อกดสวิตซ์ค่าที่อ่านได้จะมีค่าเป็น
logic 0
หรือ LOW เพราะถูกช็อตลงกราวด์
ดังนั้นเมื่อเราเขียนโค้ดอ่านค่าแล้วได้เท่ากับ LOW
หมายความว่ามีการกดสวิตซ์ เราก็จะเข้ามาสั่ง Toggle LED ดวงที่เราต้องการ
เพื่อเป็นสิ่งแสดงให้รู้ว่าไมโครคอนโทรลเลอร์สามารถอ่านค่าจากสวิตซ์ได้ถูกต้อง ส่วนคำสั่ง __delay_ms(20); เราจำเป็นต้องใส่ไว้เพื่อป้องกัน bounce ที่เกิดขึ้นจากหน้าสัมผัสของสวิตซ์แตะกันตอนเรากด
หรือปล่อย สวิตซ์ หากไม่มีการ de-bounce ไมโครคอนโทรลเลอร์จะอ่านค่าจากสวิตซ์ผิดพลาดได้
นอกจากนี้ผมยังได้เพิ่มคำสั่ง while(S2_GetValue()==LOW);
ลงไปเพื่อให้แน่ใจว่าได้ถูกกดและปล่อยเรียบร้อยแล้วจึงจะทำการ Toggle
LED
สำหรับสวิตซ์ S2 ก็จะมีการทำงานในลักษณะเดียวกันกับสวิตซ์
S1 ตัวอย่างโค้ดอันนี้เป็นการอ่านค่าจากสวิตซ์แบบง่ายๆนะครับ
ยังไม่เหมาะเอาไปใช้ในงานจริงต่างๆเพราะยังมีจุดอ่อนหลายอย่าง อย่างเช่นถ้าเรากดสวิตซ์ใดสวิตซ์หนึ่งค้างไว้ LED1
จะไม่กระพริบ
เพราะไมโครคอนโทรลเลอร์จะค้างอยู่ที่คำสั่งรอให้สวิตซ์ปล่อยอยู่
หรือถ้าลองกดสวิตซ์หลายๆครั้งเราจะสังเกตุเห็นว่าสวิตซ์ตอบสนองช้าในบางครั้งหรืออาจไม่ตอบสนองต่อการกดครั้งนั้นๆก็ได้
นี่เพราะเราใช้ __delay_ms(400); สำหรับ Toggle LED4 นั่นเอง
อ่านค่าสวิตซ์ในอินเตอร์รัพของไทม์เมอร์
จากปัญหาในตัวอย่างที่ผ่านมาผมจึงอยากนำเสนออีกหนึ่งทางแก้ไขคือการอ่านค่าของสวิตซ์ในอินเตอร์รัพของไทม์เมอร์
ในที่นี้จะใช้ Timer0
ให้ over flow แล้วเกิด interrupt ทุกๆ 1ms. ใน interrupt service routine ของ Timer0 ก็จะทำการอ่านค่าสวิตซ์พร้อมทั้งมีการ de-bounce
สวิตซ์ด้วย เมื่ออ่านค่าได้แล้วจึงส่งผ่านตัวแปรโกลบอลให้โปรแกรม main
สั่งงาน Toggle LED ตามที่กดสวิตซ์ต่อไป ส่วน LED4 ก็ให้กระพริบทุกๆ 400 ms. ใน interrupt ของ Timer0
เช่นกัน ลองมาดูตัวอย่างในวิดีโอข้างล่างนี้ได้เลยครับ
ผลการทดสอบจะเห็นว่าเวลากดสวิตซ์มีการตอบสนองที่รวดเร็ว ถึงแม้จะกดค้างไว้ LED4 ก็ยังกระพริบตามจังหวะ แต่จะมีปัญหาหากกดสวิตซ์พร้อมกันทั้ง 2 ตัว ทางแก้อาจจะต้องเขียน state-machine แยกของแต่ละสวิตซ์รวมถึงใช้ตัวแปรแยกจากกันด้วย ลองคิดกันดูนะครับ
เราเริ่มต้นโดย
copy โปรเจ็คเดิมแล้วตั้งชื่อใหม่ จากนั้นก็ใช้ MCC ในการคอนฟิค
Timer0 ให้ได้อย่างที่เราต้องการ หลังจากได้โค้ดที่ generated
ใหม่แล้วเราก็ลบโค้ด polling เดิมทิ้งซะ
แล้วไปเขียนโค้ดในส่วนของ Timer0 interrupt ซึ่งอยู่ในไฟล์ tmr0.c โดยเราจะประกาศตัวแปรที่จำเป็นต้องใช้ตามตัวอย่างข้างล่างนี้
#define Key_Delay 20
/**
Section: Global Variables Definitions
*/
volatile uint8_t timer0ReloadVal, KEYPAD_Debounce_Counter;
volatile uint8_t KeyReturn;
volatile enum Key{ NoPressed, Debounce1, Pressed, StillPressed, Debounce2} KeyState = NoPressed;
และไปเพิ่มเติม
state-machine code ในฟังก์ชัน TMR0_ISR(void) ซึ่งหากลองไล่ทำความเข้าใจจะพบว่าไม่ยากเลย ลองดูครับ
ตัวอย่างโค้ดตามนี้ครับ
switch( KeyState ){
case NoPressed:
if( !RB0 | !RB1 ){ //any keys is pressed
KEYPAD_Debounce_Counter = 0;
KeyState = Debounce1;
}
break;
case Debounce1:
KEYPAD_Debounce_Counter++;
if( KEYPAD_Debounce_Counter >= Key_Delay ){
KeyState = Pressed;
}
break;
case Pressed:
if( !RB0 ){ //S1 is pressed
KeyReturn = 1;
KEYPAD_Debounce_Counter = 0;
KeyState = StillPressed;
}
else if( !RB1 ){ //S2 is pressed
KeyReturn = 2;
KEYPAD_Debounce_Counter = 0;
KeyState = StillPressed;
}
else{ //no key pressed
KeyReturn = 0;
KeyState = NoPressed;
}
break;
case StillPressed:
if( !RB0 | !RB1 ){ //still pressed
}
else if( RB0 | RB1){ //already released
KeyState = Debounce2;
}
break;
case Debounce2:
KEYPAD_Debounce_Counter++;
if( KEYPAD_Debounce_Counter >= Key_Delay ){
KeyState = NoPressed;
}
break;
default: break;
}//switch( KeyState )
ในตัวอย่างผมจะอ่านค่าสวิตซ์จากขาของไมโครคอนโทรลเลอร์โดยตรง
ไม่ได้ใช้มาโคร ‘S1_GetValue()’
เหมือนในตัวอย่างแรกครับ เพราะมันสั้นดี
แต่มีความหมายเหมือนกันนะครับ ทำงานได้เหมือนกันเลย
สุดท้ายเราก็มาเขียนโค้ดใน
main เช็คว่าสวิตซ์ตัวไหรถูกกดก็ Toggle LED ที่สัมพันธ์กัน
แต่เราจำเป็นต้องบอก compiler ว่าตัวแปร KeyReturn ที่เราจะเช็คนั้นอยู่ภายนอกไฟล์ main ไม่เช่นนั้นเวลาคอมไพล์แล้วจะ
error ไม่ผ่านครับ อย่างนี้ครับ
extern volatile uint8_t KeyReturn;
อ้อแล้วอย่าลืมเคลียร์ค่าของ KeyReturn
ด้วยนะครับ ตัวอย่างโค้ดใน main ครับ
switch( KeyReturn )
{
case 1: LED1_Toggle(); KeyReturn = 0; break;
case 2: LED2_Toggle(); KeyReturn = 0; break;
default: break;
}
ผลการทดสอบจะเห็นว่าเวลากดสวิตซ์มีการตอบสนองที่รวดเร็ว ถึงแม้จะกดค้างไว้ LED4 ก็ยังกระพริบตามจังหวะ แต่จะมีปัญหาหากกดสวิตซ์พร้อมกันทั้ง 2 ตัว ทางแก้อาจจะต้องเขียน state-machine แยกของแต่ละสวิตซ์รวมถึงใช้ตัวแปรแยกจากกันด้วย ลองคิดกันดูนะครับ