C#实现高效稳定的串口通讯

前言

串口通讯是嵌入式开发、工业自动化、物联网等领域中非常重要的一种通信方式。在C#开发中,虽然.NET框架提供了System.IO.Ports.SerialPort类来实现串口通信,但直接使用它往往需要处理很多底层细节,如多线程、缓存管理、错误处理等。本文将分享如何在C#中实现高效稳定的串口通讯。

串口通讯的挑战

在实现串口通讯时,我们通常会面临以下挑战:

  1. 主线程阻塞:串口操作是IO操作,如果在主线程中直接执行,可能会导致UI卡顿
  2. 数据丢失:串口数据传输速度与应用程序处理速度不匹配时,容易导致数据丢失
  3. 错误处理:串口连接不稳定时,需要妥善处理各种异常情况
  4. 参数配置:串口参数(波特率、数据位、停止位、校验位等)的管理和配置
  5. 动态检测:需要实时检测串口的连接状态和列表变化

设计思路

为了应对上述挑战,主要包括以下设计思路:

1. 多线程架构

采用多线程架构,将串口操作分为三个独立线程:

  • 接收缓存线程:负责从串口读取原始数据并缓存
  • 接收数据线程:负责处理缓存数据并触发事件
  • 发送数据线程:负责将待发送数据写入串口

这种设计有效避免了串口操作阻塞主线程,提高了应用程序的响应速度和稳定性。

2. 队列缓存机制

使用线程安全的队列实现数据的缓存和处理,确保数据在多线程环境下的安全传输:

  • queueSerialCacheReceived:缓存从串口读取的原始数据
  • queueSerialDataReceived:缓存处理后的完整数据帧
  • queueSerialDataWrite:缓存待发送的数据

3. 事件驱动模型

采用事件驱动模型处理串口数据和错误:

  • EventSerialPortDataReceivedProcess:数据接收事件
  • EventSerialPortError:错误处理事件

这种设计使得库的使用更加灵活,用户可以根据需要自定义数据处理和错误处理逻辑。

4. 动态串口检测

提供实时串口检测功能,支持两种检测模式:

  • 线程检测模式:使用后台线程定期检测串口列表变化
  • 定时器检测模式:使用定时器定期检测串口列表变化

当串口列表发生变化时,会自动触发事件通知应用程序。

5. 丰富的数据转换工具

提供多种数据格式转换方法:

  • 字节数组与字符串之间的转换
  • 16进制字符串与字节数组之间的转换
  • ASCII编码与字符串之间的转换
  • 数据有效性校验

核心实现

1. 多线程串口操作

// 初始化线程
private void InitThread()
{
    //初始化接收缓存线程
    threadSerialCacheReceived = new Thread(new ThreadStart(ThreadSerialCacheReceivedFunction));
    threadSerialCacheReceived.IsBackground = true;

    //初始化接收数据线程
    threadSerialDataReceived = new Thread(new ThreadStart(ThreadSerialDataReceivedFunction));
    threadSerialDataReceived.IsBackground = true;

    //初始化发送数据线程
    threadSerialDataWrite = new Thread(new ThreadStart(ThreadSerialDataWriteFunction));
    threadSerialDataWrite.IsBackground = true;
}

// 打开线程
private void OpenThread()
{
    InitThread();
    threadSerialCacheReceived.Start();
    threadSerialDataReceived.Start();
    threadSerialDataWrite.Start();
}

2. 数据接收处理

// 串口接收事件函数
private void SerialDataReceivedEventFunction(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        byte[] readBuffer = new byte[sPort.BytesToRead];
        int count = sPort.Read(readBuffer, 0, readBuffer.Length);
        lock (queueSerialCacheReceived.SyncRoot)
        {
            for (int i = 0; i < count; i++)
            {
                queueSerialCacheReceived.Enqueue(readBuffer[i]);
            }
        }
    }
    catch (Exception error)
    {
        EventSerialPortError(this, enumSerialError.ReceivedError, error.Message);
    }
}

