基於多線程方式的串行通信接口數據接收案例
廣東職業技術技術學院 歐浩源
1、案例背景
在本博客的《【CC2530入門教程-06】CC2530的ADC工作原理與應用》中實現了電壓數據采集的程序設計,傳感器模塊以每1秒發送一幀數據的形式通過串口向上位機發送電壓數據。其數據幀由4個字節組成:一個幀頭和一個幀尾,中間兩個字節為電壓數據,其格式如下:
幀頭(0xAF) 電壓數據高8位 電壓數據低8位 幀尾(0xFA)
在篇博文中,將講述如何通過多線程的方式,從串口接收傳感器發送過來的數據幀,並將數據幀和換算后的實際電壓值顯示出來。
2、界面設計

3、引入命令空間
使用多線程的方式,需要引入命名空間:System.Threading;
使用串行通信接口,需要引入命名空間:System.IO.Ports;
4、初始化工作
首先,定義一個串行接口的對象和一個線程的字段,
然后,給主窗體添加窗體裝載事件(即Load事件),在該事件中對各個控件的屬性進行初始化工作。
SerialPort com = new SerialPort(); //實例化一個串口對象 Thread t; //定義一個線程字段 private void Form1_Load(object sender, EventArgs e) { textBox1.ReadOnly = true; //文本框只讀 textBox1.ScrollBars = ScrollBars.Vertical; //文本框支持垂直滾動條 comboBox1.Items.Add("COM1"); comboBox1.Items.Add("COM2"); comboBox1.Items.Add("COM3"); comboBox1.Items.Add("COM4"); comboBox1.Items.Add("COM5"); comboBox1.SelectedIndex = 2; comboBox2.Items.Add("4800"); comboBox2.Items.Add("9600"); comboBox2.Items.Add("19200"); comboBox2.Items.Add("57600"); comboBox2.Items.Add("115200"); comboBox2.SelectedIndex = 1; 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; }
5、設置串行接口
在進行串口通信的時候,一般的流程是:先設置通信的端口號、波特率、數據位、停止位和校驗位,然后打開串口,接着發送數據和接收數據,最后要關閉串口。
在本案例中,對串口的各個參數配置完成后,打開串口,然后啟動串口數據接收的線程,開始以輪詢的方式接收數據。
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(); } t = new Thread(com_DataReceived); //創建並啟用數據接收線程 t.Start(); } catch(Exception ex) { MessageBox.ReferenceEquals("錯誤:" + ex.Message, "串口通信"); } button1.Text = "關閉串口"; } else if (button1.Text == "關閉串口") { com.Close(); //關閉串口 t.Abort(); //終止線程 button1.Text = "打開串口"; } }
6、串口數據接收線程方法
在該方法中,只有在串口打開並連接成功的情況下在去讀取串口緩沖區中的數據。
首先通過讀取BytesToRead屬性,獲取串口接收緩沖區中的數據字節數,然后根據這個屬性實例化一個byte類型的數據,在通過Read方法將數據從緩沖區中讀取到數組中。
將數據幀以十六進制的形式顯示到窗體的文本框中。
通過計算獲得實際的電壓值,顯示到對應的Lable標簽中。
private void com_DataReceived() { while (com.IsOpen) //在串口已打開情況下讀取數據 { string strRcv = ""; int count = com.BytesToRead; //獲取串口緩沖器的字節數 byte[] readBuffer = new byte[count]; //實例化接收串口數據的數組 if (count != 0) //如果串口接收到數據 { strRcv = count.ToString() + " "; com.Read(readBuffer, 0, count); //從串口緩沖區讀出數據到數組 for (int i = 0; i < readBuffer.Length; i++) { strRcv += readBuffer[i].ToString("X2") + " "; //16進制顯示 } textBox1.Text += strRcv + "\r\n"; 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; //將數據換算為實際的電壓值 label6.Text = advalue.ToString("F2") + " V"; } } Thread.Sleep(500); }
7、程序運行結果

8、結語
串行接口通信是物聯網技術應用中常用的數據交互方式。
利用多線程的手段以輪詢的方式讀取串口緩沖區的數據是一種最基礎的方法,然而並不是唯一的方法。采用DadaReceived事件觸發的方法來接收數據也很常用,當程序設計程度稍微復雜一點,后面再慢慢講述。
