การเขียนโปรแกรมประยุกต์ C# ติดต่อกับ Serial Port Computer

การเขียนโปรแกรมประยุกต์ C# ติดต่อกับ Serial Port Computer

    

    เดิมที่ในตอนนนี้ ผมจะเขียนเรื่องการเขียนโค๊ด C# เพื่อติดต่อกับ Serial Port Computer แต่ ผมกลับลืมไปว่า ควรจะบอกเรื่องรูปแบบข้อมูล (Protocol) ที่จะเขียนติดต่อกันระหว่าง Computer กับ ไมโครคอนโทรลเลอร์ เพราะถ้าไม่ตกลงกันก่อนทั้งสองฝั่ง ก็จะไม่สามารถที่จะออกแบบโปรแกรมที่ติดต่อกันได้ เพราะไม่รู้จะเขียน รับส่งกันแบบไหน เอาเป็นว่า ผมขอกล่าวเรื่องรูปแบบการติดต่อกันไว้ตรงนี้เลยหล่ะกัน (ย้อนกลับไปอ่าน ตอนที่ 1 หากยังไม่ได้อ่าน) 

    รูปแบบการสื่อสาร หรือที่เรียกว่า โปรโตคอล นั้น ถ้าจะยกตัวอย่างง่ายๆ นั้น ก็เหมือนการที่เราต่างชาติต่างภาษา แต่ ต้องการสื่อสารกันให้เข้าใจ เราก็ต้องมีการกำหนดรูปแบบการสื่อสารให้สามารถเข้าใจได้ทั้งสองฝ่ายนั่นเอง เช่น เราคนไทย ต้องการคุยกับคนจีน หากฝ่ายใดฝ่ายหนึ่ง ไม่ยอมที่จะหัดพูดภาษาของอีกฝ่ายหนึ่ง ก็ต้องสื่อสารกันด้วยภาษากลาง เช่น ภาษาอังกฤษ ในทำนองเดียวกัน หากคอนโทรลเลอร์ต้องการจะสื่อสารให้เข้าใจ ในสิ่งที่คอมพิวเตอร์ส่งออกมา ก็ต้องมีการตกลงกันก่อน ในที่นี้ เราจะใช้สัญญลักษณ์ ตัวอักษรเป็นตัวบ่งบอก รูปแบบของภาษาที่จะสื่อสารกัน 

    

สมมุติเหตุการณ์ดังนี้ ไมโครคอนโทรลเลอร์ำทำการอ่านค่าสัญญาณแรงดันไฟตรงผ่านทางช่อง ADC  แล้วทำการส่งค่าที่อ่านได้ไปที่โปรแกรมที่เราเขียนด้วย Visual C# ที่อยู่บนคอมพิวเตอร์ทำการอัพเดทค่าบนหน้าจอ จากนั้นที่หน้าต่างของโปรแกรมที่เราเขียนขึ้น จะมีปุ่ม button ให้เรากด เมื่อเรากดปุ่มไหน ให้ส่งค่ามาบอกที่ไมโครคอนโทรลเลอร์จากนั้นให้ LED ติดตามตำแหน่งปุ่มที่กด 

จากโจทย์ืที่เรากำหนดขึ้นมา พอจะนำมาเขียนเป็นรูปแบบการสื่อสารได้ดังนี้ 

MCU ทำการส่งข้อมูลออกไป โดยมีรูปแบบดังนี้   #ADC1,ADC2,ADC3*   โดยเราให้

ซึ่ง Visual C# จะรับข้อมูลแล้วไปประมวลผลตามที่เราออกแบบไว้ จากนั้น Visual C# สามารถส่งข้อมูลออกไปหาที่ตัว MCU ได้ด้วยรูปแบบดังนี้  #Button1,Button2,Button3* โดยเราให้ 

ในโลกความเป็นจริง รูปแบบโปรโตคอลนั้น ซับซ้อนกว่านี้เยอะ แต่ ในที่นี้ เราจะทำความเข้าใจจากตัวอย่างง่ายๆ อย่างนี้ไปก่อน เพื่อเป็นพื้นฐานต่อไป 

    

    ทีนี้ เราก็เริ่มมาออกแบบโปรแกรมด้วย Visual C# เพื่อให้รับข้อมูล และส่งข้อมูลออกไปทาง Serial Port Computer เริ่มจากการออกแบบ GUI กันก่อนครับหากใครเคยเขียน .NET มาบ้าง ตรงนี้ ไม่น่าจะยากนะครับ 

