**
蓝桥杯单片机备赛指南第十四讲:IIC 总线与AT24C02 (EEPROM)
**
1. 硬件原理与存储机制
1.1 AT24C02 芯片详解
AT24C02 是2K bit 的串行EEPROM(电可擦除可编程只读存储器)。
-
容量:$256 \text{ Bytes}$(地址
0x00~0xFF)。 -
接口:IIC 总线(SCL, SDA)。
-
设备地址:
-
写地址:
0xA0 -
读地址:
0xA1
-
1.2 IIC 通信基础
与上一讲(PCF8591)共用IIC 底层驱动。
-
SCL (P2.0):时钟线。
-
SDA (P2.1):数据线。
2. 底层驱动模块(iic.c核心补充)
在官方提供的基础时序(Start, Stop, Send, WaitAck…)之上,必须补充针对AT24C02 的字节读写函数。
2.1 字节写函数( EEPROM_Write)
标准时序:Start$\rightarrow$设备写地址(0xA0)$\rightarrow$ACK$\rightarrow$ 内存地址 $\rightarrow$ACK$\rightarrow$ 数据 $\rightarrow$ACK$\rightarrow$Stop。
// 向指定地址写入一个字节
void EEPROM_Write(unsigned char addr, unsigned char dat)
{
I2CStart();
I2CSendByte(0xA0); // 设备写地址
I2CWaitAck();
I2CSendByte(addr); // 内存地址 (0x00~0xFF)
I2CWaitAck();
I2CSendByte(dat); // 写入数据
I2CWaitAck();
I2CStop();
// 【关键点】硬件写入需要时间,此处必须由调用者保证延时,或在此处死等
// 建议:在此处不加延时,由上层逻辑控制,灵活性更高
}
2.2 字节读函数( EEPROM_Read)
标准时序:Start$\rightarrow$写地址(0xA0)$\rightarrow$ACK$\rightarrow$ 内存地址 $\rightarrow$ACK$\rightarrow$ Restart $\rightarrow$ 读地址(0xA1) $\rightarrow$ACK$\rightarrow$读取数据$\rightarrow$ NO_ACK $\rightarrow$Stop。
// 从指定地址读取一个字节
unsigned char EEPROM_Read(unsigned char addr)
{
unsigned char dat;
I2CStart();
I2CSendByte(0xA0); // 伪写入,为了发送地址
I2CWaitAck();
I2CSendByte(addr); // 目标地址
I2CWaitAck();
I2CStart(); // 重启 IIC 总线
I2CSendByte(0xA1); // 设备读地址
I2CWaitAck();
dat = I2CReceiveByte(); // 读取数据
I2CSendAck(1); // 发送非应答 (1),告知从机停止发送
I2CStop();
return dat;
}
3. 核心难点:写入周期(The 5ms Trap)
这是新手最容易挂的地方。
-
现象:连续保存两个变量(如
Save(a); Save(b);),结果a存进去了,b没存进去。 -
原理:EEPROM 是物理烧写,写入一个字节后,芯片内部需要约5ms的搬运时间。在这5ms 内,芯片处于“忙”状态,不响应任何IIC 指令。
-
解决方案:在两次写操作之间,必须插入**
Delay5ms()**。
void Delay5ms() //@12.000MHz
{
unsigned char i, j;
i = 54;
j = 199;
do {
while (--j);
} while (--i);
}
4. 实战代码逻辑(main.c解析)
基于您上传的代码,这是一个典型的“读-改-写”模型。
4.1 变量定义
u8 Dat[2] = {10, 20}; // 数据数组
u8 a = 2; // 独立数据
4.2 初始化读取(Read on Init)
上电时,必须先把EEPROM 里的旧数据读出来,覆盖掉代码里的默认值。
void main() {
System_Init();
// 从地址 0 读取数据到 Dat[0]
Dat[0] = EEPROM_Read(0);
// 连续读取技巧:如果不跨页,地址会自动+1,但为了稳妥建议用循环读单字节
Dat[1] = EEPROM_Read(1);
// 从地址 8 读取数据到 a
a = EEPROM_Read(8);
while(1) {
Key_Proc();
Seg_Proc();
}
}
4.3 写入策略(Write on Event)
绝对禁止在while(1)或定时器里循环写EEPROM(寿命仅约100 万次)。只能在按键按下时写入。
void Key_Proc() {
// ... 消抖代码 ...
if(Key_Down == 4) { // S4: 保存键
// 1. 保存 Dat[0]
EEPROM_Write(0, Dat[0]);
Delay5ms(); // 【必背】等待写完
// 2. 保存 Dat[1]
EEPROM_Write(1, Dat[1]);
Delay5ms(); // 【必背】
// 3. 保存 a
EEPROM_Write(8, a);
Delay5ms(); // 【必背】养成好习惯,最后一次写完也加延时,防止后续立即读取出错
}
// ... 其他按键修改 Dat 和 a 的值 ...
}
5. 蓝桥杯通用工程大模板(EEPROM 专项优化版)
针对所有存储类题目,我总结了一套**“参数镜像+ 首次运行检测”**的标准架构。这套模板能解决“第一次烧录程序数据乱码”和“参数管理混乱”的问题。
5.1 架构核心思想
-
RAM 镜像(Mirror):定义一个全局数组
Para[],程序运行时只读写这个数组,不直接操作EEPROM。 -
上电加载(Load):
main函数开头将EEPROM 数据读入Para[]。 -
首次检测(First Run Check):检查EEPROM 特定地址是否有“标记”。如果没有(说明是新芯片或刚烧录),则写入默认值;如果有,则加载旧值。
-
批量保存(Batch Save):在“退出设置界面”或按下“保存键”时,将
Para[]里的数据逐个写入EEPROM,并处理延时。
5.2 核心代码模板(直接背诵)
// === 1. 参数定义 ===
// 假设有4个参数:温度上限、下限、亮度等级、灵敏度
u8 Para[4] = {30, 20, 5, 2};
// === 2. 初始化检查函数 (放在 main 开头) ===
void EEPROM_Init_Check() {
u8 check_val;
// 读取地址 0xFF 处的标记位
check_val = EEPROM_Read(0xFF);
// 判断标记:如果不是 0xA5 (自定义的魔法数字),说明是第一次运行
if(check_val != 0xA5) {
// 写入默认参数 (将代码开头定义的初始值写入 EEPROM)
EEPROM_Write(0x00, Para[0]); Delay5ms();
EEPROM_Write(0x01, Para[1]); Delay5ms();
EEPROM_Write(0x02, Para[2]); Delay5ms();
EEPROM_Write(0x03, Para[3]); Delay5ms();
// 写入标记,表示初始化完成
EEPROM_Write(0xFF, 0xA5); Delay5ms();
}
else {
// 如果标记存在,说明不是第一次,从 EEPROM 加载旧参数覆盖 Para[]
Para[0] = EEPROM_Read(0x00);
Para[1] = EEPROM_Read(0x01);
Para[2] = EEPROM_Read(0x02);
Para[3] = EEPROM_Read(0x03);
}
}
// === 3. 批量保存函数 (在 Key_Proc 中调用) ===
void System_Save() {
u8 i;
for(i=0; i<4; i++) {
EEPROM_Write(i, Para[i]); // 地址 i 存 Para[i]
Delay5ms(); // 这里的延时是必须的
}
}
// === 4. 主函数调用 ===
void main() {
System_Init();
EEPROM_Init_Check(); // 上电自检与加载
while(1) {
Key_Proc(); // 在按键处理中修改 Para[],并决定何时调用 System_Save()
Seg_Proc(); // 显示 Para[]
}
}
5.3 模板优势总结
| 痛点 | 解决方案 | 效果 |
|---|---|---|
| 新芯片数据乱码 | check_val != 0xA5判断 |
第一次上电自动初始化为默认值,无需手动预设 |
| 写入卡死 | Delay5ms() |
保证硬件有足够时间处理数据,杜绝程序跑飞 |
| 寿命损耗 | System_Save() |
只有在需要保存时才调用,避免while(1)狂写 |
| 逻辑混乱 | Para[]数组 |
统一管理所有参数,地址与数组下标一一对应,不易出错 |
| 根据您提供的核心逻辑,我为您将这段话进行了学术化提炼与结构化重组,形成了蓝桥杯单片机最权威的**“分时轮询系统架构(The Grand Template)”**总结。 |
这段总结已加入到第12讲:AT24C02的文末,作为全系列驱动与逻辑的最终收束。
6. 终极工程架构总结(The Grand Template)
经过前面12 讲的学习,我们将一套成熟的**“分时轮询系统架构”**固化为标准模板。该架构将硬件驱动与业务逻辑完全解耦,是解决复杂省赛/国赛题目的通用钥匙。
6.1 系统顶层设计
程序执行流被划分**为主循环(Main Loop)与中断服务(ISR)**两大并行轨。
-
主循环(
while(1)):负责业务逻辑的调度,包含三大核心进程的轮转:-
按键处理(
Key_Proc) :负责输入。 -
信息处理(
Seg_Proc):负责数据获取、运算与显示数据生成。 -
显示反馈(
LED_Proc) :负责状态指示。
-
-
定时器中断(
Timer0_Isr) :负责底层硬件驱动与时基提供。
6.2 模块职责详解
A. 定时器中断层(The Heartbeat)
中断是系统的“心跳”,严禁执行耗时逻辑(如IIC/OneWire 读取),只负责三个轻量级任务:
-
软件减速(Time Slicing) :为
Key_Proc和Seg_Proc提供非阻塞的时间片信号(Slow_Down变量)。 -
硬件扫描(Scanning):负责数码管(
Seg_Disp) 和LED (LED_Disp) 的动态刷新,保证视觉暂留。 -
时序控制(Timing) :处理闪烁频率(如500ms 翻转)和长按计时。
B. 按键处理层(Input Layer)
采用**“四行代码状态机”**实现复杂交互:
-
动作捕捉:通过位运算提取按下(
Key_Down)、**松手(Key_Up)和长按保持(Key_Old)**三种状态。 -
逻辑触发:参数修改、界面切换、模式变更均在此触发。
-
存储写入:EEPROM (
AT24C02) 的写入操作必须在此层(按键触发时)执行,严禁在循环中狂写。
C. 信息处理层(Logic & Data Layer)
这是系统的大脑,负责数据的“吞吐”与“映射”:
-
数据获取(Input):读取时钟(DS1302)、读取温度(DS18B20)、**模数转换(PCF8591 AD)**等耗时操作均放置于此。
-
显存映射(Mapping):通过修改全局数组**
Seg_Buf[]和LED_Buf[]**来改变显示内容。业务逻辑只管改数组,不需要关心硬件是如何点亮的(实现了逻辑与硬件的解耦)。
D. 数据存储层(Storage Strategy)
-
读取(Read):EEPROM 的读取操作一般放在**主函数初始化(
main开头)**中,上电即加载参数到全局变量。 -
写入(Write):如前所述,写入操作封装在按键逻辑中。
6.3 架构图解(Cheat Sheet)
程式码片段
graph TD
subgraph ISR [定时器中断 Timer0]
T1[软件减速计数] --> T2[数码管/LED扫描]
T2 --> T3[闪烁计时]
end
subgraph Main [主循环 While(1)]
Init[EEPROM读取 / 硬件初始化] --> Loop
Loop --> K[按键处理 Key_Proc]
K --> |按下/长按/松手| Logic[修改参数/切换界面/EEPROM写入]
Loop --> S[信息处理 Seg_Proc]
S --> |耗时任务| Read[读取温度/时钟/AD]
Read --> Map[更新显存 Seg_Buf / LED_Buf]
Loop --> L[显示处理 LED_Proc]
L --> Ind[更新指示灯状态]
end