深入解析PCA9665缓冲模式:I2C总线效率提升与嵌入式系统优化实践
2026/6/11 13:08:03 网站建设 项目流程

1. 项目概述与核心价值

如果你在嵌入式系统开发中用过I2C总线,尤其是需要批量读写传感器数据或者配置外设寄存器时,肯定对频繁的中断和CPU占用率感到头疼。传统的I2C控制器,比如我们熟悉的PIC系列单片机内置的MSSP模块或者STM32的I2C外设,在字节模式下,每发送或接收一个字节,就会产生一次中断,要求CPU立即介入处理下一个字节的地址或数据。在传输几十、上百个字节时,这种“来一个字节,打断一次”的模式会让主控芯片疲于奔命,系统实时性大打折扣。

NXP的PCA9665/PCA9665A这款“Fm+并行总线转I2C总线控制器”芯片,其核心杀手锏就是缓冲模式。它本质上是一个自带状态机和FIFO(先入先出)缓冲区的硬件I2C协议引擎。你可以预先告诉它:“这次要连续发68个字节给某个从设备”,然后启动传输,芯片就会自动处理起始条件、发送从机地址、挨个送出数据字节、检查应答位,直到所有字节发送完毕或者中途出错,才产生一次中断通知你。整个过程,CPU只需要在开始和结束时介入两次,中间可以安心处理其他任务。这就像从“每送一个快递都要给你打一次电话确认”,变成了“把一车快递的地址清单给我,我送完了再通知你”,效率的提升是数量级的。

这篇文章,我们就来彻底拆解PCA9665的缓冲模式,特别是Master Transmitter Buffered Mode。我不会照本宣科地翻译数据手册,而是结合我实际在工业数据采集板上使用这颗芯片的经验,带你理解它的状态机是如何运转的,中断服务程序该怎么写,以及那些数据手册里语焉不详、但实际调试中能让你少掉几根头发的关键细节。无论你是正在评估这款芯片,还是已经用上了但对某些状态码感到困惑,相信这篇深度解析都能给你带来实实在在的帮助。

2. 缓冲模式的核心设计思路

2.1 为何需要缓冲模式?从字节模式的痛点说起

在深入PCA9665之前,我们得先明白它要解决什么问题。标准I2C字节模式的操作流程,通常遵循以下状态循环:

  1. 主设备发送起始条件(S)。
  2. 主设备发送7位从机地址+1位读写方向位(SLA+R/W)。
  3. 等待从机应答(ACK)。
  4. 发送/接收一个数据字节(DATA)。
  5. 等待对应答(ACK/NACK)。
  6. 重复步骤4-5,直到所有字节传输完毕。
  7. 主设备发送停止条件(P)。

在软件模拟或简单硬件控制器中,每一个箭头指向的“等待与判断”环节,都需要CPU的参与。CPU需要不断轮询或响应中断,去检查总线状态、读写数据寄存器、设置控制位。当传输数据量增大时,CPU被频繁打断,用于处理协议本身的开销可能远超实际有效数据的处理时间。

PCA9665的缓冲模式,其设计哲学是“描述而非驱动”。开发者不再需要关心每一个比特位的收发时序,而是向芯片提交一个完整的“传输描述符”:目标是谁(SLA+W)、要发多少数据(BC[6:0])、数据本身是什么。提交完毕后,启动传输,芯片内部的状态机就会接管后续所有繁琐的协议层操作,仅在关键节点(如传输开始、结束、出错)通过中断通知CPU。这极大地解放了CPU,尤其适合那些对实时性要求高、或者主控CPU本身还要处理复杂业务逻辑的系统。

2.2 PCA9665缓冲模式的硬件基石:状态机与寄存器组

PCA9665实现这一功能,依赖于几个核心的硬件模块:

  1. 并行接口与内部缓冲区:芯片通过一个8位并行总线(与MCU相连)接收指令和数据。其内部有一个足够深的缓冲区(支持单次序列最多68字节),用于暂存待发送或已接收的数据。在缓冲模式下,我们可以一次性通过并行接口写入多个字节到其内部缓冲区。
  2. I2C状态机:这是芯片的大脑。它严格遵循I2C协议规范,自动生成START、STOP、重复START条件,自动发送地址和数据字节,并自动检测和回应ACK/NACK。状态机的每一个稳定状态都对应一个唯一的状态码,存放在I2CSTA寄存器中。
  3. 中断驱动架构:状态机的每一次状态跃迁,只要不是空闲状态(F8h),都会将串行中断标志(SI)置1,并拉低中断引脚(INT)。这强制要求CPU必须通过查询I2CSTA来了解当前发生了什么,并采取手册规定的“应用软件响应”。清除SI标志的唯一方法,是向I2CCON寄存器执行一次写操作(即使写入的值不变),这同时也会拉高INT引脚。

