目录
实验内容
基于protues8,13,搭建51单片机最小系统连接4*4矩阵键盘(P3)和两位数码管(段选P0、位选P20\P21)
功能要求;两位数码管显示00~15,00~15与矩阵键盘对应,上电后数码管不显示,按下按键后显示对应的数字。
Protues电路连接
所需元件
最小系统的部分就不再提了,第一个是2位数码管、74HC573锁存器等。
电路原理图
其他元件就不再描述了,简单介绍一下74HC573的功能。
74HC573基本功能
(原本想用锁存器控制数码管上电不亮)74HC573包含八路3态输出的非反转透明锁存器,每个锁存器都可以独立地存储和传输数据。这些锁存器在数字系统中扮演着重要的角色,如缓冲寄存器、I/O通道、双向总线驱动器和工作寄存器等。
引脚功能
- D0-D7:8位数据输入端,用于接收要存储或传输的数据。
- Q0-Q7:8位数据输出端,输出锁存器内部存储的数据或传输的数据。
- OE(Output Enable):输出使能端,低电平有效。当OE为低电平时,允许数据从锁存器输出到数据输出线;当OE为高电平时,输出线为高阻态,即不输出数据。
- LE(Latch Enable):锁存使能端,高电平有效。当LE为高电平时,锁存器处于透明状态,此时输入端的数据可以直接传输到输出端;当LE为低电平时,锁存器将数据锁存在内部,不再随输入端的数据变化而变化。
工作原理
- 数据存储:当LE为高电平时,输入端的数据(D0-D7)可以直接传输到输出端(Q0-Q7),此时锁存器处于透明状态。如果此时OE也为低电平,则数据将被有效地输出到外部电路。
- 数据锁存:当LE为低电平时,无论输入端的数据如何变化,输出端都将保持锁存状态之前的数据不变。此时,即使OE为低电平,输出端也不会随输入端的数据变化而变化。
- 输出控制:OE端用于控制输出端是否有效。当OE为低电平时,输出端有效;当OE为高电平时,输出端为高阻态,不输出数据。
应用特点
- 高噪声抵抗特性:74HC573芯片具有较高的噪声抵抗能力,能够在复杂的电磁环境中稳定工作。
- 三态总线驱动输出:输出端为三态门设计,可以方便地与系统总线接口并驱动总线。
- 置数全并行存取:所有锁存器都可以同时接收和存储数据,提高了数据传输的效率。
Keil代码
在本次实验中,因为按键只作为一个赋值的功能,所以直接让keyscan()函数返回一个key的值
并且将按键扫描嵌套到display()中,再将key赋值给num完成数码管的显示。
为了实现上电不显示的功能, 我设置了一个Key_flag标志位,通过标志位的判断决定是否显示。刚上电,Key_flag=0,所以if(Key_flag)为假,条件不成立P0没有得到有效值,故数码管不显示,当按键按下后Key_flag=1,所以if(Key_flag)为真,可以进行显示功能。
/*要求:上电时,两位数码管都不显示。按下矩阵键盘某一按键,在数码管上显示
按键的编号。编号从00~15。
*/
/*思路:矩阵键盘的扫描方式,选用“行扫描法”。
*/
#include <reg51.h>
#define uint8 unsigned char
#define uint16 unsigned int
volatile uint8 Key_flag = 0;
uint8 code table[]={0x3F,0x06,0x5B,0x4F, //共阴型数码管段码表
0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,
0x39,0x5E,0x79,0x71, //0-F
0x00}; //段码内容0x00,数码管不显示
//以下为3个子函数的声明
void display();
int keyscan();
void Delay10ms();
void main()
{
display();
}
void Delay10ms() //@12.000MHz
{
unsigned char data i, j; //将i,j两个变量的存储空间设置在data区
i = 20;
j = 113;
do
{
while (--j);
} while (--i);
}
void display()
{
unsigned int num = 0xFF; // 初始化为一个无效的数字,表示不显示
unsigned char high_digit, low_digit;
while(1)
{
int key=keyscan();
if (key != -1&& key <= 15)
{ // 如果有键按下
num = key; // 更新显示数字
Key_flag=1;
}
if (Key_flag)
{
high_digit = num / 10; // 高位数字
low_digit = num % 10; // 低位数字
// 显示十位数字
P2 = 0xFE; // 选中第一个数码管
P0 = table[high_digit];
Delay10ms();
// 显示个位数字
P2 = 0xFD; // 选中第二个数码管
P0 = table[low_digit];
Delay10ms();
}
}
}
int keyscan()
{
int key=-1;
P3=0xFE; //P3=1111,1110
if(P3!=0xFE)
{
Delay10ms();
if(P3!=0xFE)
{
switch(P3)
{
case 0xEE: key=0; break;//若读回P3的高4位为1110,则K0被按下
case 0xDE: key=1; break;//若读回P3的高4位为1101,则K1被按下
case 0xBE: key=2; break;//若读回P3的高4位为1011,则K2被按下
case 0x7E: key=3; break;//若读回P3的高4位为0111,则K3被按下
default: break;
}
while(P3!=0xFE); //等待按键释放
}
}
P3=0xFD; //P3=1111,1101
if(P3!=0xFD)
{
Delay10ms();
if(P3!=0xFD)
{
switch(P3)
{
case 0xED: key=4; break;//若读回P3的高4位为1110,则K4被按下
case 0xDD: key=5; break;//若读回P3的高4位为1101,则K5被按下
case 0xBD: key=6; break;//若读回P3的高4位为1011,则K6被按下
case 0x7D: key=7; break;//若读回P3的高4位为0111,则K7被按下
default: break;
}
while(P3!=0xFD); //等待按键释放
}
}
P3=0xFB; //P3=1111,1011
if(P3!=0xFB)
{
Delay10ms();
if(P3!=0xFB)
{
switch(P3)
{
case 0xEB: key=8; break;//若读回P3的高4位为1110,则K8被按下
case 0xDB: key=9; break;//若读回P3的高4位为1101,则K9被按下
case 0xBB: key=10; break;//若读回P3的高4位为1011,则K10被按下
case 0x7B: key=11; break;//若读回P3的高4位为0111,则K11被按下
default: break;
}
while(P3!=0xFB); //等待按键释放
}
}
P3=0xF7; //P3=1111,0111
if(P3!=0xF7)
{
Delay10ms();
if(P3!=0xF7)
{
switch(P3)
{
case 0xE7: key=12; break;//若读回P3的高4位为1110,则K12被按下
case 0xD7: key=13; break;//若读回P3的高4位为1101,则K13被按下
case 0xB7: key=14; break;//若读回P3的高4位为1011,则K14被按下
case 0x77: key=15; break;//若读回P3的高4位为0111,则K15被按下
default: break;
}
while(P3!=0xF7); //等待按键释放
}
}
return key;
}
按键扫描代码优化
-
我将列扫描的逻辑封装在了一个
for
循环中,以减少重复代码。 -
我使用了
~col
来设置P3,这样当前列就会被设置为低电平,而其他列则保持高电平。这假设您的硬件连接方式是当某列被拉低时,可以通过读取P3的低4位来确定哪一行有按键被按下。 -
按键编号的计算方式(
key = i * 4 + row;
)是基于假设每列有4个按键,并且按键是线性排列的。根据您的实际硬件连接,这个计算方式可能需要调整。 -
我保留了等待按键释放的
while
循环,但它是被注释掉的。如果您希望在检测到按键按下后立即返回,而不是等待按键释放,那么可以保留这种状态。然而,如果您希望在按键释放之前不响应新的按键按下,那么应该取消注释这个while
循环。但请注意,这样做可能会阻塞CPU,特别是在按键被长时间按下时。一个更好的做法是使用中断或定时器来轮询按键状态。
uint8_t keyscan(void)
{
uint8_t key = 0xFF; // 初始化为无按键状态
uint8_t col = 0x01; // 列扫描初始值,从0000,0001开始
// 遍历所有列(4列)
for (int i = 0; i < 4; i++)
{
P3 = ~col; // 设置当前列为低,其他列为高(注意这里使用了取反)
// 检查是否有按键被按下
if (P3 != ~col)
{
Delay10ms(); // 防抖延时
// 再次检查按键是否仍然被按下
if (P3 != ~col)
{
// 根据当前列和P3的低4位确定按键编号
// 这里假设按键连接在P3的低4位上,且每个列只对应一个按键
// 注意:这里假设按键按下时对应位为0,因此使用了P3 & 0x0F
uint8_t row = P3 & 0x0F; // 获取P3的低4位
// 计算按键编号,这里假设按键是线性排列的
// 根据您的硬件连接,这里的计算方式可能需要调整
key = i * 4 + row; // 假设每列有4个按键,编号从0到15
// 等待按键释放(可选,但请注意这可能会阻塞CPU)
// while (P3 != ~col); // 取消注释以启用等待按键释放
}
}
// 移动到下一列
col <<= 1;
}
return key;
}