รูปแสดงหน้าตาของฟอร์ม และ ชื่อ control ที่จะำนำไปกำหนดใน code

ในส่วนของการติดต่อสื่อสารกับ Serial Port นั้น เราจะติดต่อสื่อสารกับไมโครคอนโทรลเลอร์แบบ Asynchronous ถ้าจะอธิบายสั้นๆ ก็คือ การสื่อสารแบบไม่ต้องอาศัยสัญญาณนาฬิกาในการกำหนดจังหวะการสื่อสาร (สังเกตที่สายข้อมูล เราจะไม่มีสายสัญญาณเส้นใด ที่เป็นสาย Clock เลย มีเพียง Rx,Tx และ Gnd เท่านั้น) ดังนั้น มันจึงต้องมีส่วนของ Baud rate, Parity bit, Stop bit เข้ามาเกี่ยวข้อง เพื่อเป็นตัวที่จะใช้แปลงข้อมูลที่ได้รับมา พูดง่ายๆ ก็คือ ต่างฝ่าย ต่างนำข้อมูลไปแปลตีความหมายเอาเอง โดยอาศัยว่าข้อมูลที่ถูกส่งออกมา มีรูปแบบเหมือนกันทั้งสองฝั่ง มี buad rate , parity bit , stop bit เหมือนกัน ในที่นี้ ผมขอ fix ค่าเหล่านี้ ไปในโค๊ดเลย เพื่อไม่ให้โค๊ดใหญ่เกินไป ซึ่งทั้งฝ่ายโปรแกรมบน PC และโปรแกรมบน MCU จะต้องกำหนดค่าเหล่านี้ให้ตรงกัน ข้อมูลที่รับมา จึงจะสามารถตีความหมายได้ 

    เรามาส่วนหลักๆ ในโค๊ด C# กันครับ เริ่มจากเราต้อง using System.IO.Ports; เพื่อจะสร้างออปเจค serial port จาก class SerialPort เมื่อเราได้ using เข้ามาแล้ว เราจึงสามารถทีจะสร้างออปเจคได้ 

serial = new SerialPort(comportComboBox.SelectedItem.ToString(), 9600, Parity.None, 8, StopBits.One);

จะเห็นว่า ผมรับค่า comport name จาก comportComboBox.SelectedItem.ToString() ส่วน baud rate ผมกำหดค่าลงไปใน code เลย ในที่นี้คือ 9600 และให้ parity bit เป็น None ให้ Data bit มีความยาว 8 บิต และ มี stop bit เท่ากับ 1  ค่าเหล่านี้จะต้องตรงกันทั้งโปรแกรมที่เขียนด้วย C# และโปรแกรมที่เขียนให้ไมโครคอนโทรลเลอร์ (จะกล่าวถึงทีหลัง) 

เราทำการเพิ่ม event handler ให้กับออปเจค เพื่อจะแยกโค๊ดไปเขียนการรับค่าทาง serial port 

 serial.DataReceived += new SerialDataReceivedEventHandler(serial_DataReceived); // พิมพ์ serial.DataReceived += แล้่วกด Tab 2 ครั้ง โปรแกรม Visual C# ide เขาจะสร้าง method serial_DataReceived  ให้อัตโนมัติ 

ใน method serial_DataReceived เราเพิ่มโค๊ดจัดการสตริืงที่อยู่ใน buffer ของ serial port พึงระลึกไว้เสมอว่า สุดท้ายแล้ว ข้อมูลที่รับมา โปรแกรมจะมองเห็นเป็นข้อมูลที่อยู่ในรูป string ติดกันยาวพรึด แล้วแต่ว่า มีข้อมูลมาค้างอยู่เยอะเท่าไหร่ ทางฝั่ง MCU ก็เหมือนกัน ทางนั้นก็มองเห็นข้อมูลเป็น string เหมือนกัน 