理解这三个模块的协作关系至关重要:CPU通过并行总线配置寄存器、填充数据 -> 状态机驱动I2C物理总线完成传输 -> 状态机通过SI中断和I2CSTA状态码向CPU报告进度和请求下一步指令。这是一个典型的“硬件自动处理+软件事件响应”模型。

关键认知:不要将PCA9665视为一个简单的“I2C电平转换器”。它是一个拥有独立智能的I2C协处理器。你的MCU是它的“指挥官”,负责下达战略指令(目标、数据量);而PCA9665是“前线指挥官”,负责战术执行(每一位的收发、超时、仲裁)。指挥官只需要在战局发生关键变化时(占领据点、遭遇顽敌)做出决策。

3. 核心细节解析与实操要点

3.1 关键寄存器精讲

在操作缓冲模式前,必须吃透这几个寄存器。数据手册的表格是冰冷的,这里我们赋予它们“生命”。

  1. I2CCON (控制寄存器) - 芯片的“开关与模式拨盘”

    • ENSIO (Bit 6):总开关。置1使能整个I2C接口。一个极易忽略的细节:手册提到,从ENSIO置1到内部振荡器稳定,需要约550µs。这意味着你的初始化代码在设置ENSIO=1后,必须延迟至少550µs(通常用1ms更稳妥)才能进行后续的START等操作。否则芯片可能无法响应。
    • AA (Bit 7):应答控制位。在主模式下,这个位决定了PCA9665如果仲裁丢失(即试图成为主机但总线被占用),是否要以从机身份响应自己的地址。AA=0表示“我只当主机,不当从机”,这样在仲裁丢失后,它会释放总线,进入“非寻址从机模式”,不再响应任何地址。AA=1则允许它作为从机被寻址。在纯主控应用中,通常设AA=0以简化状态处理。
    • STA (Bit 5),STO (Bit 4),SI (Bit 3):这三位是状态机的直接控制/标志位。STA用于发起START条件;STO用于发起STOP条件;SI是只读的标志位,表示需要软件干预。
    • MODE (Bit 0):模式选择。必须设置为1,才能进入缓冲模式。这是启用我们今天所有功能的前提。
  2. I2CCOUNT (字节计数寄存器) - 传输的“任务清单长度”

    • BC[6:0] (Bit 6-0):这是缓冲模式的灵魂。它定义了单次序列中,需要传输的I2C数据字节的数量。注意,范围是1到68。这里的“单次序列”指的是从START之后,到下一个STOP或重复START之前,连续传输的数据流。
    • LB (Bit 7)Last Byte控制位。这个位在主接收模式从模式下才有意义。当LB=1时,PCA9665在接收到I2CCOUNT指定的最后一个字节后,会向从机返回一个NACK,以此告知从机“这是我要的最后一个字节,可以停止了”。这在主设备读取数据时非常有用。在主发送模式下,此位可忽略(通常设为0)。
  3. I2CDAT (数据寄存器) - 数据的“装卸平台”这是一个双向寄存器。在发送时,CPU向里面写入要发送的数据(从机地址+数据字节);在接收时,CPU从这里读取收到的数据。在缓冲模式下,你可以一次性写入多个字节(地址+所有数据),芯片会按顺序自动发送。

  4. I2CSTA (状态寄存器) - 系统的“仪表盘与故障码”这是一个只读寄存器,存放着当前I2C总线状态机的状态码(如08h, 18h, 28h等)。每一个非F8h(空闲)的状态码,都意味着SI=1且INT为低,要求CPU立即处理。你的中断服务程序(ISR)的核心,就是读取这个状态码,然后根据手册的“Application software response”表格,执行相应的操作(如读写I2CDAT,设置STA/STO等),最后I2CCON清除SI,让状态机继续运行。

