『7x24小时有问必答』
  

C# 串口通信 90% 的人踩的 5 个坑,附调试日志 + 完整代码

串口通信是 C# 工控、硬件对接开发中最常用的功能,但新手极易踩坑 —— 波特率不匹配导致通信失败、数据粘包解析错乱、串口占用报错、接收乱码、关闭串口程序崩溃…… 这些问题几乎覆盖了 90% 的串口开发场景。
本文从实战角度拆解 5 个高频坑(原因 + 解决方案),编写可直接复用的通用串口通信类,附调试日志和硬件对接实战,看完就能避坑、快速实现稳定的串口通信!

---

坑 1:波特率 / 校验位不匹配(通信基础错,一切都白搭)

坑点表现

• 串口能打开,但接收不到数据 / 接收全是乱码;
• 报错 “参数无效”,但代码无语法错误。

核心原因

串口通信是 “严丝合缝” 的协议,上位机和硬件(扫码枪 / 传感器 / PLC)的波特率、校验位、数据位、停止位、流控必须完全一致,哪怕一个参数错,通信就会失败:
• 波特率:最常用 9600/115200,硬件默认值可能不是 9600;
• 校验位:None(无校验)是主流,部分传感器用 Even(偶校验);
• 流控:新手常误开 RTS/CTS 流控,硬件不支持则通信中断。

