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 偶发错帧时,先问"这块缓冲区现在到底归谁"。

相关推荐
工业胶粘剂技术2 小时前
科耀K-8095M 单组分耐高温环氧 电机磁钢/新能源汽车高温结构粘接技术参数与选型
单片机·嵌入式硬件·汽车
djarmy6 小时前
MAIN.c(1): warning C318: can’t open file ‘STC8G.H’ 报错 解决 分析
c语言·开发语言·mongodb
HEJOO96 小时前
深入理解 Java volatile 关键字:原理、用法与常见误区
java·开发语言·spring
曹牧6 小时前
Java:no content to map due to end of input
java·开发语言
梦梦代码精6 小时前
《回收租赁系统技术选型避坑指南:业务闭环与二开自由度详解》
开发语言·低代码·docker·开源·代码规范
隔窗听雨眠7 小时前
记一次SQL Server数据库性能分析:从CPU100%到单配置修复的完整诊断
开发语言·数据库·php
David猪大卫7 小时前
【C++修炼】智能指针使用及原理
开发语言·c++·经验分享·笔记·学习·考研·面试
charlie1145141917 小时前
现代Qt开发之图像处理进阶:像素操作与格式转换
开发语言·图像处理·qt
李永奉7 小时前
中科蓝讯SDK开发-BT893x 面条耳机连接vivo手机后连接APP端,最后手机端设置页面手动断开蓝牙连接,此时耳机端假断开
c语言·开发语言·嵌入式硬件·物联网·智能手机·电脑
大爱编程♡7 小时前
STM32-DMA
stm32·单片机·嵌入式硬件