蓝桥杯单片机模拟赛二:DS18B20与PCF8591综合应用解析
1. 赛题解析与硬件配置要点
蓝桥杯单片机模拟赛二的这道题目确实很有意思,它把DS18B20温度传感器和PCF8591 AD/DA转换芯片这两个经典器件组合在一起,考察的是我们对嵌入式系统综合应用的能力。我当年第一次做这类题目时,也是踩了不少坑,后来在实际项目中反复使用这些器件,才真正掌握了它们的特性。
先说说硬件配置这个容易忽略但又至关重要的部分。题目要求将IAP15F2K61S2单片机内部振荡器频率设定为12MHz,这个设置直接影响所有时序相关操作。我记得有次比赛就因为忘了配置这个,DS18B20读出来的温度全是85℃(这是它的上电默认值),调试了半天才发现问题所在。
键盘工作模式跳线J5要配置为KBD矩阵按键模式,扩展方式跳线J13配置为IO模式。这些硬件配置看似简单,但要是没做对,后面程序写得再完美也是白搭。建议大家在开始编程前,先用万用表检查一下这些跳线帽的连接状态,确保硬件基础没问题。
2. DS18B20温度传感器实战技巧
DS18B20这个单总线温度传感器真是让人又爱又恨。爱的是它集成度高、精度不错,恨的是它的时序要求特别严格。我在实际项目中用过不下百个DS18B20,总结出几个关键点:
首先是初始化时序,这个最容易出问题。DS18B20要求主机发出复位脉冲(至少480μs的低电平),然后释放总线并进入接收模式。传感器会在15-60μs内拉低总线作为应答信号。这里有个细节:12MHz的8051单片机,一个机器周期是1μs,用nop指令做延时时要特别注意计算指令周期。
// DS18B20初始化示例
bit init_ds18b20() {
bit ack;
DQ = 0; // 拉低总线
delay_us(480); // 延时480μs
DQ = 1; // 释放总线
delay_us(60); // 等待60μs
ack = DQ; // 读取应答信号
delay_us(420); // 等待剩余时间
return ~ack; // 返回初始化状态
}
温度转换和读取也要注意时序。启动温度转换后要等待足够的时间,12位精度时需要750ms。我建议在转换完成后立即读取温度值,避免受到其他操作干扰。
3. PCF8591的光照测量与DAC输出
PCF8591这个芯片真的很实用,它集成了4路ADC输入和1路DAC输出,通过I2C总线控制。在做光照度测量时,要注意光敏电阻的特性是非线性的,实际应用中可能需要做线性化处理。
ADC读取时,控制字节的设置很关键:0x41表示启用模拟输出,选择通道1(光照传感器接在这个通道),设置单端输入模式。这个设置要是搞错了,读出来的数据肯定不对。
// PCF8591读取ADC值
unsigned char Read_ADC() {
unsigned char temp;
I2CStart();
I2CSendByte(0x90); // 器件地址+写模式
I2CWaitAck();
I2CSendByte(0x41); // 控制字节:启用模拟输出,通道1
I2CWaitAck();
I2CStart();
I2CSendByte(0x91); // 器件地址+读模式
I2CWaitAck();
temp = I2CReceiveByte();
I2CSendAck(1);
I2CStop();
return temp;
}
DAC输出部分有个容易踩的坑:输出电压的计算。PCF8591的DAC是8位的,参考电压为5V时,每个LSB对应5V/255≈0.0196V。但实际输出时,最好用浮点数计算后再转换为整型,避免精度损失。
4. 按键处理与状态机设计
按键处理是嵌入式系统的基本功,但要做好也不容易。题目要求按键消抖处理,我一般用10-20ms的延时消抖,既保证可靠性又不影响响应速度。
状态机设计是这个项目的核心。系统有模式界面和输出界面两种显示状态,每种界面下又有不同的工作模式。我建议用两个变量来管理状态:Sys_Mod表示系统状态(模式界面或输出界面),Output_Mod表示当前的工作模式(温度或光照度)。
// 状态变量定义
unsigned char Sys_Mod = 0; // 0:模式界面, 1:输出界面
unsigned char Output_Mod = 0; // 0:温度模式, 1:光照模式
// 按键处理函数
void Key_Proc(void) {
if(Key_Dly) return;
Key_Dly = 1;
Key_Val = Read_Key();
Key_Down = Key_Val & (Key_Old ^ Key_Val);
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
Key_Old = Key_Val;
if(Key_Down == 4) { // S4按键:模式切换
if(Sys_Mod == 0) { // 仅在模式界面有效
if(++Output_Mod == 2) Output_Mod = 0;
}
}
if(Key_Down == 5) { // S5按键:界面切换
if(++Sys_Mod == 2) Sys_Mod = 0;
}
}
这种状态机设计清晰明了,扩展性也好。如果需要增加新的模式或界面,只需要增加状态值和相应的处理逻辑即可。
5. 数码管显示与数据处理
数码管显示要注意几个细节:一是消隐处理,避免重影;二是动态扫描频率要足够高(一般大于50Hz),避免闪烁;三是亮度要均匀。
温度数据显示要保留小数点后一位,我用的是sprintf函数格式化输出,这种方法简单但效率稍低。如果对性能要求高,可以自己写转换函数。
// 温度数据显示处理
temper = Read_Temperture();
if((temper/16) == 85) { // 检测默认值
sprintf(Seg_Buf, "1 "); // 显示异常
} else {
sprintf(Seg_Buf, "1 %03.1f", temper/16.0);
}
Seg_Tran(Seg_Buf, Seg_Code);
光照度数据显示要注意不足3位时高位熄灭,这个可以用%3d格式实现,但要注意sprintf会在数字前填充空格,需要额外处理才能实现熄灭效果。
6. DAC输出控制算法
DAC输出控制是这个题目最有趣的部分,它需要根据不同的输入参数计算输出值。温度模式下的输出特性是:低于10℃输出1V,高于40℃输出5V,中间段线性变化。
这里有个关键点:输出值要实时更新,不能只在模式切换时计算。我最初就犯了这个错误,模式切换后DAC输出还是上一个模式的值。
// DAC输出控制函数
void Output_Proc(void) {
switch(Output_Mod) {
case 0: // 温度模式
if(temper/16 < 10) {
volt_stat = 10; // 对应1.0V
} else if(temper/16 > 40) {
volt_stat = 50; // 对应5.0V
} else {
volt_stat = (temper - 40) / 12;
}
break;
case 1: // 光照模式
if(Light_Stat < 10) {
volt_stat = 10;
} else if(Light_Stat > 240) {
volt_stat = 50;
} else {
volt_stat = (4 * Light_Stat + 190) / 23;
}
break;
}
}
计算完成后,需要用Write_DAC函数实际输出。注意volt_stat是我定义的内部变量,表示输出电压的10倍(如2.3V表示为23),这样处理可以避免浮点数运算。
7. 常见问题与调试技巧
在做这个项目时,我遇到了几个典型问题,这里分享给大家避免踩坑:
第一个问题是DS18B20总是返回85℃。这通常是时序问题,检查延时函数是否准确,特别是12MHz时钟下的延时。可以用示波器查看DQ线上的波形,确认复位脉冲、应答脉冲是否符合要求。
第二个问题是PCF8591读取的值不稳定。I2C总线对上拉电阻很敏感,一般用4.7kΩ的上拉电阻效果比较好。如果读取值跳动较大,可以尝试多次读取取平均值。
第三个问题是数码管显示闪烁。确保定时器中断频率足够高,每个数码管的点亮时间均匀。显示缓冲区要在主循环中更新,在中断中只做显示输出。
第四个问题是按键响应不稳定。除了软件消抖,硬件上可以在按键两端并联一个小电容(如0.1μF),进一步抑制抖动。
调试时建议分模块测试:先调通DS18B20,再调PCF8591,然后做按键和显示,最后整合在一起。每完成一个模块就充分测试,这样更容易定位问题。
8. 系统优化与扩展思路
完成基本功能后,还可以做一些优化和扩展。比如加入温度校准功能,DS18B20虽然精度不错,但个别器件可能有偏差,可以通过软件校准提高精度。
显示方面可以增加动画效果,比如模式切换时的渐变效果,让界面更友好。但要注意不要影响主要功能的实时性。
数据存储也是值得考虑的功能,可以记录温度和光照的历史数据,统计最大值、最小值、平均值等。虽然题目没要求,但实际应用中这些功能很有用。
对于更复杂的应用,可以增加报警功能,当温度或光照超过设定阈值时,通过蜂鸣器或LED闪烁提示。这也是很多实际项目的常见需求。
最后提醒大家,编程时要多写注释,变量命名要规范。比赛时间紧张,清晰的代码结构能大大提高调试效率。完成基本功能后,如果有时间再考虑优化和扩展功能。
更多推荐



所有评论(0)