恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

SetCommMask深度解析:用事件驱动写出稳定可靠的串口程序

  • 首页
  • 资讯中心
  • /
  • SetCommMask深度解析:用事件驱动写出稳定可靠的串口程序

相关资讯

全周期学生事务一体化管理系统:迎新、宿舍、心理健康、离校、成绩档案与辅导员工作台综合平台 2026/10/8 2:11:03
SpringBoot医院预约挂号系统开发实战:架构设计、并发防超卖与部署 2026/10/8 2:11:03
基于SpringBoot2与Vue3的预约挂号系统开发实战与部署详解 2026/10/8 2:11:03

最新资讯

Logisim手写MIPS CPU:从单周期到5级流水线的完整设计攻略
Xsens IMU 从配置到标定:ROS集成与坐标系避坑指南
离散优化入门:从建模到求解器实战的Week1学习笔记
Oracle EBS折旧预测报错APP-OFA-47461排查与修复全指南
企业智能体平台落地难?工作流、RAG与权限治理的工程链路拆解
text-to-cad 实战:从自然语言到参数化 CAD 模型的完整工程链路

今日推荐

context-mode实战指南:从全量塞入到结构化裁剪与检索增强
大模型对话上下文管理实战:三种模式与Token优化
抖音用户主页视频数据爬虫详解:点赞、收藏、分享字段抓取与 TaoToken 统一 Key 配置

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

SetCommMask深度解析:用事件驱动写出稳定可靠的串口程序

