从一开始毫无头绪,到一点一点功能实现,今天总算基本实现了所有的功能。我个人是用的B站西风大佬的底层模版,按键 数码管 LED灯。我把代码放出来,大家相互学习一下,检查一下bug。我个人也是小白。

我的代码有4个界面:

一、密码输入界面

二、密码清除界面

三、初始状态界面

四、开锁界面

我耗时间比较久就是写那个密码输入界面之后进入的开门状态,在那个状态下的功能逻辑的实现还是有点点复杂的。

原理图

题目

代码

关闭外设

#include <STC15F2K60S2.H>
void Close_ZYZ(void)
{
	P0 = 0xff;
	P2 = (P2 & 0x1f) | 0x80;
  P2 &= 0x1f;
  P0 = 0;
	P2 = (P2 & 0x1f) | 0xa0;
  P2 &= 0x1f;	
}

LED和继电器

#include <STC15F2K60S2.H>

void Led_Disp_ZYZ(unsigned char Addr,Enable)
{
	unsigned char Data = 0x00;
	unsigned char Data_old =0xff;
	if(Enable)
	{
		Data |= 0x01 << Addr;
	}
	else
	{
		Data &= ~(0x01 << Addr);
	}
	if(Data != Data_old)
	{
		P0 = ~Data;
		P2 = (P2 & 0x1f) | 0x80;
		P2 &= 0x1f;
    Data_old = Data;		
	}
	
}

void Relay(unsigned char Enable)
{
	unsigned char Data=0x00;
	static unsigned char Data_old =0xff;
	//读取当前P0的状态
//	Data = ~P0;//
	if(Enable)
	{
		Data |= 0x40;
	}
	else
	{
		Data &= ~0x40;
	}
	if(Data != Data_old)
	{
		P0 = ~Data;
		P2 = (P2 & 0x1f) | 0xa0;
		P2 &= 0x1f;
    Data_old = Data;//保存当前状态
	}
}

按键

#include <STC15F2K60S2.H>
//左
sbit L1 = P4^4;
sbit L2 = P4^2;
sbit L3 = P3^5;
sbit L4 = P3^4;
//右
sbit R1 = P3^3;
sbit R2 = P3^2;
sbit R3 = P3^1;
sbit R4 = P3^0;


unsigned char Key_Scan_ZYZ(void)
{
	unsigned char Key_Num = 0;//按键值
	L1 = 0;
	L2 = L3 = L4=1;//第一列
	if(R1 == 0)
	{
		Key_Num = 4;
	}
	if(R2 == 0)
	{
		Key_Num = 5;
	}
	if(R3 == 0)
	{
		Key_Num = 6;
	}
	if(R4 == 0)
	{
		Key_Num = 7;
	}
	L2 = 0;
	L1 = L3 = L4=1;//第二列
	if(R1 == 0)
	{
		Key_Num = 8;
	}
	if(R2 == 0)
	{
		Key_Num = 9;
	}
	if(R3 == 0)
	{
		Key_Num = 10;
	}
	if(R4 == 0)
	{
		Key_Num = 11;
	}
	L3 = 0;
	L2 = L1 = L4=1;//第三列
	if(R1 == 0)
	{
		Key_Num = 12;
	}
	if(R2 == 0)
	{
		Key_Num = 13;
	}
	if(R3 == 0)
	{
		Key_Num = 14;
	}
	if(R4 == 0)
	{
		Key_Num = 15;
	}
	L4 = 0;
	L1 = L3 = L2=1;//第四列
	if(R1 == 0)
	{
		Key_Num = 16;
	}
	if(R2 == 0)
	{
		Key_Num = 17;
	}
	if(R3 == 0)
	{
		Key_Num = 18;
	}
	if(R4 == 0)
	{
		Key_Num = 19;
	}
	return Key_Num;
	
}

数码管

#include <STC15F2K60S2.H>
unsigned char Smg_Data[16] ={
	  0xc0, // 0
    0xf9, // 1
    0xa4, // 2
    0xb0, // 3
    0x99, // 4
    0x92, // 5
    0x82, // 6
    0xf8, // 7
    0x80, // 8
    0x90, // 9
	  0xff, //关闭
	  0xbf, //-
	  0xc6, //c
	  0x86, //E
    0xc8,	//n
	  0x8c  //p
	};