3.2 主发送缓冲模式流程全景图

让我们结合数据手册的图10和状态表35,把整个主发送缓冲模式的流程串起来。假设我们要向从机地址0x50连续发送5个数据字节:0x01, 0x02, 0x03, 0x04, 0x05

第一阶段:初始化与启动

  1. 硬件初始化:配置MCU与PCA9665的并行接口(如地址线、数据线、读写控制、中断引脚)。
  2. 寄存器初始化
    • I2CCON = 0x40。这里ENSIO=1AA=0(我们只做主设备),STA=0,STO=0,SI=0,MODE=1写入后,等待至少550µs
    • I2CCOUNT = 0x06。因为我们要发送“从机地址+写方向位(1字节)”和“5个数据字节”,总共6个字节。所以BC[6:0] = 6(即0x06)。LB位在主发送模式下无关,设为0。
  3. 启动传输:设置STA=1来启动流程。可以通过写I2CCON=0x60(保持ENSIO=1,AA=0,MODE=1,同时STA=1)来实现。注意,设置STA和清除SI是同一个写操作完成的。状态机检测到总线空闲后,会自动发出START条件。

第二阶段:中断服务程序(ISR)的循环一旦START条件发出,状态机进入状态08h,SI置位,INT拉低,触发MCU中断(或轮询)。

  • 状态 08h: “START条件已发送”。这是第一个中断。

    • 软件响应:我们必须立即将本次传输的所有内容写入I2CDAT。顺序是:从机地址+写位(SLA+W),然后是所有数据字节。对于我们的例子:I2CDAT依次写入0xA0(0x50 << 1 | 0),0x01,0x02,0x03,0x04,0x05。注意,这里是一次性写入6个字节吗?不,对于8位并行接口,我们只能逐个字节写入。但关键在于,我们要在同一个08h状态的处理中,连续写入这6个字节。写入完成后,I2CCON(例如写入0x40)来清除SI位
    • 硬件动作:PCA9665看到SI被清除,开始自动发送我们刚写入I2CDAT的字节流。它先发送SLA+W(0xA0),然后等待ACK。根据ACK的情况和已发送的字节数,它会进入下一个状态并再次触发中断。
  • 可能的状态跃迁

    • 状态 18h: 如果从机对地址(0xA0)回了ACK,但I2CCOUNT设置为1(即只打算发地址,不发数据)。在我们的例子中I2CCOUNT=6,所以不会进入这个状态。
    • 状态 20h: 如果从机对地址(0xA0)回了NACK(从机不存在或忙)。这意味着传输失败。软件应设置STO=1发送STOP条件释放总线,然后进行错误处理。
    • 状态 28h:这是我们期望的成功状态。它表示“I2CCOUNT中指定数量的字节(SLA+W + 所有数据字节)已全部发送,且每个字节都收到了ACK”。在我们的例子中,当芯片发完0xA0, 0x01...0x05这6个字节,且每次都收到ACK后,就会进入28h状态并触发中断。
    • 状态 30h: 如果从机对地址回了ACK,但在发送某个数据字节时收到了NACK(从机可能无法接收更多数据)。此时已发送的字节数小于等于I2CCOUNT
    • 状态 38h: 仲裁丢失。在多主系统中,另一个主设备赢得了总线控制权。
  • 状态 28h 的处理(传输成功)

    • 软件响应:此时所有数据已发送完毕。我们有几个选择:
      1. 发送STOP条件,结束本次传输:写I2CCON,设置STO=1,SI=0(例如写入0x50)。芯片会发送STOP信号,然后总线进入空闲(F8h)。
      2. 发送重复START条件,紧接着开始一次新的传输(如切换为读操作):写I2CCON,设置STA=1,SI=0(例如写入0x60)。芯片会发出一个重复START,然后进入状态10h,开始下一轮。
    • 在我们的例子中,发送完5个数据后,我们选择发送STOP条件结束。至此,一次完整的主发送缓冲模式操作完成。

实操心得:状态08h是整个流程的“装弹”环节。你必须确保在响应08h中断时,写入I2CDAT总字节数严格等于I2CCOUNT中预设的值。如果写多了,多余字节不会被发送;如果写少了,状态机可能会在等待不存在的“下一个字节”时挂起或产生不可预知的行为。最好的做法是,在初始化I2CCOUNT后,用一个循环将SLA+W和所有数据字节依次写入I2CDAT

