串口通信学习,使用winform读取串口发送数据,(2)

串口通信学习,使用 WinForm 读取串口发送数据(2)

使用winform去实现最简单的数据发送与接收,获取串口之后点击连接串口,进行串口连接,在后续会显示连接的状态。串口的配置使用代码中写死的配置:

csharp 复制代码
        #region 串口配置常量
        /// <summary>
        /// 波特率(默认9600)
        /// </summary>
        private readonly int BaudRate = 9600;

        /// <summary>
        /// 校验位(默认无)
        /// </summary>
        private readonly Parity Parity = Parity.None;

        /// <summary>
        /// 数据位(默认8)
        /// </summary>
        private readonly int DataBits = 8;

        /// <summary>
        /// 停止位(默认1)
        /// </summary>
        private readonly StopBits StopBits = StopBits.One;

        #endregion

串口的名称使用:private SerialPort _serialPort; // 串口通信对象,用户可以发送可以使用"字符串"或"十六进制"格式发送数据,同时自动接收并显示来自串口的数据。用户使用发送数据的时候会以相同的发送方式去解析,比如发送十六进制数据的时候会以十六进制的方式去解析。

可以使用方向键去使用历史命令,类似命令行,使用方向键的上下去查询历史命令。

使用方式

连接上串口了

发送与接收数据

可以使用方向键去控制命令的上下行,按下Enter去发送消息。

代码

私有字段与配置常量

首先定义串口通信所需的私有字段和默认配置参数:

命名空间

csharp 复制代码
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
csharp 复制代码
#region 私有字段
private SerialPort _serialPort;               // 串口通信对象
private bool _isConnected;                    // 串口连接状态标识
private string _selectedPortName;             // 当前选中的串口号

// 命令历史记录相关
private List<string> _commandHistory;         // 存储发送过的命令历史
private int _historyIndex;                    // 当前选中的历史命令索引
#endregion

#region 串口配置常量
/// <summary>
/// 波特率(默认9600)
/// </summary>
private readonly int BaudRate = 9600;

/// <summary>
/// 校验位(默认无)
/// </summary>
private readonly Parity Parity = Parity.None;

/// <summary>
/// 数据位(默认8)
/// </summary>
private readonly int DataBits = 8;

/// <summary>
/// 停止位(默认1)
/// </summary>
private readonly StopBits StopBits = StopBits.One;
#endregion

构造函数与初始化方法

窗体加载时完成组件初始化、端口列表刷新及串口对象创建:

csharp 复制代码
#region 构造函数
/// <summary>
/// 初始化窗体及串口相关组件
/// </summary>
public Form1()
{
    InitializeComponent();
    RefreshPortList();
    InitializeSerialPort();
    InitSendFormatComboBox();
    UpdateControlStatus();

    // 初始化命令历史记录
    _commandHistory = new List<string>();
    _historyIndex = -1;
}
#endregion

#region 初始化方法
/// <summary>
/// 初始化发送格式下拉框
/// </summary>
private void InitSendFormatComboBox()
{
    comboBoxSendFormat.Items.Add(new ComboBoxItem("十六进制", SendDataFormat.Hex));
    comboBoxSendFormat.Items.Add(new ComboBoxItem("字符串", SendDataFormat.String));
    comboBoxSendFormat.DisplayMember = "DisplayName";
    comboBoxSendFormat.ValueMember = "Value";
    comboBoxSendFormat.SelectedIndex = 0;
}

/// <summary>
/// 刷新串口列表
/// </summary>
private void RefreshPortList()
{
    string currentSelectedPort = comboBoxPorts.SelectedItem?.ToString();
    string[] availablePorts = SerialPort.GetPortNames();
    var sortedPorts = availablePorts.OrderBy(port =>
    {
        if (int.TryParse(port.Replace("COM", ""), out int portNumber))
        {
            return portNumber;
        }
        return 0;
    }).ToArray();

    comboBoxPorts.Items.Clear();
    comboBoxPorts.Items.AddRange(sortedPorts);

    if (comboBoxPorts.Items.Count > 0)
    {
        comboBoxPorts.SelectedItem = !string.IsNullOrEmpty(currentSelectedPort)
            && comboBoxPorts.Items.Contains(currentSelectedPort)
            ? currentSelectedPort
            : comboBoxPorts.Items[0];
    }
}