unsigned char Smg_Pos[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

void Smg_Disp_ZYZ(unsigned char Pos,Value,Point)
{
	//消影
	P0 = 0xff;
	P2 = (P2 & 0x1f) | 0xe0;
  P2 &= 0x1f;
  P0 = Smg_Pos[Pos];//位选
  P2 = (P2 & 0x1f) | 0xc0;
  P2 &= 0x1f; 	
	P0 = Smg_Data[Value];//位选
	if(Point)    //选择小数点
		P0 &= 0x7f;
	P2 = (P2 & 0x1f) | 0xe0;
  P2 &= 0x1f;
}

I2c

#include "I2c.h"
#include "intrins.h"

#define DELAY_TIME 5
#define AT24C02_ADDR   0xA0
/** 定义I2C总线时钟线和数据线 */
sbit scl = P2^0;
sbit sda = P2^1;

/**
* @brief I2C总线中一些必要的延时
*
* @param[in] i - 延时时间调整.
* @return none
*/
void i2c_delay(unsigned char i)
{
    do
    {
        _nop_();_nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();_nop_();
        _nop_();_nop_();_nop_();_nop_();_nop_();		
    }
    while(i--);        
}

/**
* @brief 产生I2C总线启动条件.
*
* @param[in] none
* @param[out] none
* @return none
*/
void i2c_start(void)
{
    sda = 1;
    scl = 1;
    i2c_delay(DELAY_TIME);
    sda = 0;
    i2c_delay(DELAY_TIME);
    scl = 0;    
}

/**
* @brief 产生I2C总线停止条件
*
* @param[in] none
* @param[out] none.
* @return none
*/
void i2c_stop(void)
{
    sda = 0;
    scl = 1;
    i2c_delay(DELAY_TIME);
    sda = 1;
    i2c_delay(DELAY_TIME);       
}

/**
* @brief I2C发送一个字节的数据
*
* @param[in] byt - 待发送的字节
* @return none
*/
void i2c_sendbyte(unsigned char byt)
{
    unsigned char i;
//
	EA = 0;   //关闭中断,避免因为中断而影响总写读写的时序,导致读写失败。
    for(i=0; i<8; i++){
        scl = 0;
        i2c_delay(DELAY_TIME);
        if(byt & 0x80){
            sda = 1;
        }
        else{
            sda = 0;
        }
        i2c_delay(DELAY_TIME);
        scl = 1;
        byt <<= 1;
        i2c_delay(DELAY_TIME);
    }
	EA = 1;
//
    scl = 0;  
}

/**
* @brief 等待应答
*
* @param[in] none
* @param[out] none
* @return none
*/
unsigned char i2c_waitack(void)
{
	unsigned char ackbit;
	
    scl = 1;
    i2c_delay(DELAY_TIME);
    ackbit = sda; //while(sda);  //wait ack
    scl = 0;
    i2c_delay(DELAY_TIME);
	
	return ackbit;
}

/**
* @brief I2C接收一个字节数据
*
* @param[in] none
* @param[out] da
* @return da - 从I2C总线上接收到得数据
*/
unsigned char i2c_receivebyte(void)
{
	unsigned char da;
	unsigned char i;
//
	EA = 0;	
	for(i=0;i<8;i++){   
		scl = 1;
		i2c_delay(DELAY_TIME);
		da <<= 1;
		if(sda) 
			da |= 0x01;
		scl = 0;
		i2c_delay(DELAY_TIME);
	}
	EA = 1;
//
	return da;    
}

/**
* @brief 发送应答
*
* @param[in] ackbit - 设定是否发送应答
* @return - none
*/
void i2c_sendack(unsigned char ackbit)
{
    scl = 0;
    sda = ackbit;  //0:发送应答信号;1:发送非应答信号
    i2c_delay(DELAY_TIME);
    scl = 1;
    i2c_delay(DELAY_TIME);
    scl = 0; 
	sda = 1;
    i2c_delay(DELAY_TIME);
}

/**
* @brief 读写操作过程中一些必要的延时
*
* @param[in] i - 指定延时时间
* @return - none
*/
void operate_delay(unsigned char t)
{
	unsigned char i;
	
	while(t--){
		for(i=0; i<112; i++);
	}
}
/**
* @brief 向AT24c02多字节写入数据
*
* @param[in] Addr内存单元地址 Data 数据 num 数据长度
* @return - none
*/
void Wirte_E2prom(unsigned char Addr,unsigned char *Data,unsigned char num)
{
	 i2c_start();//起始信号
	 i2c_sendbyte(AT24C02_ADDR);//发送设备地址
	 i2c_waitack();//等待从机应答
	 i2c_sendbyte(Addr);//发送内存单元地址
	 i2c_waitack();//等待从机应答
	 while(num--)
	 {
			 i2c_sendbyte(*Data++);//发送写入的数据 
		 	 i2c_waitack();//等待从机应答
		   operate_delay(200);
	 }
   i2c_stop();//停止信号 
}
/**
* @brief 向AT24c02多字节读取
*
* @param[in] Addr内存单元地址 Data 数据 num 数据长度
* @return - none
*/
void Read_E2Prom(unsigned char Addr,unsigned char *Data,unsigned char num)
{
	 i2c_start();//起始信号
	 i2c_sendbyte(0xa0);//发送写设备地址
	 i2c_waitack();//等待从机应答
	 i2c_sendbyte(Addr);//发送内存单元地址
	 i2c_waitack();//等待从机应答
	 i2c_start();//起始信号
	 i2c_sendbyte(AT24C02_ADDR | 0x01);//发送读设备地址
   i2c_waitack();//等待从机应答
	 while(num--)
	 {
			 *Data++ = i2c_receivebyte();//读取内存中的数据
       if(num)
			 {
				 	 i2c_sendack(0);//产生应答信号
			 }
			 else
			 {
				 	 i2c_sendack(1);//产生非应答信号
			 }
	 }
	 i2c_stop();//停止信号 
}
/**
* @brief 向AT24c02单字节读取
*
* @param[in] Addr内存单元地址
* @return - none
*/
unsigned char Read_E2prom_Byte(unsigned char Addr)
{
	 unsigned char Temp;
	 i2c_start();//起始信号
	 i2c_sendbyte(AT24C02_ADDR);//发送写设备地址
	 i2c_waitack();//等待从机应答
	 i2c_sendbyte(Addr);//发送内存单元地址
	 i2c_waitack();//等待从机应答
	 i2c_start();//起始信号
	 i2c_sendbyte(AT24C02_ADDR | 0x01);//发送读设备地址
   i2c_waitack();//等待从机应答
	 Temp = i2c_receivebyte();//读取内存中的数据
	 i2c_sendack(1);//产生非应答信号
	 i2c_stop();//停止信号 
   return Temp;
}

主函数 Main

#include <STC15F2K60S2.H>
#include <string.h>
#include "ClosePher.h"//关闭外设
#include "Key.h"//按键
#include "Led.h"//LED灯
#include "Smg.h"//数码管
#include "I2c.h"//AT24C02

#define uint8_t unsigned char //255
#define uint16_t unsigned short int //65535
#define Password_Length   6 //密码长度
#define Door_Open_Timer   5000//开门时间
#define AT24C02_Frist_Run_Flag  0x00 //初次上电地址
#define AT24C02_Password_Addr   0x01 //数据存储的地址

uint8_t Key_Value,Key_Down,Key_Up,Key_Old;
uint16_t Key_Delay;
uint8_t Smg_Record[8]={10,10,10,10,10,10,10,10};
uint8_t Smg_Delay;
uint8_t Smg_Place;//数码管扫描位
uint8_t Smg_Point[8]={0,0,0,0,0,0,0,0};
uint8_t Led_Disp[8]={0,0,0,0,0,0,0,0};
uint8_t Smg_Mode;//初始界面标志位
uint8_t Mima = 10;//初始密码值
uint8_t PassWord[Password_Length]={10,10,10,10,10,10};//定义一个密码数组存放按键密码
uint8_t Init_Password[Password_Length]={8,8,8,8,8,8};//初始化的密码
uint8_t Chang_Password[Password_Length]={10,10,10,10,10,10};//修改密码存储数组
uint8_t Open_Data[8] ={0,10,10,10,0,15,13,14};//开门数组
uint8_t Password_Index;//密码索引
uint8_t Change_Password_Index;//修改密码索引值
uint16_t Open_Door_Timer;//开门时间
uint16_t Error_Timer;//输入密码错误时间
bit Key_Press;//按键按下标志位
bit Open_Door=0;//门状态标志位
bit Error_Flag;//密码错误标志位

void Key_Deal_ZYZ(void)//按键处理函数
{
	uint8_t i;
	if(Key_Delay)
		return;
	Key_Delay =1;//消抖 防止重复扫描
	Key_Value = Key_Scan_ZYZ();//获取按键值
	Key_Down = Key_Value & (Key_Old ^Key_Value);//检测下降沿
	Key_Up   = ~Key_Value & (Key_Old ^Key_Value);//检测上升沿
	Key_Old  = Key_Value;//按键旧事件
	switch(Key_Down)
	{
		case 5:
			Mima = 8;
		break;
		case 6:
			Mima = 4;
		break;
		case 7:
			Mima = 0;
		break;
		case 8://清除按键
			Smg_Mode =2;
		break;
		case 9:
			Mima = 9;
		break;
		case 10:
			Mima = 5;
		break;
		case 11:
			Mima = 1;
		break;
		case 12://修改密码
      if(Open_Door==1)//只有门开启才能修改密码
			{
				if(Smg_Mode !=4)//第一次按下S12 进入密码修改模式
				{
					Smg_Mode = 4;//进入密码修改界面
					memset(Chang_Password,10,sizeof(Chang_Password));//清除修改密码存储数组 方便下一次修改密码
				}
				else if(Smg_Mode == 4 && Change_Password_Index == Password_Length)//再次按下S12 密码修改完成 退出密码修改界面
				{
					Smg_Mode = 5;//开门界面
					memcpy(Init_Password,Chang_Password,Password_Length);//将密码存入初始密码
				  Open_Door_Timer=0;//重置计数
					Change_Password_Index=0;//修改密码索引值清0
				}
			}
		break;
		case 14:
			Mima = 6;
		break;
		case 15:
			Mima = 2;
		break;
		case 16://输入
			Smg_Mode = 1;
		break;
		case 18:
			Mima = 7;
		break;
		case 19:
			Mima = 3;
		break;
	}
	//输入模式:将密码存储到数组中
  if(Smg_Mode == 1 && Key_Down>=5 && Key_Down <=19 && Key_Down !=8 &&  Key_Down !=12 && Key_Down !=16 && Key_Down !=13 &&  Key_Down !=17)
	{
		if(Password_Index < Password_Length)//索引值小于密码长度
		{
      for(i=0;i<Password_Length;i++)
			{
				PassWord[i] = PassWord[i+1];//数字向左存储 {10,10,10,10,10,1} {10,10,10,10,1,2}
			}
			PassWord[Password_Length-1] = Mima;//将新按的数字存储在最右边
		}
		//按键按下
		Key_Press =1;
		//更新按键索引值
		Password_Index++;
	}
	//修改密码模式
	if(Smg_Mode==4 && Key_Down>=5 && Key_Down <=19 && Key_Down !=8 &&  Key_Down !=12 && Key_Down !=16 && Key_Down !=13 &&  Key_Down !=17)
	{
		if(Change_Password_Index<Password_Length)
		{
			for(i=0;i<Password_Length;i++)
			{
				 Chang_Password[i] =  Chang_Password[i+1];
			}
			 Chang_Password[Password_Length-1] = Mima;//将密码值存储在最右边
		}
		//更新索引值
		if(++Change_Password_Index==6)
		{
			Change_Password_Index =6;
		}
		//按键按下
	  Key_Press =1;
	}
}
void Other_Deal_ZYZ(void)//其他处理函数
{
	 //如果门打开 需要再按一下按键才能5S后关闭所有
   if(Open_Door==1)
	 {
		 if(Open_Door_Timer<=Door_Open_Timer)//5S
		 {
			 //打开继电器
			 Relay(1);
			 if(Key_Press)//如果在开门状态下有按键按下
			 {
				 Open_Door_Timer=0;//重置计数
				 Key_Press  =0;
			 }
		 }
		 else //无按键操作 全部关闭 进入关门状态
		 {
			 //密码索引清0
			 Password_Index = 0;
			 //关闭
			 Open_Door = 0;//关门
			 Relay(0);//关闭继电器
			 Close_ZYZ();//关闭所有外设
			 Smg_Mode =3;//关门状态
		 }
	 }
	 //密码错误 设备进入初始状态 等待新密码输入
	 if(Error_Flag==1)
	 {
		 if(Error_Timer<=Door_Open_Timer)//5S
		 {
			 Led_Disp[0] =1;//L1点亮
		 }
		 else
		 {
			 Led_Disp[0] =0;
			 Error_Flag = 0;
			 memset(PassWord,10,sizeof(PassWord));//清除密码
		 }
	 }
	 //密码输入时点亮L7
	 if(Smg_Mode==1 && Password_Index < 6)
	 {
		  Led_Disp[6] =1;
	 }
	 else
	 {
		 Led_Disp[6] =0;
	 }
	 //修改密码时点亮L8
	 if(Smg_Mode == 4 && Change_Password_Index<=6)
	 {
		 	  Led_Disp[7] =1;
	 }
	 else
	 {
		 	  Led_Disp[7] =0;
	 }
}
void Smg_Show_ZYZ(void)//数码管显示函数
{
	uint8_t i;
	if(Smg_Delay)
		return;
	Smg_Delay=1;//防止重复扫描
  switch(Smg_Mode)
	{
		case 1://输入模式
			if(Password_Index < Password_Length+1)
			{
				  Smg_Record[0] = 11;//-
					for(i=0;i<Password_Length;i++)
					{
						Smg_Record[7-i] = PassWord[Password_Length-1-i];//显示密码
					}
					if(Password_Index == Password_Length)//Password_Index = 6;//密码索引
					{
							if(memcmp(PassWord,Init_Password,sizeof(PassWord))==0)//比对密码
							{
								//开启就置标志位开始计时
								Open_Door=1;
								//定时器值重置
								Open_Door_Timer=0;
								Smg_Mode =5;//进入开门界面
								Password_Index=7;//跳出显示 进入开门状态 
							}
							else//密码错误 设备进入初始化初始化状态
						  {
								 Error_Timer =0;//开始计时
								 Error_Flag  =1;
								 Password_Index=0;
							}
					}
			}
		break;
		case 2://密码清除
				for(i=0;i<6;i++)
				{
					PassWord[i]=10;// 10 10 ....
					Smg_Record[i+2] = PassWord[i];//显示数组
				}
				Password_Index=0;//索引值清0				  
		 break;
		case 3://初始状态 LED 数码管全部关闭 
			 memset(PassWord,10,sizeof(PassWord));//清除密码
			 memset(Smg_Record,10,sizeof(Smg_Record));//清空显示
			 memset(Led_Disp,0,sizeof(Led_Disp));//关闭LED
			 Wirte_E2prom( AT24C02_Password_Addr,Init_Password,6);//修改的密码写入存储器
			break;
		case 4://修改密码界面
					Smg_Record[0] = 12;//C
					for(i=0;i<Password_Length;i++)
					{
						Smg_Record[7-i] = Chang_Password[Password_Length-1-i];
					}
			break;
		case 5://开门界面
					for(i=0;i<8;i++)
					{
						Smg_Record[i] = Open_Data[i];
					}
      break;
	}


}
void Timer0_Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
	EA  = 1;        //开启总中断
}
//检测是否为初次上电
void Check_Frist_Run(void)
{
	uint8_t Frist_Run_Flag;
	uint8_t Defalut_Password[Password_Length] = {8,8,8,8,8,8};//默认密码
	Frist_Run_Flag =  Read_E2prom_Byte(AT24C02_Frist_Run_Flag);//首次上电的标志位
	if(Frist_Run_Flag == 0xFF)//首次上电
	{
		 Wirte_E2prom(AT24C02_Password_Addr,Defalut_Password,6);//存储默认密码
		 Wirte_E2prom(AT24C02_Frist_Run_Flag,0x00,1);//设置首次上电标志位
	}
}
//读取存储器内的密码
void Read_AT24C02_Password(void)
{
	Read_E2Prom(AT24C02_Password_Addr,Init_Password,6);
}
void main(void)
{
	 // uint8_t i=0;
	  Check_Frist_Run();//检测是否为第一次上电
	  Read_AT24C02_Password();//读取密码
	//验证是否正确读取存储的密码
//	for(i=0;i<8;i++)
//	{
//		Smg_Record[i]=Init_Password[i];
//	}
	Close_ZYZ();//关闭外设
	Timer0_Init();	
	while(1)
	{
		Key_Deal_ZYZ();
		Smg_Show_ZYZ();
		Other_Deal_ZYZ();
	}
}

void Timer0_Isr(void) interrupt 1
{
	if(++Key_Delay == 10)//按键延时消抖
	{
		Key_Delay = 0;
		
	}
	if(++Smg_Delay == 500)//数码管动态扫描消影延迟500ms
	{
		 Smg_Delay = 0;
		 
	}
	if(++Smg_Place == 8)
	{
		Smg_Place = 0;
	}
	Smg_Disp_ZYZ(Smg_Place,Smg_Record[Smg_Place],Smg_Point[Smg_Place]);//动态扫描数码管
	Led_Disp_ZYZ(Smg_Place,Led_Disp[Smg_Place]);//动态扫描LED
	if(Open_Door==1)//开始计时
	{
		Open_Door_Timer++;
	}
	if(Error_Flag==1)
	{
		Error_Timer++;
	}
	
}

题目内的功能基本实现了,但是我自己发现一个BUG就是在修改密码时,如果输入密码,按下的次数超过6时,就会卡死在修改密码的界面,应该是我修改密码索引值的问题,我没有加限制条件。如果有其他bug可以在评论区留言,一起互相学习。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