Chat
Ask me anything
Ithy Logo

全面构建基于WinForm的SN查询与串口通信程序

实现高效SN查询与多指令串口通信的详细方案

windows forms csharp serial communication

主要收获

  • 高效读取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
Ask Ithy AI
Download Article
Delete Article