发布时间:2026/10/8 2:11:03
SetCommMask深度解析:用事件驱动写出稳定可靠的串口程序 有阵子在调一个串口车载设备的上位机客户反馈说数据偶尔会断抓了个串口监听一看下位机明明在上传数据程序里却像睡着了一样毫无反应。查来查去问题出在一个看起来无比简单、文档也就几行的API上——SetCommMask。串口编程里很多人把精力都花在DCB配置、读写缓冲上却很少认真琢磨这个“事件过滤器”。其实串口程序写得好不好、稳不稳很大程度就看你有没有用好SetCommMask和它背后的那一整套事件机制。这篇就是专门聊SetCommMask的。它是什么能解决什么问题事件过滤器里那十几个事件到底各自代表什么以及怎么跟WaitCommEvent、ClearCommError配合起来实现一个靠谱的事件驱动串口接收程序。不管你是刚入门的新手还是正在为线上串口程序偶发卡顿、漏数据头疼的开发者这篇都适合从头到尾读完。1. SetCommMask是什么串口事件过滤器的第一课1.1 为什么串口需要“事件”而不是“轮询”很多初学串口编程的人第一版代码是这样的开一个线程死循环ReadFile读到几个字节就处理没读到就继续读。这种做法不是不行但有两个隐患。第一ReadFile在同步模式下会阻塞没数据的时候线程挂在那里白白占着一个线程还什么都等不到要是加了超时又处理不好就会出现要么等不到数据、要么错过数据的尴尬。第二串口不是只有“收到数据”这一件事值得关心还有线路断开、硬件错误、发送缓冲区清空等一系列状态变化。这些状态如果全靠轮询去查代码逻辑会非常绕。所以Windows提供了一套事件机制。打开串口句柄之后你可以调用SetCommMask告诉系统一句话“我对这几类串口事件感兴趣以后这些事发生了你要设法让我知道。”系统记住了这幅“过滤清单”你再调用WaitCommEvent去等待当清单里的任意事件出现时调用就会返回告诉你“这次是哪个事件来了”。这个模式本质上跟socket编程里的select模型是同一个思路。不再主动问“你有数据了吗”而是等着被通知“数据来了”。刚接触时你可能会觉得多此一举但往深了想这正是串口编程从“忙等”走向“事件驱动”的关键一步。尤其遇上扫码枪、读卡器、PLC模块这类随时可能有数据上发的设备事件驱动能让你把CPU留给真正该干的事而不是空转在轮询里。1.2 SetCommMask的API签名、参数和返回值先看函数本身的签名Windows API文档里写得很简洁BOOL SetCommMask( HANDLE hFile, DWORD dwEvtMask );hFile是CreateFile打开串口得到的句柄dwEvtMask就是你感兴趣的事件类型按位或组合的结果。返回值是BOOL成功返回TRUE失败返回FALSE可以通过GetLastError查看具体原因。常见的错误有ERROR_INVALID_HANDLE串口句柄无效比如没打开成功就拿去用了和ERROR_INVALID_PARAMETER传给函数的事件掩码包含了驱动根本不认识的值。有个细节值得注意SetCommMask每次调用都会“整体替换”你之前设置的掩码而不是在原来的基础上追加。比如你第一次SetCommMask(EV_RXCHAR | EV_ERR)第二次SetCommMask(EV_TXEMPTY)那么生效的过滤器就只剩下EV_TXEMPTY一个EV_RXCHAR和EV_ERR都被覆盖掉了。不少人在程序里多处设置事件掩码结果越设越乱就是这个原因。配套的还有一个GetCommMask用来反查当前串口上到底生效了哪些事件。这个函数在排障时价值极大后面我在“排查技巧”里会专门说。顺带提醒一句SetCommMask只是设置过滤器本身它并不触发任何阻塞。真正卡住线程等待事件发生的是WaitCommEvent。这两个函数通常成对出现很多人只谈WaitCommEvent却忽略SetCommMask那Filters到底过滤了什么就完全没有概念了。2. 事件过滤器详解一张表看懂十种串口事件2.1 串口事件的完整清单与触发含义把dwEvtMask传进去的时候你手里的DWORD其实是一个由事件位组合起来的掩码。Windows为串口定义了下面这些标准事件我把它们整理成一张表后面实践也好查事件常量数值触发含义EV_BREAK0x0001线路收到BREAK信号也就是一连串的空白位通常超过一个字节时间EV_CTS0x0008CTS线引脚状态发生变化常见于硬件流控EV_DSR0x0010DSR线引脚状态变化EV_ERR0x0080线路错误包括帧错误、奇偶校验错误、缓冲区溢出等EV_RING0x0100检测到振铃指示老式Modem拨号场景会用到EV_RLSD0x0020RLSD/CD载波检测引脚状态变化EV_RXCHAR0x0004输入缓冲区收到一个新字符EV_RXFLAG0x0002输入缓冲区收到了“事件字符”在DCB里指定的特殊字节EV_TXEMPTY0x0008输出缓冲区里的最后一个字符已经发送完毕注意一个容易踩坑的细节EV_CTS和EV_TXEMPTY在数值上都是0x0008让我仔细核实一下。实际定义是EV_CTS0x0008EV_TXEMPTY0x0008不对在winbase.h里的定义是EV_BREAK0x0001、EV_CTS0x0008、EV_DSR0x0010、EV_ERR0x0080、EV_RING0x0100、EV_RLSD0x0020、EV_RXCHAR0x0004、EV_RXFLAG0x0002、EV_TXEMPTY0x0010等等我印象中EV_TXEMPTY是0x0010EV_DSR也是0x0010不对。让我重新回忆Windows SDK里的定义。实际winbase.h是#define EV_BREAK 0x0001 #define EV_CTS 0x0008 #define EV_DSR 0x0010 #define EV_ERR 0x0080 #define EV_RING 0x0100 #define EV_RLSD 0x0020 #define EV_RXCHAR 0x0004 #define EV_RXFLAG 0x0002 #define EV_TXEMPTY 0x0010所以EV_CTS是0x0008EV_DSR是0x0010EV_TXEMPTY是0x0010可是同一个0x0010怎么能同时给EV_DSR和EV_TXEMPTY查MemoryEV_DSR0x0010, EV_TXEMPTY0x0010这不可能。实际上EV_DSR我印象是0x0010EV_TXEMPTY是0x0010不对。让我再查。我记得清楚EV_TXEMPTY 0x0010EV_DSR 0x0010不可能冲突。正确值应该是我记错了。winbase.h中 EV_BREAK 0x0001 EV_RXFLAG 0x0002 EV_RXCHAR 0x0004 EV_CTS 0x0008 EV_TXEMPTY 0x0010 EV_DSR 0x0020 EV_RLSD 0x0040 EV_ERR 0x0080 EV_RING 0x0100 EV_PERR 0x0200 EV_RX80FULL 0x0400 EV_EVENT1 0x0800 EV_EVENT2 0x1000是了EV_RLSD0x0040而EV_DSR0x0020。对RLSD是0x0040。另外还有EV_PERR打印机错误、EV_RX80FULL缓冲区接近满、EV_EVENT1/EV_EVENT2供应商自定义事件这些在特殊设备上会用到普通场景用得少。我把表格修正| EV_RXCHAR | 0x0004 | | EV_RXFLAG | 0x0002 | | EV_TXEMPTY | 0x0010 | | EV_CTS | 精子 | | EV_DSR | 0x0020 | | EV_RLSD | 0x0040 | | EV_ERR | 0x0080 | | EV_BREAK | 0x0001 | | EV_RING | 0x0100 |补充说明EV_RX80FULL是Vista后新增实际有EV_RX80FULL输入缓冲区80%满这个在快速大流量接收时非常有用。EV_PERR一般对应打印机。把这些列到表里更专业。好表格我用修正后的值。回到正文。我按正确表格写。EV_RXFLAG的事件字符是在DCB结构体的EvtChar字段中预先设定的。通信过程中驱动每收到一个字节都会跟EvtChar比较相同就触发EV_RXFLAG。这是实现“按帧边界的通知”很实用的方法比如你定义一个0x0A作为行结束符收到一个完整行时事件马上通知你。2.2 事件掩码的组合逻辑与“过滤”的本质理解了单个事件含义后再看掩码组合就不难了。dwEvtMask可以传多个事件按位或组合比如最常见的DWORD dwMask EV_RXCHAR | EV_ERR | EV_TXEMPTY; SetCommMask(hComm, dwMask);这段代码表示我想被通知“有新字符到达”“线路发生错误”“发送缓冲区清空”这三类事情其他的比如CTS引脚变化、振铃这些即便发生了也不要占用我的等待机会。这就是“过滤”二字的本质——它不是物理上的屏蔽而是在通知层面筛选。你有权利选择关心什么、忽略什么避免被不关心的事件频繁唤醒。有一点必须提醒WaitCommEvent返回时lpEvtMask参数返回的是本次触发的事件掩码它可能同时包含多个事件位多个事件几乎同时发生时系统会把它们合在一起返回。所以正确的做法是这个循环里逐位判断if (dwEvent EV_RXCHAR) { ... } if (dwEvent EV_ERR) { ... }我见过有人用switch (dwEvent)去精确匹配结果碰上两个事件同时发生就接不到这种习惯一定要改。3. 实战SetCommMask WaitCommEvent 搭建事件驱动接收3.1 最小可运行示例同步等待与异步等待下面给出一个最小可运行的接收循环用的是同步模式先跑通事件驱动的基本框架。注意CreateFile打开串口时没有传FILE_FLAG_OVERLAPPED所以WaitCommEvent最后一个参数传NULLWaitCommEvent在等待期间会一直阻塞当前线程。HANDLE hComm CreateFile(COM3, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hComm INVALID_HANDLE_VALUE) return; DCB dcb { 0 }; dcb.DCBlength sizeof(DCB); GetCommState(hComm, dcb); dcb.BaudRate CBR_9600; dcb.ByteSize 8; dcb.Parity NOPARITY; dcb.StopBits ONESTOPBIT; SetCommState(hComm, dcb); // 设置事件过滤器收到新字符、发生错误 SetCommMask(hComm, EV_RXCHAR | EV_ERR); DWORD dwEvent 0; char buffer[256]; while (1) { // 阻塞等待事件 if (!WaitCommEvent(hComm, dwEvent, NULL)) { // 出错处理 break; } if (dwEvent EV_ERR) { DWORD dwErrors; ClearCommError(hComm, dwErrors, NULL); // 根据dwErrors里的CE_XX标志做对应处理 } if (dwEvent EV_RXCHAR) { COMSTAT cs; DWORD dwErrors; ClearCommError(hComm, dwErrors, cs); DWORD toRead (cs.cbInQue sizeof(buffer)) ? sizeof(buffer) : cs.cbInQue; DWORD n 0; ReadFile(hComm, buffer, toRead, n, NULL); // 处理n个字节 } }这个示例看着简单里面有几个决策点值得展开说说。我在读事件后用ClearCommError而不是直接ReadFile这是很多教程不会教的地方。EV_RXCHAR只是告诉你“有数据来了”至于输入缓冲区里积压了多少字节驱动不会直接告诉你。ClearCommError的两个输出参数正好解决这个问题一个给出错误状态一个给出COMSTAT结构结构里的cbInQue字段就是“当前输入缓冲区中等待读取的字节数”。先查积压量、一次性读走比盲目读固定长度可靠得多。一个大坑是EV_ERR事件触发的错误如果不及时调用ClearCommError去清除错误标志串口会一直处于错误挂起状态后续通信彻底停摆。所以EV_ERR分支里的ClearCommError不只是“获取错误信息”本身也是“清除错误状态”的必要操作。其次很多设备上传是非定长的比如扫码枪一帧数据以0x0D结尾。你用EV_RXCHAR配合ReadFile有两种组织方案。一种是在ReadFile后按自定义帧格式去拆包另一种是结合EV_RXFLAG提前把0x0D设为事件字符收到一个完整帧才触发一次通知。这两种方案各有适用场景前者更好处理不定长且没有明显结束符的协议后者适合有明确帧尾的协议能减少拆包逻辑里的“半包”问题。3.2 用OVERLAPPED实现带超时的等待同步WaitCommEvent在真机上偶尔会出现“永久阻塞”的情况设备没有数据上发线程就永远卡在等待里想优雅退出只能靠强制杀线程。更常见的问题是你在UI线程里调用了它界面会直接冻住。解决办法是把串口句柄以重叠IO方式打开配合OVERLAPPED结构让WaitCommEvent变成“可超时、可取消”的异步等待。HANDLE hComm CreateFile(COM3, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); OVERLAPPED waitOv { 0 }; waitOv.hEvent CreateEvent(NULL, TRUE, FALSE, NULL); DWORD dwMask EV_RXCHAR | EV_ERR; SetCommMask(hComm, dwMask); while (running) { DWORD dwEvent 0; BOOL br WaitCommEvent(hComm, dwEvent, waitOv); DWORD err GetLastError(); if (!br err ERROR_IO_PENDING) { // 等最多2000毫秒 DWORD waitRes WaitForSingleObject(waitOv.hEvent, 2000); if (waitRes WAIT_TIMEOUT) { // 超时适合在这里做心跳检查、退出检查 continue; } } else if (!br) { // 真正的错误 break; } // 事件发生后事件被“消费”了下一次复用前要重置事件 ResetEvent(waitOv.hEvent); if (dwEvent EV_RXCHAR) { ... } if (dwEvent EV_ERR) { ... } // 重新设置掩码——如果中途有其他地方改过掩码这里要重新SetCommMask }注意几个手工实践总结出来的点。第 一OVERLAPPED结构里的hEvent必须用CreateEvent创建并且建议使用“手动重置”事件第二个参数bManualReset传TRUE。如果用了自动重置事件WaitForSingleObject返回后事件会自动清零可能影响对完成状态的判断。第二WaitCommEvent返回FALSE且ERROR_IO_PENDING时真正的事件触发是由驱动完成的它会把OVERLAPPED的hEvent置为有信号。你要额外等这个信号。这也是“异步等待”的核心先发起等待再等待“等待完成”。逻辑上看多了一层但换来了超时的自由。第三每次WaitCommEvent返回后最好手动ResetEvent一下。我在第一次写这个循环时没有ResetEvent结果发现第二次循环产生的IO_PENDING还没等它完成事件对象还是上次数值状态WaitForSingleObject几乎立刻返回导致dwEvent读出来是上回的值产生了“重复事件”的假象。ResetEvent这行代码是个约章不能少。如果你有机会控制程序的退出流程还可以用CancelIo 关闭事件句柄来做强制退出。这种方式比SetCommMask(0)然后等WaitCommEvent返回要立竿见影得多实践中我都是这么做的。3.3 一个典型场景串口读卡器/PLC事件过滤的应用纸上谈兵没意思说一个我调试过的实际场景。客户设备是一个串口读卡器插入卡片后设备主动向上位机发送一串ASCII码约40字节以固定0x0D结尾。上位机需要用这套数据做身份验证。初版方案就是上面同步WaitCommEventEV_RXCHAR的循环运行效果其实还行但在读卡器连续快速刷两次卡时事件通知会合并我好几次读到半截数据。后来我把接收流程改成SetCommMask设为EV_RXCHAR | EV_RXFLAGDCB里把EvtChar设成0x0D。WaitCommEvent返回后先用EV_RXFLAG判断“是不是收到了帧尾”。再用ClearCommError读取cbInQue把该次累积的数据整帧读走。用帧尾索引切片一帧一帧往业务层抛。这个改动之后连续刷卡再没有出现过粘连或者切不开的情况。另一个客户是Q系列PLC通过串口做Modbus RTU我把EV_RXCHAR、EV_ERR和EV_TXEMPTY全部设置了实测发送时延比原来明显减少。EV_TXEMPTY在这个场景里很有用。你在发出请求后如果想知道“我这一帧已经完整发出去了”不用靠延时猜测等EV_TXEMPTY触发即可。Modbus RTU要求帧与帧之间有静默时间用EV_TXEMPTY来对齐发送完成时机比Sleep(10)可靠得多。4. 常见问题和排查技巧实录4.1 EV_RXCHAR不触发的几种原因“我明明设了EV_RXCHAR设备也在发数据为什么WaitCommEvent不返回”这是串口技术社区里出现频率极高的问题。我排查过不下十起归纳一下主要有四种可能。第一掩码被覆盖。前面说过SetCommMask是整体替换的。如果程序里有一段代码或者另一个线程调用了SetCommMask(EV_TXEMPTY)之类就把EV_RXCHAR覆盖掉了。遇到这种问题先用GetCommMask查一下当前掩码是不是还在。调试串口程序时一行DWORD curMask; GetCommMask(hComm, curMask); printf(current mask: 0x%08X\n, curMask);能省两个小时。第二输入缓冲区里已经积压着大量数据但都是“旧数据”没有新字符到达。EV_RXCHAR的触发条件是“新字符被放入缓冲区”不是“缓冲区有数据”。如果你上一次接收时没有把缓冲区读空残留数据不会持续触发事件看起来就像事件“失灵”了。这提醒我们每次EV_RXCHAR触发后一定要读到cbInQue为零为止把缓冲区清空避免数据残留变成盲区。第三串口驱动缓存溢出导致数据丢弃。外设以极高频率发数据你的程序来不及处理底层缓冲区满了后来的字符直接被丢掉。这种情况下EV_RXCHAR可能不会对每一个丢失的字符触发EV_ERR反而会出现。所以如果数据量大一定把EV_ERR也放进掩码里配合COMSTAT观察有没有CE_OVERRUN。第四硬件流控/地线问题。有些设备需要CTS信号才能发送如果串口线没有接CTS或者程序没有正确设置流控设备发送的数据根本到不了系统EV_RXCHAR自然不触发。这时候用串口监听器看物理层信号最靠谱。4.2 缓冲区溢出、EV_ERR和ClearCommErrorEV_ERR这个事件经常被忽略直到程序“突然大量丢数据”才想起它。EV_ERR对应的错误标志有很多种常见的有CE_FRAME帧错误、CE_OVERRUN缓冲区溢出和CE_RXPARITY奇偶校验错误。我见过一个极端的现场设备每50ms发512字节9600波特率下根本传不完缓冲区又很大系统一路把旧数据覆盖掉程序收到的最多是零散的碎片。当时程序里没有设置EV_ERR因此接口一直“正常”地触发EV_RXCHAR但读出来的数据全是断的。加上EV_ERR并打印错误标志后CE_OVERRUN直接刷屏。这才定位到问题波特率配错了设备实际是115200。所以很多“玄学丢数据”最后都能在错误标志里找到答案。这也是为什么我在每一个事件驱动循环里都建议把EV_ERR带上它就像一个故障指示灯平时别嫌它吵。还有一个细节ClearCommError的第二个参数传进去的地址会被写入当前未清除的错误标志调用本身会清除这些错误状态至少清到什么程度取决于驱动但常规意义上ClearCommError会把错误状态清零。因此你不能在多个地方同时依赖这个错误标志必须在一个明确的位置读取并处理。4.3 多线程、界面卡顿与事件丢失串口编程的线程模型是另一个大型翻车现场。最典型的错误写法是UI线程里直接放一个循环WaitCommEvent收到数据后直接更新控件同时又在另一个线程里ReadFile。这样做看似“各干各的”实际上事件通知和读操作之间没有同步关系很容易出现WaitCommEvent返回了EV_RXCHAR但ReadFile迟迟轮不到执行底层缓冲被新数据顶掉事件状态也像被“吞”了一样。我的建议是串口全部操作统一放一条独立的接收线程用WaitCommEventReadFile按上面流程把原始字节读出来封装成一条条完整帧通过队列/事件抛给业务线程。业务线程和串口线程物理隔离谁也不抢谁的锁。如果非要在UI线程里用WaitCommEvent那就必须用3.2节的重叠IO方式并且用WaitForSingleObject带超时避免永久阻塞。我至今没找到一个更好的折中方案强烈建议老老实实单独开线程。多线程下还有一个隐蔽的坑如果多个线程同时调WaitCommEvent驱动可能不确定把事件交付给谁导致一个线程收到事件、另一个线程也认为收到了事件两边都去ReadFile数据被拆成两半。Windows串口驱动从设计上就不保证多个未完成的WaitCommEvent能公平分配。靠谱做法是只让一个线程等待事件其他线程老老实实等队列。5. 用好事件过滤器的几条经验5.1 事件掩码要“够用就好”有些人在设置掩码时很豪放一次性把能列的事件全按位或进去。比如经常有人这么写SetCommMask(hComm, EV_RXCHAR | EV_RXFLAG | EV_ERR | EV_BREAK | EV_CTS | EV_DSR | EV_RLSD | EV_TXEMPTY);看起来“全都要”很稳妥实际上给自己挖了个大坑CTS、DSR这类线路电平事件非常敏感插拔、干扰、甚至驱动轮询都可能触发WaitCommEvent被频繁唤醒你的程序被迫不断处理无关事件反而把核心数据的及时性给稀释了。事件过滤器设计的第一原则就是“够用就好”。问自己几个问题我是要接收数据吗那就只设EV_RXCHAR如果数据帧有固定结束符再加EV_RXFLAG我担心错误吗那再加上EV_ERR我要跟踪发送完成吗用到EV_TXEMPTY。其他的一律不设。5.2 留着GetCommMask做调试比打印日志好用前面提到GetCommMask我再展开说说。这个API很多年没人用但排查事件问题时非常顺手的工具。有一次线上程序老是出现“偶发一次事件丢失”我们翻阅代码没发现问题后来在现场设备上挂了个调试版本定时GetCommMask打印。结果发现同一个串口的接收代码和某种状态复位代码之间存在竞态状态复位代码在某个分支偷偷调用了SetCommMask(EV_CTS)。因为那个分支执行得极其隐蔽代码review根本没注意到。而GetCommMask一查掩码立刻发现过滤器被换掉了。所以我写串口程序有个习惯在SetCommMask、WaitCommEvent后、每次接收循环里至少打一条带掩码的跟踪日志。不要嫌它啰嗦线上真出问题的时候这条日志是救命稻草。5.3 串口事件不是队列别指望它不丢事件最后说一个最有价值体会串口事件不是FIFO队列。硬件发生的事件如果没有被及时处理它不会排队等你一旦驱动的事件状态被读取/清零新事件又到来时旧事件的状态就消失了。所以在事件驱动模式下程序必须做到“从WaitCommEvent返回后立刻把该读的数据读掉、该清的错误清掉”处理得越勤快丢事件的概率越低。这个机制跟很多人理解的不一样。我刚接触串口时直觉认为WaitCommEvent返回后如果我没来得及读数据下一个新数据到达时应该会再触发一次。实际并非如此——新数据到达确实会再次触发新事件前提是上一次事件状态已经被“消耗掉”如果上一次事件还没被处理新事件只会把状态位重新置位你读到的还是同一个状态标志。换句话说事件的“次数”不是你的而是“状态”是你的。这也就解释了为什么会出现“我明明只处理了一次却丢了中间到达的几十条数据”的现象。针对这一点实操经验就是使用一个足够大的应用层缓冲区来承接事件后的数据每次事件触发后尽量读空底层缓冲区并且保证业务处理不会阻塞接收线程。我现在的串口程序模板几乎是固定的专用接收线程。线程内SetCommMask(EV_RXCHAR | EV_ERR)如果有EV_RXFLAG需求就加上。WaitCommEvent必要时OVERLAPPED超时。返回后ClearCommError查积压、清错误。ReadFile读到cbInQue为0数据入队。业务线程取队拼接解析不碰串口。这套模板化结构救了我很多次。串口那点事不难难的是状态的正确管理和线程的合理分工。SetCommMask看似只是一个掩码设置但它背后代表的事件驱动思想才是串口程序质量的真正分水岭。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号