เมนู

วันพฤหัสบดีที่ 31 มีนาคม พ.ศ. 2559

อ่านค่าจากสวิตซ์ ด้วย MPLABX และ MCC



              ในบทความนี้ผมตั้งใจว่าจะเขียนถึงการอ่านค่าจากสวิตซ์ หรือปุ่มกด เพราะสวิตซ์เป็นอีกหนึ่งอุปกรณ์สำคัญในงานไมโครคอนโทรลเลอร์ ที่เป็นเหมือกับ 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> ซึ่งเราจะใช้ในการแสดงผลลัพธ์ของการกดสวิตซ์

                                                 รูปที่ 1 schematic of Experiment shield

เริ่มต้นสร้างโปรเจ็ค
            ในการเขียนโปรแกรมเราจะสร้างโปรเจ็คใหม่ใน 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 เช่นกัน ลองมาดูตัวอย่างในวิดีโอข้างล่างนี้ได้เลยครับ


                  เราเริ่มต้นโดย 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 แยกของแต่ละสวิตซ์รวมถึงใช้ตัวแปรแยกจากกันด้วย ลองคิดกันดูนะครับ

ไม่มีความคิดเห็น:

แสดงความคิดเห็น