เมนู

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

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

MPLABX MCC UART

             
                 ห่างหายไปนานสำหรับบล็อกนี้ พึ่งจะได้มีเวลาว่างมาเขียนอีกครั้ง /(^_^)\
สำหรับบทความนี้ตั้งใจจะเขียนถึงโมดูล USART ในตัว PIC เพราะค่อนข้างมีประโยชน์มากสำหรับงานทางด้าน embedded อย่างที่ผมทำอยู่ อย่างแรกคือมันเป็น user interface ที่สามารถสื่อสารกับตัวเราได้ เพราะสำหรับ micro controller ซึ่งไม่มีหน้าจอแล้ว มันค่อนข้างลำบากที่จะรู้ว่าตอนนี้เจ้า micro controller กำลังทำอะไรอยู่ แต่เราสามารถที่จะใช้โมดูล USART นี้เป็น user interface สื่อสารกับเราผ่านทางหน้าจอคอมพิวเตอร์ โดยอาศัยเจ้า usb-to-serial module ที่มาพร้อมกับบอร์ด PIC Get Start 8 และโปรแกรม terminal บนคอมพิวเตอร์ของเราเป็นตัวกลางและตัวช่วย เราก็จะสามารถสื่อสารกับ PIC ด้วยภาษาอังกฤษแล้ว นอกจากนี้เจ้าโมดูล USART ยังเป็นการสื่อสารแบบ serial ที่นิยมกันมาก สามารถใช้กับโมดูลต่างๆได้มากมาย ไม่ว่าจะเป็น Bluetooth-to-serial ที่เอาไว้ติดต่อกับ android phone ผ่านทาง Bluetooth เพื่อสื่อสารหรือทำหุ่นยนต์ไว้เล่นก็ได้ หรือจะเป็น WiFi-to-serial ที่เอาไว้ติดต่อทาง WiFi ก็เห็นมีใช้กันอยู่มากมาย ดังนั้น USART จึงน่าที่จะเรียนรู้ไว้ใช้งานเป็นอันดับแรก

                โมดูล USART นั้นมาจากคำว่า Universal Synchronous Asynchronous Receiver Transmitter ซึ่งหมายถึงว่ามันสามารถสื่อสารได้ทั้งแบบ Synchronous และแบบ Asynchronous โดยแบบ Synchronous จะมีขาสำหรับส่ง(transmit), ขาสำหรับรับ(receive), และขาสัญญานนาฬิกา(clock) เพื่อ sync กับข้อมูล  ส่วนแบบ Asynchronous จะมีเฉพาะขาส่ง กับขารับ เท่านั้น ซึ่งที่ผมจะใช้ก็จะเป็นแบบ Asynchronous ที่มีเพียง 2 ขาเท่านั้นและโมดูลส่วนใหญ่ก็จะใช้แบบ Asynchronous ทั้งนั้นเท่าที่ผมเคยใช้

          ในการเขียนโปรแกรมเพื่อใช้งานโมดูล USART นั้นจำเป็นที่จะต้องเข้าไปคอนฟิค รีจิสเตอร์ หลายตัวของโมดูลและยังจะต้องกำหนดว่าจะให้โมดูลสื่อสารกับคอมพิวเตอร์ที่ความเร็วเท่าไหร่ หรือก็คือการกำหนด baud rate นั่นเองโดยทั่วไปจะใช้งานที่ baud rate = 9600 แล้วใช้ data bits = 8 ไม่มี Parity bit ใช้ stop bit = 1 และไม่มี hardware flow control คราวนี้ถ้าไปดูจาก datasheet จะเห็นว่าค่อนข้างวุ่นวายพอควรเลยทีเดียวสำหรับมือใหม่ หรือถึงแม้มือเก่า (แก่) อย่างผมยังพลาดออกบ่อยไปเพราะ mcu ตัวใหม่ๆจะทำงานที่สปีดสูงๆ โมดูล USART ก็สามารถสื่อสารที่ baud rate สูงๆก็เลยต้องมีรีจิสเตอร์สำหรับคอนฟิคเพิ่มขึ้น นอกจากนี้ขา Tx กับ Rx ก็อาจจะ multiplex กับโมดูลอื่นอีกก็ต้องคอยเช็ค คอยเลือกให้ถูกต้อง เฮ้อเหนื่อยกว่าจะทำให้มันสื่อสารได้  แต่หลังจากมี MCC มาช่วยในการคอนฟิคแล้ว ทุกอย่างมันง่ายขึ้นเยอะเลยทีเดียวครับ แค่เลือก clock สำหรับ mcu ให้ถูกต้องจากนั้นก็มาคอนฟิคโมดูล USART ว่าจะใช้ baud rate เท่าไหร่ มันก็จะคำนวณค่าที่จะใช้ให้เสร็จสรรพ แถมคำนวณค่าผิดพลาดให้เรารู้ด้วยว่าอยู่ในช่วงที่ยอมรับได้หรือไม่ นอกจากนี้ก็ยังมีออปชั่นให้เลือกว่าจะใช้เป็นแบบ polling หรือแบบ interrupt ทั้งฝั่งส่งและรับ ถ้าเลือกเป็นแบบ interrupt มันจะสร้างเป็นแบบ circular buffer ให้เราเสร็จสรรพทั้งฝั่งส่งและรับ สามารถกำหนดขนาดของ buffer ได้ด้วยนะครับ  เมื่อก่อนนี้จะทำ circular buffer สำหรับ USART นี่ผมต้องหาจากเน็ทให้วุ่นวายไปหมด แล้วส่วนใหญ่ก็ไม่ค่อยเวิคส์เพราะไม่เข้าใจที่หามาได้   อันสุดท้ายสำหรับใครที่อยากใช้ printf กับ USART ก็สามารถเลือกให้ MCC สร้างฟังก์ชัน putch() ที่ printf จะเรียกใช้งานได้ด้วย สะดวกมากมายเลยครับ

                ในวีดีโอได้แสดงการสร้างโปรเจ็คและใช้ MCC เช็ทค่าโมดูล Timer0 ให้เกิด interrupt ทุกๆ 500 ms เพื่อ toggle LED จากนั้นจึงคอนฟิคโมดูล USART  ให้สามรถรับค่าและส่งค่ากับ PC ได้ โดยกำหนด baud rate ที่ 9600 และใช้ polling นอกจากนี้ก็เลือกให้สามารถใช้งาน printf() ได้เพื่อความสะดวก จากนั้นจึงโปรแกรม hex file ลง PIC16F1938 ผ่านทาง ds30loaderGUI โดยตัว ds30loaderGUI นี้มีออปชั่นให้เลือกว่าหลังจาก upload ไฟล์เสร็จแล้วก็จะออโตเมติคเปิดแท็ป terminal ให้เราเลย ซึ่งถ้าเรากำหนด baud rate ที่ถูกต้องไว้ก่อนแล้วมันก็จะแสดงข้อความที่เราโปรแกรมไว้ขึ้นมาให้เห็นทันที ไม่ต้องไปหาโปรแกรม terminal ตัวอื่นมาเปิด มาคอนเน็ค ให้เสียเวลากันอีกแล้วครับ เมื่อโปรแกรมเริ่มทำงานเราก็จะเห็นข้อความที่ PIC ส่งมาให้เราพิมพ์ตัวอักษรอะไรก็ได้ลงไป จากนั้นตัว PIC มันจะ echo กลับมาบอกว่าเราพิมพ์ตัวอะไรลงไป ในขณะที่ LED ก็กระพริบช้าๆ เพื่อบอกให้เรารู้ว่าตัว PIC มันกำลังทำงานอยู่นั่นเอง ลองทดลองกันดูนะครับ  ถ้าต้องการแนะนำอะไรก็คอมเม้นมาได้นะครับ