蓝桥杯单片机复习笔记:过渡模拟二
第一部分:软件逻辑与算法 (大脑篇)
这部分是程序的灵魂,决定了单片机怎么“思考”。
1. 数码管光标闪烁 (500ms)
核心思想:利用定时器制造一个“心跳”,然后根据心跳决定显示数字还是黑屏。
-
原理:
Timer_500Ms负责计时,Seg_Star_Flag负责翻转状态(0变1,1变0)。 -
代码解析:
// --- 定时器中断内 ---
if(++Timer_500Ms == 500) // 每500毫秒触发一次
{
Timer_500Ms = 0; // 必须清零,不然下次没法计时了
Seg_Star_Flag ^= 1; // 【考点】异或操作:0变1,1变0,实现“亮/灭”切换
}
// --- 数码管显示函数内 ---
// 保护机制:如果当前位没有数据(11),就不处理,防止乱码
if(Seg_Input[2] == 11)
{
// 【三目运算符】
// 翻译:Flag是1吗?是就正常显示数字;不是就显示10(10代表熄灭)
// 效果:数字 -> 熄灭 -> 数字 -> 熄灭 ...
Seg_Buf[3 + Seg_Input_Index] = Seg_Star_Flag ? Seg_Input[Seg_Input_Index] : 10;
}
2. 小数点输入逻辑 (状态机)
核心思想:为了防止用户乱按小数点,必须加“锁”。
Point_Flag就是那把锁。一次输入只能按一次小数点。
// --- 按键处理函数 ---
case 11: // S11 小数点键按下
// 【三重安检】
// 1. Seg_DispMode == 0: 必须在采集界面
// 2. Point_Flag == 0: 之前没按过小数点
// 3. Seg_Input[0] != 11: 第1位必须有数字(不能上来就按小数点)
if(Seg_DispMode == 0 && Point_Flag == 0 && Seg_Input[0] != 11)
{
Seg_Point[2 + Seg_Input_Index] = 1; // 当前位置点亮小数点
Point_Wela = Seg_Input_Index; // 记住小数点在第几位(核心!)
Point_Flag = 1; // 上锁!之后再按S11也进不来了
}
break;
case 16: // S16 采集/确认键按下
if(Seg_Disp_Mode != 0) // 如果不在采集界面 -> 切换到采集界面
{
Seg_Input_Index = 0; // 光标归零
Point_Flag = 0; // 解锁小数点
for(i = 0; i < 3; i++) Seg_Input[i] = 11; // 清空数组
Seg_Disp_Mode = 0; // 状态切换
}
else // 已经在采集界面了 -> 说明用户输完了,想确认
{
// 【有效性检查】
// 如果没按过小数点(Point_Flag==0) 或者 没输满3位
// 判定为:无效输入!
if(Point_Flag == 0 || Seg_Input_Index < 3)
{
Seg_Input_Index = 0;
Seg_Point[3] = Seg_Point[4] = 0; // 灭掉小数点
Point_Flag = 0;
for(i = 0; i < 3; i++) Seg_Input[i] = 11; // 清空重来
}
else // 输入合法
{
// 提取数据:百位*100 + 十位*10 + 个位
// 注意:这里原代码Seq_Input可能是笔误,应为 Seg_Input
Temperature = Seg_Input[0] * 100 + Seg_Input[1] * 10 + Seg_Input[2] + 5;
}
}
break;
3. 长按 vs 短按 (按键进阶)
核心思想:用时间来区分意图。
-
按下开始计时。
-
抬起时,看计时器:时间短 = 短按;时间长 = 长按。
// --- 步骤1:按下开始计时 ---
if(Seg_Disp_Mode == 2) // 只有在设置界面才有效
{
if(Key_Down == 14) // S14 被按下
Time_Flag = 1; // 开启计时开关
}
// --- 步骤2:定时器里数数 ---
if(Time_Flag == 1)
{
// 如果一直按着,Count_500Ms就会一直加
if(++Count_500Ms == 600)
Count_500Ms = 600; // 封顶限制,防止溢出
}
// --- 步骤3:判断是长是短 ---
// 【情况A:短按逻辑】
// 如果松手时(Key_Up),计数值还没到500(0.5秒)
if(Count_500Ms < 500)
{
if(Key_Up == 14) // 手抬起来了
{
Time_Flag = Count_500Ms = 0; // 状态清零,为下次做准备
if(++Parameter[Parameter_Index] > 70) // 正常加1
Parameter[Parameter_Index] = 10;
}
}
// 【情况B:长按逻辑】
else
{
// 注意:这里是用 Key_Old 判断,说明手还按着没松开!
if(Key_Old == 14)
{
// 只要按着,代码跑到这就加1,跑得很快,实现“连发”
if(++Parameter[Parameter_Index] > 70)
Parameter[Parameter_Index] = 10;
}
// 松手后清零
if(Key_Up == 14)
Time_Flag = Count_500Ms = 0;
}
4. LED 亮度等级 (PWM技术)
核心思想:“欺骗眼睛”。灯其实一直在闪,只是闪得太快,人眼看着就像“变暗”了。
-
Led_Num:一个从0跑到9的计数器。 -
Led_Pwm:亮度阈值(比如3,6,9)。-
如果
Led_Pwm是 3:10次里亮3次(暗)。 -
如果
Led_Pwm是 9:10次里亮9次(亮)。
-
// --- 定时器中断内快速执行 ---
if(++Led_Num == 10) Led_Num = 0; // 0-9 循环计数
// 【PWM 核心生成】
if(Led_Num < Led_Pwm)
Led_Disp(Led_Pos, ucLed[Led_Pos]); // 亮灯
else
Led_Disp(Led_Pos, 0); // 灭灯
// --- 根据温度设置亮度阈值 ---
if(Temperature > Parameter_Ctrol[0])
Led_Pwm = 3; // 高温 -> 占空比 30% -> 最暗?(看题目要求,通常越小越暗)
else if(Temperature < Parameter_Ctrol[0] && Temperature > Parameter_Ctrol[1])
Led_Pwm = 6; // 舒适 -> 占空比 60% -> 中等
else
Led_Pwm = 9; // 低温 -> 占空比 90% -> 最亮
第二部分:硬件底层驱动 (手脚篇)
这部分是单片机的“生理本能”,控制锁存器和译码器。
1. 硬件原理图解
单片机控制外设主要靠两个门神:
-
译码器 (74HC138):管家,决定选谁(LED还是数码管?)
-
锁存器 (74HC573):大门,决定数据是穿过去还是被锁住。
2. 初始化 (Init.c)
目的:上电第一件事,把所有可能乱叫乱亮的东西全关掉。
#include <Init.h>
void System_Init()
{
// 1. 关闭 LED
P0 = 0xff; // 准备数据:全1 (LED是低电平亮,全1就是全灭)
// 这里的位运算是为了只改变P2的高3位(Y4C),不影响低5位
P2 = P2 & 0x1f | 0x80; // 选中 Y4 (LED)
P2 &= 0x1f; // 锁存!(关门,保持全灭状态)
// 2. 关闭 蜂鸣器/继电器
P0 = 0x00; // 准备数据:全0 (蜂鸣器高电平响,全0是关)
P2 = P2 & 0x1f | 0xa0; // 选中 Y5 (蜂鸣器)
P2 &= 0x1f; // 锁存!
}
3. LED 驱动 (Led.c) —— 高分写法
亮点:使用了 static 缓存状态。
为什么这么写? 如果不记录状态,当你为了点亮 L2 去修改 P0 时,可能会不小心把原本亮着的 L1 给灭了。
#include <Led.h>
// addr: 灯的编号(0-7)
// enable: 1=亮, 0=灭
void Led_Disp(unsigned char addr, enable)
{
// static:静态变量,函数运行完也不会丢失数据,像记事本一样
static unsigned char temp = 0x00; // 逻辑状态记事本
static unsigned char temp_old = 0xff; // 硬件状态备份
// --- 步骤1:只改记事本 ---
if(enable)
temp |= 0x01 << addr; // 将第addr位 置1
else
temp &= ~(0x01 << addr); // 将第addr位 置0
// --- 步骤2:只有状态变了才动硬件 (高效!) ---
if(temp != temp_old)
{
P0 = ~temp; // 取反,因为LED是低电平亮 (逻辑1 -> 硬件0)
P2 = P2 & 0x1f | 0x80; // 打开 Y4 锁存器
P2 &= 0x1f; // 锁住数据
temp_old = temp; // 更新备份
}
}
4. 主函数 (main.c)
#include <Init.h>
#include <Led.h>
void main()
{
System_Init(); // 上电先灭掉所有无关外设
while(1)
{
// 这里的 1 代表 enable,点亮第0个灯
// 得益于 Led_Disp 里的 temp!=temp_old 判断,
// 虽然在死循环里,但不会一直疯狂操作IO口,很稳定。
Led_Disp(0, 1);
}
}