// 接收缓存线程函数
private void ThreadSerialCacheReceivedFunction()
{
    List<byte> listData = new List<byte>();
    while (sPort.IsOpen)
    {
        int cacheLength = queueSerialCacheReceived.Count;
        if (cacheLength > 0)
        {
            for (int i = 0; i < cacheLength; i++)
            {
                listData.Add((byte)queueSerialCacheReceived.Dequeue());
                if (listData.Count >= SerialReceviedLengthMax)
                {
                    lock (queueSerialCacheReceived.SyncRoot)
                    {
                        queueSerialDataReceived.Enqueue(listData.ToArray()); //转为帧队列
                    }
                    listData.Clear();
                }
            }
        }
        else if (listData.Count > 0)
        {
            lock (queueSerialCacheReceived.SyncRoot)
            {
                queueSerialDataReceived.Enqueue(listData.ToArray()); //转为帧队列
            }
            listData.Clear();
        }
        Thread.Sleep(SerialReceviedTimeInterval);
    }
    EventSerialPortError(this, enumSerialError.LinkError, "端口连接断开或未开启端口!");
    queueSerialCacheReceived.Clear();
}

// 接收数据线程函数
private void ThreadSerialDataReceivedFunction()
{
    while (sPort.IsOpen)
    {
        lock (queueSerialDataReceived.SyncRoot)
        {
            if (queueSerialDataReceived.Count > 0)
            {
                byte[] byteData = (byte[])queueSerialDataReceived.Dequeue();
                EventSerialPortDataReceivedProcess(this, byteData); //触发事件
                continue;
            }
        }
        Thread.Sleep(SerialReceviedTimeInterval);
    }
    lock (queueSerialDataReceived.SyncRoot)
    {
        queueSerialDataReceived.Clear();
    }
}

3. 数据发送处理

// 发送数据线程函数
private void ThreadSerialDataWriteFunction()
{
    byte[] tempbyte=null;
    while (sPort.IsOpen)
    {
        lock (queueSerialDataWrite.SyncRoot)
        {
            if (queueSerialDataWrite.Count > 0)
            {
                byte[] byteData = (byte[])queueSerialDataWrite.Dequeue();
                try
                {
                    sPort.Write(byteData, 0, byteData.Length);
                    tempbyte = byteData;
                }
                catch (Exception e)
                {
                    EventSerialPortError(this, enumSerialError.WriteError, e.Message);
                    return;
                }
            }
        }
        Thread.Sleep(SerialWriteTimeInterval);
    }

    lock (queueSerialDataWrite.SyncRoot)
    {
        queueSerialDataWrite.Clear();
    }
}

// 串口发送数据
public void Write(byte[] arrData)
{
    if (arrData.Length > 0)
    {
        if (sPort.IsOpen)
        {
            lock (queueSerialDataWrite.SyncRoot)
            {
                queueSerialDataWrite.Enqueue((byte[])arrData.Clone());
            }
        }
        else
        {
            EventSerialPortError(this, enumSerialError.LinkError, "端口未开启!");
        }
    }
}

4. 动态串口检测

// 检测串口列表并处理
private void DetectSerialPortListProcess()
{
    //获取当前串口列表
    nowSerialPortList.Clear();
    foreach (string item in SerialPort.GetPortNames())
    {
        nowSerialPortList.Add(item.ToString());
    }

    //串口列表比对
    if (CompareArray(nowSerialPortList.ToArray(), bakSerialPortList.ToArray()) == false)
    {
        //获取串口信息
        GetSerialPortInfo(DicSerialPortInfo);

        //更新备份列表
        bakSerialPortList.Clear();
        foreach (string item in nowSerialPortList)
        {
            int index = StrSerialPortDefaultInfo.ToList().IndexOf(DicSerialPortInfo[item]);
            if (DicSerialPortInfo.ContainsKey(item) && (index >= 0))
            {
                bakSerialPortList.Insert(0, item);
            }
            else
            {
                bakSerialPortList.Add(item);
            }
        }

        //触发事件
        EventSerialPortList(bakSerialPortList.ToArray());
    }
}

5. 数据转换工具

// byte[]转成string
public static string ToString(byte[] byteData)
{
    return System.Text.Encoding.Default.GetString(byteData);
}

// string类型转成byte[]
public static byte[] ToByteArray(string strData)
{
    return System.Text.Encoding.Default.GetBytes(strData);
}

// byte[]转16进制格式string
public static string ToHexString(byte[] bytes)
{
    string returnStr = "";
    if (bytes != null)
    {
        for (int i = 0; i < bytes.Length; i++)
        {
            returnStr += bytes[i].ToString("X2") + " ";
        }
    }
    return returnStr;
}