解决代码(参数标准化配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

///  <summary>

///  初始化串口参数(通用硬件适配版)

///  </summary>

///  <param name="portName">串口名(如COM3)

///  <param name="baudRate">波特率

///  <returns>配置好的SerialPort对象</returns>

private  SerialPort  InitSerialPort(string  portName,  int  baudRate =  9600)

{

      SerialPort serialPort =  new  SerialPort();

      try

      {

            // 核心参数:必须和硬件完全一致

            serialPort.PortName = portName;

            serialPort.BaudRate = baudRate;

            serialPort.Parity = Parity.None;  // 无校验(90%硬件默认)

            serialPort.DataBits =  8;  // 数据位8位(标准)

            serialPort.StopBits = StopBits.One;  // 停止位1位(标准)

            serialPort.Handshake = Handshake.None;  // 关闭流控(新手必关)

            // 关键:避免接收数据截断

            serialPort.ReadBufferSize =  4096;

            serialPort.WriteBufferSize =  4096;

            // 超时设置:避免卡死

            serialPort.ReadTimeout =  500;

            serialPort.WriteTimeout =  500;

            // 禁用自动接收(手动处理更稳定)

            serialPort.DtrEnable =  false;

            serialPort.RtsEnable =  false;

            

            return  serialPort;

      }

      catch  (Exception ex)

      {

            LogHelper.WriteLog($"串口参数配置失败:{ex.Message}");  // 调试日志

            throw;

      }

}

避坑技巧

1. 先查硬件手册:扫码枪 / 传感器的默认串口参数(如扫码枪常用 9600,N,8,1);
2. 用串口调试助手(如 SSCOM)先验证参数:能通信再写代码。

---

坑 2:数据粘包(接收的数据 “粘在一起”,解析错乱)

坑点表现

• 硬件单次发 “123”,上位机收到 “123456”(包含上一次的 “456”);
• 按固定长度解析时,数据错位、校验失败。

核心原因

• 串口是 “流数据”,无天然分隔符,硬件连续发送时,上位机缓冲区会拼接多帧数据;
• 接收事件触发时机随机,可能只收到半帧数据,也可能收到多帧。

解决代码(基于 “结束符 / 固定长度” 拆包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

// 全局缓冲区:缓存未解析的串口数据

private  readonly  StringBuilder _recvBuffer =  new  StringBuilder();

// 结束符(如扫码枪常用\r\n作为结束符)

private  const  string  EndMark =  "\r\n";

///  <summary>

///  串口数据接收事件(核心:拆包处理)

///  </summary>

private  void  SerialPort_DataReceived(object  sender, SerialDataReceivedEventArgs e)

{

      try

      {

            SerialPort sp = (SerialPort)sender;

            // 读取缓冲区所有数据(避免截断)

            string  recvData = sp.ReadExisting();

            LogHelper.WriteLog($"原始接收数据:{recvData}");  // 调试日志

            // 步骤1:写入全局缓冲区

            _recvBuffer.Append(recvData);

            string  bufferStr = _recvBuffer.ToString();

            // 步骤2:按结束符拆包(适配扫码枪/传感器)

            if  (bufferStr.Contains(EndMark))

            {

                  // 拆分多帧数据

                  string[] frames = bufferStr.Split(new[] { EndMark }, StringSplitOptions.RemoveEmptyEntries);

                  foreach  (string  frame  in  frames)

                  {

                        if  (!string.IsNullOrEmpty(frame))

                        {

                              LogHelper.WriteLog($"解析出完整帧:{frame}");

                              // 处理单帧数据(如扫码枪的条码)

                              OnFrameReceived(frame);

                        }

                  }

                  // 步骤3:清空已解析的部分,保留未完成的帧(如最后半帧)

                  _recvBuffer.Clear();

                  // 若最后有未完成的帧,重新写入缓冲区

                  if  (bufferStr.EndsWith(EndMark))  return;

                  int  lastEndIndex = bufferStr.LastIndexOf(EndMark);

                  _recvBuffer.Append(bufferStr.Substring(lastEndIndex + EndMark.Length));

            }

      }

      catch  (Exception ex)

      {

            LogHelper.WriteLog($"数据拆包失败:{ex.Message}");

      }

}

///  <summary>

///  处理单帧完整数据(业务逻辑)

///  </summary>

private  void  OnFrameReceived(string  frameData)

{

      // 示例:处理扫码枪条码

      if  (frameData.Length >  0)

      {

            // 跨线程更新UI(WinForms/WPF)

            this.Invoke(new  Action(() => {

                  txtRecvData.Text = frameData;

            }));

      }

}

避坑技巧

1. 优先用硬件支持的 “结束符”(如 \r\n、0x0D),无结束符则用 “固定长度” 拆包;
2. 全局缓冲区必须线程安全(串口接收事件是异步线程)。

---

坑 3:串口占用(报错 “访问被拒绝”,程序启动失败)

坑点表现

• 报错 “System.IO.IOException: 访问端口 COM3 被拒绝”;
• 程序关闭后,串口仍被占用,需重启电脑。

核心原因

• 串口未正确释放:程序崩溃 / 异常退出时,SerialPort 未执行 Close ();
• 其他程序(如串口助手、杀毒软件)占用串口;
• 多线程重复打开同一串口。

解决代码(安全打开 / 释放串口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

///  <summary>

///  安全打开串口(防重复打开/占用)

///  </summary>

///  <returns>是否打开成功</returns>

public  bool  SafeOpenSerialPort()

{

      // 步骤1:先检查串口是否已打开

      if  (_serialPort !=  null  && _serialPort.IsOpen)

      {

            LogHelper.WriteLog($"串口{_serialPort.PortName}已打开");

            return  true;

      }

      try

      {

            // 步骤2:检查串口是否存在

            string[] ports = SerialPort.GetPortNames();

            if  (!ports.Contains(_serialPort.PortName))

            {

                  LogHelper.WriteLog($"串口{_serialPort.PortName}不存在");

                  return  false;

            }

            // 步骤3:尝试打开串口

            _serialPort.Open();

            // 绑定接收事件(打开后再绑定,避免异常)

            _serialPort.DataReceived += SerialPort_DataReceived;

            LogHelper.WriteLog($"串口{_serialPort.PortName}打开成功");

            return  true;

      }

      catch  (UnauthorizedAccessException)

      {

            LogHelper.WriteLog($"串口{_serialPort.PortName}被占用");

            return  false;

      }

      catch  (Exception ex)

      {

            LogHelper.WriteLog($"串口打开失败:{ex.Message}");

            return  false;

      }

}

///  <summary>

///  安全关闭串口(必写:防占用/崩溃)

///  </summary>

public  void  SafeCloseSerialPort()

{

      if  (_serialPort ==  null)  return;

      try

      {

            // 步骤1:解绑事件(避免关闭时触发接收事件)

            _serialPort.DataReceived -= SerialPort_DataReceived;

            

            // 步骤2:检查并关闭串口

            if  (_serialPort.IsOpen)

            {

                  _serialPort.DiscardInBuffer();  // 清空接收缓冲区

                  _serialPort.DiscardOutBuffer();  // 清空发送缓冲区

                  _serialPort.Close();

                  LogHelper.WriteLog($"串口{_serialPort.PortName}关闭成功");

            }

      }

      catch  (Exception ex)

      {

            LogHelper.WriteLog($"串口关闭失败:{ex.Message}");

      }

      finally

      {

            // 步骤3:释放资源(关键:防内存泄漏/串口占用)

            _serialPort.Dispose();

            _serialPort =  null;

      }

}

避坑技巧

1. 程序退出时强制关闭串口:在 FormClosing / 应用退出事件中调用 SafeCloseSerialPort ();
2. 用工具排查占用:如 “Serial Port Monitor” 查看哪个进程占用串口。

---

坑 4:接收乱码(串口能收到数据,但全是 “???” 或方块)

坑点表现

• 硬件发送 “ABC123”,上位机收到 “���123” 或 “锟斤拷”;
• 中文 / 特殊字符解析错误,英文 / 数字正常。

核心原因

• 编码不匹配:硬件用 GBK/GB2312 发送,上位机用 UTF8 接收(最常见);
• 波特率错误(隐性乱码,易被忽略);
• 串口数据位 / 校验位错误,导致字节传输错误。

解决代码(指定编码接收)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

///  <summary>

///  读取串口数据(指定编码,防乱码)

///  </summary>

///  <param name="sp">串口对象

///  <param name="encoding">编码(硬件常用GBK/ASCII)

///  <returns>正确编码的字符串</returns>

private  string  ReadSerialData(SerialPort sp, Encoding encoding)

{

      try

      {

            if  (sp.BytesToRead ==  0)  return  string.Empty;

            

            // 方式1:按字节读取(推荐,避免编码自动转换)

            byte[] buffer =  new  byte[sp.BytesToRead];

            sp.Read(buffer,  0, buffer.Length);

            string  data = encoding.GetString(buffer);

            

            // 方式2:若用ReadExisting,需设置SerialPort的Encoding属性

            // sp.Encoding = encoding;

            // string data = sp.ReadExisting();

            

            return  data;

      }

      catch  (Exception ex)

      {

            LogHelper.WriteLog($"读取数据乱码:{ex.Message}");

            return  string.Empty;

      }

}

// 调用示例(扫码枪常用ASCII,传感器常用GBK)

string  recvData = ReadSerialData(_serialPort, Encoding.GetEncoding("GBK"));

避坑技巧

1. 优先用 “字节读取 + 手动编码转换”,避免 SerialPort 自动编码的坑;
2. 测试编码:依次用 ASCII、GBK、UTF8 测试,能正确解析的就是硬件编码。

---

坑 5:关闭串口崩溃(点关闭按钮,程序直接闪退)

坑点表现

• 关闭串口 / 退出程序时,报错 “InvalidOperationException”,程序崩溃;
• 报错 “线程间操作无效:从不是创建控件的线程访问它”。

核心原因

• 串口接收事件是异步线程,关闭串口时,接收线程还在运行,访问已释放的串口对象;
• 接收事件中直接更新 UI,关闭串口时 UI 线程已销毁,触发跨线程异常。

解决代码(线程安全关闭 + UI 跨线程处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

// 全局锁:防多线程同时操作串口

private  readonly  object  _serialLock =  new  object();

///  <summary>

///  线程安全关闭串口(防崩溃核心)

///  </summary>

public  void  ThreadSafeCloseSerialPort()

{

      // 步骤1:加锁,避免和接收线程冲突

      lock  (_serialLock)

      {

            if  (_serialPort ==  null)  return;

            try

            {

                  // 步骤2:暂停接收事件(关键)

                  _serialPort.DataReceived -= SerialPort_DataReceived;

                  // 步骤3:清空缓冲区,避免关闭时触发数据接收

                  if  (_serialPort.IsOpen)

                  {

                        _serialPort.DiscardInBuffer();

                        _serialPort.DiscardOutBuffer();

                        // 延迟关闭(避免线程未退出)

                        Thread.Sleep(100);

                        _serialPort.Close();

                  }

            }

            catch  (Exception ex)

            {

                  LogHelper.WriteLog($"线程安全关闭串口失败:{ex.Message}");

            }

            finally

            {

                  _serialPort.Dispose();

                  _serialPort =  null;

                  _recvBuffer.Clear();  // 清空缓冲区

            }

      }

}

///  <summary>

///  跨线程更新UI(WinForms通用)

///  </summary>

///  <param name="action">UI操作

private  void  SafeUpdateUI(Action action)

{

      if  (this.InvokeRequired)

      {

            // 跨线程:委托更新

            this.Invoke(action);

      }

      else

      {

            // 主线程:直接执行

            action();

      }

}

// 接收事件中调用示例

private  void  SerialPort_DataReceived(object  sender, SerialDataReceivedEventArgs e)

{

      try

      {

            string  recvData = ReadSerialData(_serialPort, Encoding.ASCII);

            // 安全更新UI

            SafeUpdateUI(() => {

                  txtRecvData.AppendText($"{DateTime.Now}:  {recvData}\r\n");

            });

      }

      catch  (Exception ex)

      {

            LogHelper.WriteLog($"接收数据异常:{ex.Message}");

      }

}

避坑技巧

1. 关闭串口前必须解绑 DataReceived 事件;
2. 所有 UI 更新必须通过 Invoke/BeginInvoke,禁止直接在接收事件中操作 UI;
3. 加锁保护串口操作,避免多线程冲突。

---

通用串口通信类(可复用,带完整注释)

整合以上避坑点,编写通用串口通信类,直接复制即可用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

using  System;

using  System.IO.Ports;

using  System.Text;

using  System.Threading;

using  System.Windows.Forms;

namespace  SerialPortDemo

{

       ///  <summary>

       ///  通用串口通信类(防坑版)

       ///  </summary>

      public  class  SerialPortHelper  :  IDisposable

      {

            #region  字段

            private  SerialPort _serialPort;  // 串口对象

            private  readonly  StringBuilder _recvBuffer =  new  StringBuilder();  // 接收缓冲区

            private  readonly  object  _serialLock =  new  object();  // 线程锁

            private  string  _endMark =  "\r\n";  // 帧结束符

            private  Encoding _encoding = Encoding.ASCII;  // 编码

            #endregion

            #region  事件

             ///  <summary>

             ///  完整帧数据接收事件

             ///  </summary>

            public  event  Action<string> OnFrameReceived;

            #endregion

            #region  属性

             ///  <summary>

             ///  帧结束符

             ///  </summary>

            public  string  EndMark

            {

                  get  => _endMark;

                  set  => _endMark =  value  ??  "\r\n";

            }

             ///  <summary>

             ///  串口编码

             ///  </summary>

            public  Encoding Encoding

            {

                  get  => _encoding;

                  set  => _encoding =  value  ?? Encoding.ASCII;

            }

             ///  <summary>

             ///  串口是否已打开

             ///  </summary>

            public  bool  IsOpen => _serialPort !=  null  && _serialPort.IsOpen;

            #endregion

            #region  初始化

             ///  <summary>

             ///  初始化串口

             ///  </summary>

             ///  <param name="portName">串口名

             ///  <param name="baudRate">波特率

             ///  <param name="parity">校验位

             ///  <param name="dataBits">数据位

             ///  <param name="stopBits">停止位

             public  void  Init(string  portName,  int  baudRate =  9600,

                  Parity parity = Parity.None,  int  dataBits =  8, StopBits stopBits = StopBits.One)

            {

                  lock  (_serialLock)

                  {

                        _serialPort =  new  SerialPort

                        {

                              PortName = portName,

                              BaudRate = baudRate,

                              Parity = parity,

                              DataBits = dataBits,

                              StopBits = stopBits,

                              Handshake = Handshake.None,

                              ReadBufferSize =  4096,

                              WriteBufferSize =  4096,

                              ReadTimeout =  500,

                              WriteTimeout =  500,

                              DtrEnable =  false,

                              RtsEnable =  false

                        };

                        // 绑定接收事件

                        _serialPort.DataReceived += SerialPort_DataReceived;

                        LogHelper.WriteLog($"串口{portName}初始化成功,波特率:{baudRate}");

                  }

            }

            #endregion

            #region  打开/关闭串口

             ///  <summary>

             ///  安全打开串口

             ///  </summary>

             ///  <returns>是否成功</returns>

             public  bool  Open()

            {

                  lock  (_serialLock)

                  {

                        if  (_serialPort ==  null)

                        {

                              LogHelper.WriteLog("串口未初始化");

                              return  false;

                        }

                        if  (_serialPort.IsOpen)  return  true;

                        try

                        {

                              _serialPort.Open();

                              LogHelper.WriteLog($"串口{_serialPort.PortName}打开成功");

                              return  true;

                        }

                        catch  (UnauthorizedAccessException)

                        {

                              LogHelper.WriteLog($"串口{_serialPort.PortName}被占用");

                              return  false;

                        }

                        catch  (Exception ex)

                        {

                              LogHelper.WriteLog($"串口打开失败:{ex.Message}");

                              return  false;

                        }

                  }

            }

             ///  <summary>

             ///  线程安全关闭串口

             ///  </summary>

             public  void  Close()

            {

                  lock  (_serialLock)

                  {

                        if  (_serialPort ==  null)  return;

                        try

                        {

                              // 解绑事件

                              _serialPort.DataReceived -= SerialPort_DataReceived;

                              if  (_serialPort.IsOpen)

                              {

                                    _serialPort.DiscardInBuffer();

                                    _serialPort.DiscardOutBuffer();

                                    Thread.Sleep(100);

                                    _serialPort.Close();

                              }

                              LogHelper.WriteLog($"串口{_serialPort.PortName}关闭成功");

                        }

                        catch  (Exception ex)

                        {

                              LogHelper.WriteLog($"串口关闭失败:{ex.Message}");

                        }

                        finally

                        {

                              _serialPort.Dispose();

                              _serialPort =  null;

                              _recvBuffer.Clear();

                        }

                  }

            }

            #endregion

            #region  数据发送/接收

             ///  <summary>

             ///  发送数据(字符串)

             ///  </summary>

             ///  <param name="data">发送内容

             ///  <returns>是否成功</returns>

             public  bool  SendData(string  data)

            {

                  if  (!IsOpen)

                  {

                        LogHelper.WriteLog("串口未打开,发送失败");

                        return  false;

                  }

                  try

                  {

                        byte[] sendBytes = _encoding.GetBytes(data);

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

                        LogHelper.WriteLog($"发送数据:{data}(字节数:{sendBytes.Length})");

                        return  true;

                  }

                  catch  (Exception ex)

                  {

                        LogHelper.WriteLog($"发送数据失败:{ex.Message}");

                        return  false;

                  }

            }

             ///  <summary>

             ///  串口数据接收事件(拆包+防乱码)

             ///  </summary>

             private  void  SerialPort_DataReceived(object  sender, SerialDataReceivedEventArgs e)

            {

                  try

                  {

                        SerialPort sp = (SerialPort)sender;

                        if  (sp.BytesToRead ==  0)  return;

                        // 按字节读取,指定编码

                        byte[] buffer =  new  byte[sp.BytesToRead];

                        sp.Read(buffer,  0, buffer.Length);

                        string  recvData = _encoding.GetString(buffer);

                        LogHelper.WriteLog($"原始接收:{recvData}(字节数:{buffer.Length})");

                        // 拆包处理

                        lock  (_recvBuffer)

                        {

                              _recvBuffer.Append(recvData);

                              string  bufferStr = _recvBuffer.ToString();

                              if  (bufferStr.Contains(_endMark))

                              {

                                    string[] frames = bufferStr.Split(new[] { _endMark }, StringSplitOptions.RemoveEmptyEntries);

                                    foreach  (string  frame  in  frames)

                                    {

                                          OnFrameReceived?.Invoke(frame);  // 触发完整帧事件

                                    }

                                    // 清空缓冲区,保留未完成帧

                                    _recvBuffer.Clear();

                                    if  (!bufferStr.EndsWith(_endMark))

                                    {

                                          int  lastIndex = bufferStr.LastIndexOf(_endMark);

                                          _recvBuffer.Append(bufferStr.Substring(lastIndex + _endMark.Length));

                                    }

                              }

                        }

                  }

                  catch  (Exception ex)

                  {

                        LogHelper.WriteLog($"接收数据异常:{ex.Message}");

                  }

            }

            #endregion

            #region  释放资源

             public  void  Dispose()

            {

                  Close();

            }

            #endregion

      }

}

---

串口调试技巧(快速定位问题)

技巧 1:调试日志打印(必加!)

编写简单的日志类,记录所有串口操作,方便排查问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

using  System;

using  System.IO;

///  <summary>

///  调试日志辅助类

///  </summary>

public  static  class  LogHelper

{

      private  static  readonly  string  _logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,  "SerialPortLog.txt");

       ///  <summary>

       ///  写入日志

       ///  </summary>

       ///  <param name="content">日志内容

       public  static  void  WriteLog(string  content)

      {

            try

            {

                  string  log =  $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}]  {content}\r\n";

                  File.AppendAllText(_logPath, log, Encoding.UTF8);

            }

            catch

            {

                  // 日志写入失败不影响主程序

            }

      }

}

技巧 2:工具辅助调试

1. **串口调试助手(SSCOM / 串口助手)**:先验证硬件通信,确认参数 / 编码 / 数据格式;
2.  Serial Port Monitor:监控串口数据收发,排查数据粘包 / 乱码;
3.  Device Manager:查看串口是否存在、驱动是否正常。

技巧 3:分步调试

1. 先测试 “打开串口”:无占用、参数正确;
2. 测试 “发送数据”:硬件能收到(用工具监控);
3. 测试 “接收数据”:拆包、编码正确;
4. 测试 “关闭串口”:无崩溃、无占用。

---

实战对接:扫码枪 / 传感器

场景 1:对接扫码枪(串口版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

// 1. 初始化串口助手

private  SerialPortHelper _serialHelper =  new  SerialPortHelper();

// 2. 窗体加载时初始化

private  void  FrmSerialPort_Load(object  sender, EventArgs e)

{

      // 扫码枪参数:COM3、9600、N、8、1,结束符\r\n,编码ASCII

      _serialHelper.Init("COM3",  9600);

      _serialHelper.EndMark =  "\r\n";

      _serialHelper.Encoding = Encoding.ASCII;

      // 绑定完整帧接收事件

      _serialHelper.OnFrameReceived += SerialHelper_OnFrameReceived;

}

// 3. 接收扫码枪条码

private  void  SerialHelper_OnFrameReceived(string  frameData)

{

      // 跨线程更新UI

      SafeUpdateUI(() => {

            txtBarcode.Text = frameData;  // 显示条码

            LogHelper.WriteLog($"扫码枪条码:{frameData}");

      });

}

// 4. 打开/关闭串口

private  void  btnOpen_Click(object  sender, EventArgs e)

{

      bool  isOpen = _serialHelper.Open();

      lblStatus.Text = isOpen ?  "串口已打开"  :  "串口打开失败";

      lblStatus.ForeColor = isOpen ? Color.Green : Color.Red;

}

private  void  btnClose_Click(object  sender, EventArgs e)

{

      _serialHelper.Close();

      lblStatus.Text =  "串口已关闭";

      lblStatus.ForeColor = Color.Gray;

}

// 5. 窗体关闭时释放

private  void  FrmSerialPort_FormClosing(object  sender, FormClosingEventArgs e)

{

      _serialHelper.Dispose();

}

场景 2:对接工业传感器(如温湿度传感器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// 传感器参数:COM4、115200、N、8、1,结束符0x0D,编码GBK

_serialHelper.Init("COM4",  115200);

_serialHelper.EndMark =  "\r";  // 0x0D对应\r

_serialHelper.Encoding = Encoding.GetEncoding("GBK");

// 接收传感器数据(如"温度:25.5,湿度:60%")

_serialHelper.OnFrameReceived += (frame) => {

      SafeUpdateUI(() => {

            txtSensorData.Text = frame;

            // 解析温湿度

            if  (frame.Contains("温度:") && frame.Contains("湿度:"))

            {

                  string  temp = frame.Split(':',  ',')[1];

                  string  humi = frame.Split(':',  ',')[3];

                  txtTemp.Text =  $"{temp}℃";

                  txtHumi.Text =  $"{humi}%";

            }

      });

};

---

总结

1.  参数匹配是基础:波特率 / 校验位 / 编码必须和硬件一致,先用工具验证;
2.  数据粘包靠拆包:用结束符 / 固定长度拆分帧数据,全局缓冲区缓存未完成帧;
3.  串口占用靠释放:程序退出 / 关闭时必须安全释放串口,加锁防多线程冲突;
4.  乱码靠编码:优先字节读取 + 手动编码转换,避免自动编码坑;
5.  关闭崩溃靠线程安全:解绑事件、跨线程更新 UI、延迟关闭串口。
这份教程覆盖了 C# 串口通信的所有高频坑和解决方案,通用串口类可直接复用,调试技巧能快速定位问题,新手也能轻松实现和扫码枪、传感器等硬件的稳定通信!
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码