การเขียนโปรแกรมประยุกต์ C# ติดต่อกับ Serial Port Computer
เดิมที่ในตอนนนี้ ผมจะเขียนเรื่องการเขียนโค๊ด C# เพื่อติดต่อกับ Serial Port Computer แต่ ผมกลับลืมไปว่า ควรจะบอกเรื่องรูปแบบข้อมูล (Protocol) ที่จะเขียนติดต่อกันระหว่าง Computer กับ ไมโครคอนโทรลเลอร์ เพราะถ้าไม่ตกลงกันก่อนทั้งสองฝั่ง ก็จะไม่สามารถที่จะออกแบบโปรแกรมที่ติดต่อกันได้ เพราะไม่รู้จะเขียน รับส่งกันแบบไหน เอาเป็นว่า ผมขอกล่าวเรื่องรูปแบบการติดต่อกันไว้ตรงนี้เลยหล่ะกัน (ย้อนกลับไปอ่าน ตอนที่ 1 หากยังไม่ได้อ่าน)
รูปแบบการสื่อสาร หรือที่เรียกว่า โปรโตคอล นั้น ถ้าจะยกตัวอย่างง่ายๆ นั้น ก็เหมือนการที่เราต่างชาติต่างภาษา แต่ ต้องการสื่อสารกันให้เข้าใจ เราก็ต้องมีการกำหนดรูปแบบการสื่อสารให้สามารถเข้าใจได้ทั้งสองฝ่ายนั่นเอง เช่น เราคนไทย ต้องการคุยกับคนจีน หากฝ่ายใดฝ่ายหนึ่ง ไม่ยอมที่จะหัดพูดภาษาของอีกฝ่ายหนึ่ง ก็ต้องสื่อสารกันด้วยภาษากลาง เช่น ภาษาอังกฤษ ในทำนองเดียวกัน หากคอนโทรลเลอร์ต้องการจะสื่อสารให้เข้าใจ ในสิ่งที่คอมพิวเตอร์ส่งออกมา ก็ต้องมีการตกลงกันก่อน ในที่นี้ เราจะใช้สัญญลักษณ์ ตัวอักษรเป็นตัวบ่งบอก รูปแบบของภาษาที่จะสื่อสารกัน
สมมุติเหตุการณ์ดังนี้ ไมโครคอนโทรลเลอร์ำทำการอ่านค่าสัญญาณแรงดันไฟตรงผ่านทางช่อง ADC แล้วทำการส่งค่าที่อ่านได้ไปที่โปรแกรมที่เราเขียนด้วย Visual C# ที่อยู่บนคอมพิวเตอร์ทำการอัพเดทค่าบนหน้าจอ จากนั้นที่หน้าต่างของโปรแกรมที่เราเขียนขึ้น จะมีปุ่ม button ให้เรากด เมื่อเรากดปุ่มไหน ให้ส่งค่ามาบอกที่ไมโครคอนโทรลเลอร์จากนั้นให้ LED ติดตามตำแหน่งปุ่มที่กด
จากโจทย์ืที่เรากำหนดขึ้นมา พอจะนำมาเขียนเป็นรูปแบบการสื่อสารได้ดังนี้
MCU ทำการส่งข้อมูลออกไป โดยมีรูปแบบดังนี้ #ADC1,ADC2,ADC3* โดยเราให้
# แสดงการเริ่มต้นส่งข้อมูล
ADCx ค่าอนาล๊อกที่อ่านได้จากช่อง Analog input ของ MCU มีค่าอยู่ระหว่าง 0-1023
* แสดงจุดสิ้นสุดข้อมูล
ซึ่ง Visual C# จะรับข้อมูลแล้วไปประมวลผลตามที่เราออกแบบไว้ จากนั้น Visual C# สามารถส่งข้อมูลออกไปหาที่ตัว MCU ได้ด้วยรูปแบบดังนี้ #Button1,Button2,Button3* โดยเราให้
# แสดงการเริ่มต้นส่งข้อมูล
Buttonx แสดงค่าสถานะการกดปุ่ม 0:ปุ่มไม่ถูกกด, 1:ปุ่มถูกกด
* แสดงจุดสิ้นสุดข้อมูล
ในโลกความเป็นจริง รูปแบบโปรโตคอลนั้น ซับซ้อนกว่านี้เยอะ แต่ ในที่นี้ เราจะทำความเข้าใจจากตัวอย่างง่ายๆ อย่างนี้ไปก่อน เพื่อเป็นพื้นฐานต่อไป
ทีนี้ เราก็เริ่มมาออกแบบโปรแกรมด้วย 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