4. 实操过程与核心环节实现

4.1 驱动层程序设计框架

理解了原理和状态机,我们来设计一个简洁而健壮的驱动层。以下是一个基于状态机的伪代码框架,它不依赖于具体的MCU型号,突出了与PCA9665交互的核心逻辑。

// PCA9665 寄存器定义 (假设通过并行总线映射到MCU的特定地址) #define PCA9665_I2CCON (*((volatile uint8_t *)0x8000)) #define PCA9665_I2CSTA (*((volatile uint8_t *)0x8001)) #define PCA9665_I2CDAT (*((volatile uint8_t *)0x8002)) #define PCA9665_I2CADR (*((volatile uint8_t *)0x8003)) // 从机地址寄存器 #define PCA9665_I2CCOUNT (*((volatile uint8_t *)0x8004)) // I2CCON 位定义 #define I2CCON_AA (1 << 7) #define I2CCON_ENSIO (1 << 6) #define I2CCON_STA (1 << 5) #define I2CCON_STO (1 << 4) #define I2CCON_SI (1 << 3) #define I2CCON_MODE (1 << 0) // 0=字节模式,1=缓冲模式 // 全局状态变量 typedef enum { I2C_IDLE, I2C_MT_BUFFERED_TX, // 主发送缓冲模式进行中 I2C_MR_BUFFERED_RX, // 主接收缓冲模式进行中 I2C_ERROR } i2c_state_t; static i2c_state_t current_state = I2C_IDLE; static uint8_t tx_buffer[68]; // 发送缓冲区 static uint8_t tx_index = 0; static uint8_t tx_total = 0; // PCA9665 初始化函数 void PCA9665_Init(void) { // 1. 初始化并行总线接口(GPIO、FSMC等),此处省略硬件相关代码 // ... // 2. 初始化PCA9665寄存器 PCA9665_I2CCON = 0x00; // 确保芯片禁用 delay_ms(1); // 设置自身从机地址(如果不用作从机可忽略,但建议设置) PCA9665_I2CADR = (MY_SLAVE_ADDR << 1); // GC位默认为0 // 使能I2C接口,设置为缓冲模式,AA=0(纯主模式) PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE; // 0x40 | 0x01 = 0x41 // 等待振荡器稳定 (>550us) delay_ms(1); // 清除任何可能挂起的中断标志 PCA9665_I2CCON &= ~(I2CCON_STA | I2CCON_STO); // 通过写I2CCON清除SI(如果之前有) PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE; // 再次写入0x41 current_state = I2C_IDLE; } // 主发送缓冲模式启动函数 uint8_t PCA9665_MasterTransmitBuffered(uint8_t slave_addr, uint8_t *data, uint8_t len) { if (len == 0 || len > 68) return 0; // 参数检查,最大68字节 if (current_state != I2C_IDLE) return 0; // 总线忙 // 1. 填充本地发送缓冲区:地址+数据 tx_buffer[0] = (slave_addr << 1) | 0; // SLA+W for (int i = 0; i < len; i++) { tx_buffer[i + 1] = data[i]; } tx_index = 0; tx_total = len + 1; // 总字节数 = 地址(1) + 数据(len) // 2. 设置字节计数寄存器 I2CCOUNT PCA9665_I2CCOUNT = tx_total; // LB位为0(主发送模式忽略) // 3. 切换状态,准备启动传输 current_state = I2C_MT_BUFFERED_TX; // 4. 设置STA位,启动传输(同时会清除之前的SI) PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE | I2CCON_STA; // 0x41 | 0x20 = 0x61 return 1; // 启动成功,后续由中断处理 } // PCA9665 中断服务程序 (核心!) void PCA9665_ISR(void) { uint8_t status = PCA9665_I2CSTA; // 读取状态码 switch (current_state) { case I2C_MT_BUFFERED_TX: switch (status) { case 0x08: // START条件已发出 case 0x10: // 重复START条件已发出 // 状态08h/10h:需要加载SLA+W和所有数据字节 // 但我们不能一次性写入,需按PCA9665要求,在本次中断响应中写入全部 // 实际上,我们提前把数据准备好了,现在只需按顺序写入I2CDAT for (uint8_t i = 0; i < tx_total; i++) { PCA9665_I2CDAT = tx_buffer[i]; } // 写入完成后,清除SI,让状态机继续 PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE; // 0x41 break; case 0x28: // 所有字节发送成功,并收到ACK // 传输成功!发送STOP条件结束本次传输 PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE | I2CCON_STO; // 0x41 | 0x10 = 0x51 current_state = I2C_IDLE; // 这里可以置位一个标志,通知主程序传输完成 break; case 0x20: // SLA+W 发送后收到NACK(地址错误) case 0x30: // 数据字节发送后收到NACK(从机无法接收) // 传输失败,发送STOP条件释放总线 PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE | I2CCON_STO; // 0x51 current_state = I2C_ERROR; // 记录错误状态码 break; case 0x38: // 仲裁丢失 // 在多主系统中,可以尝试重新开始 // 简单处理:释放总线,进入空闲 PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE; // 0x41 current_state = I2C_IDLE; break; default: // 收到未预期的状态码,按错误处理 PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE | I2CCON_STO; current_state = I2C_ERROR; break; } break; // 其他状态(如主接收)的处理案例可以在此添加 case I2C_IDLE: default: // 在空闲状态下收到中断,可能是未处理完的旧中断,清除SI PCA9665_I2CCON = I2CCON_ENSIO | I2CCON_MODE; break; } }