/// <summary>
/// 初始化串口对象并注册数据接收事件
/// </summary>
private void InitializeSerialPort()
{
    _serialPort = new SerialPort
    {
        BaudRate = this.BaudRate,
        Parity = this.Parity,
        DataBits = this.DataBits,
        StopBits = this.StopBits,
        Encoding = Encoding.Default
    };
    _serialPort.DataReceived += SerialPort_DataReceived;
}
#endregion

串口通信核心逻辑

包括连接、断开、数据接收与发送等关键操作:

复制代码
#region 串口通信
/// <summary>
/// 串口数据接收事件处理方法
/// </summary>
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        if (_serialPort.IsOpen && _serialPort.BytesToRead > 0)
        {
            int byteCount = _serialPort.BytesToRead;
            byte[] receiveBuffer = new byte[byteCount];
            _serialPort.Read(receiveBuffer, 0, byteCount);

            SendDataFormat currentFormat = SendDataFormat.Hex;
            this.Invoke(new Action(() =>
            {
                currentFormat = ((ComboBoxItem)comboBoxSendFormat.SelectedItem).Value;
            }));

            string displayContent = ParseReceiveData(receiveBuffer, currentFormat);
            string formatName = GetFormatFriendlyName(currentFormat);

            this.Invoke(new Action(() =>
            {
                AppendColoredLog($"[接收({formatName})] {displayContent}\r\n");
            }));
        }
    }
    catch (Exception ex)
    {
        this.Invoke(new Action(() =>
        {
            AppendColoredLog($"[错误] {ex.Message}\r\n");
        }));
    }
}

