使用 C# WinForm 制作简单的串口调试助手
很久之前就已经发现了C# WinForm开源的控件界面库Sunny.UI,于是想着做一个Demo来用上Sunny.UI界面库。于是就想着做一个串口调试助手的Demo。
下面我就创建一个工程,并且加载Sunny.UI控件库,我这个项目还加载了另一个库叫做SeeSharpTools.JY.GUI,但是没有使用其中的控件,添加它以备后面使用。
第一步:在VS2022编译器的NuGet管理工具中搜索Sunny.UI,然后下载下来。如下图笔者已经安装了Sunny.UI库:
安装Sunny.UI库后,在项目的工具箱中会由该库。如下图:
第二步:拖动控件布局界面。笔者的界面如上图所示。我们可以参考Sunny.UI库的说明文档修改控件的属性。Sunny.UI说明文档。以按钮为例,该说明书详细说明了每一个属性的作用,如下图:
另外Sunny.UI库控件的属性单独进行了分类,如下图中笔者的按钮控件属性:
界面布局完毕。
第三步:编写代码。
C# 为我们提供了串口类SerialPort,我们可以直接使用。
1.串口搜索按钮部分代码:
private void m_btnSearch_Click(object sender, EventArgs e){try{string[] items = SerialPort.GetPortNames();m_ctrlPort.Items.Clear();m_ctrlPort.Items.AddRange(items);m_ctrlPort.SelectedIndex = m_ctrlPort.Items.Count > 0 ? 0 : -1;if (items.Length > 0){m_ctrlBaud.SelectedIndex = 10;m_ctrlData.SelectedIndex = 0;m_ctrlStop.SelectedIndex = 0;m_ctrlProof.SelectedIndex = 0;}else{MessageBox.Show("当前无串口连接!");}}catch (Exception ex){MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");}}
说明:
①我们调用SerialPort类的GetPortNames()可以获取当前计算机的串行端口名的数组。
②在C#中的CombBox控件比C++更加简单,可以直接在属性中添加字符串。具体如下图中的波特率:
2.连接按钮部分的代码如下:
private void m_btnConnect_Click(object sender, EventArgs e){if (!m_serialPort.IsOpen){if (m_ctrlPort.SelectedItem == null){MessageBox.Show("请选择正确的串口", "提示");return;}// 设置串口参数m_serialPort.PortName = m_ctrlPort.Text.ToString();m_serialPort.BaudRate = Convert.ToInt32(m_ctrlBaud.SelectedItem.ToString());m_serialPort.DataBits = Convert.ToInt32(m_ctrlData.SelectedItem.ToString());// 设置停止位if (m_ctrlStop.Text == "One"){m_serialPort.StopBits = StopBits.One;}else if (m_ctrlStop.Text == "Two"){m_serialPort.StopBits = StopBits.Two;}else if (m_ctrlStop.Text == "OnePointTwo"){m_serialPort.StopBits = StopBits.OnePointFive;}else if (m_ctrlStop.Text == "None"){m_serialPort.StopBits = StopBits.None;}// 设置奇偶校验位if (m_ctrlProof.Text == "Odd"){m_serialPort.Parity = Parity.Odd;}else if (m_ctrlProof.Text == "Even"){m_serialPort.Parity = Parity.Even;}else if (m_ctrlProof.Text == "None"){m_serialPort.Parity = Parity.None;}try{m_ctrlPort.Enabled = false;m_ctrlBaud.Enabled = false;m_ctrlData.Enabled = false;m_ctrlStop.Enabled = false;m_ctrlProof.Enabled = false;m_btnSearch.Enabled = false;m_serialPort.Open();m_btnConnect.Text = "断开";m_Led.OnColor = Color.FromArgb(110, 190, 40);}catch (Exception ex){MessageBox.Show("串口打开失败!");}m_serialPort.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceive);}else if (m_btnConnect.Text == "断开"){m_ctrlPort.Enabled = true;m_ctrlBaud.Enabled = true;m_ctrlData.Enabled = true;m_ctrlStop.Enabled = true;m_ctrlProof.Enabled = true;m_btnSearch.Enabled = true;m_serialPort.DiscardInBuffer();m_serialPort.Close();m_btnConnect.Text = "连接";m_Led.OnColor = Color.Red;}}
说明:
①上述代码设置好串口参数后,调用SerialPort类的Open()函数即可连接串口。
②DataReceived:SerialPort
类的 DataReceived
事件是在接收到数据时触发的。当串口接收缓冲区中有数据时,就会引发此事件。DataReceived
事件提供了一个 SerialDataReceivedEventArgs
对象,该对象包含有关事件的详细信息。请注意,DataReceived
事件处理程序应该尽可能快地执行,以避免阻塞其他事件的接收。
③定义DataReceived事件触发的串口接收函数和显示函数,代码如下:
#region(接收串口数据)
// 串口缓冲区
List<byte> pBuffer = new List<byte>(4096);
// 串口缓冲区最大字节数
int nBufferMaxSize = 4096;private void SerialDataReceive(object sender, SerialDataReceivedEventArgs e)
{if (m_serialPort.IsOpen == false){m_serialPort.Close();return;}// 读取缓存的数据长度int nByteLen = m_serialPort.BytesToRead;byte [] bRecv = new byte[nByteLen];// 将读取的数据存入缓存数组中m_serialPort.Read(bRecv,0,nByteLen);string strText = "接收的字节数:";statusStrip1.Items[1].Text = strText + nByteLen.ToString();pBuffer.Clear();// 将接收的数据存入缓冲区pBuffer.AddRange(bRecv);byte[] rBuffer = new byte[9192];pBuffer.CopyTo(0, rBuffer, 0, pBuffer.Count);Task.Run(() =>PrintfData(rBuffer, pBuffer.Count, 1));}
#endregion#region (打印数据)
void PrintfData(byte[] buffer,int nLength,int nTR)
{Int16 nLen;StringBuilder sb = new StringBuilder();if (nTR == 0)sb.Append("发送:");elsesb.Append("接收:");string strRecv = "";if (uiRB_ASCII_Recv.Checked){strRecv = sb.ToString();strRecv += Encoding.Default.GetString(buffer);}else if(uiRB_HEX_Recv.Checked){// 转为字节显示for (nLen = 0; nLen < nLength; nLen++){sb.Append(buffer[nLen].ToString());sb.Append(" ");}strRecv = sb.ToString();}MethodInvoker mi = new MethodInvoker(()=>{ if(m_tbReceive.Lines.Count() > 20)m_tbReceive.Clear();m_tbReceive.AppendText(strRecv + "\r\n");});BeginInvoke(mi);
}
#endregion
说明:
①上述代码使用了异步Task,其中调用Task.Run的函数,Task.Run是在线程池上执行任务。关于Run函数的详细说明可以参考官方文档。Run().
②MethodInvoker类的说明:在C#中,MethodInvoker
是一个委托(delegate),它定义了与具有无返回值参数的方法匹配的签名。换句话说,MethodInvoker
是一个可以引用任何不接受参数且不返回值的方法的委托。MethodInvoker通常用于Windows Forms应用程序中,当你需要在非UI线程上更新UI控件时。由于UI控件只能由创建它们的线程(通常是主UI线程)进行操作,所以当你在后台线程中更新UI时,需要使用 MethodInvoker
来确保UI更新操作在正确的线程上执行。
③这两个类对于我来说,接触的也是比较少,只能先放在这里,以后用到的话再来详细查看说明。
3.发送按钮部分的代码如下:
private void m_btnSend_Click(object sender, EventArgs e){if (m_serialPort.IsOpen){string strData = m_tbSend.Text;int nBufferLen = strData.Length;byte[] sendData = new Byte[nBufferLen];sendData = System.Text.Encoding.Default.GetBytes(strData);//转码try{// 写数据m_serialPort.Write(sendData, 0, sendData.Length);Task.Run(() => PrintfData(sendData, sendData.Length, 0));string strText = "发送的字节数:";statusStrip1.Items[0].Text = strText + sendData.Length.ToString();}catch{MessageBox.Show("发送失败!");}}else{MessageBox.Show("串口未打开!");}}
说明:
①使用SerialPort类的Write函数向串口缓冲区中写入数据。
第四步、代码调试结果。
软件运行后界面如下图,可以实现基本的数据收发:
关于Sunnu.UI控件库的使用就到这里了,有关串口的介绍还有很多细节的地方,如果想了解更多细节,大家可以直接去看别人的开源代码。
说明:另外也可以直接使用工具箱中提供的串口组件:SerialPort。关于SerialPort组件的使用,大家可以参考这篇文章。源码开源C#桌面应用开发:串口调试助手
本人水平有限,代码有不足之处请谅解。欢迎大家一起交流学习。
源码地址
参考文献:
1.C#开发: 通信篇-串口调试助手
2.C#开发串口调试助手的详细教程
3.Sunny.UI说明文档