FIFO 批量传输要先把取出的数据放进待确认缓存

设备协议常见的第一版设计是一次读取一条记录。主机发读取命令,设备返回一条数据,主机确认后设备删除这一条。

这个结构简单,但数据积压后效率很低。每条记录都要走一次请求、响应、确认。BLE、串口、RS485、LoRa、TCP 小包协议里都会遇到类似问题:链路可用时间有限,批量取数据更合适。

批量取数据后,真正麻烦的地方通常不在协议格式,而在设备端队列。

很多 MCU 项目会复用现成 FIFO。FIFO 的接口通常是 readpop:读一段数据出来,读指针就往前走。这意味着数据已经离开原来的位置。

如果主机还没有确认收到,设备就把这批数据从 FIFO 里取走,中途断线或主机解析失败时,这批数据就丢了。

把数据再塞回 FIFO 也不稳。FIFO 只保证按写入顺序从头到尾取出,取出来的数据已经离开原来的位置。重新写回去会排到队尾,顺序会变;如果这期间又有新数据写入,记录顺序更容易乱。

不要让 FIFO 承担确认语义

FIFO 适合保存顺序,不适合表达“主机已经收到”。

协议层需要的语义是:

  1. 设备取出一批记录。
  2. 主机没有确认前,这批记录可以重复发送。
  3. 主机确认后,这批记录才算完成。
  4. 下一次读取再推进到后面的记录。

这个语义不能直接压在 FIFO 的 pop 上。pop 的动作已经改变了队列位置,它没有“临时借出再放回原位”的能力。

待确认缓存的作用

稳定的做法是在 FIFO 上面加一个小缓存,保存刚刚取出的那一批记录。

可以把它看成两层:

FIFO:保存还没进入同步流程的数据
pending_batch:保存已经发给主机、等待确认的数据

第一次批量读取时,设备从 FIFO 里取出若干条记录,放到 pending_batch,然后把 pending_batch 发给主机。

主机还没有 ACK 时,设备不再从 FIFO 取下一批。主机重复请求时,设备继续发送同一个 pending_batch。这样断线重连、手机端解析失败、上层任务重试,都不会让数据从设备端消失。

收到匹配的 ACK 后,设备清空 pending_batch。这时这批数据才算真正完成,下一次读取才能从 FIFO 继续取新的数据。

batch_id 比起始记录更干净

批量确认时,给这批数据分配一个 batch_id 会更清楚。

主机确认“刚才那一批”,设备不要求主机推测“从哪个记录 ID 开始删几条”。记录 ID 可能回绕,记录数量也可能因为版本升级变化。用 batch_id 后,设备只需要判断当前待确认批次是否匹配。

同一时间只允许一个待确认批次时,batch_id 只需要区分当前这批和下一批。队列很短的嵌入式设备里,用一个递增的 8 位 ID 通常就够用。

单条读取也可以复用这个结构

保留单条读取接口时,不需要维护另一套状态。

单条读取可以看成 max_count = 1 的批量读取。它同样进入 pending_batch,只是这一批只有一条记录。单条 ACK 成功后,也走清空 pending_batch 的流程。

这样做的好处是设备端只有一套确认状态。批量接口和单条接口不会互相抢队列,也不会出现“一条记录在单条缓存里,另一批记录在批量缓存里”的维护负担。

适用范围

这个问题不只出现在 BLE。

只要满足下面三个条件,就应该考虑待确认缓存:

串口离线数据导出、RS485 历史告警读取、LoRa 低速记录上传、TCP 小包事件同步,都会遇到同一个结构问题。

结论

批量传输提高同步效率,但不能直接把 FIFO 当成确认队列。

FIFO 最大的限制是数据取出后无法原位放回。重新写回会改变顺序,直接丢弃会破坏可靠性。

在 FIFO 和协议响应之间放一个 pending_batch,可以把顺序存储和确认删除分开:FIFO 管还没开始同步的数据,待确认缓存管已经发出但还没 ACK 的数据。这个边界清楚后,批量传输就不会因为 FIFO 的 pop 语义丢记录或乱顺序。

本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。