4.2 主接收缓冲模式流程精讲

理解了主发送,主接收就很容易触类旁通。核心区别在于LB位的运用和状态码的不同。

流程简述:

  1. 初始化:与主发送类似,设置ENSIO=1,MODE=1
  2. 设置I2CCOUNTLBBC[6:0]设为要接收的纯数据字节数LB位是关键:
    • LB=0:接收完所有字节后,PCA9665会对最后一个字节也回复ACK。这通常用于“我还要继续读”的场景,需要主机在读完数据后主动发送STOP或重复START来终止。
    • LB=1:接收完最后一个字节后,PCA9665会自动回复NACK,通知从机“这是最后一个字节了”。之后芯片可以自动处理STOP或重复START,更为常用。
  3. 启动传输:设置STA=1
  4. 状态08h处理:写入SLA+R(从机地址+读位)到I2CDAT,然后清除SI。
  5. 后续状态
    • 48h: 从机地址无应答(NACK),失败。
    • 50h: 成功接收完I2CCOUNT指定的所有字节,且LB=0(对所有字节回了ACK)。
    • 58h: 成功接收完所有字节,且LB=1(对最后一个字节回了NACK)。这是最常用的成功状态。
  6. 读取数据:在状态50h58h的中断里,你需要从I2CDAT寄存器中连续读取I2CCOUNT,才能把缓冲区里的数据全部取出来。数据是在状态机运行期间自动存入PCA9665内部缓冲区的,在成功状态的中断触发时,所有数据都已就绪。

注意事项:主接收模式下,在状态08h你只需要写入SLA+R这一个字节到I2CDAT,而不是像主发送那样写入所有数据。数据接收是自动完成的。I2CCOUNT设置的是期望接收的数据字节数,LB位控制的是最后一个ACK/NACK的行为。

5. 常见问题与排查技巧实录

即使理解了原理和流程,实际调试中依然会遇到各种坑。下面是我在多个项目中总结出的常见问题与解决方法。

5.1 问题排查速查表

