
简介这是一份面向嵌入式开发初学者与单片机工程师的24C256存储器中文技术资料围绕I2C接口EEPROM的存储原理、地址管理与数据传输控制展开帮助读者理解并规避连续存储中常见的地址越界与数据覆盖问题。资源包内含1个doc文档压缩包约33KB篇幅精炼适合作为MSP430等平台驱动24C256时的速查与排错参考。资料重点剖析了16位地址中低6位页内地址与高9位页码的划分方式指出连续写入时若存储块大小不是64、32、16、8、4、2的整数倍数据会在页边界处回卷覆盖并给出按32字节对齐、不足补零的解决思路。同时附有MSP430F449的IIC控制模块代码片段涵盖延时、启动、停止、发送0/1、读写单字符及应答检查等函数便于读者直接对照移植。目前已有1913人学习下载适合需要快速掌握24C256页写机制与I2C底层时序的开发者参考。1. 24C256 中文资料拆解512×64 存储结构下为什么连续写 11 字节也会翻车如果你手头正调一块 I2C 的 EEPROM型号是 24C256大概率会遇到一个很玄学的现象单字节读写都正常连续写一小段数据回读却发现后半段被改写了或者干脆从本页开头重新覆盖。这份中文资料的价值就在这——它没有停在“I2C 时序怎么拉高拉低”这种层面而是直接点破了 24C256 最容易被忽视的存储地址问题。24C256 是 512×64 的存储结构总容量 32768 字节地址是 16 位整型数但有效数据只有 15 位低 6 位表示页内偏移 063高 9 位表示页码 0511。资料里给了一个很典型的反直觉例子每次连续写 11 字节地址按 0、11、22、33、44、55、66 递增看上去没问题但写到地址 55 时本页只剩 9 个字节空间凑不够 11 个芯片内部地址指针翻回本页 0 地址继续写数据就被覆盖了。这份资料适合正在用 MSP430 或类似单片机裸机驱动 24C256 的工程师尤其是那些“时序对了、ACK 也回了、数据就是不对”的现场调试人员。2. 地址位与页边界把 15 位有效地址拆成页号和页内偏移2.1 为什么 32768 字节的地址只有 15 位有效24C256 的数据容量是 32768 字节也就是 32K对应地址范围 032767。16 位地址线理论上能表示 65536 个地址但 24C256 只用到了低 15 位最高位不参与译码。资料里写得很清楚低 6 位05表示页内偏移范围 063连续的 9 位614表示页码范围 0511。512 页 × 64 字节/页 32768 字节刚好对上。这个拆分方式决定了你在写驱动时不能把地址当成一个单纯的线性整数来用。很多新手写 EEPROM 驱动习惯性地把地址直接塞进两个字节发出去然后循环写 N 个字节认为芯片会自己处理翻页。但 24C256 的连续写模式有一个硬边界一页最多 64 字节写到页尾后地址指针自动回卷到本页起始地址而不是跳到下一页。我一般会在驱动层加一个地址分解函数把线性地址拆成页号和页内偏移写之前先判断这次要写的数据会不会跨页。如果会跨页就拆成多次写每次不超过当前页剩余空间。// 地址分解从线性地址提取页号和页内偏移 // addr: 0 ~ 32767 // page: 0 ~ 511 // offset: 0 ~ 63 void addr_decode(unsigned int addr, unsigned int *page, unsigned char *offset) { *offset addr 0x3F; // 低6位页内偏移 *page (addr 6) 0x1FF; // 高9位页码 } // 计算当前地址到本页末尾还能写多少字节 // 返回值剩余可连续写的字节数1~64 unsigned char page_remain(unsigned int addr) { unsigned char offset addr 0x3F; return 64 - offset; }逻辑说明addr_decode把 16 位地址拆成页码和页内偏移方便你在调试时打印出来看当前落在哪一页。page_remain返回从当前地址到本页末尾的剩余字节数如果这个值小于你要写的长度就必须先写一部分、等 EEPROM 内部写周期完成、再重新发起下一次写。参数说明addr是线性字节地址范围 032767page和offset是输出参数分别返回页码和页内偏移。page_remain的返回值最小是 1当地址正好在页末时最大是 64当地址正好在页首时。2.2 页写模式下的地址回卷现象资料里举的例子非常典型每次写 11 字节地址序列是 0、11、22、33、44、55、66。前五次写都没问题因为 054 这段地址覆盖的页内偏移分别是 010、1121、2232、3343、4454都在第 0 页的 063 范围内。但第六次从地址 55 开始写 11 字节5565 已经超出了第 0 页的 63 边界。芯片内部地址指针在写到 63 之后自动回卷到第 0 页的 0 地址继续把剩下的字节写到 0、1、2……位置把之前存的数据覆盖掉。这个行为不是 bug是 24C256 连续写模式的固有特性。资料里给出的解决办法是如果使用连续存储模式地址安排上要使存储块的大小为 64、32、16、8、4、2除此之外都不能使用连续地址存储。如果每次采集的有效数据是 30 字节在连续存储模式下要按照 32 为单位存储不足的字节补零处理。我一般会在应用层做一个对齐写入的封装不管上层要写多少字节都按 2 的幂次对齐到页边界。比如每次写 30 字节就分配 32 字节的空间写 30 字节有效数据加 2 字节补零。这样每次写的起始地址都是 32 的倍数永远不会跨页。// 按32字节对齐写入不足补零 // p: 数据缓冲区指针 // addr: 线性地址必须是32的倍数 // len: 有效数据长度1~32 void write_aligned_32(unsigned char *p, unsigned int addr, unsigned char len) { unsigned char buf[32]; unsigned char i; // 拷贝有效数据 for(i 0; i len; i) { buf[i] p[i]; } // 不足32字节的部分补零 for(; i 32; i) { buf[i] 0x00; } // 调用底层连续写函数一次写32字节 WriteNbyte(buf, addr, 32); // 等待EEPROM内部写周期完成典型值5ms delay(10); }逻辑说明write_aligned_32先把有效数据拷贝到 32 字节的临时缓冲区不足的部分补零然后一次性写入 32 字节。因为 32 是 64 的因数只要起始地址是 32 的倍数这次写入就不会跨页。参数说明p是上层传入的数据指针addr必须是 32 的倍数否则对齐逻辑失效len是有效数据长度范围 132。delay(10)是等待 EEPROM 内部写周期完成24C256 的典型写周期是 5ms留 10ms 余量比较稳妥。3. I2C 底层驱动从 start_IIC 到 WriteNbyte 的完整链路3.1 起始、停止与位发送的时序实现资料里给出的 MSP430F449 驱动代码是典型的软件模拟 I2C 实现通过操作 P2 口的方向寄存器来控制 SDA 和 SCL 的拉高拉低。这种写法在 MSP430 上很常见因为很多型号的硬件 I2C 模块用起来限制较多软件模拟反而更灵活。先看起始条件start_IIC先把 P2OUT 的低两位清零然后通过 P2DIR 寄存器控制 SDA 和 SCL 的方向。P2DIR 某位为 1 表示输出为 0 表示输入。P2DIR0XFD把 P2.1 设为输入外部上拉电阻把 SDA 拉高P2DIR0XFB把 P2.2 设为输入SCL 也被拉高。然后P2DIR|0X02把 SDA 拉低P2DIR|0X04把 SCL 拉低。这个顺序保证了在 SCL 高电平期间 SDA 产生下降沿符合 I2C 起始条件。停止条件stop_IIC反过来先拉低 SDA再拉高 SCL最后拉高 SDA在 SCL 高电平期间产生 SDA 上升沿。发送 0 和发送 1 的区别在于 SDA 的状态send_zero先拉低 SDA再拉高 SCL再拉低 SCLsend_one先释放 SDA设为输入靠上拉电阻拉高再拉高 SCL再拉低 SCL。每个位发送后都有一个IIC_Delay()用三个_NOP()做短延时保证时序满足 24C256 的要求。// 发送一个字节MSB先行 // data_out: 要发送的字节 void send_char(unsigned char data_out) { unsigned char i, tmp 0x80; for(i 0; i 8; i) { if((data_out tmp) 0) send_one(); // 当前位为1 else send_zero(); // 当前位为0 tmp / 2; // 掩码右移从bit7到bit0 } }逻辑说明send_char从最高位开始逐位发送tmp初始为 0x80每次循环除以 2依次对应 bit7 到 bit0。判断data_out tmp的结果决定发送 1 还是 0。参数说明data_out是要发送的字节调用前需要确保 I2C 总线处于起始条件之后的状态。这个函数不处理 ACKACK 由单独的iic_ACK函数处理。3.2 连续写与连续读的地址发送顺序WriteNbyte是连续写的核心函数。它先发起始条件然后发设备地址 0xA224C256 的写地址7 位地址 0x51 左移一位加写标志 0等 ACK然后发地址高字节addr/256等 ACK再发地址低字节addr%256等 ACK然后循环发送数据字节每发一个等一次 ACK最后发停止条件延时 10ms 等待内部写周期完成。这里有一个容易踩坑的地方24C256 的地址是 16 位的但设备地址字节只占 8 位所以地址需要分两个字节发送先高后低。资料里的send_char(addr/256)和send_char(addr%256)就是做这件事。如果你只发一个字节的地址芯片会把它当成高字节低字节默认是 0结果就是所有数据都写到 256 的倍数地址上。ReadNbyte的流程稍微复杂一点先发起始条件发写地址 0xA2等 ACK发地址高字节等 ACK发地址低字节等 ACK然后重新发起始条件Restart发读地址 0xA3等 ACK然后循环读数据。每读一个字节后如果不是最后一个字节发 ACK 告诉芯片继续给数据如果是最后一个字节发 NACK 告诉芯片停止然后发停止条件。// 连续读先写地址再重启读 // p: 接收缓冲区 // addr: 起始地址 // number: 要读的字节数 void ReadNbyte(unsigned char *p, unsigned int addr, unsigned char number) { start_IIC(); send_char(0xA2); // 写设备地址 iic_ACK(); send_char(addr / 256); // 地址高字节 iic_ACK(); send_char(addr % 256); // 地址低字节 iic_ACK(); start_IIC(); // 重启切换为读模式 send_char(0xA3); // 读设备地址 iic_ACK(); do { *p read_char(); p; if(number ! 1) S_ACK(); // 非最后一个字节发ACK } while(--number); iic_NACK(); // 最后一个字节后发NACK stop_IIC(); }逻辑说明ReadNbyte先用写模式发送要读的起始地址然后发 Restart 条件切换到读模式。循环读数据时每读一个字节判断是否还有后续字节有就发 ACK没有就发 NACK最后发停止条件。参数说明p是接收缓冲区指针调用者需要保证缓冲区足够大addr是要读的起始地址16 位number是要读的字节数注意这个变量在while(--number)中被递减传入后会被修改。4. 避坑与排查24C256 连续写数据出错的五个现场记录4.1 现象写 11 字节第 6 次开始数据覆盖本页开头原因地址 55 开始写 11 字节551166超出第 0 页的 63 边界芯片内部地址指针回卷到本页 0 地址继续写覆盖了之前地址 02 的数据。解决写之前用page_remain计算当前页剩余空间如果剩余空间小于要写的长度就拆成两次写。第一次写到页末延时等待写周期完成第二次从下一页页首继续写。4.2 现象单字节读写正常连续写超过 64 字节后数据错乱原因24C256 的连续写模式一页最多 64 字节超过 64 字节后地址指针回卷不是跳到下一页。很多新手以为芯片会自动翻页实际上不会。解决在驱动层限制单次连续写的最大长度为 64 字节超过 64 字节的数据拆成多次写每次写之前重新发送起始地址。4.3 现象写入后立即回读数据不对延时 10ms 后回读正常原因24C256 在收到停止条件后启动内部写周期典型时间 5ms最大可能到 10ms。在写周期内芯片不响应任何 I2C 命令回读会失败或读到旧数据。解决每次写操作后加delay(10)或者用 ACK 轮询的方式判断芯片是否完成内部写周期。ACK 轮询的做法是发起始条件发写地址 0xA2如果芯片回 ACK 说明写周期完成如果没回 ACK 就继续等。4.4 现象地址超过 255 后写入的数据全部跑到 256 的倍数地址上原因地址只发送了一个字节芯片把低字节默认当成 0。24C256 的地址是 16 位的必须分两个字节发送先高后低。解决检查WriteNbyte和ReadNbyte中是否发送了addr/256和addr%256两个字节。如果只发了一个字节补上高字节的发送逻辑。4.5 现象I2C 总线上挂多个设备24C256 不响应 ACK原因24C256 的 7 位设备地址是 0x51写地址 0xA2读地址 0xA3。如果总线上有其他 EEPROM 型号比如 24C02 的地址是 0xA0地址冲突会导致 24C256 不响应。解决确认总线上每个设备的地址不冲突。24C256 的 A0、A1、A2 引脚可以配置地址的低 3 位如果多个 24C256 挂同一条总线通过这三个引脚区分地址。5. 进阶技巧用 ACK 轮询替代固定延时把写入周期压到最短固定delay(10)虽然简单但每次写都等 10ms批量写入时累积起来很可观。24C256 的典型写周期是 5ms如果每次都能在写周期刚结束时立刻发起下一次写整体写入速度能提升接近一倍。我一般会用 ACK 轮询来做这件事。具体做法是每次写操作发完停止条件后不直接延时而是循环发起起始条件加写地址 0xA2如果芯片回 ACK 说明内部写周期已完成可以立即进行下一次写如果没回 ACK就继续轮询直到超时。// ACK轮询等待EEPROM内部写周期完成 // 返回值0成功1超时 unsigned char wait_ack_polling(void) { unsigned int timeout 1000; // 超时计数根据主频调整 while(timeout--) { start_IIC(); send_char(0xA2); // 发写地址 // 读SDA状态判断ACK P2DIR 0xFD; // SDA设为输入 IIC_Delay(); P2DIR 0xFB; // SCL拉高 IIC_Delay(); if((P2IN 0x02) 0x00) { // SDA为低收到ACK P2DIR | 0x04; // SCL拉低 stop_IIC(); return 0; // 写周期完成 } P2DIR | 0x04; // SCL拉低 stop_IIC(); IIC_Delay(); } return 1; // 超时 }逻辑说明wait_ack_polling在循环中反复发起起始条件并发送写地址每次读 SDA 线状态。如果 SDA 为低说明芯片拉低了 SDA 表示 ACK内部写周期已完成。如果循环结束还没收到 ACK返回超时错误。参数说明timeout的初始值需要根据主频和 I2C 速率调整。在 1MHz 主频、软件模拟 I2C 的情况下1000 次循环大约对应几十毫秒足够覆盖 24C256 的最大写周期。如果主频更高可以适当减小这个值。用 ACK 轮询替代固定延时后连续写 32KB 数据的总时间从原来的 512 页 × 10ms 5120ms 降到大约 512 页 × 5ms 2560ms节省了一半时间。这个技巧在数据采集频率较高、需要频繁存储的场景下很有用。从那以后我每次调 EEPROM 驱动都会先写一个页边界测试从地址 0 开始每次写 11 字节写 10 次然后回读全部数据看第 6 次写的数据有没有覆盖前面的内容。这个测试能在一分钟内暴露地址回卷问题比在完整应用里调试快得多。希望帮到你。本文还有配套的精品资源点击获取