当前位置: 首页 > news >正文

串口通信“第二次总超时”的复盘

一次看似简单的“重试两次”,为何第二次永远收不到数据?
本文把问题从表面现象一直追到硬件协议层,给出可落地的最终方案。


1 问题现象

在 Windows 平台下,我们的代码大致如下(精简后):

// 最外层:业务重试 2 次
for (int i = 0; i < 2; ++i) {PurgeComm(hComm, PURGE_RXCLEAR | PURGE_TXCLEAR);SendCommData(hComm, szCmd, 30);     // 30 字节if (RecvCommData(hComm, szRecv, 18)) // 想收 18 字节return SUCCESS;Sleep(100);
}
  • 第 1 轮:
    发送 → 接收 → 很快返回 18 字节,但内容因干扰而“错误”。
    逻辑判失败 → 进入第 2 轮。

  • 第 2 轮:
    同样步骤,却永远超时(ReadFile 一直读不到 0 字节)。


2 逐层排查

排查项结果结论
线路波形TX 两次都成功发出 30 字节;RX 第 2 次无任何波形设备未回数据
串口缓存每轮都 PurgeComm旧数据已清空
超时参数单轮 ReadFile 最多 150 ms + 18×100 ms = 1.95 s,足够长不是时间不够
波特率 / 时钟已确认正确排除硬件速率问题
设备说明书一帧指令一次应答,重复指令不响应根因已找到

3 根本原因

设备协议本身只支持“单次应答”。

  • 收到第 1 帧 → 立即回 18 字节。
  • 再收第 2 帧(内容完全相同)→ 直接丢弃,不发任何数据

因此:

  • 第 1 轮即使因干扰拿到“错误帧”,也把唯一一次应答“用掉”。
  • 第 2 轮再发,设备不回 → FIFO 永远空 → ReadFile 超时。

4 最终解决方案

方案说明代码示例
A. 一次性收对把单轮超时拉大,确保首轮就能把完整帧收齐。出现错误直接报错,不再重发同一帧。ReadTotalTimeoutConstant = 1000; ReadTotalTimeoutMultiplier = 0;
B. 协议级重试若必须重试,修改设备固件或协议:带序号/ACK,使设备能区分“重发”。由硬件/固件团队完成
C. 上层策略把重试逻辑改为“断电复位→重新上电→再发指令”,让设备重新初始化。电源控制脚或继电器

如果硬件无法改动,方案 A 是唯一可行且零成本的落地做法


5 推荐参数组合(方案 A)

COMMTIMEOUTS cto = {0};
cto.ReadIntervalTimeout        = 20;   // 字节间 20 ms 容错
cto.ReadTotalTimeoutMultiplier = 0;    // 不字节累加
cto.ReadTotalTimeoutConstant   = 1000; // 整帧 1 s 足够
SetCommTimeouts(hComm, &cto);// 去掉内部循环重试
BOOL RecvCommData(HANDLE hComm, char* buf, DWORD want)
{DWORD got = 0;return ReadFile(hComm, buf, want, &got, NULL) && got == want;
}

6 一句话总结

第二次超时不是 Windows 的问题,而是设备协议“只回一次”。
要么第一轮就保证正确,要么改协议/硬件;否则永远重发无效。

http://www.dtcms.com/a/327707.html

相关文章:

  • XC7A15T-1FTG256C Xilinx AMD Artix-7 FPGA
  • 后端找数据库
  • nvm install 14.21.3 时npm 无法下载和识别
  • 项目实例-页面
  • 股票智能体系统的设计与开发
  • VisualSVN Server 4.2.1 安装教程 - 64位下载与配置步骤详解
  • 【Docker项目实战】使用Docker部署Vikunja任务管理工具
  • 【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day2
  • 嵌入式 - Linux软件编程:文件IO
  • 零售业CRM实战:如何打通线上线下客户数据?
  • Ansible 基本使用
  • UE官方文档学习 C++ TAarry 查询(三)Contain,Find函数的使用
  • Redis--day4--实战-黑马点评--搭建环境
  • WPS JS宏 通用方法整理汇总 实时更新
  • 【Vue 3 响应式系统深度解析:reactive vs ref 全面对比】
  • MySQL(下)
  • C语言入门完结篇_结构体、枚举、时间函数的、变量类型(C语言划分内存各个区块的方法)、文件操作
  • MyBatis 缓存与 Spring 事务相关笔记
  • Spring-Cache 缓存数据
  • damn the jvm again(2)
  • 编程模型设计空间的决策思路
  • 什么是跨域访问问题,如何解决?
  • Windows怎样配置动态磁盘
  • [SC]SystemC中的SC_FORK和SC_JOIN用法详细介绍
  • android端自定义通话通知
  • VUE的8个生命周期
  • Orange的运维学习日记--41.Ansible基础入门
  • sqli-labs通关笔记-第44关 POST字符型堆叠注入(单引号闭合 手工注入+脚本注入3种方法)
  • demo 英雄热度榜 (条件筛选—高亮切换—列表渲染—日期显示)
  • Full GC 频率优化实战