第一次接触I2C总线的同学可能会觉得这个协议有点复杂,毕竟它涉及到主从设备、设备地址、时序控制等多个概念。但别担心,我用实际项目经验告诉你,只要掌握几个关键点,就能快速上手。I2C就像是一个班级里的点名系统:老师(主设备)通过学号(设备地址)点名,被点到的同学(从设备)才能站起来回答问题(数据交互)。
在蓝桥杯单片机开发板上,I2C总线连接了两个重要外设:PCF8591(AD/DA转换芯片)和AT24C02(EEPROM存储器)。这两个器件共享SCL(P2.0)和SDA(P2.1)两条信号线。实际开发中,我们需要先配置好硬件环境:
这里有个容易踩坑的地方:官方提供的I2C驱动代码需要稍作修改才能使用。主要修改包括:
PCF8591这个芯片特别有意思,它就像个"翻译官",能把模拟世界的连续信号(比如光照强度)和数字世界的离散信号互相转换。在实际项目中,我经常用它来读取光敏电阻和电位器的值。
要让PCF8591正常工作,首先要搞懂它的"身份证"(设备地址)和"指令集"(控制字)。蓝桥杯板子上的PCF8591地址固定为0x90(写)和0x91(读),这是因为它的A0-A2引脚都接地了。
控制字的配置是很多同学的痛点,我总结了一个记忆口诀:
比如要读取光敏电阻的值,控制字就是0x01;读取电位器则是0x03。DA输出固定用0x40。
在实际编码时,我建议封装两个函数:
c复制// DA输出函数
void PCF8591_DA_Output(unsigned char value) {
I2CStart();
I2CSendByte(0x90); // 设备地址+写
I2CWaitAck();
I2CSendByte(0x40); // 控制字:DA输出
I2CWaitAck();
I2CSendByte(value); // 输出值(0-255)
I2CWaitAck();
I2CStop();
}
// AD采集函数
unsigned char PCF8591_AD_Read(unsigned char channel) {
unsigned char value;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(channel); // 选择通道
I2CWaitAck();
I2CStop();
I2CStart();
I2CSendByte(0x91); // 设备地址+读
I2CWaitAck();
value = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return value;
}
使用时需要注意:DA输出范围是0-255对应0-5V,AD采集返回值也是0-255。实际电压值需要换算,公式是:电压 = (数值/255)*5V。
AT24C02相当于单片机的"小笔记本",掉电后数据也不会丢失。我在环境监测项目中常用它来存储阈值参数。
AT24C02的设备地址是0xA0(写)和0xA1(读)。与PCF8591不同,它不需要控制字,但需要指定存储地址(0-255)。这里有个重要注意事项:连续写入时要加5ms延时,否则可能失败。
我推荐的读写函数实现:
c复制// 写入数据
void AT24C02_Write(unsigned char addr, unsigned char dat) {
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
Delay5ms(); // 必须的延时
}
// 读取数据
unsigned char AT24C02_Read(unsigned char addr) {
unsigned char dat;
I2CStart();
I2CSendByte(0xA0);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStop();
I2CStart();
I2CSendByte(0xA1);
I2CWaitAck();
dat = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return dat;
}
在实际项目中,我总结了几点经验:
现在我们把两个器件结合起来,实现一个完整的应用场景:监测环境光照强度,当超过阈值时报警,并能保存设置。
系统功能包括:
硬件连接:
主程序框架:
c复制void main() {
unsigned char light, threshold;
// 初始化
threshold = AT24C02_Read(100); // 读取存储的阈值
if(threshold == 0xFF) threshold = 100; // 默认值
while(1) {
light = PCF8591_AD_Read(0x01); // 读取光强
// 显示处理
DisplayLight(light);
DisplayThreshold(threshold);
// 阈值判断
if(light > threshold) {
LED_On(0); // 超阈值报警
} else {
LED_Off(0);
}
// 按键处理
KeyProcess(&threshold);
}
}
按键处理函数:
c复制void KeyProcess(unsigned char *threshold) {
if(S4_Pressed()) { // 增加阈值
(*threshold)++;
AT24C02_Write(100, *threshold); // 保存
}
if(S5_Pressed()) { // 减少阈值
(*threshold)--;
AT24C02_Write(100, *threshold);
}
}
在调试这类项目时,我常用的方法:
遇到最多的问题是设备无响应,通常的解决步骤:
这个项目虽然不大,但涵盖了I2C通信、AD采集、数据存储等核心知识点。在实际比赛中,可以根据题目要求灵活调整功能组合。比如增加DA输出控制LED亮度,或者用电位器实时调整阈值等。