/// <summary>
/// 连接串口
/// </summary>
private void ConnectSerialPort()
{
    try
    {
        if (comboBoxPorts.SelectedItem == null)
        {
            AppendColoredLog("未选择可用串口\r\n");
            return;
        }

        if (!_serialPort.IsOpen)
        {
            _selectedPortName = comboBoxPorts.SelectedItem.ToString();
            _serialPort.PortName = _selectedPortName;
            _serialPort.Open();
            _isConnected = true;
            UpdateControlStatus();
            AppendColoredLog($"串口 {_selectedPortName} 连接成功!\r\n");
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"串口连接失败: {ex.Message}", "错误",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

/// <summary>
/// 断开串口连接
/// </summary>
private void DisconnectSerialPort()
{
    try
    {
        if (_serialPort.IsOpen)
        {
            _serialPort.Close();
            _isConnected = false;
            UpdateControlStatus();
            AppendColoredLog("串口已断开连接\r\n");
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"串口断开失败: {ex.Message}", "错误",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

/// <summary>
/// 发送数据到串口
/// </summary>
private void SendSerialData()
{
    if (!_isConnected || !_serialPort.IsOpen)
    {
        MessageBox.Show("请先连接串口!", "提示",
            MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
    }

    string inputContent = txtMessage.Text.Trim();
    if (string.IsNullOrEmpty(inputContent))
    {
        MessageBox.Show("请输入要发送的数据!", "提示",
            MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
    }

    try
    {
        SendDataFormat sendFormat = ((ComboBoxItem)comboBoxSendFormat.SelectedItem).Value;
        byte[] sendData = ProcessSendData(inputContent, sendFormat);

        _serialPort.Write(sendData, 0, sendData.Length);

        string formatName = GetFormatFriendlyName(sendFormat);
        string displayContent = sendFormat == SendDataFormat.Hex
            ? BytesToHexString(sendData)
            : inputContent;
        AppendColoredLog($"[发送({formatName})] {displayContent}\r\n");

        if (!_commandHistory.Contains(inputContent))
        {
            _commandHistory.Add(inputContent);
        }
        _historyIndex = _commandHistory.Count;

        txtMessage.Clear();
        txtMessage.Focus();
    }
    catch (FormatException ex)
    {
        MessageBox.Show($"数据格式错误:{ex.Message}\n示例:十六进制(01 0A FF 或 0x110x22) | 字符串(测试数据)",
            "格式错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    catch (ArgumentException ex)
    {
        MessageBox.Show($"参数错误:{ex.Message}", "输入错误",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    catch (Exception ex)
    {
        MessageBox.Show($"数据发送失败:{ex.Message}", "错误",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}
#endregion

数据处理辅助方法

用于格式转换、解析与校验:

csharp 复制代码
#region 数据处理辅助方法
/// <summary>
/// 解析接收到的字节数据
/// </summary>
private string ParseReceiveData(byte[] dataBytes, SendDataFormat format)
{
    if (dataBytes == null || dataBytes.Length == 0) return string.Empty;

    if (format == SendDataFormat.Hex)
    {
        return BytesToHexString(dataBytes);
    }
    else if (format == SendDataFormat.String)
    {
        return Encoding.UTF8.GetString(dataBytes);
    }
    else
    {
        return BytesToHexString(dataBytes);
    }
}

/// <summary>
/// 处理待发送的数据
/// </summary>
private byte[] ProcessSendData(string input, SendDataFormat format)
{
    if (format == SendDataFormat.Hex)
    {
        return HexStringToBytes(input);
    }
    else if (format == SendDataFormat.String)
    {
        return Encoding.UTF8.GetBytes(input);
    }
    else
    {
        throw new ArgumentException("不支持的发送格式");
    }
}

/// <summary>
/// 字节数组转十六进制字符串
/// </summary>
private string BytesToHexString(byte[] bytes)
{
    if (bytes == null || bytes.Length == 0) return string.Empty;

    StringBuilder hexBuilder = new StringBuilder(bytes.Length * 3);
    foreach (byte b in bytes)
    {
        hexBuilder.AppendFormat("{0:X2} ", b);
    }
    return hexBuilder.ToString().Trim();
}

/// <summary>
/// 十六进制字符串转字节数组
/// </summary>
private byte[] HexStringToBytes(string hexString)
{
    string cleanHex = hexString.ToLower()
                                  .Replace("0x", "")
                                  .Replace(" ", "")
                                  .Replace("\r", "")
                                  .Replace("\n", "")
                                  .Replace("\t", "");

    if (cleanHex.Length % 2 != 0)
    {
        throw new ArgumentException("十六进制字符串长度必须为偶数");
    }

    if (!Regex.IsMatch(cleanHex, "^[0-9a-f]+$"))
    {
        throw new FormatException("包含非法字符,仅允许0-9、A-F、a-f、0x/0X、空格");
    }

    byte[] result = new byte[cleanHex.Length / 2];
    for (int i = 0; i < result.Length; i++)
    {
        string hexByte = cleanHex.Substring(i * 2, 2);
        result[i] = Convert.ToByte(hexByte, 16);
    }
    return result;
}

/// <summary>
/// 获取枚举对应的友好显示名称
/// </summary>
private string GetFormatFriendlyName(SendDataFormat format)
{
    if (format == SendDataFormat.Hex)
    {
        return "十六进制";
    }
    else if (format == SendDataFormat.String)
    {
        return "字符串";
    }
    else
    {
        return "十六进制";
    }
}
#endregion

UI 操作与事件处理

确保线程安全地更新界面,并响应用户交互:

csharp 复制代码
#region UI操作辅助方法
/// <summary>
/// 安全追加带颜色的日志
/// </summary>
private void AppendColoredLog(string text)
{
    if (txtReceived.InvokeRequired)
    {
        txtReceived.Invoke(new Action<string>(AppendColoredLog), text);
    }
    else
    {
        txtReceived.SelectionStart = txtReceived.TextLength;
        txtReceived.SelectionLength = 0;
        txtReceived.AppendText(text);
        txtReceived.ScrollToCaret();
    }
}

/// <summary>
/// 更新控件状态
/// </summary>
private void UpdateControlStatus()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateControlStatus));
        return;
    }

    btnConnect.Text = _isConnected ? "断开串口" : "连接串口";
    btnSend.Enabled = _isConnected;
    txtMessage.Enabled = _isConnected;

    _selectedPortName = comboBoxPorts.SelectedItem?.ToString() ?? string.Empty;
    lblStatus.Text = _isConnected
        ? $"已连接: {_selectedPortName} ({BaudRate} bps)"
        : "未连接";
    lblStatus.ForeColor = _isConnected ? System.Drawing.Color.Green : System.Drawing.Color.Red;
}

/// <summary>
/// 处理上下方向键调取历史命令
/// </summary>
private void HandleHistoryKey(Keys keyCode)
{
    if (_commandHistory.Count == 0) return;

    if (keyCode == Keys.Up)
    {
        _historyIndex--;
        if (_historyIndex < 0) _historyIndex = 0;
    }
    else if (keyCode == Keys.Down)
    {
        _historyIndex++;
        if (_historyIndex >= _commandHistory.Count)
        {
            _historyIndex = _commandHistory.Count;
            txtMessage.Clear();
            return;
        }
    }

    if (_historyIndex >= 0 && _historyIndex < _commandHistory.Count)
    {
        txtMessage.Text = _commandHistory[_historyIndex];
        txtMessage.SelectionStart = txtMessage.TextLength;
    }
}
#endregion

#region 控件事件处理
private void btnConnect_Click(object sender, EventArgs e)
{
    if (!_isConnected)
    {
        ConnectSerialPort();
    }
    else
    {
        DisconnectSerialPort();
    }
}

private void btnSend_Click(object sender, EventArgs e)
{
    SendSerialData();
}

private void txtMessage_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
    {
        HandleHistoryKey(e.KeyCode);
        e.Handled = true;
        e.SuppressKeyPress = true;
        return;
    }

    if (e.KeyCode == Keys.Enter && _isConnected)
    {
        SendSerialData();
        e.Handled = true;
        e.SuppressKeyPress = true;
    }
}

private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    _serialPort?.Close();
    _serialPort?.Dispose();
}
#endregion

自定义类型定义

最后是用于下拉框绑定和数据格式枚举的辅助类:

csharp 复制代码
#region 发送和解析的数据类型
/// <summary>
/// 发送和解析的数据类型
/// </summary>
public class ComboBoxItem
{
    public string DisplayName { get; set; }
    public SendDataFormat Value { get; set; }

    public ComboBoxItem(string displayName, SendDataFormat value)
    {
        DisplayName = displayName;
        Value = value;
    }
}

/// <summary>
/// 串口发送/接收数据格式枚举
/// </summary>
public enum SendDataFormat
{
    /// <summary>
    /// 十六进制格式
    /// </summary>
    Hex,
    /// <summary>
    /// 字符串格式(UTF8编码)
    /// </summary>
    String
}
#endregion

设计器代码

csharp 复制代码
using System.Windows.Forms;

namespace SerialPortDemo
{
    partial class Form1
    {
        /// <summary>
        /// 必需的设计器变量。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        private TextBox txtMessage;
        private Button btnSend;
        private Button btnConnect;
        private TextBox txtReceived;
        private Label lblStatus;

        /// <summary>
        /// 清理所有正在使用的资源。
        /// </summary>
        /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows 窗体设计器生成的代码

        /// <summary>
        /// 设计器支持所需的方法 - 不要修改
        /// 使用代码编辑器修改此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {
            this.txtMessage = new System.Windows.Forms.TextBox();
            this.btnSend = new System.Windows.Forms.Button();
            this.btnConnect = new System.Windows.Forms.Button();
            this.txtReceived = new System.Windows.Forms.TextBox();
            this.lblStatus = new System.Windows.Forms.Label();
            this.comboBoxPorts = new System.Windows.Forms.ComboBox();
            this.comboBoxSendFormat = new System.Windows.Forms.ComboBox();
            this.SuspendLayout();
            // 
            // txtMessage
            // 
            this.txtMessage.Location = new System.Drawing.Point(138, 351);
            this.txtMessage.Name = "txtMessage";
            this.txtMessage.Size = new System.Drawing.Size(307, 21);
            this.txtMessage.TabIndex = 0;
            this.txtMessage.KeyDown += new System.Windows.Forms.KeyEventHandler(this.txtMessage_KeyDown);
            // 
            // btnSend
            // 
            this.btnSend.Location = new System.Drawing.Point(455, 351);
            this.btnSend.Name = "btnSend";
            this.btnSend.Size = new System.Drawing.Size(100, 21);
            this.btnSend.TabIndex = 1;
            this.btnSend.Text = "发送";
            this.btnSend.UseVisualStyleBackColor = true;
            this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
            // 
            // btnConnect
            // 
            this.btnConnect.Location = new System.Drawing.Point(138, 14);
            this.btnConnect.Name = "btnConnect";
            this.btnConnect.Size = new System.Drawing.Size(100, 30);
            this.btnConnect.TabIndex = 2;
            this.btnConnect.Text = "连接串口";
            this.btnConnect.UseVisualStyleBackColor = true;
            this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
            // 
            // txtReceived
            // 
            this.txtReceived.Location = new System.Drawing.Point(12, 50);
            this.txtReceived.Multiline = true;
            this.txtReceived.Name = "txtReceived";
            this.txtReceived.ReadOnly = true;
            this.txtReceived.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.txtReceived.Size = new System.Drawing.Size(543, 290);
            this.txtReceived.TabIndex = 3;
            // 
            // lblStatus
            // 
            this.lblStatus.Location = new System.Drawing.Point(244, 14);
            this.lblStatus.Name = "lblStatus";
            this.lblStatus.Size = new System.Drawing.Size(311, 30);
            this.lblStatus.TabIndex = 4;
            this.lblStatus.Text = "未连接";
            this.lblStatus.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
            // 
            // comboBoxPorts
            // 
            this.comboBoxPorts.FormattingEnabled = true;
            this.comboBoxPorts.Location = new System.Drawing.Point(12, 18);
            this.comboBoxPorts.Name = "comboBoxPorts";
            this.comboBoxPorts.Size = new System.Drawing.Size(120, 20);
            this.comboBoxPorts.TabIndex = 5;
            // 
            // comboBoxSendFormat
            // 
            this.comboBoxSendFormat.FormattingEnabled = true;
            this.comboBoxSendFormat.Location = new System.Drawing.Point(12, 351);
            this.comboBoxSendFormat.Name = "comboBoxSendFormat";
            this.comboBoxSendFormat.Size = new System.Drawing.Size(120, 20);
            this.comboBoxSendFormat.TabIndex = 6;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(567, 382);
            this.Controls.Add(this.comboBoxSendFormat);
            this.Controls.Add(this.comboBoxPorts);
            this.Controls.Add(this.lblStatus);
            this.Controls.Add(this.txtReceived);
            this.Controls.Add(this.btnConnect);
            this.Controls.Add(this.btnSend);
            this.Controls.Add(this.txtMessage);
            this.Name = "Form1";
            this.Text = "串口通信工具";
            this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private ComboBox comboBoxPorts;
        private ComboBox comboBoxSendFormat;
    }
}
相关推荐
kylezhao20192 小时前
C#上位机开发数据持久化:xml数据导入导出
xml·开发语言·c#
怀民民民2 小时前
双通道点光源追踪系统
单片机·嵌入式硬件·开源·操作系统·串口·硬件·frtos
YJlio2 小时前
CSDN年度总结2025:技术逐梦不止,步履坚定向前
windows·学习·流程图
꧁༺℘₨风、凌๓༻꧂2 小时前
C# WPF 项目中集成 Pdf查看器
pdf·c#·wpf
stella·2 小时前
服务器割接,我所学习到的内容。
linux·运维·服务器·学习·shell·割接
时光追逐者2 小时前
ASP.NET Core 依赖注入的三种服务生命周期
后端·c#·asp.net·.net·.netcore
sealaugh322 小时前
AI(学习笔记第十七课)langchain v1.0(SQL Agent)
人工智能·笔记·学习
山土成旧客2 小时前
【Python学习打卡-Day30】模块化编程:从“单兵作战”到“军团指挥”
开发语言·python·学习
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]iomap
linux·笔记·学习