void serial_DataReceived(object sender, SerialDataReceivedEventArgs e)

        {

            string d = serial.ReadLine();

            int intBegin = d.IndexOf("#");

            int intEnd = d.IndexOf("*");

            int length = intEnd - (intBegin+1);

            char[] ch = { ','};

            raw = d;

            data = d.Substring(intBegin + 1, length).Split(ch);

            this.BeginInvoke(new updateAnalogLabel(updateLabel)); 

        }

ใน serial_DataReceived() นี้ ผมทำตามเงื่อนไขที่ได้ตกลงกันไว้ก่อนหน้านี้ ก็คือ ทาง MCU จะส่งข้อมูลกลับมาที่ PC ในรูปแบบ #ADC1,ADC2,ADC3* ดังนั้นผมก็ทำการหาตำแหน่งข้อมูลเริ่มต้นและสิ้นสุดก่อน จากนั้น ก็ทำการแยกข้อมูลที่เหลือ (ADC1,ADC2,ADC3) ด้วยการแยกข้อมูลออกด้วยตัวแยก comma นำข้อมูลไปเก็บใน ตัวแปร array ที่ชื่อ data

ส่วนการอัพเดทตัวเลขที่ได้รับมาไปที่ control ต่างๆบนฟอร์มนั้น เราต้องอาศัย delegate พังก์ชั่นจัดการในส่วนนี้ เพราะในระหว่างที่รับข้อมูลอยู๋นั้น มันไม่มีส่วนไหนของโปรแกรมไปอัพเดทข้อมูลบนฟอร์มเลย เราจึงต้องพึง delegate เพื่อทำ callback ฟังก์ชั่น

private delegate void updateAnalogLabel();

        private void updateLabel()

        {

            analog1Label.Text = data[0];

            analog2Label.Text = data[1];

            analog3Label.Text = data[2];

            serialRecieveTextBox.AppendText(raw);

        }

สังเกตใน serial_DataReceived() มีการเรียก updateAnalogLabel()

ในส่วนของการส่งข้อมูลออกจากโปรแกรมไปทาง serial port ที่เราได้ตกลงกันไว้คือ โปรแกรม C# จะทำการส่งสถานะการกดปุ่มบนฟอร์ม เพื่อไปบอกให้ MCU ขับหลอด LED  ซึ่งรูปแบบข้อมูลเรากำหนดไว้ดังนี้ #Button1,Button2,Button3* ซึ่งในส่วนของการส่งข้อมูลออก เราก็แค่ ดักจับ event ที่กดปุ่มแล้วก็เรียกฟังก์ชั่น serial.writer() เรามาดูโค๊ดการส่งข้อมูลออก serial port กันครับ

private void sendStateButton()

        {

            string s = "#" + Convert.ToInt32(stateButton1).ToString() + "," + Convert.ToInt32(stateButton2).ToString() + "," + Convert.ToInt32(stateButton3).ToString() + "*";

            if (serial != null)

            {

                if (serial.IsOpen)

                {

                    serial.Write(s);

                }

            }

        }

อย่ากที่บอกครับ ข้อมูลที่วิ่งเข้าออก serial port มันเป็นแค่สตริง เพราะฉะนั้น ผมจึงต้องทำการแปลงให้ข้อมูลให้เป็นสตริงก่อน แล้วนำมาต่อๆ กัน ให้เป็นข้อมูลยาวๆ แล้วก็เขียนออกไปที่ serial port 

ที่เหลือก็ไม่มีอะไรมากแล้วครับ อยู่ที่ลูกเล่นที่เราจะใส่ไปให้โปรแกรมเราดูดีซะมากกว่า ลองโหลดโค๊ดไปศึกษากันดูครับ ผมอัพไว้ที่ dropbox แล้ว https://www.dropbox.com/s/lks6vd7ulg1phw3/easySerial2013.zip?dl=0

ใน ตอนที่ 3 เราจะมาดูแนวคิดการสร้างโค๊ดทางฝั่ง MCU พอจัดการกับข้อมูลที่ได้รับจาก C# และการส่งข้อมูลออกมาทางช่อง USART ของมันครับ 

อ้างอิง : SerialPort Class http://msdn.microsoft.com/en-us/library/system.io.ports.serialport.aspx

            Delegates Tutorial http://msdn.microsoft.com/en-us/library/aa288459(v=vs.71).aspx