实现基于SN查询与串口通信的WinForm程序
通过输入SN获取Excel数据并实现串口指令发送与回显校验的完整指南
关键要点
- Excel数据读取与SN匹配:使用适当的库读取Excel文件,根据输入的SN精确查找对应行数据。
- 串口通信配置与管理:配置串口参数,包括波特率、数据位、停止位等,确保与设备的通信稳定。
- 回显数据校验机制:实现有效的回显数据校验,确保指令发送的准确性和数据完整性。
项目概述
本指南将详细介绍如何使用C#和WinForms创建一个应用程序,该程序能够通过输入的SN(序列号)从Excel文件中获取相应的行数据,并通过串口发送指令,同时对设备的回显数据进行校验。整个过程涵盖了Excel数据读取、串口通信的配置与管理,以及回显数据的验证,确保系统的可靠性和准确性。
开发环境与准备工作
所需工具与库
在开始开发之前,确保以下工具和库已经安装配置到您的开发环境中:
- Visual Studio 2019或更高版本
- .NET Framework 4.7.2或更高版本
- Excel读取库,例如EPPlus或ExcelDataReader
- System.IO.Ports用于串口通信
- NuGet包管理器用于安装所需的第三方库
项目结构设计
核心功能模块
项目主要分为以下几个核心模块:
- 用户界面设计:设计WinForm界面,包括输入SN的文本框、搜索按钮、显示Excel数据的控件、串口配置界面等。
- Excel数据读取模块:实现从Excel文件中读取数据,并根据输入的SN查找对应的行。
- 串口通信模块:配置串口参数,发送指令,并接收回显数据。
- 回显校验模块:对接收到的回显数据进行校验,确保数据的正确性。
详细实现步骤
步骤一:创建WinForm项目
使用Visual Studio创建一个新的Windows Forms应用程序项目。命名为SNSerialCommunicationApp。
1.1 添加必要的控件
在主窗体(MainForm)上添加以下控件:
- 文本框(TextBox)用于输入SN,命名为txtSN
- 按钮(Button)用于搜索SN,命名为btnSearch,文本设为“搜索”
- 数据网格视图(DataGridView)用于显示Excel中的数据,命名为dgvExcelData
- 组合框(ComboBox)用于选择串口,命名为cbxSerialPort
- 文本框用于输入波特率,命名为txtBaudRate
- 按钮用于打开串口,命名为btnOpenPort,文本设为“打开串口”
- 文本框用于输入要发送的指令,命名为txtCommand
- 按钮用于发送指令,命名为btnSendCommand,文本设为“发送指令”
- 文本框用于显示接收到的回显数据,命名为txtReceivedData
1.2 设置控件布局
合理布局上述控件,确保用户界面清晰、易用。例如,将SN输入和搜索按钮放在顶部,数据网格视图位于中部,串口配置和指令发送部分放在底部。
步骤二:实现Excel数据读取功能
2.1 安装Excel读取库
通过NuGet包管理器安装EPPlus或ExcelDataReader。本指南使用EPPlus作为示例。
2.2 编写Excel读取代码
在项目中添加一个类文件,例如ExcelHelper.cs,实现Excel数据的读取与SN匹配:
using System;
using System.Data;
using System.IO;
using OfficeOpenXml;
public class ExcelHelper
{
public static DataRow GetRowBySN(string filePath, string sn)
{
FileInfo existingFile = new FileInfo(filePath);
using (ExcelPackage package = new ExcelPackage(existingFile))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[1]; // 假设数据在第一个工作表
int rowCount = worksheet.Dimension.Rows;
int colCount = worksheet.Dimension.Columns;
for (int row = 2; row <= rowCount; row++) // 从第二行开始,假设第一行为表头
{
if (worksheet.Cells[row, 1].Text.Trim().Equals(sn, StringComparison.OrdinalIgnoreCase))
{
DataTable dt = new DataTable();
for (int col = 1; col <= colCount; col++)
{
if (row == 2)
dt.Columns.Add(worksheet.Cells[1, col].Text);
dt.Rows[0][col - 1] = worksheet.Cells[row, col].Text;
}
return dt.Rows[0];
}
}
}
return null;
}
}
2.3 实现搜索按钮事件
在MainForm.cs中,为搜索按钮添加点击事件处理,调用ExcelHelper.GetRowBySN方法:
private void btnSearch_Click(object sender, EventArgs e)
{
string sn = txtSN.Text.Trim();
if (string.IsNullOrEmpty(sn))
{
MessageBox.Show("请输入SN号");
return;
}
string excelFilePath = @"C:\Path\To\Your\ExcelFile.xlsx"; // 替换为实际的Excel文件路径
DataRow row = ExcelHelper.GetRowBySN(excelFilePath, sn);
if (row != null)
{
DataTable dt = new DataTable();
dt.Columns.AddRange(row.Table.Columns.Cast<DataColumn>.ToArray());
dt.Rows.Add(row.ItemArray);
dgvExcelData.DataSource = dt;
}
else
{
MessageBox.Show("未找到对应的SN数据");
dgvExcelData.DataSource = null;
}
}
步骤三:配置与管理串口通信
3.1 初始化串口参数
在MainForm类中,添加串口对象并初始化可用端口列表:
using System.IO.Ports;
public partial class MainForm : Form
{
private SerialPort serialPort;
public MainForm()
{
InitializeComponent();
InitializeSerialPort();
}
private void InitializeSerialPort()
{
serialPort = new SerialPort();
serialPort.DataReceived += SerialPort_DataReceived;
// 填充可用串口列表
string[] ports = SerialPort.GetPortNames();
cbxSerialPort.Items.AddRange(ports);
if (ports.Length > 0)
cbxSerialPort.SelectedIndex = 0;
// 设置默认波特率
txtBaudRate.Text = "9600";
}
}
3.2 实现打开串口功能
为“打开串口”按钮添加点击事件,配置并打开串口:
private void btnOpenPort_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
serialPort.Close();
btnOpenPort.Text = "打开串口";
return;
}
try
{
serialPort.PortName = cbxSerialPort.SelectedItem.ToString();
serialPort.BaudRate = int.Parse(txtBaudRate.Text);
serialPort.Parity = Parity.None;
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Open();
btnOpenPort.Text = "关闭串口";
MessageBox.Show("串口已打开");
}
catch (Exception ex)
{
MessageBox.Show($"无法打开串口: {ex.Message}");
}
}
3.3 实现数据接收事件
当串口接收到数据时,触发回显数据的接收和显示:
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = serialPort.ReadExisting();
this.Invoke(new Action(() =>
{
txtReceivedData.AppendText($"接收: {data}\n");
VerifyEcho(data);
}));
}
private void VerifyEcho(string receivedData)
{
string sentCommand = txtCommand.Text.Trim();
if (receivedData.Trim().Equals(sentCommand, StringComparison.OrdinalIgnoreCase))
{
MessageBox.Show("回显正确");
}
else
{
MessageBox.Show("回显错误");
}
}
3.4 实现发送指令功能
为“发送指令”按钮添加点击事件,发送用户输入的指令到串口:
private void btnSendCommand_Click(object sender, EventArgs e)
{
if (!serialPort.IsOpen)
{
MessageBox.Show("串口未打开");
return;
}
string command = txtCommand.Text.Trim();
if (string.IsNullOrEmpty(command))
{
MessageBox.Show("请输入要发送的指令");
return;
}
try
{
serialPort.WriteLine(command);
txtReceivedData.AppendText($"发送: {command}\n");
}
catch (Exception ex)
{
MessageBox.Show($"发送指令失败: {ex.Message}");
}
}
步骤四:完善用户界面与用户体验
4.1 优化控件布局
确保所有控件整齐排列,标签清晰,用户能够方便地输入SN、选择串口、发送指令以及查看接收的数据。
4.2 添加异常处理
在所有可能发生异常的地方添加适当的异常处理,确保程序在出现错误时能够友好地提示用户,并防止程序崩溃。
4.3 增强用户反馈
通过消息框或状态栏及时向用户反馈操作结果,如串口是否打开成功、SN是否找到、指令发送是否成功等。
功能测试与验证
测试步骤
- 验证Excel数据读取:输入一个已存在的SN,点击“搜索”,检查数据网格视图是否正确显示对应行的数据。
- 验证串口配置:选择正确的串口和波特率,点击“打开串口”,确保串口成功打开,按钮文本切换为“关闭串口”。
- 验证指令发送与回显:输入要发送的指令,点击“发送指令”,检查接收数据是否与发送指令一致,并确认回显校验结果。
- 异常情况测试:尝试输入不存在的SN,或选择错误的串口参数,确保程序能够正确处理并提示错误信息。
常见问题及解决方案
- 问题:串口无法打开,提示端口不存在或被占用。
- 解决方案:检查设备管理器中串口号是否正确,确保选择的端口未被其他程序占用。
- 问题:发送指令后未收到回显。
- 解决方案:确认设备连接正常,检查串口参数配置是否正确,确保指令格式符合设备要求。
- 问题:Excel文件路径错误或格式不支持。
- 解决方案:确保Excel文件路径正确,文件格式为.xlsx或.xls,并且表头包含SN列。
项目扩展与优化
功能扩展
- 多串口支持:扩展程序以支持同时管理多个串口通信。
- 日志记录功能:添加日志记录,记录所有发送和接收的指令及数据,便于后续分析与调试。
- 动态配置串口参数:允许用户在程序运行时动态调整串口参数,如波特率、数据位等。
- 用户权限管理:实现用户登录与权限管理,确保只有授权用户能够访问和操作该程序。
性能优化
-
异步操作:使用异步编程模式,避免阻塞主线程,提高程序响应速度。
-
资源释放:确保在程序关闭时,所有串口和文件资源都能被正确释放,防止资源泄漏。
-
界面优化:优化用户界面布局和样式,提高用户体验。
示例代码汇总
完整MainForm代码示例
using System;
using System.Data;
using System.IO.Ports;
using System.Windows.Forms;
namespace SNSerialCommunicationApp
{
public partial class MainForm : Form
{
private SerialPort serialPort;
private string excelFilePath = @"C:\Path\To\Your\ExcelFile.xlsx"; // 替换为实际的Excel文件路径
public MainForm()
{
InitializeComponent();
InitializeSerialPort();
}
private void InitializeSerialPort()
{
serialPort = new SerialPort();
serialPort.DataReceived += SerialPort_DataReceived;
// 填充可用串口列表
string[] ports = SerialPort.GetPortNames();
cbxSerialPort.Items.AddRange(ports);
if (ports.Length > 0)
cbxSerialPort.SelectedIndex = 0;
// 设置默认波特率
txtBaudRate.Text = "9600";
}
private void btnSearch_Click(object sender, EventArgs e)
{
string sn = txtSN.Text.Trim();
if (string.IsNullOrEmpty(sn))
{
MessageBox.Show("请输入SN号");
return;
}
DataRow row = ExcelHelper.GetRowBySN(excelFilePath, sn);
if (row != null)
{
DataTable dt = new DataTable();
dt.Columns.AddRange(row.Table.Columns.Cast<DataColumn>.ToArray());
dt.Rows.Add(row.ItemArray);
dgvExcelData.DataSource = dt;
}
else
{
MessageBox.Show("未找到对应的SN数据");
dgvExcelData.DataSource = null;
}
}
private void btnOpenPort_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
serialPort.Close();
btnOpenPort.Text = "打开串口";
MessageBox.Show("串口已关闭");
return;
}
try
{
serialPort.PortName = cbxSerialPort.SelectedItem.ToString();
serialPort.BaudRate = int.Parse(txtBaudRate.Text);
serialPort.Parity = Parity.None;
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Open();
btnOpenPort.Text = "关闭串口";
MessageBox.Show("串口已打开");
}
catch (Exception ex)
{
MessageBox.Show($"无法打开串口: {ex.Message}");
}
}
private void btnSendCommand_Click(object sender, EventArgs e)
{
if (!serialPort.IsOpen)
{
MessageBox.Show("串口未打开");
return;
}
string command = txtCommand.Text.Trim();
if (string.IsNullOrEmpty(command))
{
MessageBox.Show("请输入要发送的指令");
return;
}
try
{
serialPort.WriteLine(command);
txtReceivedData.AppendText($"发送: {command}\n");
}
catch (Exception ex)
{
MessageBox.Show($"发送指令失败: {ex.Message}");
}
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = serialPort.ReadExisting();
this.Invoke(new Action(() =>
{
txtReceivedData.AppendText($"接收: {data}\n");
VerifyEcho(data);
}));
}
private void VerifyEcho(string receivedData)
{
string sentCommand = txtCommand.Text.Trim();
if (receivedData.Trim().Equals(sentCommand, StringComparison.OrdinalIgnoreCase))
{
MessageBox.Show("回显正确");
}
else
{
MessageBox.Show("回显错误");
}
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
}
}
}
}
ExcelHelper类代码示例
using System;
using System.Data;
using System.IO;
using OfficeOpenXml;
public class ExcelHelper
{
public static DataRow GetRowBySN(string filePath, string sn)
{
FileInfo existingFile = new FileInfo(filePath);
using (ExcelPackage package = new ExcelPackage(existingFile))
{
ExcelWorksheet worksheet = package.Workbook.Worksheets[1]; // 假设数据在第一个工作表
int rowCount = worksheet.Dimension.Rows;
int colCount = worksheet.Dimension.Columns;
for (int row = 2; row <= rowCount; row++) // 从第二行开始,假设第一行为表头
{
if (worksheet.Cells[row, 1].Text.Trim().Equals(sn, StringComparison.OrdinalIgnoreCase))
{
DataTable dt = new DataTable();
for (int col = 1; col <= colCount; col++)
{
if (row == 2)
dt.Columns.Add(worksheet.Cells[1, col].Text);
dt.Rows[0][col - 1] = worksheet.Cells[row, col].Text;
}
return dt.Rows[0];
}
}
}
return null;
}
}
附加功能与优化代码示例
使用异步编程提高响应速度
为了避免界面阻塞,可以将串口数据接收改为异步处理:
private async void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = await serialPort.ReadLineAsync();
this.Invoke(new Action(() =>
{
txtReceivedData.AppendText($"接收: {data}\n");
VerifyEcho(data);
}));
}
日志记录功能
添加日志记录,记录所有发送和接收的指令及数据:
using System.IO;
public class Logger
{
private string logFilePath;
public Logger(string path)
{
logFilePath = path;
}
public void Log(string message)
{
using (StreamWriter sw = new StreamWriter(logFilePath, true))
{
sw.WriteLine($"{DateTime.Now}: {message}");
}
}
}
在MainForm中使用Logger:
private Logger logger = new Logger(@"C:\Path\To\LogFile.txt");
private void btnSendCommand_Click(object sender, EventArgs e)
{
// 发送指令代码...
logger.Log($"发送指令: {command}");
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// 接收数据代码...
logger.Log($"接收数据: {data}");
}
示例项目成果展示
用户界面截图
以下是程序的用户界面示例:
| 控件 | 功能描述 |
|---|---|
| txtSN | 输入SN号以查询对应的Excel数据 |
| btnSearch | 点击后进行SN查询并显示结果 |
| dgvExcelData | 显示从Excel中获取的SN对应行的数据 |
| cbxSerialPort | 选择要使用的串口 |
| txtBaudRate | 输入或显示当前串口的波特率 |
| btnOpenPort | 打开或关闭串口连接 |
| txtCommand | 输入要发送的串口指令 |
| btnSendCommand | 发送输入的指令到串口设备 |
| txtReceivedData | 显示从串口接收到的回显数据 |
结论
通过本指南,您已经掌握了如何使用C#和WinForms创建一个能够通过输入SN从Excel文件中获取对应数据,并通过串口发送指令及校验回显的完整应用程序。该程序不仅具备基本的功能,还可通过进一步扩展和优化,满足更复杂的业务需求。无论是在生产管理、设备控制还是数据分析领域,这种集成化的解决方案都能显著提高工作效率和数据准确性。
参考资料
Last updated February 13, 2025