使用 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.串口搜索按钮部分代码:
c#
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.连接按钮部分的代码如下:
c#
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事件触发的串口接收函数和显示函数,代码如下:
c#
#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("发送:");
else
sb.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.发送按钮部分的代码如下:
c#
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#桌面应用开发:串口调试助手
本人水平有限,代码有不足之处请谅解。欢迎大家一起交流学习。
参考文献: