简介这份中文资料围绕I2C接口存储器24C256的说明与使用展开面向单片机开发初学者及需要驱动该芯片的嵌入式工程师重点解决连续存储时地址管理不当导致的数据覆盖与写入错误问题。压缩包内共1个doc文档约33KB以文字讲解配合代码片段的形式呈现便于边读边对照调试。资料系统梳理了24C256的存储原理、16位地址结构、页内与页地址划分、连续存储模式及IIC控制模块实现并给出MSP430F449平台下的延时、启动、停止、发送0与1、读写单字符、应答检查等基础函数示例。针对每次写入11字节在地址55处出错的现象文档分析了跨页覆盖原因提出存储块大小应取64、32、16、8、4、2的解决思路并说明不足字节补零的处理方式。目前已有1913人学习下载适合需要快速掌握24C256读写时序与排错方法的读者参考。1. 24C256 中文资料怎么用从一颗存储芯片到可复现的读写方案24C256 是一颗 256Kbit32KB的 I2C 串行 EEPROM很多做嵌入式、传感器模组、工控板卡的工程师第一次接触它往往是在一块现成板子上看到一颗 8 脚小芯片丝印写着 24C256然后开始找中文资料。问题在于网上能搜到的中文资料大多是零散的引脚图、时序截图和一段来路不明的驱动代码真正要落地时你会发现卡点不在“看不懂”而在“参数对不上、地址算错、写进去读出来是 0xFF”。这篇笔记就按一线调试顺序把 24C256 的容量换算、I2C 地址、页写边界、跨页处理、写周期等待和常见翻车点讲清楚让新手能照着跑通熟手能拿去核对边界。适合正在用 MCU、Linux 用户态 I2C 或逻辑分析仪调 EEPROM 的从业者。2. 24C256 的容量、地址与页结构先把账算对2.1 256Kbit 到底是多少字节地址线为什么是 15 根24C256 的命名里24C 是 I2C EEPROM 系列256 表示 256Kbit。换算成字节是 256 × 1024 ÷ 8 32768 字节也就是 32KB。这个容量决定了片内地址需要 15 根地址线因为 2^15 32768。很多中文资料只写“32K 字节”但实际访问时地址是两字节传输高字节在前低字节在后。高字节里只有低 7 位有效最高位在多数型号里是 dont care但为了兼容性建议固定写 0。这里有一个容易混淆的点24C256 的“256”不是 256 字节也不是 256KB而是 256Kbit。如果你在代码里按 256 字节去算页边界后面一定翻车。我一般会在驱动里直接定义宏#define EEPROM_24C256_SIZE_BYTES (32768U) #define EEPROM_24C256_PAGE_SIZE (64U) #define EEPROM_24C256_PAGE_MASK (EEPROM_24C256_PAGE_SIZE - 1U)逻辑说明SIZE_BYTES 用于边界检查PAGE_SIZE 用于跨页拆分PAGE_MASK 用于计算当前地址在页内的偏移。参数说明不同厂家 24C256 的页大小可能不同常见有 32 字节、64 字节、128 字节三种必须查你手上那颗料的具体手册。如果你不确定最稳妥的办法是写一个跨页测试从地址 0 开始连续写 200 字节再读回来比对如果每 64 字节断一次说明页大小是 64。2.2 7 位 I2C 地址怎么定A0/A1/A2 不是随便接的24C256 的 I2C 从机地址是 7 位高 4 位固定为 1010低 3 位由 A2、A1、A0 引脚决定。也就是说同一根 I2C 总线上最多挂 8 片 24C256地址范围是 0x50 到 0x57。注意这里说的是 7 位地址不是 8 位读写地址。很多中文资料里写“写地址 0xA0读地址 0xA1”那是把 7 位地址左移一位后再加上读写位的结果。如果你在 Linux 用户态用 i2c-tools它用的是 7 位地址所以应该写 0x50而不是 0xA0。A0/A1/A2 的接法直接决定地址。常见做法是全部接地地址就是 0x50。如果你板子上有多片就要按二进制组合去接。这里有一个血泪经验有些板子为了省事把 A0/A1/A2 直接接到 VCC 或 GND但走线很长导致地址识别不稳定。如果你发现 i2cdetect 能扫到地址但读写偶尔失败先查这三根线的电平是否干净。# Linux 下扫描 I2C 总线上的 24C256 i2cdetect -y 1 # 预期输出中会出现 0x50 到 0x57 中的一个或多个逻辑说明i2cdetect 会遍历 7 位地址空间发送快速写探测。参数说明-y 表示跳过交互确认1 是 I2C 总线编号具体编号用 i2cdetect -l 查看。如果扫不到任何地址先确认总线编号、上拉电阻和供电再确认 A0/A1/A2 是否接对。2.3 页写边界64 字节一页跨页必须拆24C256 支持页写一次 I2C 事务最多可以连续写一页。页大小常见是 64 字节。页写的内部机制是你给出起始地址然后连续送数据芯片内部地址指针会自动递增但只递增页内偏移不会自动进位到下一页。也就是说如果你从地址 0x3F 开始写 10 个字节前 1 个字节落在第 0 页的最后一个位置后面 9 个字节会回卷到第 0 页的开头覆盖掉原来的数据。这就是跨页翻车的典型现象写进去的数据顺序乱了或者老数据被意外覆盖。正确的做法是在驱动层把任意长度的写操作拆成“页内写 跨页写”的组合。下面是一个可复现的拆分逻辑int eeprom_write_bytes(uint16_t addr, const uint8_t *buf, uint16_t len) { uint16_t written 0; while (written len) { uint16_t cur_addr addr written; uint16_t page_offset cur_addr EEPROM_24C256_PAGE_MASK; uint16_t space_in_page EEPROM_24C256_PAGE_SIZE - page_offset; uint16_t chunk (len - written) space_in_page ? (len - written) : space_in_page; if (eeprom_page_write(cur_addr, buf written, chunk) ! 0) { return -1; } eeprom_wait_ready(); written chunk; } return 0; }逻辑说明每次循环计算当前地址在页内的偏移得出本页还能写多少字节然后只写这么多。写完一页后等待写周期完成再继续下一页。参数说明addr 是 15 位片内地址buf 是数据缓冲区len 是总长度。eeprom_wait_ready 的实现见下一章。这个函数不处理地址回卷因为拆分后每次 chunk 都不会跨页。3. 用 MCU 和 Linux 用户态两种方式跑通读写3.1 裸机 MCU 的 I2C 时序起始、地址、应答、写周期在裸机 MCU 上24C256 的写操作分两种字节写和页写。字节写就是一次只写一个字节页写是一次写一页。无论哪种时序都是起始条件 → 发送 7 位地址 写位 → 等待 ACK → 发送高字节地址 → 等待 ACK → 发送低字节地址 → 等待 ACK → 发送数据 → 等待 ACK → 停止条件。写完之后芯片进入内部写周期典型 5ms最大 10ms。在这段时间内芯片不会应答任何 I2C 事务所以你必须等待。等待写周期有两种做法固定延时和应答轮询。固定延时简单但效率低应答轮询是反复发送起始条件 地址 写位如果芯片回 ACK说明写周期结束。我一般用应答轮询因为不同批次的芯片写周期差异不小固定延时容易在低温或低压下不够。void eeprom_wait_ready(void) { uint32_t timeout 100000; while (timeout--) { if (i2c_start() 0) { if (i2c_send_byte(0xA0) 0) { // 7 位地址 0x50 左移一位 写位 i2c_stop(); return; } i2c_stop(); } } // 超时处理记录错误或复位 I2C 总线 }逻辑说明每次尝试发送起始条件和写地址如果收到 ACK说明芯片已经准备好。参数说明0xA0 是 7 位地址 0x50 的写方向字节如果你用的是 0x51这里就是 0xA2。timeout 是轮询次数按 I2C 速率和芯片写周期估算100000 次在 100kHz 下大约几百毫秒足够覆盖 10ms 写周期。读操作相对简单起始 → 发送地址 写位 → 发送高字节地址 → 发送低字节地址 → 重复起始 → 发送地址 读位 → 读取 N 个字节 → 最后一个字节回 NACK → 停止。注意读操作没有写周期读完就可以继续。3.2 Linux 用户态 i2c-tools 与 eeprom 驱动的读写命令在 Linux 上如果你只是临时验证可以用 i2c-tools 的 i2cget 和 i2cset。但要注意i2cset 默认是 SMBus 写字节不是 I2C 块写对 24C256 不一定适用。更可靠的方式是用 i2ctransfer它可以构造任意 I2C 消息。# 向地址 0x50 的 24C256 的片内地址 0x0000 写入 0x55 i2ctransfer -y 1 w30x50 0x00 0x00 0x55 # 从片内地址 0x0000 读取 1 字节 i2ctransfer -y 1 w20x50 0x00 0x00 r1逻辑说明w30x50 表示向 7 位地址 0x50 写 3 个字节分别是高地址、低地址、数据。r1 表示读 1 个字节。参数说明-y 跳过确认1 是总线编号。如果你要读多个字节把 r1 改成 rN。注意i2ctransfer 不会自动处理跨页和写周期连续写多字节时仍然要自己拆页和等待。如果你要在应用层做批量读写更常见的做法是内核的 at24 驱动。它会把 EEPROM 映射成一个二进制文件通常在 /sys/bus/i2c/devices/1-0050/eeprom。你可以用 dd 或 hexdump 直接读写。# 读取前 64 字节 hexdump -C /sys/bus/i2c/devices/1-0050/eeprom | head -n 4 # 写入一个字节到偏移 0x10 printf \xAA | dd of/sys/bus/i2c/devices/1-0050/eeprom bs1 seek16 convnotrunc逻辑说明at24 驱动内部已经处理了页写拆分和写周期等待所以你不需要在应用层再拆。参数说明bs1 表示块大小 1 字节seek16 表示偏移 16convnotrunc 表示不截断文件。注意eeprom 文件的大小就是芯片容量写入超出范围会报错。3.3 用逻辑分析仪抓一次完整写时序确认 ACK 和停止位如果你手上有逻辑分析仪抓一次完整写时序是最快的排错方式。重点看几个点起始条件是否干净7 位地址和读写位是否正确每个字节后的 ACK 是否拉低停止条件是否在第九个时钟后出现。常见问题是停止条件太早导致最后一个字节没写进去。另一个问题是上拉电阻太大上升沿太缓ACK 被误判。我一般会抓一次“写两个字节到地址 0x0000”的波形然后对照手册的时序图。如果 ACK 位是高说明从机没有应答先查地址和供电。如果数据位在时钟高电平期间不稳定查走线和上拉。逻辑分析仪不需要很贵几十兆采样率就够看 100kHz 的 I2C。4. 24C256 避坑与排查5 个真实翻车记录4.1 写进去读出来全是 0xFF现象用 i2cset 写了一个字节再用 i2cget 读结果是 0xFF。原因i2cset 默认走 SMBus 写字节只发送一个字节数据没有发送两字节片内地址24C256 把第一个数据字节当成了高地址后续行为完全错位。解决改用 i2ctransfer明确发送高地址、低地址、数据。或者用 at24 驱动的 eeprom 文件。4.2 连续写 100 字节只有前 64 字节对现象从地址 0 开始写 100 字节读回来发现前 64 字节正确后 36 字节是旧数据或 0xFF。原因没有拆页芯片在页内回卷后 36 字节覆盖了第 0 页开头而不是写到第 1 页。解决在驱动层按页大小拆分每写一页等待写周期。如果你用的是 at24 驱动这个问题不存在因为内核已经处理了。4.3 写完后立刻读数据没更新现象写操作返回成功但立刻读同一个地址读到的还是旧值。原因没有等待写周期。24C256 在写周期内不应答如果你在写周期结束前发起读芯片不会响应有些驱动会返回旧缓存或错误。解决每次写操作后调用 eeprom_wait_ready或者固定延时 10ms。用 at24 驱动时内核会阻塞直到写完成。4.4 i2cdetect 能扫到 0x50但读写报 Remote I/O error现象i2cdetect 显示 0x50 存在但 i2ctransfer 报 Remote I/O error。原因A0/A1/A2 的电平在扫描时是浮空的被误判为某个地址实际读写时电平被拉偏导致地址不匹配。解决检查 A0/A1/A2 是否直接接 GND 或 VCC不要浮空。如果必须浮空加上拉或下拉电阻。4.5 低温下写周期变长固定延时不够现象常温下写 10ms 延时没问题到了零下 20 度偶尔写失败。原因EEPROM 的写周期随温度降低而变长有些批次在低温下最大写周期超过 10ms。解决改用应答轮询不要用固定延时。如果必须用延时把延时加到 20ms 以上并做写后读校验。5. 进阶技巧用写后校验和磨损均衡延长 24C256 寿命24C256 的擦写寿命典型是 100 万次听起来很多但如果你每秒钟写一次同一个地址不到 12 天就用完了。实际项目中我一般会做两件事写后校验和磨损均衡。写后校验很简单每次写完一个块立刻读回来比对如果不一致重写一次最多重试 3 次。这个习惯能挡住大部分偶发写入失败尤其是电源波动或 I2C 干扰导致的位翻转。int eeprom_write_verify(uint16_t addr, const uint8_t *buf, uint16_t len) { uint8_t tmp[64]; for (int retry 0; retry 3; retry) { if (eeprom_write_bytes(addr, buf, len) ! 0) continue; if (eeprom_read_bytes(addr, tmp, len) ! 0) continue; if (memcmp(buf, tmp, len) 0) return 0; } return -1; }逻辑说明写完后立刻读回用 memcmp 比对。参数说明tmp 的大小要大于等于 len这里假设单次校验不超过 64 字节。如果 len 很大可以分块校验。重试 3 次仍然失败说明硬件有问题应该上报错误而不是继续写。磨损均衡稍微复杂一点。如果你的应用是频繁写一个配置参数不要每次都写同一个地址而是把 32KB 分成多个槽位轮流写。每个槽位带一个序号和 CRC启动时扫描所有槽位找序号最大且 CRC 正确的那个。这样写入次数被分散到整个芯片寿命从 100 万次变成 100 万 × 槽位数。常见做法是每 64 字节一个槽32KB 就是 512 个槽寿命直接拉到 5 亿次以上。还有一个细节24C256 的页写是 64 字节但如果你只写 1 个字节芯片内部仍然会擦写整个页。所以频繁写单字节时磨损均衡的收益更明显。我一般会在配置区预留 4KB 做槽位剩下的 28KB 存日志或只读数据。最后说一个我自己的习惯每次拿到一颗新的 24C256先做三件事——用 i2cdetect 确认地址用 i2ctransfer 写一个字节再读回来用逻辑分析仪抓一次波形。这三步做完后面基本不会出大问题。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?