尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

51单片机驱动24C02:I2C时序、Proteus仿真与掉电保存实现

51单片机驱动24C02:I2C时序、Proteus仿真与掉电保存实现 简介针对51单片机EEPROM 24C02应用开发的完整实例包以C语言实现I2C总线通信与EEPROM读写操作并配套Proteus仿真工程适用于单片机入门学习者、电子竞赛备赛者及课程设计参考。资源包共11个文件核心为24c02.c源程序与24c02.hex烧录文件另有24c02.Uv2工程文件以及备份、编译生成的LST/OBJ/M51等辅助文件可完整还原Keil开发环境下的编译、调试与下载流程整体仅17KB轻便易分享。该实例已有1295人学习下载验证了其实用价值。通过仿真运行与源码对照可直观理解24C02芯片的器件地址、时序控制、数据读写机制以及51单片机模拟I2C协议的编程技巧帮助读者快速掌握非易失性存储的软硬件协同设计方法提升嵌入式系统调试与开发能力。1. 从“断电丢数据”说起24C02 在 51 单片机里的真实位置“单片机掉电后还要记住数据”这个需求几乎每个做 51 单片机硬件设计的人都会遇到。24C02 是 I2C 接口的 EEPROM容量 2Kbit也就是 256 字节支持掉电保存Proteus 仿真的价值不是把电路画完就结束而是能在不焊板子的情况下先调通 I2C 时序再用 C 语言把驱动写出来最后把 Keil 生成的 hex 装进 Proteus 的模型里跑通。适合做课程设计、毕业设计也适合被“仿真不出数据”卡住的人。标题里的“234”是例程编号不必纠结重点是三个工具链怎么衔接C 语言写驱动、Proteus 仿真验证、代码落地到真实硬件。2. 24C02 的原理与 I2C 时序先搞清楚 0 和 1 怎么送到芯片在写 C 语言驱动之前最该看的是 24C02 的存储结构和 I2C 总线的字节级时序。51 单片机里除了部分增强型型号自带硬件 I2C 或 SMBus绝大多数 AT89C51、STC89C52RC 都是拿普通 GPIO 模拟 I2C。常见做法是P2.0 作为 SCLP2.1 作为 SDA外部各接一个 4.7kΩ 上拉电阻。Proteus 元件库里直接搜 24C02C 就能找到可用的仿真模型主时钟按项目常用值设成 12MHz。2.1 24C02 的 256 字节存储与 8 字节页缓冲24C02 的标称容量是 2Kbit换成字节是 256 字节寻址范围 0x000xFF。它内部不按扇区而是按页组织每页 8 字节0x000x07 是第 0 页0x080x0F 是第 1 页依此类推。页写一次最多可以连续写入 8 个字节并且只占一个写周期如果写入范围跨页地址计数器会从页尾回到页首把已经写好的字节覆盖掉。参数数值说明存储容量256 × 8 bit字节地址 0x00~0xFF页大小8 字节0x00~0x07 为第 0 页器件地址0xA0写/0xA1读7 位地址 0x50 左移一位写周期典型 5ms页写或字节写完成后需等待最大时钟100kHz标准/ 400kHz快速GPIO 模拟建议放在 100kHz 以下表中的“7 位地址左移一位”很容易被忽略。I2C 总线上先送的从机地址字节其实由 7 位地址加 1 位读写标志组成。24C02 的高 7 位固定是 0b1010000最后的 R/W 决定方向所以写地址是 0xA0读地址是 0xA1。如果 A0、A1、A2 引脚接 VCC对应的地址位也要改成 1否则仿真里会出现“写了没反应读回来全 0xFF”的症状。2.2 I2C 开始条件、停止条件与应答位逻辑上最容易错的三件事I2C 的物理层不复杂但三条规则容易在代码里写反。第一起始条件和停止条件都发生在 SCL 为高电平期间。SDA 从 1 跳变到 0 是 startSDA 从 0 跳变到 1 是 stop。数据字节传输时 SDA 只允许在 SCL 为低电平时变化若在 SCL 高电平期间变化从机会把它当成 start 或 stop整个帧就乱了。第二每个字节必须凑满 9 个时钟。前 8 个时钟发送数据第 9 个时钟是应答位。主机发送字节后要释放 SDA由从机在 SCL 高电平期间拉低 SDA表示 ACK。读数据时反过来主机在接收完每个字节后如果需要继续读下一个字节要在第 9 个时钟前主动拉低 SDA表示“我还想要”读到最后一个字节时发非应答让从机停止输出。第三页写没有“自动跨页”能力。很多人以为连续写多个字节可以直接跨越页边界实际 24C02 在页内递增跨页就回卷。页写前要判断(addr % 8 len) 8一旦成立就得拆成两段写。这一条在 Proteus 仿真里最容易暴露写出来看似成功读回后数据和预期不符。2.3 器件地址 0xA0/0xA1A2/A1/A0 怎样参与寻址24C02 的 A0、A1、A2 三个引脚决定了从机地址的低三位。默认情况下这三个引脚接地地址为 0b000所以器件完整的 7 位地址是 0b1010000加上读写位就是 0xA0 / 0xA1。用户可以在同一根 I2C 总线上挂最多 8 片 24C02每片 A0/A1/A2 引脚接不同的高/低电平就能分别访问。Proteus 仿真里如果把这三个引脚悬空一些版本会判定为低电平但更稳的做法是直接接地。单片机的 SCL、SDA 不能直接驱动 24C02 的输入因为 24C02 的 SDA 是开漏输出必须靠外部上拉电阻提供高电平。这一节可以看成 2.1 表格的补充地址对了、上拉有了I2C 的物理基础才算完整。3. 用 C51 写 24C02 驱动字节读写、分地址写读与页写Keil C51 里新建工程后把晶振频率按 12MHz 设置代码里用 P2.0、P2.1 模拟 SCL、SDA。整个驱动分四层宏定义与 sbit 位定义、微秒延时、I2C 起始/停止/应答、24C02 的写读接口。我按从底到上的顺序给出代码每一层都标注了与 Proteus 仿真对应的行为。3.1 I2C 底层时序函数的 C 语言实现// 24C02_I2C.cGPIO 模拟 I2C 主机12MHz 51 单片机 12T 模式 #include reg51.h sbit SCL P2^0; // 时钟线连接 24C02 的 SCL sbit SDA P2^1; // 数据线连接 24C02 的 SDA void I2C_Delay(void) { // 每拍约 5us 12MHz 12T unsigned char i 5; while (i--); } void I2C_Start(void) { SDA 1; I2C_Delay(); SCL 1; I2C_Delay(); SDA 0; I2C_Delay(); // SCL 高电平期间 SDA 下降沿 start SCL 0; I2C_Delay(); } void I2C_Stop(void) { SDA 0; I2C_Delay(); SCL 1; I2C_Delay(); SDA 1; I2C_Delay(); // SCL 高电平期间 SDA 上升沿 stop } bit I2C_SendByte(unsigned char dat) { unsigned char i; bit ack; for (i 0; i 8; i) { SCL 0; I2C_Delay(); if (dat 0x80) SDA 1; else SDA 0; // MSB 先出 I2C_Delay(); SCL 1; I2C_Delay(); // 从机在 SCL 高电平期间采样 dat 1; } SCL 0; I2C_Delay(); SDA 1; I2C_Delay(); // 释放 SDA等待从机应答 SCL 1; I2C_Delay(); ack SDA; // 第 9 个时钟高电平读 ACK0应答 SCL 0; I2C_Delay(); return ack; } unsigned char I2C_ReadByte(bit ack) { unsigned char i, dat 0; for (i 0; i 8; i) { SCL 0; I2C_Delay(); SCL 1; I2C_Delay(); dat 1; if (SDA) dat | 0x01; // 在 SCL 高电平期间读 SDA } SCL 0; I2C_Delay(); if (ack) SDA 0; else SDA 1; // ack1 发应答ack0 发非应答 I2C_Delay(); SCL 1; I2C_Delay(); SCL 0; I2C_Delay(); SDA 1; I2C_Delay(); return dat; }代码逻辑说明起始条件里 SDA 必须先释放再拉低顺序相反就变成数据位而不是起始位。发送字节时每次先在 SCL 低电平时改 SDA再拉高 SCL 让从机采样保证 SDA 在 SCL 高电平期间稳定。读字节时主机不驱动 SDA从机在 SCL 高电平期间放数据I2C_ReadByte(1)表示发送 ACKI2C_ReadByte(0)表示最后字节的非应答。参数说明I2C_Delay的循环次数不宜照抄。Keil 优化级别如果开到 9空循环可能被优化掉I2C 时序完全失效。我一般把优化级别设在 0~4 级或者改用_nop_()保证延时存在。Proteus 仿真阶段可以先跑慢速验证逻辑后再缩短延时。3.2 24C02 的字节写与当前地址读24C02 的字节写包含 start、器件写地址、字节地址、数据、stop 五个步骤。读操作要先“伪写”一个字节地址把内部地址指针指过去再发重复起始和器件读地址最后读回数据。void EEPROM_WriteByte(unsigned char addr, unsigned char dat) { I2C_Start(); I2C_SendByte(0xA0); // 器件地址 写方向 I2C_SendByte(addr); // 24C02 内部字节地址 I2C_SendByte(dat); I2C_Stop(); EEPROM_WaitReady(); // 等待内部写周期结束 } unsigned char EEPROM_ReadByte(unsigned char addr) { unsigned char dat; I2C_Start(); I2C_SendByte(0xA0); // 伪写先设置地址指针 I2C_SendByte(addr); I2C_Start(); // 重复起始不是 stop 再 start I2C_SendByte(0xA1); // 器件地址 读方向 dat I2C_ReadByte(0); // 最后一个字节发非应答 I2C_Stop(); return dat; }这里最容易错的是读操作里的重复起始。第一次发送 0xA0 和地址后不能发 stop必须在总线上直接再产生 startI2C 协议允许这种“重启起始”的方式如果先 stop 再 start大多数情况也能工作但多了一段总线空闲期仿真时可能暴露从机未释放总线的问题。3.3 页写与连续读8 字节边界是关键页写把最多 8 个字节一次性写入同一页只占用一个写周期。实现上仍然是一次写事务只是地址后面连续跟多个数据字节。void EEPROM_WritePage(unsigned char addr, unsigned char *buf, unsigned char len) { unsigned char i; I2C_Start(); I2C_SendByte(0xA0); I2C_SendByte(addr); for (i 0; i len; i) { I2C_SendByte(buf[i]); } I2C_Stop(); EEPROM_WaitReady(); }参数说明addr 是页内起始地址len 必须是“不会跨页”的长度。比如从 0x05 写 5 个字节实际会落到 0x05、0x06、0x07、0x00、0x01后面的字节把页首覆盖了这是拿到仿真数据后最常碰到的“灵异现象”。跨页判断写成(addr % 8 len) 8成立时要把数据拆成两次写。连续读则不受页边界限制地址会继续递增但超过 0xFF 会回到 0x00读的数据量也不要超过存储容量。4. Proteus 8 Professional 仿真元件选取、hex 加载与示波器查时序Proteus 里做 24C02 仿真的目标不是“让灯亮”而是验证 I2C 写读流程。Proteus 8 Professional 的界面默认是画原理图加 PCB仿真只需要原理图部分。元件库名可能因版本略有差异但 24C02 的模型通常都能搜到。4.1 最小电路元件表与上拉电阻最小电路里除了单片机、24C02、晶振和复位电路还要两个 4.7kΩ 上拉电阻接在 SCL、SDA 上。I2C 是开漏总线没有上拉电阻时 SDA 可能一直为低或者波形变得很钝仿真里看到的响应位会全部错误。元件数量取值 / 型号建议接法单片机1AT89C5112MHz 晶振30pF 电容EEPROM124C02 / 24C02CSCL 接 P2.0SDA 接 P2.1上拉电阻24.7kΩSCL、SDA 到 VCC排阻110kΩ × 8P0 口做数据显示时使用把 P0 口拿来显示读取结果时必须接上拉因为 AT89C51 的 P0 是开漏结构。仿真里漏接上拉时P0 读出的数据会不稳定甚至显示成 0x00查板块容易方向错误。注意24C02 的写保护引脚 WP 必须接地。如果 WP 接 VCC整个芯片只读不写页写代码执行后读回来全是旧数据这个现象容易被误判成 I2C 时序错误。4.2 在 Proteus 中加载 Keil 生成的 hex 文件Keil 编译通过后在 Output 选项卡里勾选 Create HEX File重新 build项目目录下会生成 hex 文件。如果 Keil5 新建工程时找不到 AT89C51去 Device 列表里选 Atmel 下的 AT89C51或者确认 C51 支持包已经装上。回到 Proteus双击 AT89C51在 Program File 栏选择这个 hex晶振频率设成 12MHz与代码里的延时假设保持一致。先跑一个最小验证程序写入 0x5A 到 0x10再读回并送到 P0 口。若程序中还有其他外设建议先把外设注释掉单独测 EEPROM。仿真结果不是直接看出“读写成功”而是看 P0 口的逻辑电平。Proteus 左下角的运行按钮启动后程序停在 while(1)P0 显示的值如果等于 0x5A说明这条通路已经通了。补充一个经常被忽略的细节Proteus 仿真里 51 单片机程序是从地址 0x0000 开始执行的hex 文件的加载是即时生效的如果在仿真运行中修改了 hex需要停止后重新加载否则单片机里还是旧程序出现“改了代码没反应”的错觉。4.3 用虚拟示波器观察 SCL/SDA 波形要确认 I2C 时序是否标准可以在 Proteus 左侧工具箱中选择虚拟仪器拖出 Oscilloscope示波器把通道 A 接到 SCL通道 B 接到 SDA。仿真启动后按下暂停在波形上能清楚看到 start 条件SDA 在 SCL 高电平期间从高变低紧接着的 8 个时钟是地址字节第 9 个时钟后如果从机应答SDA 会被拉低一段然后是字节地址和数据字节。如果没有示波器也可以用逻辑探针观察电平但看不出边沿与时钟的相位关系。示波器时间轴调到每格 10μs 左右12MHz 晶振下 I2C 每一位约 5μs能直接看到边沿。看到 start 条件但没有 ACK就从器件地址和上拉上查看到 ACK 但没有后续数据就查代码里的发送顺序。4.4 仿真失败的三类检查点第一类是完全无波形。此时先看电源和 GND 是否连接再看 24C02 的 WP 是否接地。Proteus 8 仿真中模型引脚悬空有时会被识别成浮空不一定会按低电平处理所以 A0、A1、A2、WP 全部显式接地最稳妥。第二类是有波形但总是 NACK。先查器件地址0xA0 写、0xA1 读注意 0x50 是裸地址不是总线地址。初学者最容易把 0x50 直接当地址发出去这样高 7 位变成了 0b010100024C02 当然不应答。第三类是读写偶尔成功偶尔失败。这多半是延时不够或优化级别问题。回到I2C_Delay把循环次数翻倍或在 Keil 里关闭高优化重新编译后再仿真。如果仿真在“写周期等待”处长时间不继续优先确认 EEPROM_WaitReady 的实现是否和写周期匹配。5. 掉电保存的进阶改法ACK 轮询、取反校验与跨页测试当基础读写已经在 Proteus 里跑通再往下做就是真正的“掉电保存”工程。这里给三个可以直接套用的进阶手段它们在真实硬件上同样有效。5.1 用 ACK 轮询代替固定延时前面代码里的 EEPROM_WaitReady 用固定延时简单但浪费 CPU。更规范的做法是利用“内部写周期期间从机不响应任何命令”这一特性循环向从机发送器件地址直到收到 ACK 再结束。void EEPROM_WaitReady(void) { do { I2C_Start(); if (I2C_SendByte(0xA0) 0) { // ACK0写周期已结束 I2C_Stop(); return; } I2C_Stop(); // NACK 时先收总线再重试 } while (1); }这里的关键是每次 NACK 后一定要先产生 stop让总线回到空闲状态再开始下一次查询。如果省略 stop从机可能把下一次 start 当成数据位导致轮询死循环。5.2 取反校验与上电恢复掉电瞬间写到一半的情况无法完全避免所以存储参数时不要裸存一个字节。常见做法是写三个字节数据本身、数据取反、版本号。上电时先读这三个字节若数据与取反结果不一致就丢弃这一组用默认值启动。这个小技巧比单纯读一次靠谱得多而且好处是在仿真阶段可测人为把第二个字节改成错误值再复位单片机能看到程序正确回退到默认值。5.3 仿真验证的收尾节奏最后一步建议按这个顺序验证先删除代码里的写操作只保留读恢复逻辑连续三次复位单片机确认每次读回数据一致再把地址改成跨页位置确认数据确实回卷最后把延时降到最短看 I2C 波形是否仍然稳定。这套顺序能覆盖 I2C 最常见的问题面也方便把同一个 hex 直接烧进实物。先看 SDA再看 ACK最后处理跨页是调 24C02 一直有效的顺序。本文还有配套的精品资源点击获取
返回列表