
Jellyfish从89C51到AI8051学习贴第十五天!
今天继续学习《8051U深度入门到32位51大型实战教学视频》第十二集 密码锁。
0x01 学习重点
0x02 学习心得
-
4种复位模式
- 上电复位 (POR - Power-On Reset)
- 触发方式:当 VCC 电源从 0V 攀升至系统工作电压时,内部硬件检测到电压跨越阈值,自动触发。
- 特点:
- 冷启动:这是最彻底的复位,会重置所有寄存器、I/O 状态及内部存储空间。
- 标志位:通常在寄存器中表现为
POF (Power-On Flag)。
- 典型场景:设备第一次插入电池或接通电源。
- 低压复位 (LVR - Low Voltage Reset / Brown-out)
- 触发方式:系统运行过程中,电源电压跌落至设定的阈值(如 2.4V 或 3.3V)以下时触发。
- 特点:
- 保护机制:防止电压过低导致 CPU 逻辑混乱、写 Flash 错误或程序跑飞。
- 可调性:现代单片机通常允许在 ISP 下载软件 或软件寄存器中设置不同的掉电检测电压(LVD)。
- 典型场景:电池电量耗尽瞬间、大电流负载(如电机启动)导致电压掉闸。
- 复位脚复位 (NRST - External Reset Pin)
- 触发方式:外部复位引脚(RST/NRST)被施加了特定的电平信号(通常是持续一定时间的低电平,STC 某些型号为高电平)。
- 特点:
- 硬件干预:由外部电路(如复位按键、外部监控芯片)物理控制。
- 电路设计:通常需要外接 RC 复位电路(10k电阻 + 0.1μF电容)来过滤干扰波形。
- 典型场景:用户按下设备上的物理“Reset”小孔按键。
- 看门狗复位 (WDT - Watchdog Timer Reset)
- 触发方式:看门狗计数器溢出。程序必须在规定时间内“喂狗”(重置计数值),如果程序死锁或跑飞未能及时喂狗,计数器归零即触发复位。
- 特点:
- 自愈功能:是嵌入式系统摆脱“死机”状态的最后防线。
- 软硬结合:需要在软件中初始化看门狗频率,并在主循环(
while(1))中妥善放置喂狗代码。
- 典型场景:程序因强电磁干扰进入死循环,或逻辑陷入阻塞无法退出。
计算看门狗超时时间(24Mhz下)
\frac{12\times 32768\times2^{6}}{24\times10^{6} }=1.048576秒
//初始化的时候要运行这个函数断开重置usb
void USB_Reset_U(void)
{
P3M0 = 0x00;
P3M1 = 0x00;
P3M0 &= ~0x03;
P3M1 |= 0x03;
USBCON = 0X00;
USBCLK = 0X00;
IRC48MCR = 0X00;
Delay10ms();
}
//配置看门狗寄存器
EN_WDT=1; //开启看门狗
IDL_WDT=1; //空闲模式继续累计看门狗
WDT_CONTR|=0x05; //配置喂狗超时时间,101 分频64,12x32768x2^6/(24*10^6)=1.048576
EA=1; //开启总中断
//定时器调度情况如下:
TASK_COMPONENTS Task_Comps[]=
{
//状态 计数 周期 函数
{0, 1, 1, Seg_Task}, /* task 1 Period: 1ms */
{0, 10, 10, Key_Task},
{0, 1000, 1000, Dog},
{0, 10, 10, Key33_Task},
};
//关键定义
u8 SEG_NUM[] = {
0x3F, /*'0', 0*/
0x06, /*'1', 1*/
0x5B, /*'2', 2*/
0x4F, /*'3', 3*/
0x66, /*'4', 4*/
0x6D, /*'5', 5*/
0x7D, /*'6', 6*/
0x07, /*'7', 7*/
0x7F, /*'8', 8*/
0x6F, /*'9', 9*/
0x77, /*'A', 10*/
0x7C, /*'B', 11*/
0x39, /*'C', 12*/
0x5E, /*'D', 13*/
0x79, /*'E', 14*/
0x71, /*'F', 15*/
0x40, /*'-', 16*/
0x00, /*' ', 17*/
0x80, /*'.', 18*/
0x5C, /*'O', 19*/
0x73, /*'P', 20*/
0x79, /*'E', 21*/
0x54, /*'N', 22*/
0x3E, /*'U', 23*/
};
u8 T_NUM[] =
{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
u8 Key_Vol = 0;
u8 Key33_Vol = 0;
u8 Seg_no = 0;
u8 Disp_Buf[8] = {16, 16, 16, 16, 16, 16, 16, 16}; // 定义一个全局或静态数组,存放 8 位数码管要显示的数字
u8 open[8] = {16, 16, 16, 16, 19, 20, 21, 22}; // 定义密码
u8 passwd[8] = {1, 2, 3, 4, 5, 6, 7, 8}; // 定义密码
u8 version[3] = {23, 1, 0}; // 定义密码
u8 dis_ver = 3;
//关键函数
// --- 数码管显示 1ms 调度任务逻辑 ---
//正常显示Disp_Buf中的内容,当dis_ver大于0时,后三位显示版本号,dis_ver会每秒减一。
void Seg_Task(void)
{
if (Seg_no == 0)
{
Display_Seg(SEG_NUM[Disp_Buf[0]], ~T_NUM[Seg_no]);
}
else if (Seg_no == 1)
{
Display_Seg(SEG_NUM[Disp_Buf[1]], ~T_NUM[Seg_no]);
}
else if (Seg_no == 2)
{
Display_Seg(SEG_NUM[Disp_Buf[2]], ~T_NUM[Seg_no]);
}
else if (Seg_no == 3)
{
Display_Seg(SEG_NUM[Disp_Buf[3]], ~T_NUM[Seg_no]);
}
else if (Seg_no == 4)
{
Display_Seg(SEG_NUM[Disp_Buf[4]], ~T_NUM[Seg_no]);
}
else if (Seg_no == 5)
{
if (dis_ver == 0)
{
Display_Seg(SEG_NUM[Disp_Buf[5]], ~T_NUM[Seg_no]);
}
else
{
Display_Seg(SEG_NUM[version[0]], ~T_NUM[Seg_no]);
}
}
else if (Seg_no == 6)
{
if (dis_ver == 0)
{
Display_Seg(SEG_NUM[Disp_Buf[6]], ~T_NUM[Seg_no]);
}
else
{
Display_Seg(SEG_NUM[version[1]] | SEG_NUM[18], ~T_NUM[Seg_no]);
}
}
else if (Seg_no == 7)
{
if (dis_ver == 0)
{
Display_Seg(SEG_NUM[Disp_Buf[7]], ~T_NUM[Seg_no]);
}
else
{
Display_Seg(SEG_NUM[version[2]], ~T_NUM[Seg_no]);
}
}
Seg_no++;
if (Seg_no >= 8)
Seg_no = 0;
}
// --- 矩阵键盘 10ms 调度任务逻辑 ---
void Key_Task(void)
{
u8 i, j;
u8 current_raw_key = 0xff; // 本次扫描到的原始物理键值
u8 r_temp = 0, c_temp = 0;
// --- 步骤一:扫描行 ---
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
//_nop_(); // 【核心修复】给电平建立时间
if (ROW1 == 0)
r_temp = 0; // 第一行按下
else if (ROW2 == 0)
r_temp = 4; // 第二行按下
else
{
goto scan_process;
} // 没按键,直接跳到末尾清零
// --- 步骤二:扫描列 ---
COL1 = 1;
COL2 = 1;
COL3 = 1;
COL4 = 1;
ROW1 = 0;
ROW2 = 0;
_nop_(); // 给电平建立时间
if (COL1 == 0)
c_temp = 0;
else if (COL2 == 0)
c_temp = 1;
else if (COL3 == 0)
c_temp = 2;
else if (COL4 == 0)
c_temp = 3;
else
{
goto scan_process;
}
current_raw_key = r_temp + c_temp; // 计算出当前物理键值
scan_process:
// --- 步骤三:双稳态消抖 + 松手检测 ---
if (current_raw_key == 0xff)
{
Key_Vol = 0;
}
Key_Vol++;
if (current_raw_key != 0xff && Key_Vol == 2) // 只有当检测到确实有按键(不等于0xff),才更新
{
for (i = 0; i < 8; i++)
{
if (Disp_Buf[i] == 16)
{
if (Disp_Buf[7] == 22)
{
for (j = 0; j < 8; j++)
{
Disp_Buf[j] = 16;
}
}
Disp_Buf[i] = current_raw_key + 1;
CLR_WDT = 1; // 按键按下的时候喂狗
break;
}
}
// 这里可以触发一个“按键按下”的标志位,供外部使用
}
// 恢复扫描初态,准备下一次 10ms
COL1 = 0;
COL2 = 0;
COL3 = 0;
COL4 = 0;
ROW1 = 1;
ROW2 = 1;
}
// --- 软件复位按键判断 10ms 调度任务逻辑 ---
void Key33_Task(void)
{
if (P33 == 0)
{
Key33_Vol++;
if (Key33_Vol == 5)
{
IAP_CONTR = 0x20; //软件复位
}
}
else
{
Key33_Vol = 0;
}
}
// --- 看门狗及密码检测 1000ms 调度任务逻辑 ---
void Dog()
{
bit Is_Equal = 1; // 假设相等
u8 i;
if (Disp_Buf[0] == 16)
{
CLR_WDT = 1; //当第一位是“-”时,喂狗。
}
if (dis_ver != 0)
{
dis_ver--; //显示版本号的倒计时。
}
if (Disp_Buf[7] != 16 && Disp_Buf[7] != open[7])
{
// 3. 比较内容
for (i = 0; i < 8; i++)
{
if (Disp_Buf[i] != passwd[i])
{
Is_Equal = 0;
break;
}
}
if (Is_Equal)
{
for (i = 0; i < 8; i++)
{
Disp_Buf[i] = open[i];
}
}
else
{
for (i = 0; i < 8; i++)
{
Disp_Buf[i] = 16;
}
}
}
}