现象可能原因排查步骤与解决方案
无法进入中断,SI始终为01. PCA9665未正确初始化或使能。
2. 并行总线连接错误(地址、数据、控制线)。
3. 中断引脚(INT)未连接或配置错误。
4.ENSIO使能后等待时间不足。
1. 确认I2CCONENSIOMODE位已设置为1。
2. 用逻辑分析仪或示波器检查并行总线的读写时序,确认MCU能正确读写PCA9665寄存器。
3. 检查INT引脚的上拉电阻和MCU中断输入配置。
4. 在设置ENSIO=1后,增加至少1ms的延时。
一直卡在状态08h10h在状态08h/10h的中断服务程序中,没有正确清除SI标志确认在状态08h/10h的case分支最后,执行了写I2CCON的操作(例如PCA9665_I2CCON = 0x41;)。这是让状态机继续运行的关键。
传输中途停止,状态码异常(如38h仲裁丢失)1. 总线上有其他主设备冲突。
2. 总线被意外拉低(SCL/SDA短路或设备故障)。
3. 电源不稳定导致PCA9665复位。
1. 检查是否为多主系统,若是,需实现仲裁逻辑。
2. 用示波器检查SCL和SDA波形,看是否有毛刺、电平不达标或被持续拉低。
3. 检查PCA9665的电源和复位电路。
从机无应答(状态20h48h1. 从机地址错误。
2. 从机设备不存在、未上电或损坏。
3. 从机忙或处于复位状态。
4. 总线上下拉电阻值不合适,导致信号边沿太缓。
1. 双重检查从机7位地址,并确认左移了一位(addr << 1)。
2. 单独测试从机设备。
3. 查看从机数据手册,确认其最大时钟频率和时序要求,PCA9665的Fm+模式最高支持1MHz。
4. 根据总线电容和速度,调整上拉电阻(通常4.7kΩ-10kΩ)。
能收到成功状态码(28h,58h),但数据不对1.主发送模式:在状态08h写入I2CDAT的字节顺序或数量错误。
2.主接收模式:在成功状态中断后,没有及时或没有读够次数从I2CDAT读取数据。
3. 并行总线数据位序(MSB/LSB)搞反。
1. 主发送时,确保写入的第一个字节是SLA+W,后续是数据,且总数等于I2CCOUNT
2. 主接收时,在50h/58h状态,必须用循环连续读取I2CDAT寄存器I2CCOUNT次。
3. 检查MCU与PCA9665并行接口的数据线连接是否D0对D0, D1对D1。
只能传输一次,第二次传输失败1. 传输完成后没有正确回到IDLE状态(F8h)。
2. 上一次传输的错误状态没有清除。
3. 全局状态变量current_state没有在传输结束或出错时重置为I2C_IDLE
1. 在传输结束(成功或失败)发送STOP后,等待状态机回到F8h(空闲)。
2. 在每次启动新传输前,确保current_state == I2C_IDLE,并重新初始化I2CCOUNT和缓冲区索引。
3. 在ISR的每个状态处理分支末尾,正确更新current_state

5.2 调试技巧与高级用法

  1. 状态码打印:在开发初期,将每次中断读取到的I2CSTA状态码通过串口打印出来。这是最直接的调试手段,可以让你清晰地看到状态机的流转路径是否符合预期。
  2. 逻辑分析仪是神器:一定要用逻辑分析仪(如Saleae)抓取I2C总线(SCL/SDA)的波形。你可以直观地看到START、地址、数据、ACK/NACK、STOP的每一个位,并与你程序中的状态码对应起来。任何时序问题都无所遁形。
  3. 超时机制:状态机依赖中断,但如果中断因故未触发,程序就会死等。必须为每次I2C操作添加软件超时机制。例如,在启动传输后启动一个定时器,如果在预期时间内(如10ms)未完成传输(未进入最终成功或失败状态),则强制复位PCA9665(拉低再拉高其复位引脚,或重新初始化I2CCON)并报告超时错误。
  4. AA位的灵活运用:即使在主控应用中,将AA位设为1也有其价值。如果你的系统设计允许PCA9665在某些情况下作为从机被访问(例如用于固件升级或诊断),那么设置AA=1并处理好从机模式下的中断(状态60h,80h等),可以实现双向通信,增加系统灵活性。
  5. 混合模式使用:PCA9665也支持传统的字节模式(MODE=0)。对于非常短小的、非周期性的传输,使用字节模式可能编程更简单。缓冲模式更适合于有规律、成块的数据搬运。在实际项目中,可以根据不同外设的特点,混合使用两种模式。

最后,再分享一个我踩过的“坑”:某次调试中,发现连续传输大量数据时,偶尔会丢最后一个字节。排查良久才发现,在状态28h(发送成功)的中断里,我立即去读取了某个标志位并启动了下一轮传输,但没有等待STOP条件真正在总线上发出。虽然设置了STO=1并清除了SI,但STOP信号的产生需要一点时间。在极端情况下,下一轮的START可能会紧挨着上一轮的STOP,甚至导致时序混乱。解决方案是:在发送STOP条件后,增加一个短暂循环,等待I2CSTA状态变为F8h(空闲),或者至少等待几十微秒,再开始下一次操作。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询