蓝桥杯之第四周第二讲

蓝桥杯单片机复习笔记- 第七讲:模拟温度采集记录器

  • 一、 功能设计要求 (原题全解)

    本试题要求设计一个具备温度采集、数值转换、参数设置保护及PWM亮度调节功能的智能系统。以下是基于任务书的详尽功能拆解:

    1. 系统初始化与量程要求
    • 上电状态: 默认处于 温度采集界面

    • 采集有效范围: 0 - 85℃。

    • 参数设置范围: 10 - 70℃。

    • 默认参数:

    • 温度上限值 (TMAX): 30℃

    • 温度下限值 (TMIN): 20℃

    1. 数码管显示界面要求

    系统共有三个显示界面,需清晰区分“输入状态”与“实际显示状态”:

    • 界面1:温度采集界面 (默认)

    • 进入方式: 上电默认或按下 S16。

    • 显示内容: 提示符 A + 当前输入的温度数据。

    • 格式: 提示符显示为 熄灭 (实际显示通常为A或不显示提示符,根据图表源码52,第一列是"A",第二列是"提示符 熄灭",通常理解为首位显示功能符A,或者按题目图示只显示数据)。根据表格 第一列为 “A”,故第一位显示 A,后续显示输入数据。

    • 交互特效: 当前正在输入的数码管单元需以 0.5秒/次 的速度闪烁 。

    • 数据单位: mV (输入时模拟电压值,但题目语境为温度输入,显示℃)。

    • 界面2:数据显示界面

    • 显示内容: 提示符 C + 当前实际温度数据。

    • 格式:

    • 第一位:显示 C

    • 后续位:显示实际温度 (如 23)。

    • 数据处理: 实际温度数据需采用 四舍五入 后保留 整数部分

    • 界面3:参数设置界面

    • 进入方式: 按下 S12。

    • 显示内容: 提示符 P + TMAX + TMIN。

    • 格式:

    • 第一位:显示 P

    • 左侧数据区:显示 TMAX (如 30)。

    • 右侧数据区:显示 TMIN (如 20)。

    • 交互特效: 当前选中的参数单元格需以 0.5秒/次 的速度闪烁 。

    • 默认焦点: 每次从数据界面进入参数界面,默认选中 TMAX

    1. 按键功能逻辑
    • S1-S10 (数字键): 对应 0-9 输入。仅在 温度采集界面 有效 。

    • S11 (小数点): 输入小数点。仅在 温度采集界面 有效 。

    • S16 (采集/确认):

    • 功能:

    1. 在非采集界面按下:进入 温度采集界面

    2. 在采集界面按下:

    • 若数据合法:保存当前值,四舍五入 计算后返回数据显示界面 。

    • 若数据无效:清空当前输入,重新开始 。

    • S12 (界面切换):

    • 功能: 在“数据显示界面”和“参数设置界面”之间循环切换 。

    • 退出校验 (难点): 从参数界面退出时,需检查 TMAX TMIN

    • 若合理:参数生效,进入数据界面。

    • 若不合理 (TMAX < TMIN):自动恢复为 进入参数设置界面前的有效参数,并进入数据界面 (注:此处源码80描述为恢复并进入数据界面,但也常理解为恢复后停留在设置界面提示错误,需结合L4逻辑,源码80原文是"自动恢复进入参数设置界面前的有效参数,进入数据界面") 。

    • S13 (参数切换): 仅在参数设置界面有效。切换选择 TMAX 或 TMIN 。

    • S14 (加) & S15 (减):

    • 有效性: 仅在参数设置界面有效 。

    • 功能: 对当前选中的参数进行加/减 1℃ 操作 。

    • 长按功能 (难点): 长按 0.5秒 后,可实现 快速 加、减功能 。

    1. LED 指示与亮度控制 (PWM)

    系统需根据当前温度 与参数 () 的关系,控制LED的 点亮状态亮度等级

    • 状态指示 (L1-L4):

    • L1 (高温报警): 当$T > T_{MAX}$ 时点亮,否则熄灭 。

    • L2 (正常范围): 当 $T*{MIN} \le T \le T*{MAX}$ 时点亮,否则熄灭 。

    • L3 (低温报警): 当 $T < T_{MIN}$ 时点亮,否则熄灭 。

    • L4 (错误指示): 当出现错误的参数设置操作 (如退出时参数不合理) 时点亮,直至下一次正确的参数设置后熄灭 。

    • 亮度等级控制 (PWM):

    • 1级亮度 (最暗/特定亮度): 当$T > T_{MAX}$ 时 。

    • 2级亮度 (中等亮度): 当 $T*{MIN} \le T \le T*{MAX}$ 时 。

    • 3级亮度 (最亮/特定亮度): 当 $T < T_{MIN}$ 时 。


