基於金沙灘51單片機的電子密碼鎖程序
很久之前做的一個課設,在B站發了效果視頻,發現忘記分享代碼了,現在整理分享一下。
零、設計報告
1.設計要求
這部分是講的整個系統實現了什么功能。
1.1、密碼的設定,此密碼是固定在程序存儲器ROM中,假設預設的密碼為“123456”共6位密碼。
1.2、密碼的輸入: 用4*4矩陣鍵盤中的10個按鍵分別表示0~9的數字,完成6位密碼的輸入。
1.3、密碼的修改:能夠修改密碼,修改以后的密碼掉電不丟失,即下一次上電以后用新設定密碼進入系統。
1.4、密碼錯誤蜂鳴器發出報警音。
2.系統硬件電路設計
這部分其實就是金沙灘開發板上的電路圖,再對其作一些講解,懂的可以略過這部分,但是設計報告上要寫明的。
2.1 主控芯片STC89C52RC模塊
單片機電路原理圖
STC89C52RC是STC公司生產的一種低功耗、高性能CMOS8位微控制器,具有8K字節系統可編程Flash存儲器。STC89C52使用經典的MCS-51內核,但是做了很多的改進使得芯片具有傳統的51單片機不具備的功能。在單芯片上,擁有靈巧的8 位CPU 和在系統可編程Flash,使得STC89C52為眾多嵌入式控制應用系統提供高靈活、超有效的解決方案。
STC89C52RC有40個引腳,8k字節Flash,512字節RAM, 32 位I/O 口線,看門狗定時器,內置4KB EEPROM,MAX810復位電路,3個16 位定時器/計數器,4個外部中斷,一個7向量4級中斷結構(兼容傳統51的5向量2級中斷結構),全雙工串行口。另外 STC89C52 可降至0Hz 靜態邏輯操作,支持2種軟件可選擇節電模式。空閑模式下,CPU 停止工作,允許RAM、定時器/計數器、串口、中斷繼續工作。掉電保護方式下,RAM內容被保存,振盪器被凍結,單片機一切工作停止,直到下一個中斷或硬件復位為止。最高運作頻率35MHz,6T/12T可選。
2.1.1 STC89C52RC主要功能特性
- 8K字節程序存儲空間;
- 512字節數據存儲空間;
- 內帶4K字節EEPROM存儲空間;
- 可直接使用串口下載;
2.2 按鍵電路模塊
電路采用4x4的矩陣按鍵,節省IO口,效率高。矩陣鍵盤又稱行列鍵盤,它是用四條I/O線作為行線,四條I/O線作為列線組成的鍵盤。
在行線和列線的每個交叉點上設置一個按鍵。這樣鍵盤上按鍵的個數就為4x4個。
按鍵電路原理圖
2.3 LCD1602顯示模塊
LCD1602液晶顯示器是廣泛使用的一種字符型液晶顯示模塊。它是由字符型液晶顯示屏(LCD)、控制驅動主電路HD44780及其擴展驅動電路HD44100,以及少量電阻、電容元件和結構件等裝配在PCB板上而組成。項目使用LCD1602模塊與用戶交互。
LCD1602顯示電路原理圖
2.4 蜂鳴器模塊
本項目使用的是無源蜂鳴器,無源蜂鳴器利用電磁感應現象,為音圈接入交變電流后形成的電磁鐵與永磁鐵相吸或相斥而推動振膜發聲,接入直流電只能持續推動振膜而無法產生聲音,只能在接通或斷開時產生聲音。因此需要給一定的脈沖才能發出聲音。
蜂鳴器電路原理圖
2.5 EEPROM存儲模塊
EEPROM模塊采用的是24C01芯片,EEPROM (Electrically Erasable Programmable read only memory)是指帶電可擦可編程只讀存儲器。是一種掉電后數據不丟失的存儲芯片。每塊24C01有256字節,使用I2C進行通信,頁寫緩沖區最高可達8字節,數據保存時間大於二百年,是很方便的存數據的芯片,本項目的密碼就保存於此。
EEPROM存儲電路原理圖
3、系統程序設計
這部分是講的整個系統的程序是怎么實現的,以及通過程序流程圖對程序的解讀。
3.1 程序流程圖
設備開機后,讀取EEPROM上對應地址的密碼,沒有密碼使用默認密碼123456,然后請輸入密碼。用戶輸入密碼后,對比用戶輸入的跟存在EEPROM的密碼是否一樣,一樣的話就顯示密碼正確,密碼不對就報警並重新讓用戶輸入密碼。輸入成功之后可以讓用戶輸入新密碼或者退出。輸入新密碼之后保存到EEPROM上。
3.1.1程序 按鍵輸入部分
按鍵輸入負責監聽按鍵事件,然后把對應的按鍵轉換成按鍵值存入輸入密碼數組,並顯示“*”。按鍵掃描使用定時器進行定時判斷按鍵是否按下。
3.1.2 保存密碼和讀取密碼部分
密碼是保存在EEPROM上的,這里我們用的EEPROM為AT24C01要實現單片機通過模擬I2C總線對AT24C01的讀或寫,在發送起始條件之后,必須是器件的控制字節,其中高四位為器件類型識別碼,EEPROM芯片一般應為1010,接着三位為片選,最后一位為讀寫位,當讀寫為1時為讀操作,即主器件從從器件讀數據,為0時為寫操作,即主器件把信息寫到所選的從器件中[1]。接着是發送讀或寫的地址。無論是讀還是寫,在I2C總線上傳送的每一字節都是8個bit 串行數據的傳送總是從最高位開始的,每一數據幀后面都有一個確認為,也叫應答位[1]。因此,在讀數據的時候,需要先將變量x中的各二進位向左移一位,再與SDA上的數據通過按位“或“運算存入變量x中。而數據則剛好相反,先將待寫的數據通過和數0x80按位“與”運算將最高位數據送到SDA,再將該數據中的各二進位向左移一位。上述兩個過程重復8次,即可完成一個字節的讀或寫。按照此流程,把一串密碼讀寫完成。
3.2 I2C通信協議簡介
I2C總線是由Philips公司開發的一種簡單、雙向二線制同步串行總線。它只需要兩根線即可在連接於總線上的器件之間傳送信息。
主器件用於啟動總線傳送數據,並產生時鍾以開放傳送的器件,此時任何被尋址的器件均被認為是從器件.在總線上主和從、發和收的關系不是恆定的,而取決於此時數據傳送方向。如果主機要發送數據給從器件,則主機首先尋址從器件,然后主動發送數據至從器件,最后由主機終止數據傳送;如果主機要接收從器件的數據,首先由主器件尋址從器件.然后主機接收從器件發送的數據,最后由主機終止接收過程。在這種情況下.主機負責產生定時時鍾和終止數據傳送。
4、調試及性能分析
這部分是對整個系統的測試方案,這邊當時寫得比較隨便,湊合能用,其實大致也就這樣。
4.1 軟件調試
把程序下載到單片機里,通過按鍵和單片機交互,看是否達到要求,不能達到要求的話,調整程序,重復下載,直到滿足要求為止。
5、總結與致謝
這部分是講的…………你懂的,哈哈。
通過本次項目,我們深入了解了單片機的硬件,軟件的各種知識,知道了單片機在實際應用中的作用很大,也許我家樓下的密碼鎖就是這么設計的。了解了單片機的一些擴展功能,更深入的了解了單片機的原理。
在此感謝指導老師對我們的精心指導,通過這次課程設計,我們知道了理論和實際的距離,也知道了理論和實際想結合的重要性,也從中得知了很多書本上無法得知的知識。自己今后將會更加的把理論知識和實際應用結合起來,提高自己的能力。最后再次感謝指導老師對我們的幫助。
一、程序代碼
新建Keil項目,單片機選Intel的8051,在工程的.c文件內寫入如下代碼:
#include <reg52.h>
/* 全局運行參數定義 */
#define SYS_MCLK (11059200/12) //系統主時鍾頻率,即振盪器頻率÷12
/*蜂鳴器相關*/
bit staBuzzer = 0; //蜂鳴器狀態控制位,1-鳴叫、0-關閉
sbit BUZZER = P1^6; //蜂鳴器控制引腳
void Waring();
/*--------------------------------LCD1602函數聲明---------------------------*/
//LCD1602函數聲明
void InitLcd1602(); //初始化
void LcdWriteCmd(unsigned char cmd); //寫命令
void LcdSetCursor(unsigned char x, unsigned char y); //寫指針
void LcdShowStr(unsigned char x, unsigned char y,unsigned char *str, unsigned char len);
/*--------------------------------EEPROM讀寫函數----------------------------*/
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len);
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);
/*--------------------------------按鍵驅動函數聲明---------------------------*/
void KeyDriver(); //按鍵變化檢測
/*--------------------------------密碼輸入函數聲明---------------------------*/
void inputpassword(); //密碼輸入特效支持(智能隱藏)
/*--------------------------------系統控制函數聲明---------------------------*/
void control();
#define PWN 16 /*密碼位數*/
unsigned char code PassWordAddr = 0x17; //密碼儲存地址
/*密碼存儲格式:前兩個固定字符:PW,P為0x17,PW后緊接密碼(字符)*/
unsigned char InputStr[PWN] = //輸入的密碼
{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
unsigned char PassWord[PWN]; //正確的密碼
unsigned char system = 0; //流程控制變量
/*0:開始,1設置密碼,2輸入密碼,3顯示密文*/
bit PWRight = 0; //密碼是否對
bit EnterSta = 0; //回車狀態
void main()
{
TMOD = 0x01;
TH0 = 0xFC;
TL0 = 0x66;
EA = 1;
ET0 = 1;
TR0 = 1;
T2CON = 0x00; //配置T2工作在16位自動重載定時器模式
RCAP2H = ((65536-SYS_MCLK/1500)>>8); //配置重載值,每秒產生1500次中斷,
RCAP2L = (65536-SYS_MCLK/1500); //以使刷新率達到100Hz無閃爍的效果
TH2 = RCAP2H; //設置初值等於重載值
TL2 = RCAP2L;
ET2 = 1; //使能T2中斷
PT2 = 1; //設置T2中斷為高優先級
TR2 = 1; //啟動T2
InitLcd1602();
//E2Write(PWhead,PassWordAddr,2); //去掉密碼頭
while(1)
{
control();
}
}
/*--------------------------------系統控制函數聲明---------------------------*/
void control()
{
static unsigned char systemb = 0;
if(system == 0) //系統剛啟動
{
unsigned char PWhead[2] = {0,0};
if(system != systemb)
{
systemb = system;
LcdWriteCmd(0x01); //清屏
system = systemb;
LcdShowStr(0,0,"Loading...... ", 16);
}
E2Read(PWhead,PassWordAddr,2); //讀密碼頭
if((PWhead[0] == 'P')&&(PWhead[1] == 'W')) //存在密碼頭
{
E2Read(PassWord,PassWordAddr+2,sizeof(PassWord));//讀密碼
system = 2; //輸入密碼
}else{ //不存在密碼頭
system = 1; //設置密碼
}
}
if(system == 1) //如果是設置密碼
{
if(system != systemb)
{
systemb = system;
LcdWriteCmd(0x01); //清屏
system = systemb;
LcdShowStr(0,1,"Set Password! ", 16);
}
inputpassword(); //運行密碼特效
KeyDriver(); //運行按鍵驅動
if((PWRight == 1)&&(EnterSta == 1))
{
PWRight = 0;
EnterSta = 0;
if((InputStr[0] == ' ')||((InputStr[0]<('9'+1))&&(InputStr[0]>('0'-1))))
//如果是合法的密碼
{
//寫密碼頭
E2Write("PW",PassWordAddr,2);
//跳過密碼頭寫密碼
E2Write(InputStr,(PassWordAddr+2),sizeof(InputStr));
system = 0; //重新開始系統
}
}
}
if(system == 2) //如果是輸入密碼
{
if(system != systemb)
{
systemb = system;
LcdWriteCmd(0x01); //清屏
system = systemb;
LcdShowStr(0,1,"Enter Password! ", 16);
//LcdShowStr(0,0,PassWord, PWN);//直接顯示密碼
}
inputpassword(); //運行密碼特效
KeyDriver(); //運行按鍵驅動
if((PWRight == 1)&&(EnterSta == 1)) //如果密碼正確
{
PWRight = 0;
EnterSta = 0;
system = 3; //顯示密文
}
}
if(system == 3) //顯示密文
{
if(system != systemb)
{
systemb = system;
LcdShowStr(0,0,"Pwd is correct! ", 16);
}
KeyDriver(); //運行按鍵驅動
}
Waring();//報警
}
/*-------------------------密碼輸入控制部分----------------------------------*/
unsigned char inputI = 0; //輸入指針
bit clear = 1; //變*標志位,0為到1s
bit time1 = 0; //密碼延時定時器
unsigned int cnt = 0; //延時計數器
unsigned char code staR = '*';
unsigned char code blank = ' ';
unsigned char AinputI; //打印inputI
void esc() //按下清除鍵
{
switch(system)
{
case 1: //輸入密碼的時候
case 2:
if(inputI != 0)
{
time1 = 0; //停止變*
inputI--;
LcdShowStr(inputI,0,&blank,1);
InputStr[inputI] = blank;
}
break;
case 3:
system = 2;
break;
default: break;
}
}
//可以和按鍵一起放到1ms定時器中調用
void pwtime()
{
if(time1 == 1) //如果在運行
{
cnt++;
if(cnt>1000) //1s到了
{
cnt = 0;
clear = 0; //超時變成*
time1 = 0; //關閉密碼延時定時器
}
}
}
void inputpassword()
{
static unsigned char inputIb = 1; //輸入指針備份
if(clear == 0)
{
clear = 1; //取消標志
if(inputI > 0 )
LcdShowStr(inputI-1,0,&staR,1);
}
if(inputIb != inputI) //如果有新輸入
{
inputIb = inputI; //備份
AinputI = (inputI + '0');
if(inputI>1) //上一個字符馬上變成*
{
LcdShowStr(inputI-2,0,&staR,1);
}
time1 = 1; //打開延時定時器
cnt = 0; //清零計數
if(inputIb == 0) //清空了輸入
{
time1 = 0; //停止變*
}
LcdShowStr(15,1,&AinputI,1); //打印inputI
LcdSetCursor(inputI, 0); //光標控制
//LcdWriteCmd(0x0E); //光標模式,這里在初始化里寫了
}
}
//字符串清空函數,*p字符串,n清空的個數
void empty(unsigned char *p,unsigned char n)
{
unsigned char i;
for(i = 0;i<n;i++)
{
(*p++) = blank; //依次清空
}
}
//密碼核對函數,直接對比InputStr[i],PassWord[i]的內容
void isright()
{
unsigned char i; //循環對比
EnterSta = 1;
switch(system)
{
case 1:
PWRight = 1;
inputI = 0; //輸入指針歸零
break;
case 2:
for(i = 0;i<PWN;i++)
{
if(InputStr[i] != PassWord[i]) //如有不同
break; //退出
}
if(i == PWN) //如果有PWN個相同
{
LcdShowStr(0,1,"True! ",16); //提示密碼正確
PWRight = 1;
}else{
LcdShowStr(0,1,"False! ",16); //提示密碼錯誤
PWRight = 0;
staBuzzer = 1;//報警!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
inputI = 0; //輸入指針歸零
empty(InputStr,sizeof(InputStr)); //清空輸入
LcdShowStr(0,0,InputStr,sizeof(InputStr)); //顯示清空后的輸入
break;
case 3:
system = 1;
break;
default:break;
}
//
// if(system == 3) //查看密文時,按enter鍵修改密碼
// {
// system = 1;
// }
//
// if(system == 2) //輸入密碼狀態
// {
// for(i = 0;i<PWN;i++)
// {
// if(InputStr[i] != PassWord[i]) //如有不同
// break; //退出
// }
//
// if(i == PWN) //如果有PWN個相同
// {
// LcdShowStr(0,1,"Right! ",16); //提示密碼正確
// PWRight = 1;
// }else{
// LcdShowStr(0,1,"Try again! ",16); //提示密碼錯誤
// PWRight = 0;
// }
// inputI = 0; //輸入指針歸零
//
// empty(InputStr,sizeof(InputStr)); //清空輸入
// LcdShowStr(0,0,InputStr,sizeof(InputStr)); //顯示清空后的輸入
// }
//
// if(system == 1) //如果是設置密碼
// {
// PWRight = 1;
// inputI = 0; //輸入指針歸零
// }
}
#undef PWN //結束宏定義
//密碼輸入控制函數
void InputSetNumber(unsigned char ccode)
{
if(inputI<16)
{
if ((ccode>='0') && (ccode<='9')) //數字鍵輸入當前位設定值
{
InputStr[inputI] = ccode; //保存
LcdShowStr(inputI,0,&ccode,1); //顯示輸入
inputI++; //輸入指針+1
}
}
}
void SetLeftShift()
{
}
void SetRightShift()
{
}
/*--------------------------以下是蜂鳴器報警部分-------------------------------*/
bit buti = 0;//蜂鳴器定時器狀態
bit buSta = 0;//蜂鳴器狀態
unsigned char bucnout = 0;//蜂鳴器計數器
unsigned char butimes = 0;//響聲計次
void Waring()
{
if(staBuzzer == 1)
{
butimes = 6;//警報響的次數
staBuzzer = 0;
buti = 1;//開始計數
}
if(butimes)
{
if(bucnout>127)
{
buSta = 0;
}else{
buSta = 1;
}
}
}
void buzzTime()
{
if(buti == 1)
{
bucnout++;
if(bucnout>254)
{
bucnout = 0;
if(butimes == 0)
{
buti = 0;
}else{
butimes--;
}
}
}
}
/*--------------------------以下是矩陣按鍵驅動-------------------------------*/
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code KeyCodeMap[4][4] = { //矩陣按鍵到標准鍵碼的映射表
{ '1', '2', '3', 0x26 }, //數字鍵1、數字鍵2、數字鍵3、向上鍵
{ '4', '5', '6', 0x25 }, //數字鍵4、數字鍵5、數字鍵6、向左鍵
{ '7', '8', '9', 0x28 }, //數字鍵7、數字鍵8、數字鍵9、向下鍵
{ '0', 0x1B, 0x0D, 0x27 } //數字鍵0、ESC鍵、 回車鍵、 向右鍵
};
unsigned char KeySta[4][4] = { //全部矩陣按鍵的當前狀態
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
void KeyAction(unsigned char keycode)
{
if ((keycode>='0') && (keycode<='9')) //數字鍵輸入當前位設定值
{
InputSetNumber(keycode);//調用字符串儲存函數,用於存輸入的密碼
}
else if (keycode == 0x25) //向左鍵,向左切換設置位
{
SetLeftShift();
}
else if (keycode == 0x27) //向右鍵,向右切換設置位
{
SetRightShift();
}
else if (keycode == 0x0D) //回車鍵,切換運行狀態/保存設置
{
isright(); //調用密碼核對
}
else if (keycode == 0x1B) //Esc鍵,靜音/取消當前設置
{
esc();
}
}
//按鍵驅動函數,檢測按鍵動作,調度相應動作函數,需在主循環中調用
void KeyDriver()
{
unsigned char i, j;
static unsigned char backup[4][4] = { //按鍵值備份,保存前一次的值
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
for (i=0; i<4; i++) //循環檢測4*4的矩陣按鍵
{
for (j=0; j<4; j++)
{
if (backup[i][j] != KeySta[i][j]) //檢測按鍵動作
{
if (backup[i][j] != 0) //按鍵按下時執行動作
{
KeyAction(KeyCodeMap[i][j]); //調用按鍵動作函數
}
backup[i][j] = KeySta[i][j]; //刷新前一次的備份值
}
}
}
}
// 按鍵掃描函數,需在定時中斷中調用,推薦調用間隔1ms
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩陣按鍵掃描輸出索引
static unsigned char keybuf[4][4] = { //矩陣按鍵掃描緩沖區
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
};
//將一行的4個按鍵值移入緩沖區
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按鍵狀態
for (i=0; i<4; i++) //每行4個按鍵,所以循環4次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00)
{ //連續4次掃描值為0,即4*4ms內都是按下狀態時,可認為按鍵已穩定的按下
KeySta[keyout][i] = 0;
}
else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
{ //連續4次掃描值為1,即4*4ms內都是彈起狀態時,可認為按鍵已穩定的彈起
KeySta[keyout][i] = 1;
}
}
//執行下一次的掃描輸出
keyout++; //輸出索引遞增
keyout &= 0x03; //索引值加到4即歸零
switch (keyout) //根據索引值,釋放當前輸出引腳,拉低下次的輸出引腳
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default: break;
}
}
/*-----------------------以下是LCD1602顯示驅動-------------------------------*/
//定義LCD1602顯示相關的IO接口
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;
//等待液晶准備好
void LcdWaitReady()
{
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do {
LCD1602_E = 1; //使能LCD1602
sta = LCD1602_DB; //讀取狀態字
LCD1602_E = 0; //避免干擾其他器件
} while (sta & 0x80); //第七位等於1表示液晶正忙,重復檢測直到第七位等於0為止
}
//向LCD1602液晶寫入一字節命令,cmd為待寫入命令值
void LcdWriteCmd(unsigned char cmd)
{
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_E = 1;
LCD1602_E = 0;
}
//向LCD1602液晶寫入一字節數據,dat為待寫入數據值
void LcdWriteDat(unsigned char dat)
{
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_E = 1;
LCD1602_E = 0;
}
//設置顯示RAM起始地址,亦即光標位置,(x,y)對應屏幕上的字符的坐標
void LcdSetCursor(unsigned char x, unsigned char y)
{
unsigned char addr;
if (y == 0) //由輸入的屏幕坐標計算顯示RAM的地址
addr = 0x00 + x; //第一行字符地址從0x00起始
else
addr = 0x40 + x; //第二行字符地址從0x40起始
LcdWriteCmd(addr | 0x80); //設置RAM地址
}
//在液晶上顯示字符串,(x,y)-對應屏幕上的起始坐標,
//str-字符串指針,len-需顯示的字符長度
void LcdShowStr(unsigned char x, unsigned char y,unsigned char *str, unsigned char len)
{
LcdSetCursor(x, y); //設置起始地址
while (len--) //連續寫入len個字符數據
{
LcdWriteDat(*str++); //先取str指向的數據,然后str自加1
}
}
void InitLcd1602()//初始化1602液晶
{
LcdWriteCmd(0x38); //16*2顯示,5*7點陣,8位數據接口
LcdWriteCmd(0x0E); //顯示器開,光標打開
LcdWriteCmd(0x06); //文字不動,地址自動+1
LcdWriteCmd(0x01); //清屏
}
/*--------------------------------以下是I2C總線協議--------------------------*/
#include <intrins.h>
sbit I2C_SDA = P3^6;
sbit I2C_SCL = P3^7;
//I2C延時函數
void I2CDelay()
{
_nop_(); //1us
_nop_();
_nop_();
_nop_();
}
//I2C起始信號
void I2CStart()
{
I2C_SDA = 1; //確保SDA,SCL為高電平
I2C_SCL = 1;
I2CDelay(); //延時,讓其他器件識別
I2C_SDA = 0; //先拉低SDA
I2CDelay();
I2C_SCL = 0; //再拉低SCL
}
//I2C寫函數,dat:要寫入的字節,返回為應答值:1應答、0非應答
bit I2CWrite(unsigned char dat)
{
unsigned char mask; //臨時變量
bit ack; //應答值變量
for(mask = 0x80;mask != 0;mask>>=1) //從高位到低位依次取值
{
if((mask&dat) == 0) //如果是0
I2C_SDA = 0; //寫0
else
I2C_SDA = 1; //寫1
I2CDelay(); //等其他器件反應
I2C_SCL = 1; //讓其他器件讀取
I2CDelay(); //其他器件讀取要時間,等
I2C_SCL = 0; //准備發送下一位
}
I2C_SDA = 1; //發送完后釋放總線,以檢測從機應答
I2CDelay(); //等其他器件反應
I2C_SCL = 1; //讓其他器件發送應答位
ack = I2C_SDA; //讀取應答位
I2CDelay(); //等待
I2C_SCL = 0; //保持住總線,以進行其他操作
return (~ack); //返回應答位,1成功,0失敗
}
//I2C停止信號
void I2CStop()
{
I2C_SDA = 0; //確保SDA,SCL為低電平
I2C_SCL = 0;
I2CDelay(); //等待其他器件反應
I2C_SCL = 1; //先拉高SCL
I2CDelay();
I2C_SDA = 1; //再拉高SDA
I2CDelay();
}
//I2C讀並發送非應答信號函數,返回讀到的字節
unsigned char I2CReadNAK()
{
unsigned char dat,mask; //臨時變量,dat存數據,mask循環輔助
I2C_SDA = 1; //釋放總線,讀操作,讀IO口置1
for(mask = 0x80;mask != 0;mask >>= 1) //從高位依次讀取
{
I2CDelay(); //等待SDA被其他器件送入要讀取的數據
I2C_SCL = 1; //表示要讀了
if(I2C_SDA == 0) //讀數據
dat &= ~mask; //讀到0,當前位置零
else
dat |= mask; //讀到1,當前位置壹
I2CDelay(); //等其他器件反應
I2C_SCL = 0; //讓從機准備發送下一位
}
I2C_SDA = 1; //准備發送非應答信號
I2CDelay();
I2C_SCL = 1; //讓從機接收應答位
I2CDelay();
I2C_SCL = 0; //保持住總線,准備下一個操作
return dat; //返回收到的數據
}
//I2C讀並發送應答信號函數,返回讀到的字節
unsigned char I2CReadACK()
{
unsigned char dat,mask; //臨時變量,dat存數據,mask循環輔助
I2C_SDA = 1; //釋放總線,讀操作,讀IO口置1
for(mask = 0x80;mask != 0;mask >>= 1) //從高位依次讀取
{
I2CDelay(); //等待SDA被其他器件送入要讀取的數據
I2C_SCL = 1; //表示要讀了
if(I2C_SDA == 0) //讀數據
dat &= ~mask; //讀到0,當前位置零
else
dat |= mask; //讀到1,當前位置壹
I2CDelay(); //等其他器件反應
I2C_SCL = 0; //讓從機准備發送下一位
}
I2C_SDA = 0; //准備發送應答信號(與NAK就這不同)
I2CDelay();
I2C_SCL = 1; //讓從機接收應答位
I2CDelay();
I2C_SCL = 0; //保持住總線,准備下一個操作
return dat; //返回收到的數據
}
/*---------------------------以下是EEPROM驅動-------------------------------*/
//EEPROM讀取函數,buf:讀取出的數據儲存指針,
//addr:EEPROM中讀數據的起始地址,len:讀取數據的長度
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
do { //用尋址檢查當前是否可進行讀操作
I2CStart();
if (I2CWrite(0x50<<1)) //如果應答(表示不忙)
{
break; //跳出循環
}
I2CStop();
} while(1);
I2CWrite(addr); //寫入起始地址
I2CStart(); //重復發送啟動信號
I2CWrite((0x50<<1)|0x01); //尋址,讀操作
while (len > 1) //連續讀取len-1個字節
{
*buf++ = I2CReadACK(); //還沒到最后一個字節,發送應答信號
len--;
}
*buf = I2CReadNAK(); //最后一個字節,發送非應答
I2CStop(); //發送停止信號
}
//EEPROM寫入數據函數,buf:源數據指針,addr:EEPROM中的起始地址,len:寫入長度
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
while (len > 0)
{
//等待上次寫入操作完成
do { //用尋址操作查詢當前是否可進行讀寫操作
I2CStart();
if (I2CWrite(0x50<<1)) //應答則跳出循環,非應答則進行下一次查詢
{
break;
}
I2CStop();
} while(1);
//按頁寫模式連續寫入字節
I2CWrite(addr); //寫入起始地址
while (len > 0)
{
I2CWrite(*buf++); //寫入一個字節數據
len--; //待寫入長度計數遞減
addr++; //EEPROM存儲地址遞增
if ((addr&0x07) == 0) //檢查地址是否到達頁邊界,24C02每頁8字節,檢測低3位是否為零即可
{
break; //到達頁邊界時,跳出循環,結束本次寫操作
}
}
I2CStop();
}
}
/*------------------------------中斷函數------------------------------------*/
/* T0中斷服務函數,按鍵掃描、計時器、蜂鳴器控制 */
void InterruptTimer0() interrupt 1
{
TH0 = 0xfc;
TL0 = 0x66;
KeyScan();
pwtime();
buzzTime();
}
/* T2中斷服務函數,LED動態掃描、蜂鳴器控制 */
void InterruptTimer2() interrupt 5
{
TF2 = 0; //清零T2中斷標志
//由蜂鳴器狀態位控制蜂鳴器
if (buSta == 1)
BUZZER = ~BUZZER; //蜂鳴器鳴叫
else
BUZZER = 1; //蜂鳴器靜音
}
不知道怎么回事,在Keil里縮進是正確的,上傳到這里有點小錯誤,也暫時沒時間來得及調整了,哈哈。
這是剛學51不久寫的代碼,很多不規范的地方,目前也沒時間整理了,不過上面的注釋是比較詳細的,就不再多解釋了,不懂的可以在下方留言討論,哈哈。
二、效果演示
B站視頻鏈接:51單片機電子密碼鎖設計_嗶哩嗶哩 (゜-゜)つロ 干杯~-bilibili
三、總結
這個似乎是用到了狀態機,當時不知道,就想切換模式。
這里我比較滿意的是密碼的效果,不是按一下就是*號,而是等一會兒才變,靈感來自安卓手機,如果不想要密碼效果,直接注釋那個函數的調用就可以了,很方便,可能這就是解耦的意義所在吧。
興趣是最好的老師,希望這個能給你帶來學習的興趣,歡迎交流哦,哈哈