Ask me anything
Ithy Logo
Sign in to save

实现基于SN查询与串口通信的WinForm程序

通过输入SN获取Excel数据并实现串口指令发送与回显校验的完整指南

csharp winform serial communication

关键要点

  • 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

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI