1. 蓝桥杯I2C实战:从零认识AT24C02与PCF8591
在蓝桥杯单片机竞赛中,I2C总线技术是必考的重点内容,而AT24C02 EEPROM和PCF8591 ADC/DAC转换芯片则是两个最常用的外设。很多同学第一次接触这两个芯片时可能会觉得头疼,其实只要掌握了它们的通信规律,用起来非常简单。我自己在备赛过程中也踩过不少坑,后来发现官方提供的I2C底层驱动已经帮我们封装好了最复杂的部分,我们只需要关注如何调用这些函数来实现数据存储和转换功能。
AT24C02就像一个"不会忘记事情的小本本",即使单片机断电重启,之前存储的数据依然还在。这在比赛中特别实用,比如记录设备开机次数、保存用户设置参数或者存储比赛过程中的关键数据。而PCF8591则是一个"信号翻译官",它能把模拟世界的连续信号(比如光线强度、温度变化)转换成数字信号给单片机处理,也能把数字信号转换成模拟信号去控制外部设备。
竞赛实战技巧:比赛中官方会提供I2C的底层驱动文件(i2c.c和i2c.h),我们不需要自己编写复杂的I2C协议代码,只需要在理解基本原理的基础上,学会调用这些现成的函数来操作两个外设。这大大降低了难度,让我们可以更专注于应用逻辑的实现。
2. AT24C02 EEPROM的数据持久化存储实战
2.1 芯片特性与工作原理
AT24C02是一款2KB容量的EEPROM芯片,采用I2C接口通信,总共256个存储单元(地址范围0x00-0xFF),每个单元可以存储1字节数据。它最大的特点就是掉电不丢失,写入的数据可以保存10年以上。
在实际比赛中,我经常用它来存储系统参数和运行状态。比如有一个题目要求记录设备的累计工作时间,即使用户断电重启,这个计时也不能清零。用AT24C02就能完美解决这个问题——每隔一段时间把当前计时值存入EEPROM,下次开机时再读取出来继续计时。
硬件地址识别:AT24C02的硬件地址是0xA0(写操作)和0xA1(读操作)。这里有个小细节需要注意:地址的最后一位表示操作类型,0表示写,1表示读。所以写操作时地址是0xA0,读操作时是0xA1。
2.2 写入数据的完整流程
写AT24C02的流程就像给朋友寄快递:先联系快递公司(启动I2C总线),然后告诉收件人地址(发送存储地址),最后交付包裹(发送数据)。每一步都要确认对方收到(等待应答),全部完成后结束寄件(停止总线)。
具体的代码实现如下:
// 写AT24C02函数
void vWrite_EEPROM(u8 add, u8 dat) {
IIC_Start(); // 启动I2C通信
IIC_SendByte(0xA0); // 发送器件地址(写模式)
IIC_WaitAck(); // 等待应答
IIC_SendByte(add); // 发送存储地址
IIC_WaitAck(); // 等待应答
IIC_SendByte(dat); // 发送要存储的数据
IIC_WaitAck(); // 等待应答
IIC_Stop(); // 停止I2C通信
vDelay_Ms(5); // 延时5ms,确保写入完成
}
重要注意事项:最后那个5ms的延时绝对不能省略!我第一次写代码时就忘了这个延时,结果数据写入总是不成功。后来查资料才知道,AT24C02每次写入后需要一定的内部处理时间,如果连续写入太快,会导致数据错误甚至损坏芯片。
2.3 读取数据的操作步骤
读数据的流程稍微复杂一些,需要先从写模式切换到读模式。这个过程就像:你先告诉店员你要找哪本书(写入地址),然后店员把书拿给你(读取数据)。
// 读AT24C02函数
u8 ucRead_EEPROM(u8 add) {
u8 dat; // 定义变量存储读取的数据
IIC_Start(); // 启动I2C通信
IIC_SendByte(0xA0); // 发送器件地址(写模式)
IIC_WaitAck(); // 等待应答
IIC_SendByte(add); // 发送要读取的地址
IIC_WaitAck(); // 等待应答
IIC_Start(); // 发送重复起始信号
IIC_SendByte(0xA1); // 发送器件地址(读模式)
IIC_WaitAck(); // 等待应答
dat = IIC_RecByte(); // 接收数据
IIC_SendAck(1); // 发送非应答信号
IIC_Stop(); // 停止I2C通信
return dat; // 返回读取的数据
}
模式切换的奥秘:这里最关键的是那个重复起始信号(IIC_Start())。它不像停止再重新开始那样完全终止通信,而是保持总线活跃的同时改变通信方向。这种设计提高了通信效率,是I2C协议的一个巧妙之处。
2.4 多字节和复杂数据类型存储技巧
在实际应用中,我们经常需要存储超过1字节的数据,比如整数、浮点数甚至字符串。这就需要一些特殊的处理技巧:
// 存储16位无符号整数
u16 u16_value = 1234;
vWrite_EEPROM(0x00, u16_value >> 8); // 写入高8位
vWrite_EEPROM(0x01, u16_value & 0xFF); // 写入低8位
// 读取时反向组合
u16 read_value = (ucRead_EEPROM(0x00) << 8) | ucRead_EEPROM(0x01);
// 存储浮点数(保留3位小数)
float float_value = 3.141;
vWrite_EEPROM(0x02, (u16)(float_value * 1000) / 256);
vWrite_EEPROM(0x03, (u16)(float_value * 1000) % 256);
// 读取浮点数
float read_float = (ucRead_EEPROM(0x02) * 256 + ucRead_EEPROM(0x03)) / 1000.0f;
实际应用案例:在最近一届蓝桥杯比赛中,有一道题要求记录温度传感器的历史数据。我使用AT24C02循环存储最近10次的温度读数,每次开机后都能显示历史记录。实现方法是用一个指针记录当前存储位置,每次写入后指针加2(因为每个温度值是16位),达到存储区末尾时回绕到开头,这样就实现了循环存储。
3. PCF8591的ADC/DAC信号转换应用
3.1 芯片功能与通道配置
PCF8591是一个集成了4路ADC输入和1路DAC输出的芯片,通过I2接口与单片机通信。它就像单片机的"感官系统",能够感知模拟世界的变化,也能输出模拟信号控制外部设备。
通道配置详解:
- 通道0(0x40):通常连接外部模拟信号
- 通道1(0x41):通常连接光敏电阻,检测光线强度
- 通道2(0x42):备用通道
- 通道3(0x43):通常连接旋转电位器,检测电压变化
在蓝桥杯开发板上,这些通道通常已经硬件连接好了,我们只需要知道哪个通道对应哪个传感器即可。
3.2 ADC采集实战与数据转换
读取ADC值的函数编写相对简单,但有一些细节需要注意:
// 读取ADC函数
u8 Read_ADC(u8 chnl) {
u8 ADC_Val = 0;
I2CStart(); // 启动I2C
I2CSendByte(0x90); // 发送写命令(PCF8591地址)
I2CWaitAck(); // 等待应答
I2CSendByte(chnl); // 发送通道选择命令
I2CWaitAck(); // 等待应答
I2CStart(); // 重复起始信号
I2CSendByte(0x91); // 发送读命令
I2CWaitAck(); // 等待应答
ADC_Val = I2CReceiveByte(); // 读取ADC值
I2CSendAck(1); // 发送非应答
I2CStop(); // 停止I2C
return ADC_Val; // 返回ADC值
}
数据转换技巧:PCF8591是8位ADC,输出范围0-255,对应0-5V电压。但在实际显示时,我们通常需要转换成更直观的数值:
// 将ADC值转换为实际电压(0-500,表示0.00-5.00V)
u16 voltage = adc_value * 100 / 51;
// 转换为0-99的百分比值
u8 percentage = adc_value / 2.57f;
// 转换为1-5的等级值(用于光强度等级显示)
u8 level = adc_value / 51.1f + 1;
实战经验:读取ADC值时有个重要技巧——连续读取两次。第一次读取往往不太准确,第二次读取的值才是稳定的。这是因为PCF8591内部需要一次转换来稳定输出。我在代码中通常会这样处理:
// 正确的ADC读取方法
ucRead_ADC(0x41); // 第一次读取,丢弃结果
ch1 = ucRead_ADC(0x41); // 第二次读取,使用这个值
3.3 DAC输出功能与应用
DAC功能让我们可以用数字控制模拟输出,这在控制电机速度、调节LED亮度等场景中非常有用:
// 写DAC函数
void Write_DAC(u8 data) {
I2CStart(); // 启动I2C
I2CSendByte(0x90); // 发送写命令
I2CWaitAck(); // 等待应答
I2CSendByte(0x40); // 启用DAC输出
I2CWaitAck(); // 等待应答
I2CSendByte(data); // 发送要输出的数据
I2CWaitAck(); // 等待应答
I2CStop(); // 停止I2C
}
应用实例:在一次比赛中,我使用DAC功能生成呼吸灯效果。通过循环增加再减少DAC输出值,控制LED的亮度平滑变化:
// 呼吸灯效果实现
void breathing_led() {
static u8 dac_val = 0;
static bit direction = 0;
Write_DAC(dac_val); // 输出当前值
if (direction == 0) {
dac_val++;
if (dac_val >= 255) direction = 1;
} else {
dac_val--;
if (dac_val <= 0) direction = 0;
}
}
4. 竞赛中的综合应用与调试技巧
4.1 典型竞赛场景实现
在蓝桥杯比赛中,经常需要同时使用AT24C02和PCF8591完成复杂任务。比如这样一个典型场景:通过电位器调节LED亮度,并保存最后的亮度设置。
// 系统初始化
void system_init() {
vTimer2_Init(); // 初始化定时器
// 其他初始化代码
}
// EEPROM处理函数
void eeprom_process() {
static u8 last_brightness = 0;
u8 current_brightness = Read_ADC(0x43) / 2.55f; // 转换为百分比
if (current_brightness != last_brightness) {
vWrite_EEPROM(0x10, current_brightness); // 保存新值
last_brightness = current_brightness;
}
}
// 主循环
void main() {
system_init();
u8 saved_brightness = ucRead_EEPROM(0x10); // 读取保存的亮度
while (1) {
eeprom_process(); // 检测并保存亮度变化
// 其他处理逻辑
}
}
4.2 常见问题与调试方法
在实际开发中,经常会遇到各种问题。根据我的经验,90%的问题都出在以下几个方面:
I2C通信失败:首先检查硬件连接,SCL和SDA线是否接反或者接触不良。然后用逻辑分析仪或者示波器检查信号波形,看是否符合I2C时序要求。
数据写入不成功:最常见的原因是忘记加写入后的延时。AT24C02需要几毫秒的写入时间,连续写入时必须加延时。另外还要检查地址是否超出范围(0x00-0xFF)。
ADC读数不稳定:软件上可以采用多次读取取平均值的滤波算法。硬件上可以在模拟输入口加一个小电容(0.1uF)滤波。
DAC输出不准:用万用表测量实际输出电压,与理论值对比。通常误差在可接受范围内,如果误差太大,可能是参考电压不准或者负载阻抗太小。
调试小技巧:在关键位置添加调试输出,比如通过串口打印当前操作步骤和结果。这样当程序出现问题时,可以快速定位到故障点。
记得在正式比赛前,多练习几种不同的应用场景,熟练掌握这两个器件的特性和使用方法,这样在比赛中就能得心应手,遇到任何题目都能快速找到解决方案。

201

被折叠的 条评论
为什么被折叠?



