Led模块
一、 System_Init():系统初始化逻辑
目的:上电时强制关闭所有外设(LED、蜂鸣器、继电器),防止状态紊乱。
1. 核心代码对齐解析
| 代码行 | 逻辑操作 | 硬件动作 |
|---|---|---|
P0 = 0xff; |
准备数据 | 准备好“全 1”信号(高电平)。 |
| `P2 = P2 & 0x1f | 0x80;` | 打开 LED 门 |
P2 &= 0x1f; |
锁死门 | 关闭锁存器选通,LED 状态固定为全灭(共阳极 1 为灭)。 |
P0 = 0x00; |
准备数据 | 准备好“全 0”信号(低电平)。 |
| `P2 = P2 & 0x1f | 0xa0;` | 打开 模块门 |
P2 &= 0x1f; |
再次锁死 | 状态固定,确保此时蜂鸣器不响、继电器不吸合。 |
2. 关键点:P2 的高三位
-
P2 的高三位(P2.5, P2.6, P2.7)连接到了 138译码器。
-
通过改变这三位的值,单片机可以决定 P0 端口的数据流向哪一个硬件模块。

二、 Led_Disp():精确位映射驱动
这个函数最难理解的是位运算。它的目的是:你想让第几个灯亮,它就只动那一个位,不影响别的位。
第一步:在内存中“画草图” (变量 temp)
C
static unsigned char temp = 0x00; // 这就是我们的“虚拟灯板”
-
temp是一个 8 位的数字(0000 0000)。 -
每一位代表一个灯。比如
0000 0001代表第 0 号灯亮。
第二步:修改“草图”上的某一位
C
if(enable)
temp |= 0x01 << addr; // 【点亮逻辑】
else
temp &= ~(0x01 << addr); // 【熄灭逻辑】
假设 addr = 2(控制第 3 盏灯):
-
0x01 << 2得到0000 0100。 -
|=(或运算):强制把第 2 位变 1,其他位不变。 -
&= ~(与非运算):强制把第 2 位变 0,其他位不变。
第三步:把“草图”变成真实的电流
C
if(temp != temp_old) // 只有画稿变了,才去操作硬件
{
P0 = ~temp; // 【关键】取反!
// 因为硬件是“共阳极”,P0口输出 0 才会亮,1 反而灭。
// 我们逻辑里 1 是亮,所以给硬件前要颠倒一下。
P2 = P2 & 0x1f | 0x80; // 选通 LED 锁存器,把 P0 的数据刷进去
P2 &= 0x1f; // 锁定数据
temp_old = temp; // 记录下现在的样子,下次对比
}

这份笔记将题目要求的三个核心逻辑与你提供的源代码进行了逐行对应。左侧是逻辑原理解析,右侧是代码中的具体实现位置。
过度模拟二
1. 小数输入与浮点数合成
核心逻辑:
先记录数字,再记录小数点位置。确认采集时,先算出“纯整数”,再根据小数点位置除以 10 的 N 次方。
(1) 小数点按下 (记录位置)
在 Key_Proc 函数的 case 11 中:
C
case 11://小数点输入
// 检查:必须在采集模式 && 没按过小数点 && 第一位有数 [cite: 25]
if(Seg_Disp_Mode == 0 && Point_Flag == 0 && Seg_Input[0] != 11)
{
// 1. 点亮当前数码管的小数点
Seg_Point[2+Seg_Input_Index] = 1;
// 2.【关键】记录此时光标在哪一位 (Point_Wela)
// 比如输入 "2.",此时 Index=1,Point_Wela 就记为 1
Point_Wela = Seg_Input_Index;
// 3. 上锁,防止再次按小数点
Point_Flag = 1;
}
break;
(2) 确认采集 (合成浮点数)
在 Key_Proc 函数的 case 16 中:
C
case 16://温度采集
// ...省略前面的合法性检查...
else//小数点使能 进行范围判断
{
// 1. 先把数组拼成一个完整的整数 (例如输入 2.5,这里算出 25)
Temperature = Seg_Input[0] * 100 + Seg_Input[1] * 10 + Seg_Input[2] + 5;
// 2.【关键】根据小数点位置进行除法运算
// 逻辑:总共有3位。如果小数点在第1位(Wela=1),则 (3-1)=2,循环除2次10
// 25 -> 2.5 -> 0.25 (这里根据你的代码逻辑,可能有细微差异,但原理是移位)
while(3 - Point_Wela)
{
Temperature /= 10.0; // 变成浮点数
Point_Wela++;
}
// ...后续判断是否 > 85 ... [cite: 47]
}
break;
2. 按键的高级处理 (长按 vs 短按)
核心逻辑:
利用定时器中断给 Count_500Ms 充当秒表。按键处理函数根据秒表读数决定走哪个分支。
(1) 秒表 (定时器后台计时)
在 Timer0Server 中断函数中:
C
if(Time_Flag == 1) // 如果按键按下的标志位被置1
{
// 每毫秒 +1,直到 600 (封顶)
if(++Count_500Ms == 600)
Count_500Ms = 600;
}
(2) 按键逻辑 (分流处理)
在 Key_Proc 函数中 (以 S14 为例):
C
// A. 按下检测:启动秒表
if(Seg_Disp_Mode == 2)
{
if(Key_Down == 14) // S14按下
Time_Flag = 1; // 【启动】告诉中断开始计时
}
// B. 短按逻辑 (松手时,时间 < 500ms)
if(Count_500Ms < 500)
{
if(Key_Up == 14)// S14抬起
{
Time_Flag = Count_500Ms = 0; // 清零秒表
// 执行一次加法
if(++Parameter[Parameter_Index] > 70)
Parameter[Parameter_Index] = 10;
}
}
// C. 长按逻辑 (时间 >= 500ms) [cite: 29]
else
{
// 注意:这里是检测 Key_Old (保持按下状态)
if(Key_Old == 14)
{
// 连发代码 (你的代码这里缺少限速,建议加上 if(Count%50==0))
if(++Parameter[Parameter_Index] > 70)
Parameter[Parameter_Index] = 10;
}
// 长按后松手,也要清零
if(Key_Up == 14)
Time_Flag = Count_500Ms = 0;
}
3. 数据显示与四舍五入
核心逻辑: 题目要求四舍五入保留整数 。 计算机浮点转整数默认是“向下取整”(例如 25.9 变成 25)。 算法:+0.5 再取整。
代码对应位置
在 Seg_Proc 函数的 case 1 (数据显示界面) 中:
源代码 (当前存在截断误差):
C
case 1://数据显示界面
Seg_Buf[0] = 13;//C
// ...省略熄灭代码...
// 原代码直接强转,丢失精度 (25.9 -> 25)
Seg_Buf[4] = (unsigned char)Temperature / 10;
Seg_Buf[5] = (unsigned char)Temperature % 10;
break;
修正后的笔记 (加入四舍五入):
C
case 1://数据显示界面
// ...
// 【修正】先 +0.5 实现四舍五入
// 例如 Temp=25.6 -> +0.5=26.1 -> 取整=26
unsigned char Temp_Disp = (unsigned char)(Temperature + 0.5);
Seg_Buf[4] = Temp_Disp / 10;
Seg_Buf[5] = Temp_Disp % 10;
break;