首页 / 资讯中心 / 文章详情

STM32F103驱动AT24C02:I2C协议详解与软硬件实现

STM32F103驱动AT24C02:I2C协议详解与软硬件实现 ★ FEATURED ARTICLE
1. 项目拆解为什么 I2C 和 AT24C02 是嵌入式绕不开的组合1.1 AT24C02 到底能解决什么问题做 STM32F103 开发迟早会碰上一类需求设备停机重启之后有些数据不能丢。比如用户设置的亮度档位、设备唯一编号、出厂校准参数、运行次数统计。RAM 一掉电全清零Flash 虽然能存数据但擦写次数有限、按扇区操作太重这时候就需要一颗 EEPROM。AT24C02 是市面上最普及的串行 EEPROM 之一容量 2Kbit 也就是 256 字节接口就是 I2C两个引脚搞定通信8 引脚 SOP 封装小板子一画就完事成本也就几毛钱。这个项目选 STM32F103 AT24C02 的组合有个很现实的原因I2C 是嵌入式系统里出现频率最高的总线协议之一而 AT24C02 又是协议最简单、最不容易出错的 I2C 从设备。把这一套跑通后面接 OLED、接传感器、接其它 I2C 从机套路基本都一样。所以这个组合不只是存数据更是一块练手 I2C 协议的标准实验板。1.2 硬件 I2C 与软件模拟 I2C到底怎么选这部分争议很大。网上很多帖子直接说 STM32F103 的硬件 I2C 有 bug建议一律用 GPIO 模拟。我的结论是F103 的硬件 I2C 确实存在一些容易踩的坑比如事件标志位处理不当会导致卡死旧版标准外设库里也确实出现过一些兼容性问题但用 HAL 库实现硬件 I2C、把超时机制写好后量产项目里我用得很多没出过问题。真正的问题在于很多人对 I2C 时序理解不到位硬件 I2C 一旦出错黑盒式的问题排查比软件模拟难得多。软件模拟 I2C 的好处是时序完全可控、任意引脚都能用、出错能直接看波形和代码逻辑调试体验非常友好。坏处是占用 CPU、没有硬件自动处理时序高频率大批量传输时效率不如硬件外设。我的建议很简单学习阶段和产品原型阶段先用软件模拟把协议吃透等逻辑完全验证通了再根据需求换硬件 I2C 或者继续用模拟版两者代码结构基本一致。这篇文章两种方案都会给方便你对比和取舍。1.3 这个项目能让你建立哪些能力通过这一套完整的读写流程你能一次性把几个关键技能点打穿看 I2C 时序图、理解起始条件和停止条件、搞懂应答信号、明白从机地址怎么拼、理解页写边界陷阱、学会排查总线死锁。这些能力是相通的解决了 AT24C02你再看其它 I2C 器件的 datasheet 会顺畅很多。项目难度定位在中低档适合刚会用 STM32 点灯、想进入总线通信领域的开发者也适合需要写驱动移植到其它 MCU 的老手快速上手。2. 硬件电路上拉电阻与器件地址小细节决定成败2.1 AT24C02 的引脚功能与硬件地址配置AT24C02 一共 8 个引脚正面朝上左下角是 A0逆时针依次是 A1、A2、GND、SDA、SCL、WP、VCC。A0/A1/A2 是硬件地址选择引脚直接决定这颗芯片在 I2C 总线上的地址。把这三个引脚全部接地器件地址就是 1010000写地址字节是 0xA0读地址字节是 0xA1如果 A0 接高电平写地址就变成 0xA2读地址是 0xA3。一条 I2C 总线上最多可以挂 8 颗 AT24C02靠的就是这三个引脚组合出不同地址。WP 是写保护引脚接低电平代表允许写入接高电平或悬空时整颗芯片只读。很多新手第一次调不通查了半天发现是 WP 引脚悬空导致写入失败读出来全是 0xFF。我的习惯是把 WP 直接接 GND只有产品量产阶段需要防误写时才用 GPIO 控制它。VCC 接 3.3V 或 5V 都可以AT24C02 工作电压范围一般是 1.8V 到 5.5V。但要注意电平范围宽不代表你可以随便混接这涉及到后面提到的总线电平匹配问题。2.2 I2C 上拉电阻阻值怎么算I2C 总线标准规定总线上必须要有上拉电阻SCL 和 SDA 各一颗把线拉高到电源电压。STM32F103 的 I2C 引脚虽然内部有上拉但强度不足外部电阻不能省。选电阻值要考虑两件事上升时间和灌电流能力。I2C 标准模式100kHz要求的上升时间不超过 1000ns快速模式400kHz要求不超过 300ns。上升时间由 RC 决定R 是上拉电阻C 是总线电容PCB 走线、引脚电容、从机输入电容累加典型值在 20pF 到 50pF 之间。以 50pF 总线电容、标准模式 100kHz 为例R 的最大值可以这样估算上升时间约等于 0.85 倍的 R 乘 C那么 R 不能超过 1000ns 除以 0.85 再除以 50pF算出来大约 23k 欧。再看下限I2C 协议要求器件输出低电平时能拉出 3mA 电流以 3.3V 电源、低电平最大 0.4V 计算R 最小不低于 3.3V 减 0.4V 再除以 3mA约 1k 欧。所以 1k 到 23k 之间都合规实际工程最常用的是 4.7k 和 10k如果总线上挂的设备多、走线长用 2.2k 更稳。我的默认选择是 4.7k调试时偶尔用 10k 也没问题。2.3 5V 与 3.3V 电平匹配怎么处理这里必须单独说因为很多人在这里翻车。STM32F103 是 3.3V 器件但它的很多引脚是 5V 容忍的I2C 引脚接 4.7k 上拉到 5V 也能工作。难点在于 AT24C02 在系统里可能单独用 5V 供电而主控是 3.3V这时候 SDA 和 SCL 的高电平是 5V直接接到主控引脚上虽然 F103 的 PB6/PB7 能容忍 5V但如果换其它不兼容 5V 的 MCU就存在烧引脚的风险。稳妥做法有两种方案一是所有器件统一 3.3V 供电上拉电阻接 3.3V简单可靠也是我推荐新手首选方案二是从机 5V、主机 3.3V此时上拉电阻可以用 5V但需要在 I2C 线上串 330 欧到 1k 欧的限流电阻或者用电平转换芯片分板卡两侧。热词里提到的STM32F103 5V 转 3.3V 电路大多数场景指的就是这种电平适配问题不要只想着稳压芯片要先梳理清楚总线电平域。2.4 最小系统与调试工具准备STM32F103 最小系统并不复杂主控芯片、8MHz 晶振加两个负载电容、复位电路、3.3V 供电、SWD 下载接口再加一个去耦电容阵列。AT24C02 挂在 I2C1 的 PB6SCL和 PB7SDA上外围就是两颗 4.7k 上拉电阻VCC 接 3.3VA0/A1/A2/WP 全部接地这个电路画完不超过十分钟。调试工具方面ST-Link V2 配合 Keil MDK 的 SWD 模式足够日常开发和下载。但排查 I2C 时序问题强烈建议准备一个逻辑分析仪二三十块钱的 8 通道版本就够用能够直接抓取波形确认起始条件、地址字节、ACK 应答是否规范。模拟器软件只适合验证协议逻辑不能替代真实波形排查问题。3. 协议拆解起始、应答、页写与读流程逐个击破3.1 三个基础时序起始条件、停止条件、字节传输规则I2C 通信的所有操作都建立在三个基础时序上。起始条件START是 SCL 为高电平期间SDA 发生一个高到低的跳变表示总线开始传输停止条件STOP是 SCL 为高电平期间SDA 发生一个低到高的跳变表示传输结束。这两个条件在每个完整通信帧里都会出现但要注意读操作中间还会用到重复起始条件这是在 SCL 为高电平时 SDA 再次从高拉低不需要先发停止条件就能重新发起一次通信。字节传输规则是每个字节 8 位高位在前MSB firstSDA 上的数据在 SCL 高电平期间必须保持稳定只有在 SCL 低电平期间才能变化。第 9 个时钟周期是应答位发送方释放 SDA接收方拉低 SDA 表示 ACK不拉表示 NACK。对于 AT24C02主机发送完每个字节后都要等待从机拉低 SDA 返回 ACK这个等待过程如果超时通常意味着地址错误、芯片没上电或者时序有问题。3.2 器件地址字节0xA0、0xA1、0x50别搞混AT24C02 的地址字节是 8 位由四部分组成固定的 1010、A2、A1、A0、以及读写位。A0/A1/A2 全部接地时7 位地址是 0x50加上写位 0 变成 0xA0加上读位 1 变成 0xA1。这里要注意很多资料说器件地址是 0x50那是 7 位地址你在代码里实际调用 I2C 发送函数时发的是 8 位地址字节写地址是 0xA0读地址是 0xA1。如果混用最常见的结果就是写不进去或者读不出来。页写和读操作里还会出现重复起始条件后的第二次地址发送这时要把读写位翻过来。比如先发写命令和寄存器地址接着以重复起始条件重新发送器件地址读写位置 1 变成读模式。这个转换如果漏了总线会一直停留在写状态读操作自然失败。3.3 写操作字节写和页写跨页回卷是最大陷阱AT24C02 支持两种写模式。字节写最简单主机发起始条件发送写地址字节再发送存储地址然后发送一个字节数据等待 ACK最后发停止条件。连续写就是页写一次最多写 8 个字节因为 2Kbit 的芯片被分成 32 页每页 8 字节。页写最大的坑是跨页回卷。比如当前存储地址为 0x07你尝试连续写 4 个字节芯片并不会把 0x08 之后的字节写进去而是从 0x07 写入第一个字节后后续数据直接回到 0x00 继续写把页面起始位置覆盖掉。预防方法是在写之前判断空间是否足够检查当前地址的低 3 位加上本次写入长度是否超过 8如果超过就拆分两次写或者干脆退化成单字节循环写。我写的驱动函数里都会带这个跨页检查建议你也养成这个习惯。3.4 读操作立即地址读、选择性读、连续读读操作比写操作稍微绕一点因为有总线方向切换。立即地址读发送读地址后直接读当前指针位置的数据适合读取上次访问结束处的字节实际用得不多。选择性读是标准用法主机先发写地址字节加存储地址指定要读的位置然后重复起始条件发读地址字节之后主机转为接收模式读取 SDA 上的数据。连续读就是在选择性读的基础上主机持续接收数据每收一个字节回 ACK直到想停止时回 NACK 然后发停止条件。主机在读模式下每接收完一个字节后要主动控制应答位最后一个字节发送 NACK告诉从机不需要了然后停止。如果漏掉 NACK 直接发停止条件虽然从机也可能正常停止但严谨的时序应该先 NACK 再 STOP。我见过有人读全部 256 字节时最后一个字节还在回 ACK结果从机继续发送总线状态就乱了。4. 代码实现从工程搭建到读写函数完整落地4.1 Keil MDK 工程搭建与 ST-Link 下载配置工程创建这步本身不难但配置项目一多就容易乱。先打开 Keil MDK新建工程选择具体芯片型号比如 STM32F103C8T6然后配置 RCC 时钟外部 8MHz 晶振经过 PLL 倍频到 72MHz 系统主频APB2 总线 72MHzAPB1 总线 36MHzI2C1 外设挂在 APB1 上它的输入时钟就是 36MHz。这个时钟频率会直接影响 I2C 波特率计算后面配置 I2C 外设时要用到。GPIO 配置有两个关键点PB6 和 PB7 要配置为复用开漏输出同时使能外部上拉但最终高电平靠外部 4.7k 电阻保证。注意不是推挽输出I2C 引脚必须是开漏结构否则多个器件同时操作总线时会直接短路。配置好时钟和 GPIO 后如果需要用硬件 I2C 外设还要在 RCC 中使能 I2C1 时钟。ST-Link 这边SWD 接口接 SWDIO、SWCLK、GND、3.3V 四根线就行MDK 的 Debug 选项里选择 ST-Link DebuggerSettings 里确认能识别到芯片下载算法选择对应 Flash然后编译烧录。4.2 软件模拟 I2C 版本最稳的兼容方案先给软件模拟版的完整实现。这个方案的优点是不依赖具体 I2C 外设把任意两个 GPIO 拉出来就能通信调试时可读性和可控性都非常好。核心代码如下// 软件模拟 I2C 引脚定义可自由修改 #define I2C_SCL_PORT GPIOB #define I2C_SCL_PIN GPIO_PIN_6 #define I2C_SDA_PORT GPIOB #define I2C_SDA_PIN GPIO_PIN_7 #define I2C_SCL_HIGH() I2C_SCL_PORT-BSRR I2C_SCL_PIN #define I2C_SCL_LOW() I2C_SCL_PORT-BRR I2C_SCL_PIN #define I2C_SDA_HIGH() I2C_SDA_PORT-BSRR I2C_SDA_PIN #define I2C_SDA_LOW() I2C_SDA_PORT-BRR I2C_SDA_PIN #define I2C_SDA_READ() (I2C_SDA_PORT-IDR I2C_SDA_PIN)引脚配置为开漏输出模式置高时引脚释放由外部上拉电阻拉高置低时引脚拉低输出低电平。读取 SDA 状态时直接读 IDR 寄存器即可因为开漏模式下引脚可以双向使用。void I2C_Delay(void) { // 标准模式 100kHz 下约 5us 延时可根据主频调整空循环次数 volatile uint32_t i; for (i 0; i 30; i) __NOP(); } void I2C_Start(void) { I2C_SDA_HIGH(); I2C_SCL_HIGH(); I2C_Delay(); I2C_SDA_LOW(); // SCL 高电平期间 SDA 拉低产生起始条件 I2C_Delay(); I2C_SCL_LOW(); I2C_Delay(); } void I2C_Stop(void) { I2C_SDA_LOW(); I2C_SCL_HIGH(); I2C_Delay(); I2C_SDA_HIGH(); // SCL 高电平期间 SDA 拉高产生停止条件 I2C_Delay(); } void I2C_SendByte(uint8_t data) { uint8_t i; for (i 0; i 8; i) { if (data 0x80) I2C_SDA_HIGH(); else I2C_SDA_LOW(); data 1; I2C_Delay(); I2C_SCL_HIGH(); I2C_Delay(); I2C_SCL_LOW(); } } uint8_t I2C_WaitAck(void) { uint8_t timeout 100; I2C_SDA_HIGH(); // 释放 SDA等待从机拉低 I2C_Delay(); I2C_SCL_HIGH(); I2C_Delay(); while (I2C_SDA_READ() timeout--) I2C_Delay(); I2C_SCL_LOW(); I2C_Delay(); return (timeout 0); // 0 表示收到 ACK1 表示超时无 ACK } uint8_t I2C_ReceiveByte(uint8_t ack) { uint8_t i, data 0; I2C_SDA_HIGH(); for (i 0; i 8; i) { data 1; I2C_SCL_HIGH(); I2C_Delay(); if (I2C_SDA_READ()) data | 0x01; I2C_SCL_LOW(); I2C_Delay(); } // 第 9 个时钟发送应答位ack1 发送 ACKack0 发送 NACK if (ack) I2C_SDA_LOW(); else I2C_SDA_HIGH(); I2C_Delay(); I2C_SCL_HIGH(); I2C_Delay(); I2C_SCL_LOW(); I2C_SDA_HIGH(); I2C_Delay(); return data; }这个 I2C 底层完成后AT24C02 的读写函数就很直白了。字节写、页写、字节读、连续读函数如下uint8_t AT24C02_WriteByte(uint8_t addr, uint8_t data) { I2C_Start(); I2C_SendByte(0xA0); // 写地址 if (I2C_WaitAck()) { I2C_Stop(); return 1; } I2C_SendByte(addr); // 存储地址 if (I2C_WaitAck()) { I2C_Stop(); return 1; } I2C_SendByte(data); // 数据 if (I2C_WaitAck()) { I2C_Stop(); return 1; } I2C_Stop(); AT24C02_WaitWriteDone(); // 等待内部写周期完成 return 0; } uint8_t AT24C02_WritePage(uint8_t addr, uint8_t *buf, uint8_t len) { uint8_t i; // 跨页边界检查当前地址低 3 位写入长度不能超过 8 字节页大小 if ((addr 0x07) len 8) return 2; I2C_Start(); I2C_SendByte(0xA0); if (I2C_WaitAck()) { I2C_Stop(); return 1; } I2C_SendByte(addr); if (I2C_WaitAck()) { I2C_Stop(); return 1; } for (i 0; i len; i) { I2C_SendByte(buf[i]); if (I2C_WaitAck()) { I2C_Stop(); return 1; } } I2C_Stop(); AT24C02_WaitWriteDone(); return 0; } uint8_t AT24C02_ReadByte(uint8_t addr) { uint8_t data; I2C_Start(); I2C_SendByte(0xA0); I2C_WaitAck(); I2C_SendByte(addr); I2C_WaitAck(); I2C_Start(); // 重复起始条件切换为读模式 I2C_SendByte(0xA1); I2C_WaitAck(); data I2C_ReceiveByte(0); // 最后一个字节回 NACK I2C_Stop(); return data; } void AT24C02_ReadBuffer(uint8_t addr, uint8_t *buf, uint8_t len) { uint8_t i; I2C_Start(); I2C_SendByte(0xA0); I2C_WaitAck(); I2C_SendByte(addr); I2C_WaitAck(); I2C_Start(); I2C_SendByte(0xA1); I2C_WaitAck(); for (i 0; i len; i) { // 最后一个字节回 NACK其余回 ACK buf[i] I2C_ReceiveByte(i (len - 1)); } I2C_Stop(); }写周期等待函数有两种实现。一种简单粗暴写完直接延时 5ms。AT24C02 的 datasheet 标注内部写周期最大 5ms延时 5ms 就一定安全。另一种更智能写完后再发一次写地址从机如果还在忙就不会拉低 SDA 应答直到内部写周期完成。第二种方法在连续写入大量数据时能节省很多时间我强烈推荐实现如下void AT24C02_WaitWriteDone(void) { uint8_t timeout; I2C_Start(); I2C_SendByte(0xA0); timeout 1000; // 从机忙时不返回 ACK循环等待直到 ACK 出现 while (I2C_WaitAck() timeout--) { I2C_Stop(); I2C_Delay(); I2C_Start(); I2C_SendByte(0xA0); } I2C_Stop(); }注意这个函数里每次等待 ACK 失败后要先发停止条件再重新起始不能直接从等待状态切到起始条件否则总线状态是乱的。4.3 硬件 I2CHAL 库版本实现对比如果用 CubeMX 生成工程I2C1 外设配置成 I2C 模式时钟设为 100kHz7 位地址模式。寄存器配置数值不用手算CubeMX 会自动根据 APB1 时钟 36MHz 计算出对应分频系数。初始化完成后读写 AT24C02 的代码甚至比软件模拟更短// 字节写 HAL_I2C_Mem_Write(hi2c1, 0xA0, reg_addr, I2C_MEMADD_SIZE_8BIT, data, 1, 100); // 页写 HAL_I2C_Mem_Write(hi2c1, 0xA0, reg_addr, I2C_MEMADD_SIZE_8BIT, buf, len, 100); // 读单字节 HAL_I2C_Mem_Read(hi2c1, 0xA1, reg_addr, I2C_MEMADD_SIZE_8BIT, data, 1, 100); // 连续读 HAL_I2C_Mem_Read(hi2c1, 0xA1, reg_addr, I2C_MEMADD_SIZE_8BIT, buf, len, 100);这里的 100 是超时毫秒数写完后仍然建议调用一次 AT24C02_WaitWriteDone 逻辑或者直接 HAL_Delay(5)。硬件 I2C 的问题通常出现在 HAL_I2C_Mem_Write 返回 HAL_BUSY 或 HAL_ERROR 时这时候不要盲目重试先把状态码打出来看是总线忙还是应答失败。HAL 库内部有自己的重试机制但底层硬件状态一旦锁死软件重试救不回来需要重新初始化 I2C 外设甚至复位 MCU。性能对比上看软件模拟 I2C 在 72MHz 主频下实现 100kHz 时序没问题但单字节读写需要大量空循环适合低频操作硬件 I2C 有移位寄存器和中断支持连续读写效率高得多。但就 AT24C02 这种几百字节的小容量存储应用场景两者差别感知不强可靠性才是第一位的。4.4 主流程测试与数据校验写主循环测试时一个经典的验证方法是先往固定地址写一组数据比如 0xAA然后读出来比对再把整个 256 字节区域全部写入递增数读出校验。比较完整的测试流程如下int main(void) { uint8_t write_buf[16]; uint8_t read_buf[16]; uint8_t i; // 初始化时钟、GPIO、I2C略 // 测试 1单字节读写 AT24C02_WriteByte(0x10, 0xA5); if (AT24C02_ReadByte(0x10) 0xA5) // 单字节读写 OK // 测试 2页写 for (i 0; i 8; i) write_buf[i] i 1; AT24C02_WritePage(0x20, write_buf, 8); AT24C02_ReadBuffer(0x20, read_buf, 8); // 逐字节比对 read_buf 和 write_buf // 测试 3掉电保存验证 // 运行完写操作后断电重新上电再读取同一地址 // 若数据不变说明 EEPROM 保存成功 }测试 3 是很多人忽略的一步。代码里加一个串口或调试输出上电复位后读取 0x10 地址如果能读到上一次写入的 0xA5说明 EEPROM 真正完成了数据保持。这个流程看似简单却能把大多数问题暴露出来。5. 调试实录卡死、0xFF、掉电丢失排查记录5.1 程序卡死在事件标志位等待先查硬件用硬件 I2C 时最经典的现象是程序卡在 I2C 事件等待里。我调试时的固定套路是三个方向排查先看硬件连接SCL 和 SDA 有没有接反再看上拉电阻万用表量 SCL 对 VCC 的阻抗正常应该是 4.7k 左右最后看地址把示波器或逻辑分析仪接到 SDA 上触发一次通信看主机发出的地址字节是不是 0xA0。软件模拟 I2C 卡死的概率低很多但如果卡在 I2C_WaitAck 的超时循环里说明第一个地址字节就没有从机应答。检查顺序不变但可以先把引脚模式配置打印出来确认一下有些人初始化时把 SDA 配成了推挽输出这会直接导致读不到从机的低电平应答。开漏输出这个点是初学者翻车率最高的地方。5.2 读回全是 0xFF多半是写入没成功读到的数据全是 0xFF先别怀疑读函数大概率是写操作失败了。最常见的原因包括WP 引脚悬空或接高导致写保护写入后没有等待 5ms 写周期就开始读地址字节发错写到了错误的存储位置。还有一种情况是电平问题上拉电阻接到 5VSTM32 引脚容忍 5V 但信号质量差写入时部分字节校验失败。排查时可以反过来做先用软件模拟 I2C 写一个固定值然后用硬件 I2C 去读再反过来交叉验证。如果模拟写的能读出来硬件写的不行基本可以确定是硬件 I2C 的波特率配置或事件标志处理有问题。这种方法虽然土但是高效。5.3 掉电数据丢失写周期和时序要背锅数据写入时明明校验通过掉电后重新上电却丢数据这种情况最常见的原因是读写太快。AT24C02 内部写周期是自定时完成的数据并不是主机收到 ACK 后就立刻落盘而是还在内部缓存里需要几毫秒搬到非易失存储单元。如果主机发出停止条件后立即断电内部写周期根本来不及完成数据自然丢了。处理方案很简单每次写操作后必须等待写周期完成。用轮询 ACK 方式最可靠但如果你的代码里有低功耗休眠逻辑要注意在休眠前确保 EEPROM 写周期已经完成否则断电瞬间数据还是会丢。我的经验是所有对 EEPROM 的写操作统一封装成一个接口函数内部强制等待外部不允许绕过。5.4 用逻辑分析仪看 I2C 时序的实战经验逻辑分析仪是排查 I2C 问题最有效的工具几十块钱的 8 通道版本足够用开采样率到 1MHz 以上抓波形。抓 START 条件是最直观的正常情况下 SCL 高电平期间 SDA 下拉一个明显的低脉冲然后 SCL 开始翻转。如果 SDA 下降沿发生时 SCL 处于低电平那这个不是 START而是普通的数据位变化说明主机发送时序有问题。再看地址字节和数据字节I2C 协议是高位在前逻辑分析仪解码后应该直接显示 0xA0、0x00 这类值。如果解码显示乱码检查采样率是不是太低或者探头夹错了线。检查 ACK 位时重点关注第 9 个时钟周期 SDA 电平应该被从机拉低如果从机不应答SDA 保持高电平说明器件地址不对或者芯片没上电。解码器显示从机地址冲突时去查 A0/A1/A2 引脚电平与实际发送的地址是否一致。读到 0xFF 的波形还有一个典型特征主机明明发了 NACK第 9 个时钟 SDA 保持高但解码结果还是显示了多余的数据字节这是因为从机在收到 NACK 后不应再发送数据但时序错乱时从机仍然会多发一个字节。出现这种情况直接看第 9 个时钟的 SDA 状态就能定位。这个项目做完之后你可以做两个方向扩展一是把 AT24C02 的读写逻辑封装成通用驱动模块后面换用更大容量的 AT24C64 或其它 I2C EEPROM 时只需修改地址字节二是在同一条 I2C 总线上再挂一个设备比如 0.9 寸 OLED 屏实测一下多设备共存时地址冲突和总线负载的问题。我在实际项目里的习惯是先用软件模拟 I2C 把功能逻辑验证清楚再评估是否切换硬件 I2C而不是一开始就陷进硬件外设的配置细节里。EEPROM 读写这件事真正难的从来不是那几行代码而是你对时序的理解和对硬件的耐心。
阅读完成 · 觉得有帮助?
咨询建站