【ALB學習筆記】基於事件觸發方式的串行通信接口數據接收案例
基於事件觸發方式的串行通信接口數據接收案例
廣東職業技術學院 歐浩源
1、案例背景
之前寫過一篇《基於多線程方式的串行通信接口數據接收案例》的博文,討論了采用輪詢方式接收串口數據的情況。經過使用了多線程來處理,而然輪詢的辦法比較還是比較笨拙的。我們在實際的項目開發中,更加常用的是基於事件觸發的方式,這個方式不但好用,而且靈活,只是使用起來需要更多的一點專業知識。在本博文中,就“傳感器模塊每隔1秒鐘向上位機傳送4字節的電壓數據幀”的項目,對該方法的設計進行詳細的講述。
數據幀的格式:幀頭(0xAF) 電壓數據高8位 電壓數據低8位 幀尾(0xFA)
2、事件觸發方式的工作原理
在SerialPort類中有一個DataReceived事件,當串口接收到了ReceivedBytesThreshold屬性設置的字符個數或者接收到了文件結束符並將其放入了串口接收緩沖區時,就會觸發DataReceived事件。
ReceivedBytesThreshold屬性決定了串口讀緩存中數據達到多少字節時才觸發DataReceived事件,其默認值為1。如果串口接收的是固定長度的數據,則將ReceivedBytesThreshold屬性設置為接收數據的長度;如果接收數據的結尾是固定的字符或字符串,則可以采用ReadTo方法
由於DataReceived事件在輔線程中被觸發,不能與主線程中的數據顯示控件直接進行數據傳輸,必須使用間接方式來實現。當收到完整的一條數據時,返回主線程處理或在主窗體上顯示時,要使用跨線程的處理方式,在C#中可以采用控件異步委托的方法BeginInvoke或者控件同步委托的方法Invoke。
3、引入命令空間
使用多線程的方式,需要引入命名空間:System.Threading;
使用串行通信接口,需要引入命名空間:System.IO.Ports;
4、初始化工作
給主窗體添加窗體裝載事件(即Load事件),在該事件中對各個控件的屬性進行初始化工作。
重點:在這裏要給DataReceived事件添加一個委托,將事件與數據接收處理方法DataReceivedHandler關聯起來。
com.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
SerialPort com = new SerialPort(); //實例化一個串口對象 private void Form2_Load(object sender, EventArgs e) { string[] ports = { "COM1", "COM2", "COM3", "COM4", "COM5" }; foreach (string str in ports) { comboBox1.Items.Add(str); } comboBox1.SelectedIndex = 2; string[] baudrate = { "2400", "4800", "9600", "19200", "57600", "115200" }; foreach (string str in baudrate) { comboBox2.Items.Add(str); } comboBox2.SelectedIndex = 2; comboBox3.Items.Add("6"); comboBox3.Items.Add("7"); comboBox3.Items.Add("8"); comboBox3.SelectedIndex = 2; comboBox4.Items.Add("1"); comboBox4.Items.Add("1.5"); comboBox4.Items.Add("2"); comboBox4.SelectedIndex = 0; comboBox5.Items.Add("None"); comboBox5.SelectedIndex = 0; com.ReceivedBytesThreshold = 4; //設置串口接收到4個字節數據才觸發DataReceived事件 //為串口DataReceived事件添加處理方法 com.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); }
5、數據接收處理方法DataReceivedHandler
當串口接收到ReceivedBytesThreshold屬性設置的字節數時,就會觸發DataReceived事件,從而執行DataReceivedHandler方法。在該方法裏進行對串口接收數據的分析處理等工作。如果需要在這個方法裏面將接收到的數據或者對數據的處理結果顯示到窗體的控件上,那麽就需要進行跨線程的處理了。
註意:在本方法中采用了BeginInvoke方法來處理跨線程的問題,在這個過程中,涉及到Action委托和Lambda表達式的知識點是很常用的,但在這裏就不展開敘述了,大家可以百度查找學習。
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { string strRcv = ""; int count = com.BytesToRead; //獲取串口緩沖器的字節數 byte[] readBuffer = new byte[count]; //實例化接收串口數據的數組 com.Read(readBuffer, 0, count); //從串口緩沖區讀出數據到數組 for (int i = 0; i < readBuffer.Length; i++) { strRcv += readBuffer[i].ToString("X2") + " "; //16進制顯示 } this.BeginInvoke(new Action(() => { textBox1.AppendText(strRcv); })); if (readBuffer[0] == 0xAF && readBuffer[3] == 0xFA) //判斷數據的幀頭和幀尾 { Int32 ad = readBuffer[1]; double advalue; ad <<= 8; ad |= readBuffer[2]; //從數據幀中將電壓數據取出 advalue = ad; advalue = (advalue * 3.3) / 32768; //將數據換算為實際的電壓值 this.BeginInvoke(new Action(() => { label2.Text = advalue.ToString("F2") + " V"; })); } }
6、打開串口
在進行串口通信的時候,一般的流程是:先設置通信的端口號、波特率、數據位、停止位和校驗位,然後打開串口,接著發送數據和接收數據,最後要關閉串口。
private void button1_Click(object sender, EventArgs e) { if (button1.Text == "打開串口") { com.PortName = comboBox1.Text; //選擇串口號 com.BaudRate = int.Parse(comboBox2.Text); //選擇波特率 com.DataBits = int.Parse(comboBox3.Text); //選擇數據位數 com.StopBits = (StopBits)int.Parse(comboBox4.Text); //選擇停止位數 com.Parity = Parity.None; //選擇是否奇偶校驗 try { if (com.IsOpen) //判斷該串口是否已打開 { com.Close(); com.Open(); } else { com.Open(); } } catch (Exception ex) { MessageBox.ReferenceEquals("錯誤:" + ex.Message, "串口通信"); } button1.Text = "關閉串口"; } else if (button1.Text == "關閉串口") { com.Close(); //關閉串口 button1.Text = "打開串口"; } }
7、運行結果
8、結語
關於串口數據的接收讀取無非就兩種方法:一是通過輪詢方式實時讀取串口,而是通過事件觸發實現串口讀取。盡管可以使用多線程處理,但是輪詢方式讀取串口的效率並不十分高效,因此,本人覺得還是采用事件觸發方式比較好。或許有人問,為什麽只寫串口讀取數據,不講串口發送數據呢?嗯.......沒錯,串口的數據讀取和數據發送是同樣重要的,但是數據讀取的處理比數據發送要復雜很多,我想,如果能把串口的數據讀取搞明白了,那串口數據發送還成問題嗎?
【ALB學習筆記】基於事件觸發方式的串行通信接口數據接收案例