二、 系统核心架构与变量设计( main.c)

1. 变量设计思路

本题变量设计非常具有代表性,分为浮点运算辅助参数备份PWM控制三类。

C

#include <REGX52.H>
#include "Key.h"
#include "Seg.h"
#include "LED.h"
​
// ... (基础变量省略) ...
​
// === 1. 采集与计算变量 ===
u8 Seg_Input[6]={10,10,10,11,11,11}; // 输入缓冲区 (10:熄灭, 11:空)
u8 Seg_Input_Index=3;  // 输入游标,从第3位开始(前3位显示C/提示符)
u8 Point_Wela=3;       // 记录小数点按下的位置(位选)
// 浮点转换系数表:根据小数点位置决定除以多少
// 例如输入 12.3 (点在第4位),数据是123,除以10.0得到12.3
float Point_Change[3]={100.0, 10.0, 1.0}; 
u16 Temperature=0;     // 最终计算出的整数温度
bit Point_Flag=0;      // 标记是否按下了小数点
bit Error_Flag=0;      // 输入越界或逻辑错误标志
​
// === 2. 参数与长按变量 ===
// 采用双数组策略实现"参数回滚"功能
u8 T_Set_Old[2]={30,20}; // [0]TMAX, [1]TMIN - 存档值(生效值)
u8 T_Set_New[2]={30,20}; // 编辑值(显示值)
bit T_Set_Index=0;       // 当前正在修改哪一个 (0或1)
// 长按计时逻辑
bit Time_Flag=0;   // 计时启动开关
u16 Time_Count=0;  // 按下时长计数器
​
// === 3. PWM 亮度控制变量 ===
u8 LED_PWM=0; // 目标占空比 (决定亮度)
u8 LED_Num=0; // 当前周期计数值 (0-9循环)

三、 难点突破:浮点输入与转换算法

本题最大的难点在于:用户输入的是字符流(带小数点),而系统需要将其转换为整数温度,且要求四舍五入。

1. 逻辑分析

  • **输入:**用户输入1 2 . 5,缓冲区存为1, 2, 5,小数点位置记为Index=4

  • **合成:**先按整数合成125

  • **定点化:**查表Point_Change,找到对应系数10.0

  • **四舍五入:**核心公式(数值 + 0.5) / 系数。在整数运算中,等价于(数值 + 5) / 10

2. 代码实现( Key_Proc- Case 16)

C

// S16: 采集/确认键逻辑
case 16:
    if(Seg_Mode==1){ // 如果在采集界面 -> 确认输入
        // 核心算法:数据合成与四舍五入
        // 1. 合成原始整数: Seg_Input[3]*100 + ...
        // 2. 加上补尝值 5 (用于四舍五入,相当于+0.5)
        // 3. 除以对应的小数点系数 Point_Change
        Temperature=(u16)((Seg_Input[3]*100+Seg_Input[4]*10+Seg_Input[5]+5)/Point_Change[Point_Wela-4]);
        
        // 校验逻辑:必须包含小数点 且 范围在0-85之间
        if(Point_Flag==1 && Temperature>=0 && Temperature<=85){
            Seg_Mode=2;   // 数据合法,跳转到显示界面
            Error_Flag=0;
        }else{
            Error_Flag=1; // 数据非法,点亮L4
        }
        
        // 复位输入缓冲区,准备下一次采集
        for(i=3;i<6;i++){
            Seg_Input[i]=11;
        }
        Seg_Input_Index=3;
        Point_Flag=0;
        Seg_Input_Flash=3;
        Seg_Point[Point_Wela-1]=0; // 清除小数点显示
    }else{
        Seg_Mode=1; // 如果不在采集界面 -> 进入采集界面
    }
break;

四、 难点突破:长按/短按与参数回滚

在参数设置界面,S14/S15 既支持点按微调,又支持长按快速调节。同时S12 负责参数的合法性校验。

1. 长按/短按状态机思路

利用Time_Flag开启计时,在主循环检测按键状态:

  • **短按:**按键抬起( Key_Up)时,若计时<500ms,执行一次操作。

  • **长按:**按键保持按下( Key_Old)且计时>500ms,执行操作并将计时器重置为300ms(实现连续快速触发)。

2. 代码实现( Key_Proc- Mode 3)

C

