Qt 串口调试入门-3:轮发功能
Qt 串口调试入门-3:轮发功能

轮发=状态机:定时轮发、等回复轮发与超时重发

第三次汇报学习笔记:多个发送区参与轮发时,需求会迅速从“循环发送”升级到“可控策略”——支持两种模式(定时/等回复)、可配置超时、可配置重发次数,并能只跑一圈。

如果只做“每隔 N ms 轮流发送 1/2/3”,轮发其实很简单:一个定时器 + 一个索引。

但真实使用中经常遇到更复杂的场景:

  • 设备回复慢,定时轮发会把请求堆在一起;

  • 希望“收到回复再发下一帧”,避免拥塞;

  • 还希望加“超时重发”,并设置重发次数;

  • 有时还需要“只轮发一圈”。

这些需求本质上都指向同一个结论:轮发不是循环,而是状态机


1. 轮发的数据模型:先把“状态”列清楚

我把轮发需要的变量拆成几类:

参与集合

  • m_roundOrder:本轮参与轮发的发送区索引列表(例如 [0,2] 表示只轮发 1 和 3)

  • m_roundPos:当前轮发位置

  • m_roundCycle:已经完成的圈数

策略参数

  • m_roundPeriodMs:周期(定时轮发的间隔;等回复轮发里也可作为“发下一帧前的最小间隔”)

  • m_roundTimeoutMs:等待回复超时

  • m_roundMaxRetries:超时最大重发次数

状态位(状态机的关键)

  • m_roundRunning:是否正在轮发

  • m_roundWaitReplyMode:是否处于“等回复轮发”模式

  • m_roundWaitingReply:当前帧是否正在等待回复

  • m_roundRetryUsed:当前帧已重发次数

  • m_roundOnlyOnce:是否只轮发一圈

这些变量看起来多,但它们让行为非常可解释:任何异常都能定位到“是哪个状态没收敛”。


2. 两个定时器:一个管“发下一帧”,一个管“等回复超时”

轮发我最终用了两个 QTimer:

  • m_roundTimer:用于定时轮发,或在“收到回复后”延迟发下一帧;

  • m_roundReplyTimer:用于等待回复超时(仅在等回复模式启用)。

这样就避免一个定时器承担两种含义导致的混乱。


3. 状态机的核心流程

3.1 start:计算轮发顺序 + 发第一帧

启动时需要:

  1. 从 3 个发送区里筛选哪些勾选了“参加轮发”;

  2. 生成 m_roundOrder

  3. 初始化位置、圈数、重发次数;

  4. 发送当前帧;

  5. 根据模式决定进入哪种等待状态:

    • 定时轮发:启动 m_roundTimer(period)

    • 等回复轮发:设置 m_roundWaitingReply=true 并启动 m_roundReplyTimer(timeout)

3.2 advance:位置推进并决定是否结束

推进时(advance)做:

  • m_roundPos++,越界则回到 0,并 m_roundCycle++

  • 如果 m_roundOnlyOnce 且已经完成一圈:停止轮发;

  • 否则发送新的当前帧,并进入对应等待状态。

3.3 定时轮发:到点就 advance

onRoundTimerTimeout() 只做一件事:advanceRoundAndSend()

3.4 等回复轮发:收到任何数据即认为“回复到达”

很多协议里回复不一定严格对应某一帧(尤其串口工具不是协议栈),所以我做了一个“工程上够用”的约定:

  • 只要收到任何数据,就认为回复到了

  • 然后停止 replyTimer;

  • 再用 roundTimer 启动一个最小间隔,间隔到后 advance。

这种策略很朴素,但对“调试助手”足够实用,也能避免 UI 做复杂的帧匹配。

3.5 超时重发:优先重发,超过次数再跳下一帧

当 replyTimer 超时:

  • 如果 retryUsed < maxRetries:重发当前帧,retryUsed++;

  • 否则:提示“进入下一帧”,执行 advance。

并且可以在 statusBar 做即时提示,让用户知道轮发在发生什么。


4. 为什么说这是“状态机”,不是“循环”?

因为“循环”只有一个隐含状态:正在循环; 而“轮发”至少包含这些状态:

  • Running / Stopped

  • Fixed-period / Wait-reply

  • WaitingReply / NotWaitingReply

  • RetryUsed = 0..N

  • OnlyOnce / Forever

只有把状态显式化,才能保证:

  • 超时重发不会把索引推进错;

  • 收到数据时不会在错误状态触发推进;

  • 停止轮发时两个 timer 都能被收敛停止;

  • UI 上的“开始/停止”按钮能正确 enable/disable。


5. 小结

轮发这块我学到的不是“Qt 怎么写定时器”,而是:

  1. 先建模状态,再写代码;

  2. 复杂行为拆成多个 timer,各司其职;

  3. 把推进逻辑封装成 advanceRoundAndSend(),避免散落在多个槽函数里;

  4. 用 statusBar 给用户反馈,调试体验更好。

下一篇会写 UI 与显示:如何做关键字过滤、日志模式(TX/RX/时间戳)以及对齐显示,让大量数据也能看得清、不卡顿。

如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