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中,通过头文件引用,确保代码可读性和可维护性。

更多推荐