// 字符串转16进制字节数组 
public static byte[] ToHexByteArray(string hexString)
{
    hexString = CheakHexString(hexString);
    hexString = hexString.Replace(" ", "");
    byte[] returnBytes = new byte[hexString.Length / 2];
    for (int i = 0; i < returnBytes.Length; i++)
        returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
    return returnBytes;
}

完整实现示例

1. 核心类设计

SerialPortHelper类

public class SerialPortHelper
    {
        #region 常量
        public const Int32 SERIAL_RECEIVED_TIME_INTERVAL = 20;  //接收数据间隔
        public const Int32 SERIAL_RECEIVED_LENGTH_MAX = 128;    //接收帧最大长度
        public const Int32 SERIAL_WRITE_TIME_INTERVAL = 100;    //发送数据帧时间间隔

        #endregion

        #region 内部变量
        private SerialPort sPort;
        private Thread threadSerialCacheReceived;
        private Thread threadSerialDataReceived;
        private Thread threadSerialDataWrite;
        private Queue queueSerialCacheReceived;
        private Queue queueSerialDataReceived;
        private Queue queueSerialDataWrite;
        #endregion

        #region 字段
        private ConfigComType _configSerialPort;
        private Int32 _serialReceviedTimeInterval = SERIAL_RECEIVED_TIME_INTERVAL;
        private Int32 _serialReceviedLengthMax = SERIAL_RECEIVED_LENGTH_MAX;
        private Int32 _serialWriteTimeInterval = SERIAL_WRITE_TIME_INTERVAL;
        private String _serialMark = "";
        #endregion

        #region 属性
       
        public int SerialReceviedTimeInterval
        {
            get { return _serialReceviedTimeInterval; }
            set { _serialReceviedTimeInterval = value; }
        }
        public int SerialReceviedLengthMax
        {
            get { return _serialReceviedLengthMax; }
            set { _serialReceviedLengthMax = value; }
        }
        public int SerialWriteTimeInterval
        {
            get { return _serialWriteTimeInterval; }
            set { _serialWriteTimeInterval = value; } }
        public string SerialMark
        {
            get { return _serialMark; }
            set { _serialMark = value; }
        }
        public bool IsOpen
        {
            get { return sPort.IsOpen; }
        }
        public ConfigComType ConfigSerialPort
        {
            get  {return _configSerialPort; }
            set  {_configSerialPort = value;}
        }
        /// <summary>
        /// 提供一个外部可访问串口接口
        /// </summary>
        public SerialPort SPort
        {
            get {return sPort; }

            set {sPort = value; }
        }
        #endregion

        #region 构造函数
        public SerialPortHelper()
        {
            //Control.CheckForIllegalCrossThreadCalls = false; //不检测跨线程访问
            InitQueue();
            InitSerialPort();
            InitThread();
        }

        public SerialPortHelper(ConfigComType config) : this()
        {
            ConfigSerialPort = config;
        }

        public SerialPortHelper(ConfigComType config, DelegateSerialPortDataReceivedProcessEvent e) : this(config)
        {
            BindSerialPortDataReceivedProcessEvent(e);
        }

        #endregion

        #region 初始化函数
        /// <summary>
        /// 初始化串口
        /// </summary>
        private void InitSerialPort()
        {
            //初始化串口
            sPort = new SerialPort();
            sPort.DataReceived += SerialDataReceivedEventFunction;
            sPort.DtrEnable = true;
            sPort.RtsEnable = true;
            sPort.Close();
        }

        /// <summary>
        /// 初始化线程
        /// </summary>
        private void InitThread()
        {
            //初始化接收缓存线程
            threadSerialCacheReceived = new Thread(new ThreadStart(ThreadSerialCacheReceivedFunction));
            threadSerialCacheReceived.IsBackground = true;

            //初始化接收数据线程
            threadSerialDataReceived = new Thread(new ThreadStart(ThreadSerialDataReceivedFunction));
            threadSerialDataReceived.IsBackground = true;

            //初始化发送数据线程
            threadSerialDataWrite = new Thread(new ThreadStart(ThreadSerialDataWriteFunction));
            threadSerialDataWrite.IsBackground = true;
        }

        /// <summary>
        /// 初始化队列(线程安全)
        /// </summary>
        private void InitQueue()
        {
            //初始化接收缓存队列
            queueSerialCacheReceived = Queue.Synchronized(new Queue());

            //初始化接收数据队列
            queueSerialDataReceived = Queue.Synchronized(new Queue());

            //初始化发送数据队列
            queueSerialDataWrite = Queue.Synchronized(new Queue());
        }

        #endregion

        #region 串口接收事件函数
        private void SerialDataReceivedEventFunction(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                byte[] readBuffer = new byte[sPort.BytesToRead];
                int count = sPort.Read(readBuffer, 0, readBuffer.Length);
                lock (queueSerialCacheReceived.SyncRoot)
                {
                    for (int i = 0; i < count; i++)
                    {
                        queueSerialCacheReceived.Enqueue(readBuffer[i]);
                    }
                }
            }
            catch (Exception error)
            {
                EventSerialPortError(this, enumSerialError.ReceivedError, error.Message);
            }

        }
        #endregion

        #region 线程处理函数
        /// <summary>
        /// 打开线程
        /// </summary>
        private void OpenThread()
        {
            InitThread();
            threadSerialCacheReceived.Start();
            threadSerialDataReceived.Start();
            threadSerialDataWrite.Start();
        }

        /// <summary>
        /// 接收缓存线程函数
        /// </summary>
        private void ThreadSerialCacheReceivedFunction()
        {
            List<byte> listData = new List<byte>();
            while (sPort.IsOpen)
            {
                int cacheLength = queueSerialCacheReceived.Count;
                if (cacheLength > 0)
                {
                    for (int i = 0; i < cacheLength; i++)
                    {
                        listData.Add((byte)queueSerialCacheReceived.Dequeue());
                        if (listData.Count >= SerialReceviedLengthMax)
                        {
                            lock (queueSerialCacheReceived.SyncRoot)
                            {
                                queueSerialDataReceived.Enqueue(listData.ToArray()); //转为帧队列
                            }
                            listData.Clear();
                        }
                    }
                }
                else if (listData.Count > 0)
                {
                    lock (queueSerialCacheReceived.SyncRoot)
                    {
                        queueSerialDataReceived.Enqueue(listData.ToArray()); //转为帧队列
                    }
                    listData.Clear();
                }
                Thread.Sleep(SerialReceviedTimeInterval);
            }
            EventSerialPortError(this, enumSerialError.LinkError, "端口连接断开或未开启端口!");
            queueSerialCacheReceived.Clear();

        }

        /// <summary>
        /// 接收数据线程函数
        /// </summary>
        private void ThreadSerialDataReceivedFunction()
        {
            while (sPort.IsOpen)
            {
                lock (queueSerialDataReceived.SyncRoot)
                {
                    if (queueSerialDataReceived.Count > 0)
                    {
                        byte[] byteData = (byte[])queueSerialDataReceived.Dequeue();
                        EventSerialPortDataReceivedProcess(this, byteData); //触发事件
                        continue;
                    }
                }
                Thread.Sleep(SerialReceviedTimeInterval);
            }
            lock (queueSerialDataReceived.SyncRoot)
            {
                queueSerialDataReceived.Clear();
            }
        }

        /// <summary>
        /// 发送数据线程函数
        /// </summary>
        private void ThreadSerialDataWriteFunction()
        {
            byte[] tempbyte=null;
            while (sPort.IsOpen)
            {
                lock (queueSerialDataWrite.SyncRoot)
                {
                    if (queueSerialDataWrite.Count > 0)
                    {
                        byte[] byteData = (byte[])queueSerialDataWrite.Dequeue();
                        try
                        {
                            sPort.Write(byteData, 0, byteData.Length);
                            tempbyte = byteData;
                            //Console.WriteLine("发送数据:" + SerialData.ToHexString(byteData) + "\t时间:" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss fff"));
                        }
                        catch (Exception e)
                        {
                            EventSerialPortError(this, enumSerialError.WriteError, e.Message);
                            return;
                        }
                    }
                }
                Thread.Sleep(SerialWriteTimeInterval);
            }

            lock (queueSerialDataWrite.SyncRoot)
            {
                queueSerialDataWrite.Clear();
            }
        }


        /// <summary>
        /// 比较两个字节数组是否相等
        /// </summary>
        /// <param name="b1">byte数组1</param>
        /// <param name="b2">byte数组2</param>
        /// <returns>是否相等</returns>
        private bool ByteEquals(byte[] b1, byte[] b2)
        {
            if (b1 == null || b2 == null) return true;
            //if (b1.Last != b2.Length) return false;
            for (int i = 0; i < 3; i++)
                if (b1[i] != b2[i])
                    return true;
            return false;
        }
        #endregion




        #region 数据接收处理事件
        /// <summary>
        /// 定义委托
        /// </summary>
        /// <param name="arrDataReceived">接收到的数据帧</param>
        public delegate void DelegateSerialPortDataReceivedProcessEvent(object sender, byte[] arrDataReceived);

        /// <summary>
        /// 定义事件
        /// </summary>
        public event DelegateSerialPortDataReceivedProcessEvent EventSerialPortDataReceivedProcess;

        /// <summary>
        /// 绑定接收事件
        /// </summary>
        /// <param name="e">接收处理函数</param>
        public void BindSerialPortDataReceivedProcessEvent(DelegateSerialPortDataReceivedProcessEvent e)
        {
            EventSerialPortDataReceivedProcess = e;
        }

        #endregion

        #region 串口异常事件
        /// <summary>
        /// 定义委托
        /// </summary>
        /// <param name="numError">错误代号</param>
        /// <param name="strError">错误信息</param>
        public delegate void DelegateSerialPortErrorEvent(object sender, enumSerialError enumError, string strError);

        /// <summary>
        /// 定义事件
        /// </summary>
        public event DelegateSerialPortErrorEvent EventSerialPortError;

        /// <summary>
        /// 绑定串口错误事件
        /// </summary>
        /// <param name="e">事件处理函数</param>
        public void BindSerialPortErrorEvent(DelegateSerialPortErrorEvent e)
        {
            EventSerialPortError = e;
        }
        #endregion

        #region 数据发送处理
        /// <summary>
        /// 串口发送数据
        /// </summary>
        /// <param name="arrData">数据内容</param>
        public void Write(byte[] arrData)
        {
            if (arrData.Length > 0)
            {
                if (sPort.IsOpen)
                {
                    lock (queueSerialDataWrite.SyncRoot)
                    {
                        queueSerialDataWrite.Enqueue((byte[])arrData.Clone());
                    }
                }
                else
                {
                    EventSerialPortError(this, enumSerialError.LinkError, "端口未开启!");
                }
            }

        }
        /// <summary>
        /// 清空缓存
        /// </summary>
        public void DiscardInBuffer() {
            if (sPort.IsOpen)
            {
                //sPort.DiscardInBuffer();
                //queueSerialDataWrite.Clear();
                //queueSerialCacheReceived.Clear();
                //queueSerialDataReceived.Clear();
            }
    
        }

        /// <summary>
        /// 串口发送数据
        /// </summary>
        /// <param name="strData">数据内容</param>
        public void Write(string strData)
        {
            if (strData.Length > 0)
            {
                Write(SerialData.ToByteArray(strData));
            }
        }

        /// <summary>
        /// 串口发送Byte数据
        /// </summary>
        /// <param name="arrData">Byte数组数据</param>
        public void WriteByte(byte[] arrData)
        {
            Write(arrData);
        }

        /// <summary>
        /// 串口发送Char数据
        /// </summary>
        /// <param name="arrData">字符数组数据</param>
        public void WriteChar(char[] arrData)
        {
            Write(Encoding.Default.GetBytes(arrData));
        }

        /// <summary>
        /// 串口发送字符串数据
        /// </summary>
        /// <param name="strData">字符串数据</param>
        public void WriteString(string strData)
        {
            Write(strData);
        }

        /// <summary>
        /// 串口发送16进制字符串数据
        /// </summary>
        /// <param name="hexData">16进制字符串数据</param>
        public void WriteHexString(string hexData)
        {
            if (hexData.Length > 0)
            {
                Write(SerialData.ToHexByteArray(hexData));
            }
        }
        #endregion

        #region 公共函数
        /// <summary>
        /// 开启端口
        /// </summary>
        /// <param name="strError">错误信息</param>
        /// <returns>是否开启成功</returns>
        public bool OpenCom(out string strError)
        {
            //检测端口合法性
            if (DetectCom.IsCom(ConfigSerialPort.PortName) == false)
            {
                strError = "端口号不存在,无法开启端口。";
                return false;
            }
            else if (EventSerialPortDataReceivedProcess == null)
            {
                strError = "没有绑定接收数据事件";
                return false;
            }
            else
            {
               

                //开启端口
                try
                {
                    //写入串口配置
                    sPort.PortName = ConfigSerialPort.PortName;
                    sPort.BaudRate = ConfigSerialPort.BaudRate;
                    sPort.DataBits = ConfigSerialPort.DataBits;
                    sPort.StopBits = (System.IO.Ports.StopBits)ConfigSerialPort.StopBits;
                    sPort.Parity = (System.IO.Ports.Parity)ConfigSerialPort.Parity;

                    //尝试关闭串口
                    this.CloseCom(out strError);

                    sPort.Open();
                    OpenThread();
                    strError = string.Format("打开{0}串口", sPort.PortName);
                    return sPort.IsOpen;
                }
                catch (Exception e)
                {
                    strError = e.Message;
                    return false;
                }
            }
        }

        /// <summary>
        /// 开启指定配置的端口
        /// </summary>
        /// <param name="config">配置</param>
        /// <param name="strError">错误信息</param>
        /// <returns>是否开启成功</returns>
        public bool OpenCom(ConfigComType config, out string strError)
        {
            ConfigSerialPort = config;
            string str = string.Empty;
            bool ret = OpenCom(out str);
            strError = str;
            return ret;
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        /// <param name="strError">错误信息</param>
        public void CloseCom(out string strError)
        {
            try
            {
                if (SPort.IsOpen)
                {
                    sPort.Close();
                    strError = "串口关闭成功!";
                }
                else {
                    strError = "串口未打开!";
                }
               
            }
            catch (Exception e)
            {
                strError = e.Message;
            }

        }

        #endregion

    }

    #region 枚举
    /// <summary>
    /// 串口错误枚举
    /// </summary>
    public enum enumSerialError
    {
        LinkError,
        WriteError,
        ReceivedError
    }
    #endregion