if(Seg_Mode==3){
    // === 长按触发逻辑 (以S14加号键为例) ===
    if(Key_Down==14){
        Time_Flag=1; // 按下瞬间,开启计时器
    }
    
    // 分支1: 短按逻辑 (计时未满500ms)
    if(Time_Count<500){  
        if(Key_Up==14){ // 必须检测"抬起"动作
            if(++T_Set_New[T_Set_Index]>70) T_Set_New[T_Set_Index]=10; // 循环加
            // 操作完成,复位计时
            Time_Flag=0;
            Time_Count=0;
        }
    }
    // 分支2: 长按逻辑 (计时已满500ms)
    else{
        if(Key_Old==14){ // 检测"保持按下"状态
            if(++T_Set_New[T_Set_Index]>70) T_Set_New[T_Set_Index]=10;
            Time_Count = 300; // 重置为300ms,实现0.2秒一次的连续触发
        }
        else if(Key_Up==14){ // 长按结束
            Time_Flag=0;
            Time_Count=0;
        }
    }   
    // ... S15减号键逻辑同理 ...
}

3. 参数回滚逻辑(S12)

退出设置时,如果TMAX < TMIN,则视为设置无效,恢复为旧参数。

C

case 12: // 界面切换键
    if(Seg_Mode==2){
        Seg_Mode=3; // 进入设置
    }
    else if(Seg_Mode==3){ // 退出设置 -> 检查合法性
        // 检查:新设置的 TMAX 是否大于等于 TMIN
        if(T_Set_New[0] > T_Set_New[1]){ // 逻辑修正: 题目要求TMAX>=TMIN
            Seg_Mode=2; // 合法,切换界面
            // 提交事务: 将新值覆盖旧值
            T_Set_Old[0]=T_Set_New[0];
            T_Set_Old[1]=T_Set_New[1];
        }else{
            // 非法,不切换界面(或切换界面但参数无效),此处逻辑为:
            // 强制回滚: 用旧值覆盖新值,并停留在参数界面或退出?
            // 代码逻辑实现的是: 恢复旧值,且清除闪烁位
            // 注: 根据题目,反之自动恢复有效参数,进入数据界面。
            // 这里的代码逻辑似乎是直接恢复并留在了界面3,或者下次进循环处理? 
            // *实际代码逻辑*: 恢复了T_Set_New,但没有执行 Seg_Mode=2,
            // 这意味着用户必须改对参数才能退出,或者代码意图是回滚后继续让用户改。
            T_Set_New[0]=T_Set_Old[0];
            T_Set_New[1]=T_Set_Old[1];
            Seg_Set_Flash=0;
        }
    }
break;

五、 硬件驱动:LED PWM 调光

题目要求根据温度范围改变LED亮度,这是通过软件模拟PWM实现的。

1. 软件PWM原理

  • 周期: 10ms (由Timer0每1ms调用,LED_Num0-9 循环)。

  • 占空比: LED_PWM决定了在这10ms里,LED亮几毫秒。

  • 实现: if (LED_Num < LED_PWM) 亮 else 灭

2. 代码实现( LED_Proc& Timer0)

C

// 1. 业务层: 决定亮度等级 (LED_Proc)
// 题目要求3个亮度等级,这里分配为 30%, 90%, 60%
if(Temperature > T_Set_Old[0]){ // > TMAX
    LED_PWM=3; // 亮度等级1 (较暗)
}
else if(Temperature < T_Set_Old[1]){ // < TMIN
    LED_PWM=9; // 亮度等级3 (最亮)
}else{ // 中间范围
    LED_PWM=6; // 亮度等级2 (中等)
}
​
// 2. 驱动层: 执行PWM (Timer0_Server)
if(++LED_Num==10) LED_Num=0; // 产生 0-9 的锯齿波
// 比较产生PWM波形
if(LED_Num < LED_PWM){
    LED_Disp(LED_Pos, LED_Buf[LED_Pos]); // 亮
}else{
    LED_Disp(LED_Pos, 0); // 灭
}

六、 总结与复习重点

  1. **小数点处理技巧:**遇到带小数点的数码管输入,不要使用float 类型存储输入过程。最佳实践是:记录数字序列+ 记录小数点位置,最后统一进行一次浮点运算转换为整数。

  2. 长按通用模板:

    • 按下置标志位。

    • 松手时判断时间-> 短按。

    • 按住时判断时间-> 长按-> 修改时间变量实现连续触发。

  3. **事务提交模式:**在参数设置中,使用Old(存档) 和New(草稿) 两组变量。只有校验通过时才执行Old = New,否则执行New = Old(回滚)。这是嵌入式界面开发的标准模式。

  4. **PWM 模拟:**这里的PWM 频率为$1kHz / 10 = 100Hz$,足够骗过人眼实现调光,且不会有闪烁感。