项目介绍:
- 使用 Proteus 仿真软件开发,硬件包括 AT89C51、LCD1602、红、绿 LED。具有清 0 和修改密码的功能。
- 通过矩阵按键的输入,在 LCD1602 上显示,用 * 来遮盖用户输入的密码。LCD1602 和红、绿 LED 显示密码的正确性。
硬件连接:
初始密码是0123. 运行后一次按下,绿灯常亮3s.
按下密码设置后,可以修改密码
修改完密码,初始密码0123就是错误的了。
流程图:
主要代码:
main.c
#include <reg52.h>
#include "LM016L.h"
#include "Password.h"
#include "key.h"
u8 equality();
void complete();
uchar code str1[]="input code"; //输入提示
u8 code str2 = '*'; //遮盖输入的密码
u8 code str3[]="- - - -"; //密码位
u8 code str4[]="ERROR!";
u8 code str5[]="SUCCESS!";
u8 code str6[]="SetNewPassword..."; //设置新密码
u8 Set=0;
u8 code ch[]="0123456789";
u8 pos=4; // 密码输入 从 4 开始显示
u8 input_data[]="####", k=0; //输入缓存区 索引
u8 password[]="0123",j=0; //初始密码
u8 blank[]=" ";
sbit LCD0 = P2^3; // 红灯
sbit LCD1 = P2^4; // 绿灯
void main(){
u8 key_value=16;
init_1602();
wstr_1602(3,0,str1); // 提示语 从 3 列 开始 显示
wstr_1602(4,1,str3);
LCD0=1;
LCD1=1;
while(1){
key_value = keyscan();
if(key_value<10){
if(Set==0) // 密码输入 用“*” 遮住
{
wchar_1602(pos,1,&str2); // 用 “*”遮盖住 密码的输入
input_data[k++] = ch[key_value]; // 将按键值写入 缓冲区
complete(); // 比较缓冲区 与 密码区
}
if(Set==1) //密码修改 不遮盖输入
{
wchar_1602(j*2+4,1,&ch[key_value]); //
password[j++] = ch[key_value]; // 把按键值 写入到 密码区
if(j==4) // 4位密码设置完成
{
j=0; Set=0;
delay_ms(1000);
init_1602();
wstr_1602(0,0,blank);
wstr_1602(3,0,str1);
wstr_1602(4,1,str3);
}
}
if(k==4){ // 4位按键值输入完 将缓冲区的值 清除, 用 “#”来清除
input_data[0]='#';
input_data[1]='#';
input_data[2]='#';
input_data[3]='#';
k=0;
}
if(pos==10){ //密码输入的索引值,最大为10,即4位密码输入完成
pos=2;
}
pos = pos+2; //pos从4开始,每一次输入的按键值都在 +2 处
}
else if(key_value==10){ //清0
wstr_1602(4,1,str3);
pos=4;
input_data[0]='#';
input_data[1]='#';
input_data[2]='#';
input_data[3]='#';
k=0;
}
else if(key_value==11){ //设置密码
Set=1;
wstr_1602(0,0,str6);
}
}
}
// 密码输入正确 返回1
u8 equality(){
if((input_data[0]==password[0])&&
(input_data[1]==password[1])&&
(input_data[2]==password[2])&&
(input_data[3]==password[3])
){
return 1;
}
else
return 0;
}
/* */
void complete(){
if(equality()&&(pos==10)){ //正确
LCD1 = 0; // 绿灯 亮
LCD0 = 1; // 红灯 灭
wstr_1602(3,0,blank);
wstr_1602(4,1,blank);
wstr_1602(3,0,str5);
delay_ms(3000);
init_1602();
wstr_1602(3,0,str1);
wstr_1602(4,1,str3);
LCD0=1; // 关闭 红灯
LCD1=1; // 关闭 绿灯
}else if(~equality()&&(pos==10)){ //错误
LCD0 = 0;
LCD1 = 1;
wstr_1602(3,0,blank);
wstr_1602(4,1,blank);
wstr_1602(3,0,str4);
delay_ms(3000);
init_1602();
wstr_1602(3,0,str1);
wstr_1602(4,1,str3);
LCD0=1;
LCD1=1;
}
}
LM016L.h
#ifndef __LM016L_H__
#define __LM016L_H__
#include <reg52.h>
#include <intrins.h>
//#include "delay.h"
//定义使能端口
sbit RS = P2^0;
sbit RW = P2^1;
sbit E = P2^2;
#define DB P0 //数据总线 8位
void init_1602();
void wdata_1602(u8 dat);
void wreg_1602(u8 com);
void busy_1602();
void wstr_1602(u8 x, u8 y, u8 *str);
void wchar_1602(u8 x, u8 y, u8 *ch);
void lcd_delay();
#endif
LM016.c
#include "LM016L.h"
void lcd_delay(){
u16 i;
for(i<=0;i<=10000;i++){
_nop_();
}
}
void busy_1602(){
RS = 0; //命令寄存器
RW = 1; //读
E = 1;
_nop_();
while(DB&0x80);
E = 0;
//bit7 为1表示忙
}
void init_1602() //函数功能:设置LCD_1602的开显示 光标不闪烁等的功能
{
wreg_1602(0x38); //指令6,8位数据总线,双行显示,每位采用5*7点阵 指令6
wreg_1602(0x08); //指令4,关显示,关光标,无闪烁 指令4
wreg_1602(0x06); //指令3,光标自动右移,文字不移动 指令3
wreg_1602(0x01); //指令1,清显示 指令1
wreg_1602(0x0c); //指令4,开显示 指令4
}
void wreg_1602(u8 com) //函数功能:写指令函数
{
//busy_1602(); //不注释掉,LCD不显示
RS=0; //当RS=0,RW=0时,表明写入的是命令
RW=0;
DB=com; //当使能由高到低时,LCD执行相应命令
_nop_();
E=1;
_nop_();
E=0;
}
void wdata_1602(u8 dat) //函数功能:写数据函数
{
busy_1602();
RS=1; //当RS=1,RW=0时,表明写入的是数据
RW=0;
DB=dat; //当使能由高到低时,LCD执行相应命令
_nop_();
E=1;
_nop_();
E=0;
}
/*
* x为横坐标:0~40 y为纵坐标0~1
* *str是需要显示的字符串
*/
void wstr_1602(u8 x, u8 y, u8 *str){
busy_1602();
if(y) x |= 0xC0; //第二行p
x |= 0x80; //第一行
wreg_1602(x);
while(*str != '\0'){
wdata_1602( *str++ );//str先取值*str, 再str++
}
}
void wchar_1602(u8 x, u8 y, u8 *ch){
busy_1602();
if(y) x |= 0xc0; //第二行p
x |= 0x80; //第一行
wreg_1602(x); //写命令和写数据之间,必须要延时,并且足够大。
lcd_delay();
//lcd_delay();
wdata_1602(*ch);
}
key.h
#ifndef __KEY_H__
#define __KEY_H__
#include <reg52.h>
#include <stdio.h>
#include <intrins.h>
#include "delay.h"
u8 keyscan();
u8 keyscan1();
u8 keydown(void);
#endif
key.c
#include "key.h"
// 老师的按键扫描函数
//所以返回的是按键按下后的P1端口的值。 若按键按下,8位有2位是0,6位是1.
/*
u8 keyscan1(){ //P1 低位控制行,高位控制列
u8 sccode, recode;
//u8 i=0; //按键按下的是第几行,即while循环到第几次
P1 = 0xf0; //行扫描
if( (P1&0xf0) != 0xf0){ //有按键按下 P1就会变化,与原来相与 就会变化 P1和0xf不一样就会进入
delay_ms(20); //消抖
if( (P1&0xf0) != 0xf0){ //消抖后依然保持按下的状态,确实是按下了
sccode = 0xfe; //逐行扫描初值
while( (sccode&0x10) != 0){ //sccode第5位不为0就进入 sccode!=0x10
P1 = sccode; //输出行扫描码
if( (P1&0xf0) != 0xf0){ //本行有按键按下 P1的高四位有1变成0
recode = (P1&0xf0) | 0x0f; //保留P1的高4位,低四位强制置1
return ( (~sccode)+(~recode) ); //sccode高四位全1,第四位是按键按下的那一时刻的值。 recode高四位是按下按键后的,第四位全1.
}
else {
sccode = (sccode<<1)|0x01; //行扫描码左移一位。 循环左移,与_ctol_函数功能一样
//i++;
//i%=4;
}
}
}
}
return 0; //无按键按下返回0
//return 16; //无按键按下返回16
}
*/
// 0 1 2 3 返回值 0 1 2 3
// 4 5 6 7 4 5 6 7
// 8 9 清0 密码设置 8 9 10 11
// -- -- -- -- 12 13 14 15
u8 keyscan()//按键扫描函数:要去抖,若有按键按下,返回对应的按键值(0-15),没有按键按下返回16
{
u8 i,row,temp;
u8 key=16; //按键号,初值设置为16,目的是:没有按键按下时返回16;
//若不设初值(默认值为0),没有按键按下时,将返回0,会误认为0被按下
row=0xef; //从第一列开始
P1=0xff;
for(i=0;i<4;i++)
{
//P1=0xff;
P1=row; //第i列信号,对应列为低,其他全为高
row=_crol_(row,1); //生成下一列信号
temp=P1; //读入扫描信号
temp=temp&0x0f; //屏蔽高4位列信号,只保留低4位行信号
if(temp!=0x0f)//有按键被按下,因为第i列某行有按键按下,则低4位中有一位为低
{
delay_ms(20); //延时去抖
temp=P1;
temp=temp&0x0f;
if(temp!=0x0f) //再次确认有按键被按下
{
switch(temp) //根据低4位行信号,判断哪个按键被按下
{
case 0x0e:key=0+i;break; //第i列第1行按键被按下
case 0x0d:key=4+i;break; //第i列第2行按键被按下
case 0x0b:key=8+i;break; //第i列第3行按键被按下
case 0x07:key=12+i; //第i列第4行按键被按下
}
do
{
temp=P1; //再次扫描按键
temp=temp&0x0f;
}
while(temp!=0x0f); //等待按键释放
}
}
}
return(key);//扫描结束,返回按键值
}
delay.c
#include "delay.h"
void delay_us(u16 t){ //us级 4.5*t us
while(t--);
}
void delay_ms(u16 x){ //ms
int j,i;
for(i=0;i<x;i++)
{
for(j=0;j<115;j++);
}
}