I²C 从机接收总被覆盖?双缓冲要在 STOP 时交接


tags: 嵌入式,I2C,ISR,双缓冲,并发

category: 嵌入式开发
type: original
series: peripheral-practice
hook: I2C 从机收到下一帧时,上一帧为什么突然变了
data: 单缓冲: 生产者覆盖消费者 | 双缓冲: STOP 时一次性交接完整事务

I²C 从机接收总被覆盖?双缓冲要在 STOP 时交接

一句话: 中断负责收字节,主循环负责处理命令时,单缓冲无法同时保护"正在接收"和"等待消费"的两份数据;应在事务结束时一次性交接缓冲区和元数据。
适合谁读:I²C 从机、串口或其他中断接收路径出现偶发丢帧、数据错配的嵌入式开发者。

先画出两个所有者

先不要急着加 volatile。把接收区的写入者和读取者列出来:

对象 生产者 消费者 允许什么时候修改
当前接收缓冲区 ISR 无 当前事务进行中
已发布缓冲区 无 主循环 主循环处理期间只读
地址、长度、完成标志 ISR 发布 主循环读取 事务结束时一次更新

如果 ISR 和主循环都能改同一块数组,主循环读到的可能是半帧。即使每个字节的读写是原子的,整帧仍然不是原子的。

双缓冲的最小结构

c 复制代码
typedef struct {
    uint8_t data[RX_CAPACITY];
    uint16_t length;
    uint16_t address;
} rx_frame_t;

static rx_frame_t rx_buffer[2];
static uint8_t rx_write_index;
static uint8_t rx_ready_index;
static bool rx_ready;
static bool rx_overflow;

ISR 只写当前缓冲区。收到 STOP 后,先补齐长度和地址,再一次性发布索引;主循环只消费已经发布的那一块。

text 复制代码
ISR 写 A → STOP → 发布 A → 主循环读 A
ISR 改写 B → STOP → 发布 B → 主循环读 B

这样,主循环处理 A 时,ISR 可以准备 B,不会把 A 的内容改到一半。

STOP 和 REPEATED START 不是一回事

许多 I²C 主机先写寄存器地址,再用 REPEATED START 读取数据,中间没有 STOP。此时不能把"上一帧已经发布的地址"当成当前读取地址。

推荐为当前事务保留独立地址快照:

c 复制代码
void on_address_phase(uint16_t address)
{
    current_transaction_address = address;
}

void on_repeated_start_read(void)
{
    tx_address = current_transaction_address;
    start_read_response(tx_address);
}

STOP 适合提交"这一帧已经完整接收"的事实;REPEATED START 则表示总线方向变了,不代表前一帧的业务数据可以被重新解释。

缓冲区满了怎么办

双缓冲并不能让消费者无限变快。上一帧还没处理完,新帧又结束时,必须有明确策略:

策略 优点 风险
丢弃新帧 保留已发布的完整帧 需要记录溢出,主机要能重试
覆盖旧帧 不阻塞接收 主循环可能处理到被破坏的数据
增加队列 吞吐更高 需要容量、回收和背压策略

对配置命令,通常宁可丢弃并报告溢出,也不要静默覆盖一帧已经交给主循环的数据。

回归测试至少覆盖三条路径

text 复制代码
连续两帧快速写入 → 第一帧内容不被第二帧改写
写地址 + REPEATED START + READ → 读取当前事务地址
主循环故意阻塞 → 溢出有记录,已发布帧仍完整

如果只有静态检查和编译证据,应写明"所有权交接路径已检查,真实主机互操作仍待验证"。不要把没有波形或抓包日志的场景写成已经复现的现场故障。

总结

  • 单缓冲的问题不是少一个数组,而是生产者和消费者没有所有权边界。
  • STOP 时发布完整帧,主循环只消费已发布缓冲区。
  • REPEATED START 要使用当前事务地址快照。
  • 缓冲区满时必须选择丢弃、覆盖或排队,不能让策略隐含在指针操作里。

有用的话点个收藏,下次 I²C 偶发错帧时,先问"这块缓冲区现在到底归谁"。

相关推荐
1024奇点7 小时前
【仓颉语言入门 · 第二篇 第 22 课】
开发语言·ide·开源
Warren2Lynch8 小时前
C4 模型详解:利用 Visual Paradigm 可视化软件架构的综合指南
开发语言·uml
Knight_AL9 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
FRing0310 小时前
vue2干货总结2(v-bind、v-for、v-model、指令修饰符、v-bind操作class和style)
开发语言·前端·vue.js·vue2
蹊黎10 小时前
Linux 下用 C 语言实现进度条
linux·c语言·单片机
by2099910 小时前
C++ 智能指针的所有权与使用
开发语言·c++·经验分享
砚底藏山河10 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融
IT召唤狮10 小时前
开源企业级零信任项目Pangolin,零信任能挡住PHP核弹漏洞吗?
开发语言·开源·php
知已难寻10 小时前
03 · OpenStack的计算管理 Nova
运维·服务器·开发语言·云原生·云计算·openstack·开源软件
Rover Ramble10 小时前
C++ 用互斥锁实现读写锁
开发语言·c++