
1. 项目概述从手册到代码LPC210x I2C状态机编程实战如果你正在使用NXP的LXP2101/02/03系列微控制器并且需要和传感器、EEPROM或者其他I2C设备打交道那么你大概率已经翻过那份经典的UM10161用户手册。手册里那几十页关于I2C接口的说明尤其是那几张让人眼花缭乱的状态转移图和密密麻麻的状态码表格是不是让你感觉既敬畏又头疼我当年第一次接触LPC2103的I2C时也是这种感觉。手册把硬件行为描述得非常精确但如何把这些状态码和表格变成你工程里那几行能稳定跑起来的C代码中间似乎隔着一道鸿沟。这份手册的核心其实是定义了一个由硬件自动维护的I2C状态机。这个状态机非常“尽职”它帮你完成了时钟同步、起始/停止条件生成、地址匹配、应答位处理等所有底层信号时序。但它也很“高冷”每完成一个微小的总线动作比如发送完一个地址字节、收到一个应答位它就设置一个中断标志SI并更新状态寄存器I2STAT然后……它就停在那里等着你来告诉它下一步该干嘛。你的程序本质上就是为这个状态机编写的“驾驶员手册”响应每一个中断根据当前的状态码I2STAT的值去执行手册表格里规定的“应用软件响应”Application software response。所以深入解析LPC210x的I2C关键不在于背诵那26个状态码而在于理解其背后的四种基本工作模式主发送MT、主接收MR、从接收SR、从发送ST的逻辑流并掌握如何用代码“驾驶”这个状态机。今天我就结合自己踩过的坑和项目经验带你把这套状态机编程的“内功心法”彻底讲透让你能对着手册表格写出稳健高效的I2C驱动。2. I2C状态机核心架构与寄存器精讲在开始写代码之前我们必须和硬件“对好暗号”。LPC210x的I2C接口通过几个关键寄存器与我们对话理解它们是编写状态服务程序的前提。2.1 核心寄存器I2C的指挥中心LPC210x的I2C接口主要涉及三个核心寄存器I2CONSET、I2CONCLR和I2DAT。这里需要特别注意NXP的ARM7内核常用SET和CLR寄存器来分别进行位设置和位清除以避免读-修改-写过程中的竞态风险。I2CONSET/CONCLR (控制寄存器)这是状态机的“方向盘和油门刹车”。我们主要通过设置I2CONSET的位来发出指令。其关键位如下I2EN (位6)I2C接口使能位。必须置1否则接口完全不工作。这是一个常见的低级错误。STA (位5)起始条件位。置1表示“我希望发起一个起始条件”。当总线空闲时硬件会自动产生START信号如果总线忙硬件会等待总线空闲后再产生START。这是一个“请求”位而非“状态”位。STO (位4)停止条件位。置1表示“我希望发起一个停止条件”。当作为主设备时硬件会在当前字节传输完成后产生STOP信号作为从设备时置位STO可以从异常状态中恢复如总线错误0x00但不会在总线上产生STOP信号。这也是一个“请求”位。SI (位3)串行中断标志位。这是整个状态机驱动的核心。当任何一个有效的I2C状态除了0xF8出现时硬件会自动将其置1。我们的中断服务程序ISR必须在处理完当前状态后手动清除此位向I2CONCLR的SI位写1以允许状态机继续运行。忘记清SI是导致I2C“卡死”的最常见原因之一。AA (位2)应答标志位。这个位决定了在下一个应答时钟脉冲时硬件将如何动作。当处于接收方主接收或从接收时AA1表示将在下一个ACK周期发送应答拉低SDAAA0表示发送非应答保持SDA高。当处于发送方主发送或从发送时此位通常不影响发送但会影响某些状态下的行为例如从发送模式下AA0时发送最后一个字节后会进入特定状态。I2DAT (数据寄存器)这是状态机的“数据收发箱”。当你需要发送一个字节地址或数据时就写入I2DAT当你需要读取一个接收到的字节时就从I2DAT读取。重要原则必须在清除SI标志之前完成对I2DAT的读写操作。因为一旦SI被清除硬件可能立即开始处理下一个字节的收发此时再操作I2DAT就晚了。I2STAT (状态寄存器)这是状态机的“仪表盘”。它是一个只读寄存器保存着当前I2C总线和接口的精确状态0x08, 0x10, 0x18, ..., 0xF8。我们的中断服务程序的首要任务就是读取I2STAT的值然后根据这个值跳转到对应的处理分支。手册中的四张巨表Master Transmitter, Master Receiver, Slave Receiver, Slave Transmitter就是I2STAT值的“行为对照表”。I2ADR (从地址寄存器)当微控制器作为从设备时I2ADR的高7位用于设置它的7位I2C从地址。最低位GC如果置1则表示该从设备也响应通用呼叫地址0x00。这个寄存器只在从模式下有意义。2.2 状态机运行逻辑中断驱动的“问答”模型理解了寄存器我们来看状态机如何运转。整个过程是一个严格的“硬件中断软件响应”的循环硬件动作I2C硬件完成一个总线阶段如发送完START、发送完地址并收到ACK、收到一个数据字节等。状态更新硬件根据完成的操作更新I2STAT寄存器为一个特定的状态码如0x08, 0x18, 0x40等并将中断标志SI置位。触发中断如果I2C中断已使能CPU会跳转到中断服务程序ISR。软件响应在ISR中程序读取I2STAT根据状态码查表手册中的表执行规定的操作。这通常包括数据操作从I2DAT读取收到的数据或向I2DAT写入要发送的数据。控制操作通过设置I2CONSET的STA、STO、AA位来指示硬件下一步做什么例如继续发送、请求停止、是否应答等。清中断继续最后也是最关键的一步向I2CONCLR的SI位写1清除中断标志。这个动作就像对硬件说“你交代的事情我处理完了请继续吧。” 硬件检测到SI被清除才会根据你刚才设置的控制位STA/STO/AA执行下一个总线操作并再次进入步骤1。这个“硬件中断-软件查表响应-清标志”的循环贯穿了I2C通信的始终。你的驱动代码质量就取决于对这个循环和状态码表格的理解深度。3. 四大工作模式状态流深度解析与代码映射手册将状态机分为四大模式但实际编程中一个设备可能在不同时刻扮演不同角色。我们结合状态图和表格把每种模式的“故事线”和关键状态点讲清楚。3.1 主发送器模式MT (Master Transmitter)这是最常用的模式即MCU作为主设备向从设备写入数据。其典型流程是START - 发送从机地址W - (发送数据字节) * N - STOP。核心状态流解析0x08: “START条件已成功发送”。这是主模式一切事务的起点。此时你必须向I2DAT写入从机地址和写方向位SLAW然后清除SI。硬件会自动帮你发送这个地址字节。0x18: “SLAW已发送且收到了从机的应答ACK”。这是个好消息说明从机在线并准备好了接收数据。此时你有几个选择如果要发送数据向I2DAT写入第一个数据字节然后清除SI。如果想发送重复START以切换操作如改为读不操作I2DAT而是设置STA1然后清除SI。如果想直接结束传输设置STO1然后清除SI。0x28: “一个数据字节已发送且收到了ACK”。这表示一个数据字节成功送达。处理方式同0x18可以继续发数据、发重复START、或发STOP。0x20 或 0x30: 分别对应“SLAW发送后收到NACK”和“数据字节发送后收到NACK”。这通常意味着从机无应答可能是地址错误、从机忙或写入失败。此时标准的错误处理是发送一个STOP条件STO1, SI0来释放总线结束本次异常传输。0x38: “在SLAR/W或数据字节传输中仲裁丢失”。这发生在多主系统中当两个主设备同时开始传输并且你的设备检测到自己发送的位与总线实际电平不一致时。硬件会自动退出主模式转为从模式如果使能了。你的程序应该释放总线不操作I2DATSTA0, STO0, SI0或者如果你想重试可以设置STA1这样当总线空闲后硬件会自动重发START。代码映射示例伪代码风格void I2C_IRQHandler(void) { uint8_t status I2STAT; switch(status) { case 0x08: // START transmitted I2DAT (slave_addr 1) | 0; // SLAW I2CONCLR (13); // Clear SI break; case 0x18: // SLAW transmitted, ACK received case 0x28: // Data transmitted, ACK received if (tx_index tx_len) { I2DAT tx_buffer[tx_index]; // Load next data I2CONCLR (13); // Clear SI } else { // All data sent, generate STOP I2CONSET (14); // Set STO I2CONCLR (13); // Clear SI tx_complete 1; } break; case 0x20: // SLAW transmitted, NACK received case 0x30: // Data transmitted, NACK received // Error handling: generate STOP I2CONSET (14); // Set STO I2CONCLR (13); // Clear SI error_flag 1; break; case 0x38: // Arbitration lost // Option 1: Give up bus I2CONCLR (15) | (14) | (13); // Clear STA, STO, SI // Option 2: Retry later (set STA to wait for free bus) // I2CONSET (15); // Set STA // I2CONCLR (13); // Clear SI arbitration_lost 1; break; // ... other cases } }3.2 主接收器模式MR (Master Receiver)这是主设备从从设备读取数据的模式。流程为START - 发送从机地址R - (接收数据字节) * N - 最后一个字节后发送NACK - STOP。核心状态流解析0x08 / 0x10: 与主发送模式相同是起始点。但这里需要向I2DAT写入从机地址和读方向位SLAR。0x40: “SLAR已发送且收到了ACK”。从机同意发送数据。此时你需要通过设置AA位来告诉硬件在接收到第一个数据字节后你希望如何应答。如果计划接收多个字节非最后一个字节则设置AA1应答然后清除SI。如果只接收一个字节即最后一个字节则设置AA0非应答然后清除SI。0x50: “已收到一个数据字节且已返回ACK”。这意味着成功收到了一个非最后的字节。你应该立即从I2DAT读取这个数据并再次通过AA位决定下一个字节的应答策略然后清除SI。0x58: “已收到一个数据字节且已返回NACK”。这通常发生在接收最后一个字节后。你应该从I2DAT读取这最后一个数据然后通常接着发送STOP条件STO1, SI0来结束读取。0x48: “SLAR发送后收到NACK”。从机不同意发送数据。处理方式同主发送模式的0x20/0x30发送STOP。关键技巧AA位的时机在主接收模式中AA位的设置决定了下一个字节接收后的应答行为。这是一个常见的困惑点。在状态0x40发送地址后设置AA影响的是第一个数据字节的应答。在状态0x50收到一个字节后设置AA影响的是下一个第二个数据字节的应答。要结束读取必须在接收倒数第二个字节后的0x50状态将AA设为0这样在接收最后一个字节时硬件会自动发出NACK。3.3 从接收器模式SR (Slave Receiver)当MCU被其他主设备寻址并写入数据时进入此模式。核心状态流解析0x60: “自己的从地址SLAW已被接收且已返回ACK”。这意味着一个主设备正试图向你写入数据。此时你需要通过AA位来表明是否准备接收数据。AA1准备好接收并会在收到数据后返回ACK。AA0不准备接收或这是最后一个字节收到数据后将返回NACK。0x80: “之前已被寻址且已收到一个数据字节并返回了ACK”。数据已存入I2DAT。你应该读取I2DAT然后再次设置AA位来决定对下一个字节的应答策略最后清除SI。0xA0: “在仍被寻址为从接收器或从发送器时收到了STOP条件或重复START条件”。这标志着一笔主设备发起的传输结束。你的从设备恢复为未寻址状态。此时你可以选择重新使能地址识别AA1或者保持静默。从模式初始化的关键要使能从模式必须在初始化时设置I2ADR自己的地址并在I2CON中同时设置I2EN1和AA1。AA1是让硬件能够应答自身地址的关键。如果你在传输过程中将AA清零设备将暂时“隐身”不响应任何寻址直到你再次将AA置1。这是一个实现“软件地址过滤”或处理繁忙状态的有用技巧。3.4 从发送器模式ST (Slave Transmitter)当MCU被其他主设备寻址并读取数据时进入此模式。核心状态流解析0xA8: “自己的从地址SLAR已被接收且已返回ACK”。主设备请求读取数据。此时你需要将第一个要发送的数据字节写入I2DAT然后清除SI。同时通过AA位告知硬件在主机对第一个字节应答后你是否还有后续数据要发送。0xB8: “I2DAT中的数据字节已发送且收到了ACK”。主设备成功接收了一个字节并希望继续读。此时你需要将下一个数据字节写入I2DAT并再次通过AA位设置后续的应答策略然后清除SI。0xC0 / 0xC8: 这两个状态都表示“数据字节已发送但收到了NACK”。在从发送模式下主设备通过发送NACK来告知从设备“这是最后一个字节停止发送”。0xC0发生在AA1时发送数据后收到NACK0xC8发生在AA0时你已告知主机这是最后一字节发送最后一字节后收到ACK。在这两个状态下你的从设备都会切换到未寻址模式。通常的处理是读取状态后简单地清除SI即可等待下一次寻址。4. 状态机服务程序实战从表格到稳健的C代码理解了理论我们来看如何将其组织成实际可用的代码。一个健壮的I2C驱动其状态服务程序ISR是核心。4.1 程序结构设计状态码分派与上下文管理不建议用一个庞大的switch-case处理所有26个状态码。更好的做法是根据模式进行初步分派。// 定义一些全局或静态变量来维护传输上下文 typedef struct { uint8_t mode; // 当前模式IDLE, MT, MR, SR, ST uint8_t slave_addr; uint8_t *data_buf; uint16_t data_len; uint16_t index; uint8_t error; uint8_t complete; } i2c_transaction_t; static volatile i2c_transaction_t g_transaction; void I2C_IRQHandler(void) { uint8_t status I2STAT; // 读取状态寄存器 // 首先处理“杂项状态” if (status 0xF8) { // 无状态信息直接退出中断 return; } if (status 0x00) { // 总线错误这是一个严重错误需要恢复。 I2CONSET (14); // 设置STO标志 I2CONCLR (13); // 清除SI这将使硬件释放总线并复位STO g_transaction.error I2C_ERR_BUS; g_transaction.complete 1; return; } // 根据状态码的高4位或已知范围分派到不同模式的处理函数 // 状态码是有规律的例如 // 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38 属于主模式相关 // 0x40, 0x48, 0x50, 0x58 属于主接收模式 // 0x60, 0x68, 0x70, 0x78, 0x80, 0x88, 0x90, 0x98, 0xA0 属于从接收模式 // 0xA8, 0xB0, 0xB8, 0xC0, 0xC8 属于从发送模式 if ((status 0x08 status 0x38) || status 0x40 || status 0x48 || status 0x50 || status 0x58) { // 主模式相关状态 i2c_master_state_handler(status); } else if ((status 0x60 status 0xA0) || (status 0xA8 status 0xC8)) { // 从模式相关状态 i2c_slave_state_handler(status); } else { // 未知状态按错误处理 I2CONSET (14); // 尝试发送STOP I2CONCLR (13); g_transaction.error I2C_ERR_UNKNOWN_STATE; g_transaction.complete 1; } }4.2 主模式发送/接收完整示例下面我们实现一个相对完整的主模式发送函数及其对应的状态处理。这里采用查询方式非中断简化示例但逻辑与中断处理完全一致。// 假设已正确初始化I2C外设设置了时钟频率并使能了中断如果使用 I2C0CONSET (16) | (12); // I2EN1, AA1 (初始使能从机应答即使我们暂时做主模式) int i2c_master_transmit(uint8_t slave_addr, uint8_t *data, uint16_t len) { // 1. 等待总线空闲在实际应用中最好有超时机制 while ((I2C0STAT 0xF8) ! 0xF8); // 状态为0xF8表示空闲 // 2. 设置传输上下文如果是中断驱动则设置全局变量 // 3. 发起START条件 I2C0CONSET (15); // 设置STA位 // 注意这里不直接清SI因为设置STA后硬件会在总线空闲后发送START并自动进入状态0x08置位SI。 // 我们需要等待SI置位然后进入状态处理循环。 // 4. 状态处理循环这里用查询模拟中断服务 uint8_t status; uint16_t idx 0; uint8_t error 0; while (!error idx len) { // idx len 是因为地址算一个“操作” // 等待SI置位表示硬件已完成一个动作等待我们响应 while ((I2C0CONSET (13)) 0); // 等待SI1 status I2C0STAT 0xF8; // 读取状态码 switch (status) { case 0x08: // START已发送 // 发送SLAW I2C0DAT (slave_addr 1) | 0; // 写方向 I2C0CONCLR (15) | (13); // 清除STA和SI位 break; case 0x18: // SLAW已发送收到ACK if (idx len) { // 还有数据要发送 I2C0DAT data[idx]; I2C0CONCLR (13); // 清除SI } else { // 所有数据已发送发送STOP I2C0CONSET (14); // 设置STO I2C0CONCLR (13); // 清除SI // 传输完成退出循环 idx; // 让循环条件满足退出 } break; case 0x28: // 数据字节已发送收到ACK // 处理逻辑与0x18完全相同 if (idx len) { I2C0DAT data[idx]; I2C0CONCLR (13); } else { I2C0CONSET (14); I2C0CONCLR (13); idx; } break; case 0x20: // SLAW发送后收到NACK case 0x30: // 数据发送后收到NACK // 从机无应答错误处理 I2C0CONSET (14); // 发送STOP释放总线 I2C0CONCLR (13); error 1; // 标记错误 break; case 0x38: // 仲裁丢失 // 在多主系统中可以尝试重试。这里简单处理为错误。 I2C0CONCLR (15) | (13); // 清除STA和SI放弃总线 error 1; break; default: // 遇到未处理的状态发送STOP并报错 I2C0CONSET (14); I2C0CONCLR (13); error 1; break; } } // 5. 等待STOP条件真正发出总线恢复空闲 while ((I2C0STAT 0xF8) ! 0xF8); return error ? -1 : 0; // 返回执行结果 }关键提示上述代码是查询阻塞式的仅用于演示状态处理逻辑。在实际产品中强烈建议使用中断驱动配合状态机的方式。将switch-case逻辑放在中断服务程序ISR中主程序只需启动传输设置STA然后等待一个由ISR设置的“完成标志”即可这样不会阻塞系统。4.3 从模式中断服务例程要点从模式通常是完全中断驱动的因为MCU不知道主设备何时会访问自己。// 从设备接收数据示例中断服务程序片段 void i2c_slave_state_handler(uint8_t status) { switch(status) { case 0x60: // 收到自身地址SLAWACK已发 // 主设备要写数据给我们 g_rx_index 0; // 准备接收数据并应答 (AA1) I2C0CONSET (12); // 设置AA1 I2C0CONCLR (13); // 清除SI break; case 0x80: // 已寻址收到数据字节ACK已发 // 读取数据 g_rx_buffer[g_rx_index] I2C0DAT; if (g_rx_index RX_BUFFER_SIZE) { // 缓冲区快满了下一个字节发NACK I2C0CONCLR (12); // 清除AA (AA0) } else { // 继续接收 I2C0CONSET (12); // 设置AA1 } I2C0CONCLR (13); // 清除SI break; case 0x88: // 已寻址收到数据字节但我们发了NACK // 读取最后一个数据 g_rx_buffer[g_rx_index] I2C0DAT; // 此时传输可能因NACK而结束或者主设备会发STOP // 我们只需清除SI并保持AA0或根据情况重置 I2C0CONCLR (13); // 清除SI // 可以在这里设置一个“数据包接收完成”标志 g_rx_complete 1; break; case 0xA0: // 收到STOP或重复START // 本次传输结束。可以在这里处理接收到的完整数据包。 // 重置AA以准备下一次寻址如果需要继续作为从设备 I2C0CONSET (12); // AA1 I2C0CONCLR (13); // 清除SI break; // ... 处理其他从模式状态 } }5. 高级话题与避坑指南从理论到工业级稳定手册的后半部分描述了一些特殊状态和复杂场景这些往往是导致I2C通信不稳定的元凶。5.1 总线错误恢复 (Status 0x00)当在非法位置如数据传输过程中检测到START或STOP条件时会发生总线错误。硬件会自动进入状态0x00并置位SI。处理方法根据手册Table 138唯一的恢复方法是设置STO1然后清除SI。注意这里设置STO并不会在总线上产生一个STOP脉冲因为总线状态已经异常而是让硬件内部复位并释放SDA和SCL线使其恢复到“未寻址的从模式”。这是一个纯软件恢复操作。避坑点在状态0x00下不要尝试操作I2DAT寄存器也不要设置STA。严格按照STO1, SI0的步骤操作。处理完后总线理论上应恢复空闲状态0xF8。你的上层应用应该重试失败的传输。5.2 仲裁丢失处理 (Status 0x38, 0x68, 0x78, 0xB0)在多主系统中当两个主机同时开始传输且地址相同时会通过仲裁决定谁拥有总线。失败的一方会检测到仲裁丢失。0x38在主模式下仲裁丢失。硬件会自动切换到从模式如果AA1。你的程序有两个选择放弃总线STA0, STO0, SI0。等待重试STA1, SI0。这样当总线再次空闲时你的硬件会自动重新发送START条件状态0x08。这是实现“重试”机制的硬件支持。0x68, 0x78, 0xB0在主模式下仲裁丢失但丢失后立即被另一个主设备寻址为自己0x68/0xB0或通用呼叫地址0x78。这时你的设备神奇地从“竞争失败的主设备”变成了“被寻址的从设备”。你的状态处理程序应该跳转到对应的从模式状态0x60或0xA8继续处理。这是LPC210x I2C硬件一个非常智能的特性意味着仲裁丢失不会导致通信中断你可能直接转为从设备进行响应。5.3 强制访问总线与总线挂起恢复手册8.11和8.12节描述了两种极端情况强制访问当总线被异常占用如一个设备崩溃持续拉低SCL或SDA你可以通过同时设置STA和STO位然后清除SI来尝试“重置”本地I2C接口的状态并强制发起一次START。这不会在物理总线上产生STOP但能让你的控制器从软件层面尝试恢复。SCL被拉低如果SCL线被某个设备持续拉低时钟延展过长或设备故障I2C硬件无法解决。必须由拉低SCL的那个设备释放线路。这通常需要硬件排查或设备复位。SDA被拉低如果SDA线在总线空闲时被拉低当你尝试发送STARTSTA1时硬件会检测到SDA为低而SCL为高这不是一个合法的起始条件。此时硬件会自动在SCL上产生额外的时钟脉冲最多9个试图“帮助”那个拉低SDA的设备完成其未完成的数据传输或释放SDA。一旦SDA被释放硬件会立即发送正常的START。这个过程完全由硬件完成无需软件干预体现了其强大的容错能力。5.4 通用呼叫地址支持通用呼叫地址0x00是一种广播地址。如果你希望你的从设备响应广播需要将I2ADR寄存器的GC位位0设置为1。当收到地址0x00时会进入状态0x70从接收模式通用呼叫或相应的状态。这在需要对总线上所有从设备进行统一配置如复位、设置地址时非常有用。6. 调试技巧与常见问题排查实录即使理解了所有状态调试I2C仍然可能令人沮丧。以下是我总结的实战排查清单问题一通信完全无反应SCL/SDA无波形。检查1电源和上拉电阻。确保I2C总线有上拉电阻通常4.7kΩ到10kΩ且设备供电正常。检查2引脚配置。确认I2C引脚SDA, SCL已正确配置为开漏模式Open-Drain且上拉使能。LPC210x的GPIO需要设置正确的引脚功能选择寄存器PINSELx。检查3时钟使能。确认给I2C外设的PCLK时钟已使能在PCONP寄存器中。检查4I2EN位。这是最容易被忽略的必须在I2CONSET中设置I2EN1否则接口彻底关闭。问题二能发出START和地址但收不到ACK总是进入0x20或0x48状态。检查1从机地址。确认发送的7位地址左移一位后最低位方向位0为写1为读是否正确。检查2从机设备。确认从机设备已上电工作正常且地址匹配。用逻辑分析仪抓取波形看从机是否在ACK时钟脉冲期间拉低了SDA。检查3总线电容与速度。如果总线过长或负载电容过大可能导致信号边沿太慢从机无法正确识别。尝试降低I2C时钟频率通过配置I2SCLH和I2SCLL寄存器。问题三通信随机失败有时成功有时不成功。检查1中断嵌套与优先级。确保I2C中断服务程序执行时间足够短且没有被更高优先级中断长时间打断。在ISR中谨慎使用浮点运算或复杂函数调用。检查2状态处理完整性。确保对每一个状态码都进行了处理并且无一例外地清除了SI位。遗漏清SI是导致“卡死”的罪魁祸首。检查3变量共享与volatile。在中断和主程序之间共享的变量如缓冲区索引、完成标志必须用volatile关键字声明并在访问临界区时考虑简单的互斥如关闭中断。检查4电源噪声。使用示波器观察SDA和SCL线上的波形看是否有明显的毛刺或振铃。加强电源滤波或在总线上串联小电阻如22Ω到100Ω以抑制信号反射。问题四作为从设备无法被寻址。检查1AA位。在从设备初始化时必须设置AA1应答自身地址否则硬件不会应答任何寻址。检查2I2ADR寄存器。确认写入I2ADR的地址是否正确高7位。检查3总线冲突。如果总线上有另一个设备地址相同会导致不可预知的行为。调试利器状态码打印在开发初期一个极其有效的方法是将I2STAT的状态码通过串口打印出来。创建一个简单的函数在每次I2C中断时打印出进入中断时的状态码。然后对照手册的表格你可以清晰地看到状态机是否按照你预期的路径在运行是在哪个状态出了问题。这比盲目猜测波形要高效得多。最后记住I2C状态机编程的本质是“与硬件对话”。手册中的表格就是你们的“对话脚本”。你的代码不需要创造什么只需要忠实地、及时地响应硬件抛出的每一个状态。当你能够流畅地完成这场对话时LPC210x的I2C接口就会成为你手中稳定而强大的工具。