Files
T
coco df489d5640 a
2026-07-03 16:05:30 +08:00

16 KiB
Raw Blame History

串口通信

**本文引用的文件列表** - [SComPort.cpp](file://cpp/Tools/SComPort.cpp) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp) - [SComPort.h](file://h/SComPort.h) - [Constant.h](file://h/Constant.h) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp)

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考量
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本文围绕 SComPort 类对 Windows 串口通信进行系统化技术文档化,重点覆盖:

  • 串口初始化流程:CreateFile 创建句柄、SetupComm 配置缓冲区、SetCommState 设置波特率(115200)与数据格式(8位数据位、无校验、1位停止位)
  • 异步 I/O 操作中 OVERLAPPED 结构的应用,以及 ReadFile/WriteFile 与 GetOverlappedResult 的配合实现非阻塞读写
  • ClearCommError 和 PurgeComm 在错误处理与缓冲区清理中的作用
  • 基于事件驱动的通信模式:EV_RXCHAR 接收字符事件与 EV_BREAK 断线检测的实现原理
  • 串口打开、数据收发、异常处理与资源释放的完整调用示例
  • 多线程环境下线程安全策略与注意事项

项目结构

SComPort 类位于工具模块中,提供串口底层封装与上层命令交互能力;相关常量定义位于公共头文件;UI 层通过消息回调处理断线事件。

graph TB
subgraph "工具层"
SComPort["SComPort 类<br/>cpp/Tools/SComPort.cpp"]
SComPortWinXP["SComPort 兼容实现<br/>cpp/Tools/SComPort_winxp.cpp"]
Header["头文件声明<br/>h/SComPort.h"]
Const["常量定义<br/>h/Constant.h"]
end
subgraph "界面层"
MainFrm["主窗口消息处理<br/>cpp/Views/MainFrm.cpp"]
end
SComPort --> Header
SComPort --> Const
SComPortWinXP --> Header
SComPortWinXP --> Const
MainFrm --> |"WM_BREAKLINE"| SComPort

图表来源

章节来源

核心组件

  • SComPort 类:封装串口打开、配置、读写、Zmodem 收发、断线检测、日志与缓冲区清理等能力
  • 常量定义:READBUFFER_SIZE、WRITEBUFFER_SIZE、COMM_TIMEOUT 等
  • 主窗口消息处理:接收 WM_BREAKLINE 并关闭对应串口、更新设备状态

章节来源

架构总览

SComPort 采用 Windows 异步串口 I/O 模式,使用 OVERLAPPED 结构与事件对象实现非阻塞读写;通过 SetCommMask 注册事件掩码,结合 WaitCommEvent 实现事件驱动的接收路径;ClearCommError 用于查询错误与队列长度;PurgeComm 用于清理发送/接收缓冲区;断线检测通过独立线程轮询端口是否存在并发送 WM_BREAKLINE 给 UI。

sequenceDiagram
participant UI as "UI/调用方"
participant Port as "SComPort"
participant WinIO as "Windows 串口API"
participant Mask as "事件掩码(EV_* )"
participant Thread as "断线检测线程"
UI->>Port : 打开串口(OpenComm)
Port->>WinIO : CreateFile(异步标志)
Port->>WinIO : SetupComm(读写缓冲区大小)
Port->>WinIO : Get/SetCommState(波特率115200/8N1)
Port->>WinIO : SetCommMask(EV_RXCHAR/EV_BREAK/...)
Note over Port,WinIO : 初始化完成
UI->>Port : 发送数据(SendDataDirectly)
Port->>WinIO : WriteFile(Overlapped)
WinIO-->>Port : ERROR_IO_PENDING
Port->>WinIO : WaitForSingleObject(Overlapped.hEvent)
WinIO-->>Port : 完成事件
Port->>WinIO : GetOverlappedResult(获取字节数)
UI->>Port : 接收数据(ReceiveDataDirectly)
Port->>WinIO : WaitCommEvent(EV_RXCHAR)
WinIO-->>Port : 事件触发
Port->>WinIO : ClearCommError(查询InQue)
Port->>WinIO : ReadFile(Overlapped)
WinIO-->>Port : ERROR_IO_PENDING
Port->>WinIO : WaitForSingleObject(Overlapped.hEvent)
WinIO-->>Port : 完成事件
Port->>WinIO : GetOverlappedResult(获取字节数)
Thread->>WinIO : CreateFile(轮询)
WinIO-->>Thread : ERROR_FILE_NOT_FOUND
Thread->>UI : SendMessage(WM_BREAKLINE)
UI->>Port : CloseComm()

图表来源

详细组件分析

串口初始化流程

  • 句柄创建:使用 CreateFile 打开端口,启用 FILE_FLAG_OVERLAPPED 以支持异步 I/O
  • 缓冲区配置:SetupComm 设置读写缓冲区大小(由常量 READBUFFER_SIZE/WRITEBUFFER_SIZE 决定)
  • 参数设置:GetCommState 获取当前 DCB,随后设置 BaudRate=115200、ByteSize=8、Parity=NOPARITY、StopBits=ONESTOPBIT,并调用 SetCommState 应用
  • 事件掩码:SetCommMask 注册 EV_BREAK、EV_CTS、EV_DSR、EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY 等事件

章节来源

异步 I/O 与 OVERLAPPED

  • 发送路径:SendDataDirectly 中为 OVERLAPPED.hEvent 创建事件对象,调用 WriteFile;若返回 ERROR_IO_PENDING,则通过 WaitForSingleObject 等待事件;最终通过 GetOverlappedResult 获取实际写入字节数
  • 接收路径:ReceiveDataDirectly 中先调用 ClearCommError 查询 cbInQue,再根据需要调用 ReadFile;同样在 ERROR_IO_PENDING 情况下等待 Overlapped.hEvent 并用 GetOverlappedResult 获取结果
  • Zmodem 路径:ZmodemSendDataDirectly/ZmodemReceiveDataDirectly 使用独立 OVERLAPPED 对象,发送前可 PurgeComm 清理发送缓冲区,接收时根据 cbInQue 读取

章节来源

错误处理与缓冲区清理

  • ClearCommError:用于查询通信错误标志与输入缓冲区字节数,避免在无数据时盲目 ReadFile
  • PurgeComm:在关闭串口或需要清空缓冲区时使用 PURGE_RXCLEAR/PURGE_TXCLEAR/PURGE_RXABORT/PURGE_TXABORT 清理接收/发送队列
  • 日志输出:通过 PrintLogLast 将通信过程与错误信息写入日志文件,便于定位问题

章节来源

事件驱动通信模式

  • WaitCommEvent:在接收路径中等待 EV_RXCHAR 等事件触发,避免忙等
  • EV_BREAK:断线检测通过 SetCommMask 注册 EV_BREAK,并结合断线检测线程轮询端口是否存在,当端口不可访问时向 UI 发送 WM_BREAKLINE

章节来源

断线检测线程与 UI 回调

  • 断线检测线程:SComPortDetectBreakThreadFun 循环尝试以独占方式打开端口,若返回 ERROR_FILE_NOT_FOUND 则判定断线,调用 CloseComm 并通过 SendMessage(WM_BREAKLINE) 通知 UI
  • UI 处理:OnBreakLine 中根据通信 ID 关闭对应串口、更新设备状态、刷新界面

章节来源

串口打开、数据收发、异常处理与资源释放的完整调用示例

  • 打开串口:OpenComm -> CreateFile -> SetupComm -> Get/SetCommState -> SetCommMask
  • 发送数据:SendDataDirectly -> WriteFile(Overlapped) -> WaitForSingleObject -> GetOverlappedResult
  • 接收数据:WaitCommEvent(EV_RXCHAR) -> ClearCommError(cbInQue) -> ReadFile(Overlapped) -> WaitForSingleObject -> GetOverlappedResult
  • 异常处理:ERROR_IO_PENDING 分支、超时处理、错误日志
  • 资源释放:CloseComm -> PurgeComm -> CloseHandle

章节来源

多线程环境下的线程安全策略

  • 断线检测线程:独立线程轮询端口,避免阻塞主线程;线程内对串口句柄的创建/关闭遵循最小化持有原则
  • 串口对象成员:构造函数初始化成员变量,析构函数负责关闭句柄与日志;代码中保留了 m_CriticalSection 字段但未启用,建议在多线程并发访问同一实例时增加互斥保护
  • UI 与串口交互:通过消息 WM_BREAKLINE 解耦断线检测与 UI 更新,降低锁竞争

章节来源

依赖关系分析

  • SComPort 依赖 Windows 串口 APICreateFile、SetupComm、GetCommState、SetCommState、SetCommMask、WaitCommEvent、ReadFile、WriteFile、GetOverlappedResult、ClearCommError、PurgeComm、CloseHandle
  • 常量 READBUFFER_SIZE/WRITEBUFFER_SIZE、COMM_TIMEOUT 来自公共头文件
  • UI 通过消息 WM_BREAKLINE 与串口断线检测解耦
graph LR
SComPort["SComPort 类"] --> WinAPI["Windows 串口API"]
SComPort --> Const["常量定义"]
SComPort --> UI["主窗口消息处理"]
WinAPI --> |"CreateFile/SetupComm/SetCommState/SetCommMask"| Port["物理串口"]
WinAPI --> |"WaitCommEvent/ReadFile/WriteFile"| Port
WinAPI --> |"ClearCommError/PurgeComm"| Port

图表来源

章节来源

性能考量

  • 缓冲区大小:READBUFFER_SIZE/WRITEBUFFER_SIZE 为 15360 字节,适合批量传输场景;可根据设备数据速率调整
  • 超时控制:COMM_TIMEOUT 为 500ms,用于等待事件或 I/O 完成;在高延迟链路中可适当增大
  • 异步 I/OOVERLAPPED + 事件对象避免阻塞,提高吞吐;注意避免多个操作共享同一 OVERLAPPED 导致状态混淆
  • 日志输出:频繁写日志会影响性能,建议在调试阶段开启,生产环境关闭或降频

[本节为通用指导,无需列出具体文件来源]

故障排查指南

  • 打开失败:检查 CreateFile 返回值与 GetLastError;确认端口名称格式(含“\\.\”前缀)
  • 设置参数失败:检查 SetCommState 返回值;确认 DCB 字段赋值顺序与目标平台兼容性
  • 无数据可读:使用 ClearCommError 检查 cbInQue;确保已注册 EV_RXCHAR 事件并正确等待
  • 发送卡住:确认 WriteFile 返回 ERROR_IO_PENDING 后正确等待 Overlapped.hEvent;检查线程优先级与事件对象创建
  • 断线误报:断线检测线程轮询间隔与 UI 回调需匹配;避免重复创建线程导致资源泄漏
  • 日志定位:通过 PrintLogLast 输出的时间戳与错误码快速定位问题

章节来源

结论

SComPort 类通过 Windows 异步串口 API 提供了稳定可靠的串口通信能力,覆盖初始化、异步读写、事件驱动接收、断线检测与资源管理。结合 ClearCommError 与 PurgeComm,能够有效处理缓冲区与错误状态;通过 WM_BREAKLINE 与 UI 解耦,提升用户体验。建议在多线程并发访问同一实例时增加互斥保护,并根据实际数据速率调整缓冲区与超时参数。

[本节为总结性内容,无需列出具体文件来源]

附录

关键流程图:ReceiveDataDirectly

flowchart TD
Start(["进入 ReceiveDataDirectly"]) --> CheckHandle["检查串口句柄是否有效"]
CheckHandle --> |无效| Fail["返回失败"]
CheckHandle --> |有效| ClearErr["ClearCommError 查询错误与InQue"]
ClearErr --> InQueZero{"InQue 是否为0"}
InQueZero --> |是| ReturnFalse["返回 FALSE"]
InQueZero --> |否| CalcNeed["计算需要读取字节数(min(最大读取, InQue))"]
CalcNeed --> Read["ReadFile(Overlapped)"]
Read --> Pending{"返回值是否为 FALSE"}
Pending --> |是 且 错误码为 ERROR_IO_PENDING| Wait["WaitForSingleObject(Overlapped.hEvent)"]
Wait --> Gor["GetOverlappedResult 获取字节数"]
Gor --> Compare["比较实际字节数与期望字节数"]
Compare --> Log["记录日志"]
Log --> ReturnTrue["返回成功"]
Pending --> |否| CheckRead["检查 ReadFile 返回字节数"]
CheckRead --> Log2["记录日志"]
Log2 --> ReturnTrue

图表来源

关键流程图:SendDataDirectly

flowchart TD
Start(["进入 SendDataDirectly"]) --> CheckHandle["检查串口句柄与输入参数"]
CheckHandle --> |无效| Fail["返回失败"]
CheckHandle --> |有效| Prepare["准备 OVERLAPPED.hEvent 与写缓冲区"]
Prepare --> Write["WriteFile(Overlapped)"]
Write --> Pending{"返回值是否为 FALSE"}
Pending --> |是 且 错误码为 ERROR_IO_PENDING| Wait["WaitForSingleObject(Overlapped.hEvent)"]
Wait --> Timeout{"等待结果"}
Timeout --> |超时| Purge["关闭句柄并返回失败"]
Timeout --> |成功| ReturnTrue["返回成功"]
Pending --> |否| ReturnTrue

图表来源

关键流程图:断线检测线程

flowchart TD
Loop["循环检测"] --> TryOpen["尝试以独占方式打开串口"]
TryOpen --> OpenFail{"打开失败?"}
OpenFail --> |是 且 错误码为 文件不存在| Break["标记断线并关闭串口"]
Break --> PostMsg["发送 WM_BREAKLINE 给 UI"]
PostMsg --> Exit["退出线程"]
OpenFail --> |否| CloseHandle["关闭句柄并继续循环"]
CloseHandle --> Sleep["Sleep(短时间)"]
Sleep --> Loop

图表来源