# 串口通信 **本文引用的文件列表** - [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 层通过消息回调处理断线事件。 ```mermaid graph TB subgraph "工具层" SComPort["SComPort 类
cpp/Tools/SComPort.cpp"] SComPortWinXP["SComPort 兼容实现
cpp/Tools/SComPort_winxp.cpp"] Header["头文件声明
h/SComPort.h"] Const["常量定义
h/Constant.h"] end subgraph "界面层" MainFrm["主窗口消息处理
cpp/Views/MainFrm.cpp"] end SComPort --> Header SComPort --> Const SComPortWinXP --> Header SComPortWinXP --> Const MainFrm --> |"WM_BREAKLINE"| SComPort ``` 图表来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L126-L223) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L126-L221) - [SComPort.h](file://h/SComPort.h#L14-L71) - [Constant.h](file://h/Constant.h#L224-L232) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L126-L223) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L126-L221) - [SComPort.h](file://h/SComPort.h#L14-L71) - [Constant.h](file://h/Constant.h#L224-L232) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) ## 核心组件 - SComPort 类:封装串口打开、配置、读写、Zmodem 收发、断线检测、日志与缓冲区清理等能力 - 常量定义:READBUFFER_SIZE、WRITEBUFFER_SIZE、COMM_TIMEOUT 等 - 主窗口消息处理:接收 WM_BREAKLINE 并关闭对应串口、更新设备状态 章节来源 - [SComPort.h](file://h/SComPort.h#L14-L71) - [Constant.h](file://h/Constant.h#L224-L232) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) ## 架构总览 SComPort 采用 Windows 异步串口 I/O 模式,使用 OVERLAPPED 结构与事件对象实现非阻塞读写;通过 SetCommMask 注册事件掩码,结合 WaitCommEvent 实现事件驱动的接收路径;ClearCommError 用于查询错误与队列长度;PurgeComm 用于清理发送/接收缓冲区;断线检测通过独立线程轮询端口是否存在并发送 WM_BREAKLINE 给 UI。 ```mermaid 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() ``` 图表来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L286-L380) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L382-L420) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L560-L607) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L746-L779) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) ## 详细组件分析 ### 串口初始化流程 - 句柄创建:使用 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 等事件 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L155-L196) - [Constant.h](file://h/Constant.h#L224-L226) ### 异步 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 读取 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L716-L763) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L765-L900) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L382-L420) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L286-L380) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L560-L607) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L609-L744) ### 错误处理与缓冲区清理 - ClearCommError:用于查询通信错误标志与输入缓冲区字节数,避免在无数据时盲目 ReadFile - PurgeComm:在关闭串口或需要清空缓冲区时使用 PURGE_RXCLEAR/PURGE_TXCLEAR/PURGE_RXABORT/PURGE_TXABORT 清理接收/发送队列 - 日志输出:通过 PrintLogLast 将通信过程与错误信息写入日志文件,便于定位问题 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L323-L335) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L585-L595) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L937-L952) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L267-L284) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L429-L439) ### 事件驱动通信模式 - WaitCommEvent:在接收路径中等待 EV_RXCHAR 等事件触发,避免忙等 - EV_BREAK:断线检测通过 SetCommMask 注册 EV_BREAK,并结合断线检测线程轮询端口是否存在,当端口不可访问时向 UI 发送 WM_BREAKLINE 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L189-L196) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L225-L256) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L188-L196) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L224-L255) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) ### 断线检测线程与 UI 回调 - 断线检测线程:SComPortDetectBreakThreadFun 循环尝试以独占方式打开端口,若返回 ERROR_FILE_NOT_FOUND 则判定断线,调用 CloseComm 并通过 SendMessage(WM_BREAKLINE) 通知 UI - UI 处理:OnBreakLine 中根据通信 ID 关闭对应串口、更新设备状态、刷新界面 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L225-L256) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L224-L255) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) ### 串口打开、数据收发、异常处理与资源释放的完整调用示例 - 打开串口: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 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L126-L223) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284) ### 多线程环境下的线程安全策略 - 断线检测线程:独立线程轮询端口,避免阻塞主线程;线程内对串口句柄的创建/关闭遵循最小化持有原则 - 串口对象成员:构造函数初始化成员变量,析构函数负责关闭句柄与日志;代码中保留了 m_CriticalSection 字段但未启用,建议在多线程并发访问同一实例时增加互斥保护 - UI 与串口交互:通过消息 WM_BREAKLINE 解耦断线检测与 UI 更新,降低锁竞争 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L31-L57) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L87-L91) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L30-L57) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L86-L90) ## 依赖关系分析 - SComPort 依赖 Windows 串口 API(CreateFile、SetupComm、GetCommState、SetCommState、SetCommMask、WaitCommEvent、ReadFile、WriteFile、GetOverlappedResult、ClearCommError、PurgeComm、CloseHandle) - 常量 READBUFFER_SIZE/WRITEBUFFER_SIZE、COMM_TIMEOUT 来自公共头文件 - UI 通过消息 WM_BREAKLINE 与串口断线检测解耦 ```mermaid 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 ``` 图表来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284) - [Constant.h](file://h/Constant.h#L224-L232) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L268-L284) - [Constant.h](file://h/Constant.h#L224-L232) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328) ## 性能考量 - 缓冲区大小:READBUFFER_SIZE/WRITEBUFFER_SIZE 为 15360 字节,适合批量传输场景;可根据设备数据速率调整 - 超时控制:COMM_TIMEOUT 为 500ms,用于等待事件或 I/O 完成;在高延迟链路中可适当增大 - 异步 I/O:OVERLAPPED + 事件对象避免阻塞,提高吞吐;注意避免多个操作共享同一 OVERLAPPED 导致状态混淆 - 日志输出:频繁写日志会影响性能,建议在调试阶段开启,生产环境关闭或降频 [本节为通用指导,无需列出具体文件来源] ## 故障排查指南 - 打开失败:检查 CreateFile 返回值与 GetLastError;确认端口名称格式(含“\\\\.\”前缀) - 设置参数失败:检查 SetCommState 返回值;确认 DCB 字段赋值顺序与目标平台兼容性 - 无数据可读:使用 ClearCommError 检查 cbInQue;确保已注册 EV_RXCHAR 事件并正确等待 - 发送卡住:确认 WriteFile 返回 ERROR_IO_PENDING 后正确等待 Overlapped.hEvent;检查线程优先级与事件对象创建 - 断线误报:断线检测线程轮询间隔与 UI 回调需匹配;避免重复创建线程导致资源泄漏 - 日志定位:通过 PrintLogLast 输出的时间戳与错误码快速定位问题 章节来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L156-L223) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L323-L335) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L937-L952) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L155-L196) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L286-L380) - [SComPort_winxp.cpp](file://cpp/Tools/SComPort_winxp.cpp#L560-L607) ## 结论 SComPort 类通过 Windows 异步串口 API 提供了稳定可靠的串口通信能力,覆盖初始化、异步读写、事件驱动接收、断线检测与资源管理。结合 ClearCommError 与 PurgeComm,能够有效处理缓冲区与错误状态;通过 WM_BREAKLINE 与 UI 解耦,提升用户体验。建议在多线程并发访问同一实例时增加互斥保护,并根据实际数据速率调整缓冲区与超时参数。 [本节为总结性内容,无需列出具体文件来源] ## 附录 ### 关键流程图:ReceiveDataDirectly ```mermaid 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 ``` 图表来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L306-L401) ### 关键流程图:SendDataDirectly ```mermaid 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 ``` 图表来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L527-L576) ### 关键流程图:断线检测线程 ```mermaid flowchart TD Loop["循环检测"] --> TryOpen["尝试以独占方式打开串口"] TryOpen --> OpenFail{"打开失败?"} OpenFail --> |是 且 错误码为 文件不存在| Break["标记断线并关闭串口"] Break --> PostMsg["发送 WM_BREAKLINE 给 UI"] PostMsg --> Exit["退出线程"] OpenFail --> |否| CloseHandle["关闭句柄并继续循环"] CloseHandle --> Sleep["Sleep(短时间)"] Sleep --> Loop ``` 图表来源 - [SComPort.cpp](file://cpp/Tools/SComPort.cpp#L225-L256) - [MainFrm.cpp](file://cpp/Views/MainFrm.cpp#L254-L328)