蓝桥杯单片机第十五届省赛模拟题二:DS18B20与PCF8591的综合应用解析
1. 赛题解析与硬件配置要点
蓝桥杯单片机竞赛中,DS18B20温度传感器和PCF8591 AD/DA转换芯片的综合应用一直是省赛的热门考点。第十五届省赛模拟题二要求选手实现温度测量、光照采集、DAC输出、按键控制和数码管显示的多模块协同工作。在实际备赛时,硬件配置是第一步,也是最容易踩坑的环节。
根据题目要求,需将IAP15F2K61S2单片机的内部振荡器频率设置为12MHz,键盘跳线J5配置为KBD矩阵按键模式,扩展跳线J13设置为IO模式。这些配置直接影响程序时序和外设驱动,若配置错误可能导致温度读取异常、IIC通信失败甚至数码管显示乱码。我曾在调试中发现DS18B20读数漂移,最终发现是延时函数未适配12MHz主频——官方提供的驱动代码中,单总线延时函数需要乘以11倍系数(t*=11),否则时序不匹配会导致数据错误。
PCF8591的地址配置也需特别注意:写地址为0x90,读地址为0x91。若混淆地址,AD转换将无法获取光敏电阻值。实际调试时,建议先用万用表测量光敏电阻两端电压,再与AD读取的数值对比,验证转换准确性。
2. DS18B20温度采集实战技巧
DS18B20是单总线数字温度传感器,其驱动需严格遵循初始化→发送跳过ROM指令→启动转换→读取暂存器的流程。官方提供的onewire.c驱动基本可用,但需注意两个关键点:
首先是时序适配。12MHz主频下,单总线延时函数必须包含t*=11的调整,否则初始化和读写时序会偏差:
void Delay_OneWire(unsigned int t) {
t *= 11; // 12MHz时钟关键修正
while(t--);
}
其次是数据处理。DS18B20返回的16位数据中,低4位为小数部分,需转换为实际温度值。题目要求保留一位小数,可采用以下代码:
float Read_Temperature() {
unsigned char high, low;
init_ds18b20();
Write_DS18B20(0xCC); // 跳过ROM
Write_DS18B20(0x44); // 启动转换
Delay_OneWire(100); // 等待转换完成
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE); // 读暂存器
low = Read_DS18B20();
high = Read_DS18B20();
return ((high << 8) | low) / 16.0f;
}
实际测试中,发现DS18B20每次读取需间隔至少750ms,否则可能返回85℃(上电默认值)。可在主函数初始化时增加延时:
void main() {
Read_Temperature(); // 首次读取丢弃
Delay750ms(); // 官方提供的750ms延时函数
// ...后续初始化
}
3. PCF8591的光照采集与DAC输出
PCF8591同时具备AD采集和DA输出功能,采用IIC协议通信。光敏电阻连接在AIN1通道(地址0x41),需先发送控制字选择通道,再读取转换值。AD采集代码参考:
unsigned char AD_Read(unsigned char addr) {
unsigned char temp;
I2CStart();
I2CSendByte(0x90); // 写地址
I2CWaitAck();
I2CSendByte(addr); // 控制字:选择AIN1通道
I2CWaitAck();
I2CStart();
I2CSendByte(0x91); // 读地址
I2CWaitAck();
temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return temp;
}
DAC输出需注意:输出电压值 = (输入数值 / 255) × 参考电压(5V)。题目要求输出关系与温度/光照绑定,需根据曲线计算:
void DA_Write(unsigned char dat) {
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x43); // 开启DAC输出
I2CWaitAck();
I2CSendByte(dat);
I2CWaitAck();
I2CStop();
}
实测中发现,PCF8591的DA输出存在约10mV的偏差,可通过软件校准:若要求输出2.5V,实际发送值应为128,但可能需微调至127或129。建议用万用表测量实际输出电压,建立校正表。
4. 多界面显示与按键控制逻辑
数码管显示需处理两种界面:模式界面(显示温度/光照)和输出界面(显示电压)。按键S4用于模式切换(仅模式界面有效),S5用于界面切换。显示缓冲区的设计至关重要:
unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10}; // 初始熄灭
unsigned char Seg_Point[8] = {0}; // 小数点控制
void Update_Display() {
if (Seg_Disp_Mode == 0) { // 模式界面
if (Seg_Mode == 0) { // 温度模式
Seg_Buf[0] = 1; // 显示"1"
Seg_Buf[5] = temperature_10x / 100 % 10;
Seg_Buf[6] = temperature_10x / 10 % 10;
Seg_Buf[7] = temperature_10x % 10;
Seg_Point[6] = 1; // 第6位显示小数点
} else { // 光照模式
Seg_Buf[0] = 2;
Seg_Buf[5] = (Value >= 100) ? Value/100%10 : 10; // 高位灭零
Seg_Buf[6] = (Value >= 10) ? Value/10%10 : 10;
Seg_Buf[7] = Value % 10;
}
} else { // 输出界面
Seg_Buf[0] = 11; // 显示"U"标识
Seg_Buf[6] = Voltage_10x / 10;
Seg_Buf[7] = Voltage_10x % 10;
Seg_Point[6] = 1;
}
}
按键消抖采用10ms定时扫描,避免多次触发:
void Key_Proc() {
if (Key_Slow_Down++ < 10) return;
Key_Slow_Down = 0;
unsigned char key = Key_Read();
unsigned char key_down = key & (Key_Old ^ key);
Key_Old = key;
if (key_down == 4 && Seg_Disp_Mode == 0) { // S4模式切换
Seg_Mode = !Seg_Mode;
} else if (key_down == 5) { // S5界面切换
Seg_Disp_Mode = !Seg_Disp_Mode;
}
}
LED指示灯控制简单直接:L1亮表示温度模式,L2亮表示光照模式,其余熄灭。注意LED采用共阳极连接,输出0点亮:
void Led_Proc() {
ucLed[0] = !Seg_Mode; // L1:温度模式亮
ucLed[1] = Seg_Mode; // L2:光照模式亮
for (int i=2; i<8; i++) ucLed[i] = 1; // 其余熄灭
}
5. 系统集成与调试经验
多模块集成时,最大的挑战是资源冲突和时序协调。DS18B20和PCF8591都需精确延时,建议将延时函数统一管理,避免因多个延时函数导致代码冗余。
定时器中断配置为1ms基准,用于按键扫描、数码管动态刷新和功能计时:
void Timer0Init() { // 1ms@12MHz
AUXR &= 0x7F;
TMOD &= 0xF0;
TL0 = 0x18;
TH0 = 0xFC;
TF0 = 0;
TR0 = 1;
ET0 = 1;
EA = 1;
}
void Timer0_ISR() interrupt 1 {
Key_Slow_Down++;
Seg_Slow_Down++;
if (++Seg_Pos >= 8) Seg_Pos = 0;
Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]); // 数码管扫描
Led_Disp(Seg_Pos, ucLed[Seg_Pos]); // LED扫描
}
调试时常见问题及解决方案:
- 数码管闪烁:检查定时器中断频率,确保刷新率高于50Hz
- 温度读数异常:确认DS18B20电源电压稳定,数据线加上拉电阻
- IIC通信失败:用示波器检查SDA/SCL波形,确认地址正确
- 按键失灵:检查矩阵键盘扫描代码,确认消抖时间充足
最终的主循环简洁高效:
void main() {
Read_Temperature(); // 首次读取丢弃
Delay750ms();
Timer0Init();
System_Init();
while(1) {
Key_Proc(); // 按键处理
Seg_Proc(); // 显示更新
Led_Proc(); // LED控制
}
}
建议在赛前针对每个模块单独测试,再逐步集成。完整代码需注意文件结构规范,将DS18B20和PCF8591驱动分别放在onewire.c和iic.c中,通过头文件引用,确保代码可读性和可维护性。
更多推荐



所有评论(0)