1. AT24C02芯片基础认知
第一次接触AT24C02是在准备蓝桥杯比赛的时候,当时看到这个黑乎乎的小芯片,完全没想到它能在比赛中发挥这么大作用。简单来说,AT24C02就是个"不会失忆的小本本"——断电后数据也不会丢失,特别适合用来存储比赛时需要记录的关键参数。
这个芯片有256字节的存储空间,相当于能记下256个数字。别看容量小,在单片机比赛里存个系统配置参数、计数值什么的完全够用。它通过IIC总线与单片机通信,只需要两根线(SCL时钟线和SDA数据线)就能搞定数据传输,特别节省IO口资源。我后来做项目发现,很多智能硬件设备里都能见到这种芯片的身影。
最让我惊喜的是它的软件写保护功能。比如比赛中需要保存校准参数,就可以把这部分存储区域锁住,防止程序跑飞时误修改。实际使用时要注意,芯片的A0、A1、A2这三个地址引脚通常都接地(逻辑0),这样设备地址就是0xA0(写)和0xA1(读)。第一次用的时候我忘了这点,调了一下午才发现地址设错了。
2. IIC通信协议详解
IIC协议就像两个人打暗号传纸条,SCL线负责对时间(相当于说"现在开始传数据"),SDA线负责传具体内容。这里分享几个实战中总结的要领:
起始信号特别关键,相当于敲门说"我要开始通信了"。代码实现就是SDA先拉低,接着SCL拉低:
void IIC_Start() { SDA = 1; Delay_us(1); SCL = 1; Delay_us(1); SDA = 0; Delay_us(1); // 起始信号 SCL = 0; Delay_us(1); }应答信号最容易出错。每次发送完8位数据,第9个时钟周期要检测从机应答。有次比赛我就因为没检查应答,数据死活写不进去:
bit IIC_WaitAck() { SDA = 1; Delay_us(1); SCL = 1; Delay_us(1); if(SDA) { // 检测SDA是否为低电平 SCL = 0; return 1; // 无应答 } SCL = 0; return 0; // 有应答 }传输数据时要牢记:SCL高电平时SDA必须保持稳定,只有在SCL低电平时才能改变SDA。这个时序要求我当初没注意,用逻辑分析仪抓波形才发现数据跳变不对。
3. 单字节读写实战
先说说最基础的单字节写入。有次比赛我遇到个坑:写完数据直接读,结果读出来的都是乱码。后来查手册才知道,AT24C02写入后需要5ms存储时间,官方称这个为"写入周期"。正确写法应该是:
void ByteWrite(u8 addr, u8 dat) { IIC_Start(); IIC_SendByte(0xA0); // 设备地址+写 IIC_WaitAck(); IIC_SendByte(addr); // 存储地址 IIC_WaitAck(); IIC_SendByte(dat); // 要写的数据 IIC_WaitAck(); IIC_Stop(); Delay5ms(); // 必须延时! }单字节读取稍微复杂些,需要先发地址再重启总线。这个"伪写操作"我当初理解了好久:
u8 ByteRead(u8 addr) { u8 dat; IIC_Start(); IIC_SendByte(0xA0); // 设备地址+写 IIC_WaitAck(); IIC_SendByte(addr); // 要读的地址 IIC_WaitAck(); IIC_Start(); // 重启总线 IIC_SendByte(0xA1); // 设备地址+读 IIC_WaitAck(); dat = IIC_RecByte(); IIC_SendAck(1); // 非应答 IIC_Stop(); return dat; }实测发现个细节:如果读取不存在的地址,返回的都是0xFF。这个特性可以用来检测芯片是否正常工作。
4. 多字节连续读写技巧
当需要存储一组数据时,页写入能大幅提高效率。AT24C02的页大小是8字节,一次最多写8个连续地址。有次做数据采集项目,我就是用这个方法提升了存储速度:
void PageWrite(u8 addr, u8 *p, u8 len) { IIC_Start(); IIC_SendByte(0xA0); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); while(len--) { IIC_SendByte(*p++); IIC_WaitAck(); } IIC_Stop(); Delay5ms(); // 整个页写完才需要延时 }连续读取更简单,读完一个字节后发送应答0,最后一个字节发应答1。这个逻辑我画了张状态图才彻底明白:
void SeqRead(u8 addr, u8 *p, u8 len) { IIC_Start(); IIC_SendByte(0xA0); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); IIC_Start(); IIC_SendByte(0xA1); IIC_WaitAck(); while(len--) { *p++ = IIC_RecByte(); IIC_SendAck(len ? 0 : 1); // 非末字节发0 } IIC_Stop(); }特别注意:多字节读取不需要延时!这个坑我踩过,加了延时反而会导致数据错位。
5. 经典应用案例解析
在蓝桥杯比赛中,上电次数统计是个经典题型。结合AT24C02和数码管显示,完整实现如下:
u8 code SMG[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; void main() { u8 count = AT24C02_Read(0x00); // 从地址0读取 count++; // 次数加1 AT24C02_Write(0x00, count); // 写回芯片 while(1) { Display(SMG[count/10], SMG[count%10]); // 数码管显示 } }还有个实用技巧:可以用不同地址存储不同类型数据。比如:
- 0x00-0x0F 存系统配置
- 0x10-0x1F 存运行日志
- 0x20-0xFF 存用户数据
6. 调试排坑指南
最常遇到的三个坑:
写入后立即读取:必须加5ms延时,否则读的是旧数据。有次调试时我用逻辑分析仪抓波形,发现连续操作时SCL信号都变形了。
应答信号混乱:多字节读取时,非末字节要应答0,末字节应答1。这个逻辑反了会导致后续数据丢失。
地址越界:AT24C02只有256字节,地址超过0xFF会回绕。有次我写到0xFF地址后继续写,结果把0x00地址的数据覆盖了。
调试建议:
- 用LED指示通信状态
- 在关键节点插入调试输出
- 逻辑分析仪看时序波形
- 官方驱动代码要验证过再使用
7. 完整代码示例
最后分享个经过比赛验证的代码框架,包含所有基础功能:
#include <stc15.h> #define AT24C02_ADDR 0xA0 void IIC_Delay() { /* 具体延时实现 */ } void IIC_Start() { /* 起始信号 */ } void IIC_Stop() { /* 停止信号 */ } bit IIC_WaitAck() { /* 等待应答 */ } void IIC_SendByte(u8 dat) { /* 发送字节 */ } u8 IIC_RecByte() { /* 接收字节 */ } void AT24C02_Write(u8 addr, u8 dat) { IIC_Start(); IIC_SendByte(AT24C02_ADDR); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); IIC_SendByte(dat); IIC_WaitAck(); IIC_Stop(); Delay5ms(); } u8 AT24C02_Read(u8 addr) { u8 dat; IIC_Start(); IIC_SendByte(AT24C02_ADDR); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); IIC_Start(); IIC_SendByte(AT24C02_ADDR|0x01); IIC_WaitAck(); dat = IIC_RecByte(); IIC_SendAck(1); IIC_Stop(); return dat; }实际项目中,我会把这段代码封装成.c和.h文件,方便其他模块调用。记得在头文件里加上extern声明,避免重复包含问题。