C# 實現(xiàn)簡易的串口監(jiān)視上位機功能附源碼下載
實現(xiàn)上位機和下位機之間的通信,通常使用的是串口通信,接下來實現(xiàn)一個通過上位機和串口調(diào)試助手來完成串口通信測試。
首先創(chuàng)建一個WInfrom窗體應(yīng)用工程文件,創(chuàng)建過程可參考 http://www.dbjr.com.cn/article/150973.htm
在創(chuàng)建好的工程下面,通過工具箱中已有的控件完成界面的搭建,如下圖所示,為了方便初學(xué)者容易看懂程序,下圖將控件的命名一并標(biāo)注出來:

直接進入正題,將完整的工程代碼黏貼出來:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Diagnostics;
namespace Tem_Hum_Monitorring
{
public partial class Form1 : Form
{
//實例化串口
SerialPort s = new SerialPort();
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
button1.Text = "打開串口";
int[] item = { 9600,115200}; //遍歷
foreach (int a in item)
{
comboBox2.Items.Add(a.ToString());
}
comboBox2.SelectedItem = comboBox2.Items[1];
}
private void Form1_Load(object sender, EventArgs e)
{
portInit();
}
/// <summary>
/// 串口初始化
/// </summary>
private void portInit()
{
string[] ports = SerialPort.GetPortNames();
comboBox1.Items.AddRange(ports);
comboBox1.SelectedItem = comboBox1.Items[0];
}
#region 開關(guān)串口
private void button1_Click(object sender, EventArgs e)
{
try
{
if (!s.IsOpen)
{
s.PortName = comboBox1.SelectedItem.ToString();
s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
s.Open();
s.DataReceived += s_DataReceived; //"+="代表指定響應(yīng)事件時要調(diào)用的方法
button1.Text = "關(guān)閉串口";
}
else
{
s.Close();
s.DataReceived -= s_DataReceived;
button1.Text = "打開串口";
}
}
catch(Exception ee)
{
MessageBox.Show(ee.ToString());
}
}
#endregion
#region 串口接收
void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int count = s.BytesToRead;
string str = null;
if (count == 8)
{
//數(shù)據(jù)解析
byte[] buff = new byte[count];
s.Read(buff, 0, count);
foreach (byte item in buff)
{
str += item.ToString("X2") + " ";
}
richTextBox1.Text = "[" + System.DateTime.Now.ToString() + "] " + str + "\n" + richTextBox1.Text;
if (buff[0] == 0x04)
{
ID.Text = buff[0].ToString();
switch (buff[2])
{
case 0x01:
{
Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
Hum.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x02:
{
Light.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x04:
{
Dust.Text = (buff[6] + buff[7]).ToString();
break;
}
default:
break;
}
}
}
else
{
//當(dāng)接收數(shù)據(jù)不在設(shè)定的數(shù)據(jù)位范圍之內(nèi)時,會出現(xiàn)接受到的數(shù)據(jù)一直保存在接收緩存區(qū)之內(nèi),后續(xù)每次接手數(shù)據(jù)都會將上一次的數(shù)據(jù)進行疊加,造成只能通過關(guān)閉串口的方法來清除緩沖區(qū)的數(shù)據(jù)
s.DiscardInBuffer(); //丟棄來自串行驅(qū)動程序的接收緩沖區(qū)的數(shù)據(jù)
}
}
#endregion
#region 串口發(fā)送
private void button3_Click(object sender, EventArgs e)
{
string[] sendbuff = richTextBox2.Text.Split();
Debug.WriteLine("發(fā)送字節(jié)數(shù):" + sendbuff.Length);
foreach (string item in sendbuff)
{
int count = 1;
byte[] buff = new byte[count];
buff[0] = byte.Parse(item, System.Globalization.NumberStyles.HexNumber);
s.Write(buff,0,count);
}
}
#endregion
private void button2_Click(object sender, EventArgs e)
{
int count = 1;
byte[] buff = new byte[count];
buff[0] = byte.Parse("04", System.Globalization.NumberStyles.HexNumber);
s.Write(buff, 0, count);
}
}
}
在Winfrom窗體設(shè)計中,實現(xiàn)串口可以通過工具箱中的串口控件來實現(xiàn),不過一般推薦直接通過代碼來實例化串口,實例化串口需使用如下代碼來實現(xiàn):
//實例化串口 SerialPort s = new SerialPort();
串口初始化可以在窗體的Load函數(shù)中實現(xiàn),以下初始化可以自動化取當(dāng)前設(shè)備中的存在的串口,包括真實串口和虛擬串口:
private void Form1_Load(object sender, EventArgs e)
{
portInit();
}
/// <summary>
/// 串口初始化
/// </summary>
private void portInit()
{
string[] ports = SerialPort.GetPortNames();
comboBox1.Items.AddRange(ports);
comboBox1.SelectedItem = comboBox1.Items[0];
}
通過對開關(guān)按鍵button1控件的點擊事件,實現(xiàn)串口的開關(guān),通過對控件的文字修改,可以實現(xiàn)一個控件機能實現(xiàn)開又能實現(xiàn)關(guān)串口的作用:
#region 開關(guān)串口
private void button1_Click(object sender, EventArgs e)
{
try
{
if (!s.IsOpen)
{
s.PortName = comboBox1.SelectedItem.ToString();
s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
s.Open();
s.DataReceived += s_DataReceived; //"+="代表指定響應(yīng)事件時要調(diào)用的方法
button1.Text = "關(guān)閉串口";
}
else
{
s.Close();
s.DataReceived -= s_DataReceived;
button1.Text = "打開串口";
}
}
catch(Exception ee)
{
MessageBox.Show(ee.ToString());
}
}
#endregion
串口數(shù)據(jù)接收和數(shù)據(jù)解析,首先獲取數(shù)據(jù)接收緩存區(qū)數(shù)據(jù)的字節(jié)長度,通過確認長度是否是設(shè)定中的長度大小,如果是設(shè)定的8位數(shù)據(jù)長度則對接收的數(shù)據(jù)進行解析:
#region 串口接收
void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int count = s.BytesToRead;
string str = null;
if (count == 8)
{
//數(shù)據(jù)解析
byte[] buff = new byte[count];
s.Read(buff, 0, count);
foreach (byte item in buff)
{
str += item.ToString("X2") + " ";
}
richTextBox1.Text = "[" + System.DateTime.Now.ToString() + "] " + str + "\n" + richTextBox1.Text;
if (buff[0] == 0x04)
{
ID.Text = buff[0].ToString();
switch (buff[2])
{
case 0x01:
{
Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
Hum.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x02:
{
Light.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x04:
{
Dust.Text = (buff[6] + buff[7]).ToString();
break;
}
default:
break;
}
}
}
else
{
//當(dāng)接收數(shù)據(jù)不在設(shè)定的數(shù)據(jù)位范圍之內(nèi)時,會出現(xiàn)接受到的數(shù)據(jù)一直保存在接收緩存區(qū)之內(nèi),后續(xù)每次接手數(shù)據(jù)都會將上一次的數(shù)據(jù)進行疊加,造成只能通過關(guān)閉串口的方法來清除緩沖區(qū)的數(shù)據(jù)
s.DiscardInBuffer(); //丟棄來自串行驅(qū)動程序的接收緩沖區(qū)的數(shù)據(jù)
}
}
#endregion
當(dāng)接收到的數(shù)據(jù)長度不等于8的時候,將丟棄來自串行驅(qū)動程序的接收緩沖區(qū)的數(shù)據(jù),接下來通過斷點調(diào)試來分析丟棄緩沖區(qū)和不丟棄緩沖區(qū)數(shù)據(jù)兩種情況進行仿真,分析如下幾點。
使用串口助手給上位機發(fā)送數(shù)據(jù)數(shù)據(jù)位長度為8位的數(shù)據(jù),串口調(diào)試助手和上位機的終端的顯示界面如下,發(fā)送端數(shù)據(jù)和接收端數(shù)據(jù)一樣,并未出現(xiàn)異常:

將串口調(diào)試助手發(fā)送數(shù)據(jù)位修改成9位之后,進行發(fā)送,可以發(fā)現(xiàn)上位機并未接收到相關(guān)的數(shù)據(jù):

接著修改串口調(diào)試助手的發(fā)送數(shù)據(jù)位,修改成8位,可以發(fā)現(xiàn)上位機尚未能接收到來自串口調(diào)試助手發(fā)來的數(shù)據(jù),這是為什么呢?

接下來將通過斷點逐步進行調(diào)試,來解釋是為啥上位機沒有接收到調(diào)試助手發(fā)來的數(shù)據(jù),當(dāng)串口調(diào)試助手發(fā)來的數(shù)據(jù)長度位9位時,通過監(jiān)視器可以查看到接收緩沖器中的數(shù)據(jù)長度長度是9

第一次點擊完發(fā)送之后,上位機未能成功接收到數(shù)據(jù),我們就會好奇,并且一般都會點擊第二次、第三次、甚至一直點下去,觀察是否會出現(xiàn)啥異?,F(xiàn)象,當(dāng)點擊第二次時,通過監(jiān)視窗口,可以觀察到到串口緩沖區(qū)的數(shù)據(jù)長度變成了18,這是因為緩沖區(qū)將上一次接收的數(shù)據(jù)給保留了下來并沒有刪除,就算下次發(fā)送的數(shù)據(jù)長度為8位的時候,也一樣是通過疊加的方式將其保存到緩沖區(qū),這樣就會造成緩沖區(qū)的數(shù)據(jù)位長度會一直大于8;如果不通過s.DiscardInBuffer()方法丟棄來自串行驅(qū)動程序的接收緩沖區(qū)的數(shù)據(jù),就只能通過關(guān)閉串口然后重新打開相應(yīng)的串口來實現(xiàn)緩沖區(qū)的數(shù)據(jù)清除。

使用s.DiscardInBuffer()對不符合長度的數(shù)據(jù)進行丟棄,實現(xiàn)的效果如下所示:

需要完整源碼的朋友可以通過以下鏈接進行下載,如有大佬有更好的優(yōu)化意見歡迎一塊進行討論,謝謝!
鏈接: https://pan.baidu.com/s/1MXVIFQHHsEmx4p28Pz-wcQ 提取碼: ibu9
到此這篇關(guān)于C# 實現(xiàn)簡易的串口監(jiān)視上位機功能的文章就介紹到這了,更多相關(guān)c#上位機串口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
NumberToUpper數(shù)字轉(zhuǎn)中文詳解
本文介紹NumberToUpper數(shù)字轉(zhuǎn)中文的方法,大家參考使用吧2013-12-12

