全面构建基于WinForm的SN查询与串口通信程序
实现高效SN查询与多指令串口通信的详细方案
主要收获
- 高效读取Excel数据: 使用EPPlus库实现SN对应行的数据检索。
- 灵活的串口通信: 利用SerialPort类进行多指令发送与回显接收。
- 动态回显校验: 根据不同指令类型实现特定的回显验证逻辑。
项目概述
本项目旨在开发一个Windows Forms应用程序,用户可以通过输入序列号(SN)来检索Excel文件中对应的行数据,并通过串口发送多种指令,随后根据指令类型对串口回显进行特征化校验。该程序具备以下核心功能:
- 输入SN并查询Excel文件中相关数据。
- 配置串口参数并建立通信。
- 发送多类型指令并接收串口回显。
- 针对不同指令类型进行特定的回显验证。
开发环境与依赖
所需工具与库
- Visual Studio 2019或更高版本
- .NET Framework 4.7.2或更高
- NuGet包管理器
- EPPlus(用于Excel文件读取)
- System.IO.Ports(用于串口通信)
步骤详解
1. 创建WinForm项目
在Visual Studio中创建一个新的Windows Forms应用程序项目。命名为SN查询与串口通信工具。
2. 安装必要的NuGet包
通过NuGet包管理器安装以下包:
- EPPlus:用于读取和操作Excel文件。
- System.IO.Ports:用于串口通信。
3. 设计用户界面
在设计器中添加以下控件:
- TextBox:用于输入SN,命名为
txtSN。 - Button:用于触发SN查询,命名为
btnSearch,文本为“查询”。 - TextBox:用于显示Excel查询结果,命名为
txtExcelResult,设置为多行模式。 - ComboBox:用于选择串口,命名为
cmbSerialPort。 - Button:用于打开串口,命名为
btnOpenPort,文本为“打开串口”。 - TextBox:用于输入指令,命名为
txtCommand。 - Button:用于发送指令,命名为
btnSendCommand,文本为“发送指令”。 - TextBox:用于显示串口回显,命名为
txtResponse,设置为多行模式。
4. 实现Excel数据读取
使用EPPlus库读取Excel文件,根据输入的SN查找对应的行数据。以下是一个示例方法:
using OfficeOpenXml;
using System.Data;
using System.IO;
using System.Windows.Forms;
public DataRow GetExcelDataBySN(string filePath, string sheetName, string sn)
{
FileInfo existingFile = new FileInfo(filePath);
using (ExcelPackage package = new ExcelPackage(existingFile))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[sheetName];
if (worksheet == null)
{
MessageBox.Show("未找到指定的工作表。");
return null;
}
int rowCount = worksheet.Dimension.Rows;
int colCount = worksheet.Dimension.Columns;
for (int row = 2; row <= rowCount; row++) // 假设第一行为表头
{
if (worksheet.Cells[row, 1].Text.Trim() == sn)
{
DataTable dt = new DataTable();
for (int col = 1; col <= colCount; col++)
{
if (row == 2)
{
dt.Columns.Add(worksheet.Cells[row, col].Text.Trim());
}
else
{
dt.Columns[col - 1].DataType = typeof(string);
}
}
DataRow dataRow = dt.NewRow();
for (int col = 1; col <= colCount; col++)
{
dataRow[col - 1] = worksheet.Cells[row, col].Text.Trim();
}
return dataRow;
}
}
MessageBox.Show("未找到对应的SN记录。");
return null;
}
}
5. 配置串口通信
使用SerialPort类进行串口配置与通信。以下是初始化串口的方法:
using System.IO.Ports;
public SerialPort serialPort;
public void InitializeSerialPort(string portName, int baudRate)
{
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
{
ReadTimeout = 3000,
WriteTimeout = 3000
};
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
this.Invoke(new Action(() => {
txtResponse.AppendText(indata);
}));
}
6. 发送指令与回显校验
根据输入的指令类型发送指令,并依据指令类型对回显进行特征校验。以下是发送指令并验证回显的示例代码:
public void SendCommand(string command)
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.WriteLine(command);
}
else
{
MessageBox.Show("串口未打开或不存在。");
}
}
public bool ValidateResponse(string command, string response)
{
if (command.StartsWith("CMD_A"))
{
return response.Contains("ACK_A");
}
else if (command.StartsWith("CMD_B"))
{
return response.Contains("ACK_B");
}
// 添加更多指令类型的校验逻辑
return false;
}
private void btnSendCommand_Click(object sender, EventArgs e)
{
string command = txtCommand.Text.Trim();
if (!string.IsNullOrEmpty(command))
{
SendCommand(command);
// 假设在DataReceivedHandler中已将回显显示在txtResponse中
// 这里可以根据实际情况添加等待或回调机制进行校验
}
else
{
MessageBox.Show("请输入指令。");
}
}
7. 完整示例代码
以下是整合上述功能的完整代码示例:
using System;
using System.Data;
using System.IO;
using System.IO.Ports;
using System.Windows.Forms;
using OfficeOpenXml;
namespace SNSerialPortApp
{
public partial class MainForm : Form
{
private SerialPort serialPort;
public MainForm()
{
InitializeComponent();
LoadAvailablePorts();
}
private void LoadAvailablePorts()
{
cmbSerialPort.Items.AddRange(SerialPort.GetPortNames());
if (cmbSerialPort.Items.Count > 0)
{
cmbSerialPort.SelectedIndex = 0;
}
}
private void btnSearch_Click(object sender, EventArgs e)
{
string sn = txtSN.Text.Trim();
if (string.IsNullOrEmpty(sn))
{
MessageBox.Show("请输入SN。");
return;
}
string excelPath = @"C:\path\to\your\excel.xlsx"; // 修改为实际路径
string sheetName = "Sheet1"; // 修改为实际工作表名称
DataRow row = GetExcelDataBySN(excelPath, sheetName, sn);
if (row != null)
{
string result = "";
foreach (var item in row.ItemArray)
{
result += item.ToString() + "\t";
}
txtExcelResult.Text = result;
}
}
private DataRow GetExcelDataBySN(string filePath, string sheetName, string sn)
{
try
{
FileInfo existingFile = new FileInfo(filePath);
using (ExcelPackage package = new ExcelPackage(existingFile))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[sheetName];
if (worksheet == null)
{
MessageBox.Show("未找到指定的工作表。");
return null;
}
int rowCount = worksheet.Dimension.Rows;
int colCount = worksheet.Dimension.Columns;
for (int row = 2; row <= rowCount; row++)
{
if (worksheet.Cells[row, 1].Text.Trim() == sn)
{
DataTable dt = new DataTable();
for (int col = 1; col <= colCount; col++)
{
if (row == 2)
{
dt.Columns.Add(worksheet.Cells[row, col].Text.Trim());
}
else
{
dt.Columns[col - 1].DataType = typeof(string);
}
}
DataRow dataRow = dt.NewRow();
for (int col = 1; col <= colCount; col++)
{
dataRow[col - 1] = worksheet.Cells[row, col].Text.Trim();
}
return dataRow;
}
}
MessageBox.Show("未找到对应的SN记录。");
return null;
}
}
catch (Exception ex)
{
MessageBox.Show("读取Excel文件时出错:" + ex.Message);
return null;
}
}
private void btnOpenPort_Click(object sender, EventArgs e)
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
btnOpenPort.Text = "打开串口";
return;
}
string portName = cmbSerialPort.SelectedItem.ToString();
int baudRate = 9600; // 可根据需要调整
try
{
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
serialPort.Open();
btnOpenPort.Text = "关闭串口";
MessageBox.Show("串口已打开。");
}
catch (Exception ex)
{
MessageBox.Show("无法打开串口:" + ex.Message);
}
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
this.Invoke(new Action(() => {
txtResponse.AppendText(indata + Environment.NewLine);
}));
}
private void btnSendCommand_Click(object sender, EventArgs e)
{
string command = txtCommand.Text.Trim();
if (string.IsNullOrEmpty(command))
{
MessageBox.Show("请输入指令。");
return;
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.WriteLine(command);
// 这里可以添加等待机制或回调进行回显校验
// 示例中直接显示回显内容
// 实际应用中建议使用更可靠的回显处理方式
}
else
{
MessageBox.Show("串口未打开。");
}
}
private bool ValidateResponse(string command, string response)
{
if (command.StartsWith("CMD_A"))
{
return response.Contains("ACK_A");
}
else if (command.StartsWith("CMD_B"))
{
return response.Contains("ACK_B");
}
// 添加更多指令类型的校验逻辑
return false;
}
}
详细功能实现
Excel数据读取与SN查询
通过EPPlus库读取指定路径的Excel文件,遍历工作表中的每一行,匹配输入的SN,若匹配成功则提取该行的所有单元格数据并显示在界面上。此过程包括错误处理,如未找到工作表或SN记录。
串口通信配置与管理
利用SerialPort类初始化串口参数,包括端口名称、波特率、校验位等。用户可以通过下拉菜单选择可用的串口,并通过按钮控制串口的打开与关闭。串口开启后,程序将监听数据接收事件,并将接收到的数据实时显示在界面上。
指令发送与回显校验
用户可以在指令输入框中输入想要发送的指令,点击发送按钮后,指令将通过串口发送至目标设备。接收到的回显数据将显示在回显文本框中。根据指令前缀或类型,程序将执行相应的回显校验逻辑,确保指令正确执行。
示例:多指令类型的回显校验
假设有两种指令类型:
- CMD_A:期望回显包含
ACK_A - CMD_B:期望回显包含
ACK_B
校验函数如下:
private bool ValidateResponse(string command, string response)
{
if (command.StartsWith("CMD_A"))
{
return response.Contains("ACK_A");
}
else if (command.StartsWith("CMD_B"))
{
return response.Contains("ACK_B");
}
// 添加更多指令类型的校验逻辑
return false;
}
用户体验优化
为了提升用户体验,建议实现以下优化:
- 在发送指令后添加回显校验结果的提示信息。
- 为串口选择添加刷新功能,确保最新的串口信息可用。
- 在Excel读取和串口通信过程中添加进度条或加载指示器,避免程序无响应。
示例:发送指令后的回显校验提示
private void btnSendCommand_Click(object sender, EventArgs e)
{
string command = txtCommand.Text.Trim();
if (string.IsNullOrEmpty(command))
{
MessageBox.Show("请输入指令。");
return;
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.WriteLine(command);
// 假设在DataReceivedHandler中已将回显显示在txtResponse中
// 这里可以根据实际情况添加等待机制或延时后进行校验
System.Threading.Thread.Sleep(500); // 简单延时示例
bool isValid = ValidateResponse(command, txtResponse.Text);
if (isValid)
{
MessageBox.Show("指令回显校验成功。");
}
else
{
MessageBox.Show("指令回显校验失败。");
}
}
else
{
MessageBox.Show("串口未打开。");
}
}
性能优化与错误处理
优化Excel读取效率
对于大型Excel文件,逐行读取可能会导致性能瓶颈。可以考虑以下优化措施:
- 使用索引或缓存机制加快SN的查找速度。
- 仅加载必要的列,减少内存占用。
- 异步读取Excel数据,避免阻塞UI线程。
增强串口通信的稳健性
在串口通信过程中,可能会遇到数据丢失、通信中断等问题。建议采取以下措施增强程序的稳健性:
-
在发送和接收数据时添加异常处理,防止程序崩溃。
-
实现重试机制,确保指令可靠发送与接收。
-
配置适当的超时时间,避免程序长时间等待。
示例:完善的异常处理
private void InitializeSerialPort(string portName, int baudRate)
{
try
{
serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
{
ReadTimeout = 3000,
WriteTimeout = 3000
};
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
serialPort.Open();
btnOpenPort.Text = "关闭串口";
}
catch (UnauthorizedAccessException)
{
MessageBox.Show("访问被拒绝,无法打开串口。");
}
catch (IOException)
{
MessageBox.Show("串口不存在或被占用。");
}
catch (Exception ex)
{
MessageBox.Show("打开串口时发生错误:" + ex.Message);
}
}
结论
通过上述步骤,您可以构建一个功能完善的WinForm程序,实现通过输入SN查询Excel数据,并通过串口发送多种指令及其回显校验。关键在于合理使用EPPlus进行高效的Excel数据操作,以及灵活运用SerialPort类实现稳定的串口通信。此外,根据不同指令类型设计特定的回显校验逻辑,确保通信的可靠性和准确性。
参考资料
Last updated February 13, 2025