2. 使用示例

// 创建SerialPortHelper实例
SerialPortHelper serialPort = new SerialPortHelper();

// 绑定事件处理函数
serialPort.BindSerialPortDataReceivedProcessEvent(new SerialPortHelper.DelegateSerialPortDataReceivedProcessEvent(SerialPortDataReceivedProcess));
serialPort.BindSerialPortErrorEvent(new SerialPortHelper.DelegateSerialPortErrorEvent(SerialPortErrorProcess));

// 配置串口参数
ConfigComType config = new ConfigComType();
config.PortName = "COM1";
config.BaudRate = 9600;
config.DataBits = 8;
config.StopBits = StopBits.One;
config.Parity = Parity.None;

serialPort.ConfigSerialPort = config;

// 打开串口
string errorMessage;
bool result = serialPort.OpenCom(out errorMessage);
if (result)
{
    Console.WriteLine("串口打开成功!");
}
else
{
    Console.WriteLine("串口打开失败:" + errorMessage);
}

// 发送数据
byte[] data = new byte[] { 0x01, 0x02, 0x03 };
serialPort.Write(data);

// 接收数据处理
private void SerialPortDataReceivedProcess(object sender, byte[] arrDataReceived)
{
    string receivedData = SerialData.ToString(arrDataReceived);
    Console.WriteLine("接收到数据:" + receivedData);
}

// 错误处理
private void SerialPortErrorProcess(object sender, enumSerialError enumError, string strError)
{
    switch (enumError)
    {
        case enumSerialError.LinkError:
            Console.WriteLine("连接错误:" + strError);
            break;
        case enumSerialError.WriteError:
            Console.WriteLine("写入错误:" + strError);
            break;
        case enumSerialError.ReceivedError:
            Console.WriteLine("接收错误:" + strError);
            break;
    }
}

// 关闭串口
string closeError;
serialPort.CloseCom(out closeError);
Console.WriteLine(closeError);

参考资料

posted @ 2026-02-06 14:26  (*_^)?  阅读(50)  评论(0)    收藏  举报