仿真原理圖如下(proteus仿真工程文件可到本帖附件中下載)
51hei.gif (228.62 KB, 下載次數: 62)
下載附件
2022-4-27 22:15 上傳
單片機源程序如下:
- /*設計思路
- 1、利用定時器中斷0二十次產生1s時間變量miao增加到60變量分+1并把miao清零,shi同理
- 2、第一行左邊顯示年月日,右邊為功能區,‘c’———調整時間,此時定時器中斷關閉
- ‘a’———調整鬧鐘,此時顯示的是設定鬧鐘的時間,小時和分鐘。
- ‘o’———鬧鐘開啟
- 第二行左邊為當前調整的位,分別有 miao shi fen ri yue nian
- 3、采用4個按鍵 key1,key2,key3,key4
- 在時鐘模式下,調整區顯示‘TIME’:
- 當長key4兩秒后首先進入時間鐘整預選模式,此時調整區顯示‘clock’,此時單擊key1即可確定進入時間調整模式,
- 此時功能區顯示的是當前調整的位,按key1切換,key2,3進行調整,調整完畢后單擊key4退出調整模式
- 再次長按key4進入下一個預調整界面,調整區顯示‘alarm’,此時表示設置鬧種,方法與時鐘相同
- 再次長安key4進入鬧鐘開關界面,調整區顯示‘onoff’,此時單擊key1可以開啟或關閉鬧鐘,‘o’表示開啟
- 再次長按,即可退出調整模式,調整區顯示‘TIME’
- 4、第一行日期后面顯示光照強度,0~102;
- */
- #include"XPT2046.h"
- #include"LCD1602.h"
- #include<reg52.h>
- void LCD_display(void);
- void keykan(void);
- void my_alarm_clock(void);
- void shezhi(void);
- void xingqi(void);
- void datapros(void);
- void T0_init();
- void delay(uint x);
- void mybeep(void);
- void mybeeplong(void);
- void mybeeplongplus(void);
- int num[10]={0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}; // lcd1602 0~9字碼
- uchar miao=0, fen=5, shi=12 , zhou=1 , yue=6 , nian=21 ; //進位標志位 秒 分 時 周 月 年
- uchar ri=21; //進位標志位 日 不知道為啥放在上面會報錯,單獨定義就可以
- uint i=0; //定時器計時標志位
- uchar *time[]={&miao , &fen , &shi ,&ri ,&yue , &nian, &zhou}; //指針變量time存放進位標志位的地址,由于數組不能存放標量,但是卻可以存放標量的地址,采用指針的方式來間接修改變量的值
- uchar naofen=0 , naoshi=7; //鬧鐘值
- uchar *nao[]={&naofen, &naoshi}; //存放鬧鐘值的數組
- uint light_ ;
- uchar set = 0 ;
- bit kaiguan=0;
- uchar SetPlace=0; //功能選擇標志
- bit mod =0; //設置模式標志位
- bit alarmset=0 , alarm=0 ; //鬧鐘設置需要的標志位
- bit x=0; //鬧鐘開關標志位
- sbit key1=P3^1; //定義四個按鍵 key1選擇需要調整的位
- sbit key2=P3^0; //key2加
- sbit key3=P3^2; //key3減
- sbit key4=P3^3; //長按兩秒鐘進入或退出設置模式,前面三個按鍵需要進入設置模式才能使用
- void datapros() //光敏電阻數值處理
- {
-
- static uchar i;
- if(i==40)
- {
- i=0;
- light_ = (Read_AD_Data(0xA4))/10; // AIN2 光敏電阻
- }
- i++;
- }
- //光照強度顯示函數
- void lightdisplay(void){
- LCD_disp_char(1,11,num[light_/100]);
- delay(50);
- LCD_disp_char(1,12,num[light_%100/10]);
- delay(50);
- LCD_disp_char(1,13,num[light_%100%10]);
- delay(50);
- }
- void xingqi(void){ //星期計算函數
- uchar c , y , m , d;
- c=20;
- y=nian;
- m=yue;
- d=ri;
- if (m==1|| m==2) //將12月按上一年1314月處理
- {
- m+=12;
- y-=1;
- }
- zhou = ((c/4) - 2*c +y + (y/4) + (13*(m+1)/5)+d-1)%7; //蔡勒公式
- }
- void main(void) //主函數
- {
- T0_init(); //定時器初始化
- LCD_init(); //lcd1602初始化
- SPI_Start(); //AD初始化
- while(1) //死循環
- {
- datapros();
- lightdisplay();
- shezhi();
- my_alarm_clock();
- keykan();
- xingqi();
- LCD_display(); //lcd1602顯示時分秒年月日周
- }
-
- }
- void keykan(void) //按鍵處理函數
- {
- if ((alarmset==0) && (mod==0))
- {
- LCD_disp_char(1,14,' ');
- delay(50);
- }
-
- if (alarmset==1)
- {
- LCD_disp_char(1,14,'a');
- if (SetPlace==0) //功能選擇位為0就顯示“miao” 為1就顯示“fen” 以此類推
- {
- LCD_row(2);
- LCD_DispString(" fen ");
- delay(50);
- }
- switch (SetPlace) //調整位閃爍
- {
- case 0 : { LCD_disp_char(2,12,' ');
- delay(700);} ; break;
- case 1 : { LCD_disp_char(2,9,' ');
- delay(700);} ; break;
- }
-
- if(key1==0) //檢測按鍵key1是否按下
- {
- delay(10); //消除抖動
- if(key1==0)
- { //按一下功能選擇位就+1,加到7就清零
- SetPlace++;
- if(SetPlace>=2)
- SetPlace=0;
- }
-
- if (SetPlace==1)
- {
- LCD_row(2);
- LCD_DispString(" shi ");
- delay(50);
- }
- mybeep(); //蜂鳴器發聲
- while(key1==0) //檢測按鍵是否松開
- {
- delay(10);
- }
- }
-
- if(key2==0) //檢測按鍵key2是否按下
- {
- delay(10); //消除抖動
- if(key2==0)
- {
- (*nao[SetPlace])++; //按鍵按一次 指針數組*time[SetPlace]里對應的地址的數據值就+1,也就是說進位標志位年月日時分秒的值就會+1
- if ((((*nao[SetPlace]))>=60) && (SetPlace==0))
- {
- (*nao[SetPlace])=0; //秒只能加dao59
- }
- if ((((*nao[SetPlace]))>=60) && (SetPlace==1))
- {
- (*nao[SetPlace])=0; //分只能加到59
- }
-
-
- mybeep(); //蜂鳴器發聲
- while(key2==0) //檢測按鍵是否松開
- {
- delay(10);
- }
- }
- }
- if(key3==0) //檢測按鍵key3是否按下
- {
- delay(10); //消除抖動
- if(key3==0)
- {
- //按鍵按一次 指針數組*time[SetPlace]里對應的地址的數據值就+1,也就是說進位標志位年月日時分秒的值就會+1
- if ((((*nao[SetPlace]))<=0) && (SetPlace==0))
- {
- (*nao[SetPlace])=60; //秒只能加dao59
- }
- if ((((*nao[SetPlace]))<=0) && (SetPlace==1))
- {
- (*nao[SetPlace])=60; //分只能加到59
- }
- (*nao[SetPlace])--;
-
- mybeep(); //蜂鳴器發聲
- while(key3==0) //檢測按鍵是否松開
- {
- delay(10);
- }
- }
- }
- }
-
- if (mod==1) //如果進入設置模式顯示一個c
- {
- LCD_disp_char(1,14,'c');
- if (SetPlace==0) //功能選擇位為0就顯示“miao” 為1就顯示“fen” 以此類推,shi zhou ri yue nian
- {
- LCD_row(2);
- LCD_DispString("miao ");
- delay(50);
- }
- switch (SetPlace) //調整位閃爍
- {
- case 0 : { LCD_disp_char(2,15,' ');
- delay(700);} ; break;
- case 1 : { LCD_disp_char(2,12,' ');
- delay(700);} ; break;
- case 2 : { LCD_disp_char(2,9,' ');
- delay(700); } ; break;
- case 3 : { LCD_disp_char(1,9,' ');
- delay(700); } ; break;
- case 4 : { LCD_disp_char(1,6,' ');
- delay(700); } ; break;
- case 5 : { LCD_disp_char(1,3,' ');
- delay(700); } ; break;
-
- }
-
- if(key1==0) //檢測按鍵key1是否按下
- {
- delay(10); //消除抖動
- if(key1==0)
- { //按一下功能選擇位就+1,加到7就清零
- SetPlace++;
- if(SetPlace>=6)
- SetPlace=0;
- }
- switch (SetPlace) //調整為顯示
- {
- case 1 : { LCD_row(2);
- LCD_DispString(" fen ");
- delay(50);} ; break;
- case 2 : { LCD_row(2);
- LCD_DispString(" shi ");
- delay(50); } ; break;
- case 3 : { LCD_row(2);
- LCD_DispString(" ri ");
- delay(50); } ; break;
- case 4 : { LCD_row(2);
- LCD_DispString(" yue ");
- delay(50); } ; break;
- case 5 : { LCD_row(2);
- LCD_DispString("nian ");
- delay(50); } ; break;
-
- }
- mybeep(); //蜂鳴器發聲
- while(key1==0) //檢測按鍵是否松開
- {
- delay(10);
- }
-
- }
- if(key2==0) //檢測按鍵key2是否按下
- {
- delay(10); //消除抖動
- if(key2==0)
- {
-
- (*time[SetPlace])++; //按鍵按一次 指針數組*time[SetPlace]里對應的地址的數據值就+1,也就是說進位標志位年月日時分秒的值就會+1
- if ((((*time[SetPlace]))>=60) && (SetPlace==0))
- {
- (*time[SetPlace])=0; //秒只能加dao59
- }
- void LCD_display(void) //lcd1602顯示年月日時分秒周
- {
- LCD_disp_char(2,6, *time[6]); //顯示周幾 此處顯示的為自定義字符 當zhou=1的時候即*time[3]=1 對應地址0x01的字符為 一 ;以此類推
- delay(50);
- LCD_disp_char(2,15,num[*time[0]%10]); //顯示秒的個位 當miao%10的值為0時即(*time[0])==0, 此時num[0]=0x30 對應1602字符庫中的0 ,lcd1602對應第2行第16位顯示0
- delay(50); //當miao%10的值為1時即(*time[1])==1, 此時num[1]=0x31 對應1602字符庫中的1
- LCD_disp_char(2,14,num[*time[0]/10]); //秒的十位
- delay(50);
- LCD_disp_char(2,13,':');
- delay(50);
- if (alarmset==0) //時鐘模式
- {
- LCD_disp_char(2,12,num[*time[1]%10]); //分的各位
- delay(50);
-
- ……………………
- …………限于本文篇幅 余下代碼請從51黑下載附件…………
復制代碼 RAM地址映射
HD44780內置DDRAM、CGROM和CGRAM。
DDRAM就是顯示數據RAM,用來寄存待顯示的字符代碼。共80個字節,地址和屏幕的對應關系如下:
也就是說想要在LCD1602屏幕上的第一行第一個位置顯示一個“A”,就要向DDRAM的00H地址寫“A”字的代碼就OK了,但具體的寫入是要按照LCD模塊的指令格式來進行的。在寫入地址是,地址最高位D7必須為1,所以實際寫入數據應該是80H。 控制時序圖 圖3-2 讀操 作時序
圖3-3 寫操作時序 代碼實現 1、寫指令 RS=L,RW=L,D0~D7=指令碼,E=高脈沖。 寫指令函數代碼段: void LCD_write_command(uchar dat) { LCD_DB=dat; LCD_RS=0; //指令 LCD_RW=0; //寫入 LCD_E=1; //允許 LCD_E=0; delay_n40us(2); } 2、寫數據 RS=H,RW=L,D0~D7=數據,E=高脈沖。 寫數據函數代碼片段 void LCD_write_data(uchar dat ) { LCD_DB=dat; LCD_RS=1; //數據 LCD_RW=0; //寫入 LCD_E=1; //允許 LCD_E=0; delay_n40us(2); } 有了以上基礎知識我們就能夠對1602顯示屏進行基本的顯示操作了,1602內置字符發生器CGROM中儲存了160個字符,每一個字符都有一個 固定的代碼,我把它理解為地址,可以參考其字符代碼與字符圖形對應的關系。
(4)CGRAM自定義字模(簡易漢字顯示,我把它用來顯示星期) 這里說明一下lcd1602液晶是不能顯示漢字的,因為它的顯示原理是由若干個5X7或者5X11的點陣字符位組成的,又因為漢字較為復雜,所以1602的主要作用就是顯示字母、數字、符號的。但是真的不能顯示漢字嗎?也并非絕對不能。
接就是下面要說的CGRAM自定義字模。要顯示我們自定義的字符,就要用到LCD中的CGRAM存儲器(character generate RAM),而我們之前用的顯示自帶的字符用到的是DDRAM,兩個是不同的。看手冊我們知道,CGRAM的容量是64個字節,而一個字符是8個字節,所以一共能顯示8個自定義的字符。內部常用字符的顯示是從0x20開始的,0x00 ~ 0x0F是專門留給自定義字符顯示用的,0x00-0x07和0x08~0x0F顯示的內容是一樣的,也就是說0x00=0x08,0x01=0x09,以此類推。CGRAM共128個位,地址是0x40-0x7F,128/8=16正好對應的是0x00-0x0F共16個,剛才說了,0x00與0x08對應,0x01與0x09對應,共16個,這并不矛盾!說了這么多,那么怎樣顯示一個自定義字符呢?
首先我們要清楚LCD1602顯示字符的點陣大小,眼力好的可以看出來,LCD1602一個顯示字符的位置是58的點陣,也就是說它所能顯示的點陣圖形的大小是58的!要顯示一個自定義的字符,首先我們要知道所顯示自定義字符的點陣數據,也就是在一個58的點陣上那個點是黑的(將該點點黑,就是高電平----1),哪個點是白的(該點不顯示,為低電平----0),但是我們送入到LCD中的是ASCII碼,它是8位的數據,而一個顯示字符的點陣大小只是58的,顯然不夠,顯示的辦法是8*8點陣的前三列不用,也就是不顯示,我們只用后面的5列來顯示。
然后設定我們是要定義第幾個自定義字符,前面已經介紹了,LCD1602最多顯示8個自定義字符;然后要規定在液晶的什么位置顯示自定義字符,看過數據手冊我們知道,第一行第一個位置的地址是0x80,第二行一個位置的地址是0xC0。最后就是要顯示我們定義的第幾個字符其對應CGRAM地址的關系式是:
0x00:第一個(0x40) 0x01:第二個(0x48) 0x02:第三個(0x50) 0x03:第四個(0x58) 0x04:第五個(0x60) 0x05:第六個(0x68) 0x06:第七個(0x70) 0x07:第八個(0x78)
每個字符由5X8點陣組成,想要實現顯示,只需如下圖:例:以5X8點陣為例,顯示字符 A 0代表滅,1代表亮
只需將想要顯示的字符的對應位置1,就能顯示該字
01110 □█ █ █□ 10001 █ □□□ █ 10001 █ □□□ █ 10001 █ □□□ █ 11111 █ ███ █ 10001 █ □□□ █ 10001 █ □□□ █ 10001 █ □□□ █ A={0x0e,0x11,0x11,0x11,0x1f,0x11,0x11,0x11} 定義好自定義字符后,需要將定義的字符裝入CGRAM中
代碼實現如下: 1、定義了一個存儲簡單漢字的數組 uint code Xword[]={ 0x1F,0x15,0x1D,0x17,0x11,0x15,0x01,0x00, //周,代碼 0x00(由于這個lcd1602像素太小了5*8,這個周字屬實有點認不出) 0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00, //一,代碼 0x01 0x00,0x00,0x00,0x0e,0x00,0xff,0x00,0x00, //二,代碼 0x02 0x00,0x00,0xff,0x00,0x0e,0x00,0xff,0x00, //三,代碼 0x03 0x00,0x00,0xff,0xf5,0xfb,0xf1,0xff,0x00, //四,代碼 0x04 0x00,0xfe,0x08,0xfe,0x0a,0x0a,0xff,0x00, //五,代碼 0x05 0x00,0x04,0x00,0xff,0x00,0x0a,0x11,0x00, //六,代碼 0x06 0x00,0x1f,0x11,0x1f,0x11,0x11,0x1f,0x00, //日,代碼 0x07 }; 2、將數組內容寫入CGRAM void CgramWrite(void) { // 裝入CGRAM // uint i; LCD_write_command(0x06); // CGRAM地址自動加1 LCD_write_command(0x40); // CGRAM地址設為00處 for(i=0;i<64;i++) { LCD_write_data(Xword[ i]);// 按數組寫入數據 } }
由此我們就能讓我們的電子時鐘顯示簡單的漢字:一、二、三、四、五、六、日。
(五)XPT2046芯片使用簡介(XPT2046的功能有很多,我在這里只做了簡單的ADC使用方法介紹,更多詳細的內容可參考附件XPT2046中文資料。) XPT2046 是一種典型的逐次逼近型模數轉換器(SAR ADC),包含了采樣/保持、模數轉換、串口數據 輸出等功能。同時芯片集成有一個 2.5V的內部參考電壓源、溫度檢測電路,工作時使用外部時鐘。XPT2046 可以單電源供電,電源電壓范圍為 2.7V~5.5V。參考電壓值直接決定ADC的輸入范圍,參考電壓可以使用內部參考電壓,也可以從外部直接輸入 1V~VCC范圍內的參考電壓(要求外部參考電壓源輸出阻抗低)。我這塊開發板使用的參考電壓為XPT2046供電電壓5V。
我們需要了解和使用到的幾個引腳如圖所示,我用圓圈圈出來了:
圖5-1 XPT2016引腳說明 
了解完引腳之后,就要看怎么去使用和控制這些引腳來達成目標,
所以接下來要看的就是時序工作圖,要了解芯片是如何工作,如何完成采樣 保持 量化 編碼這幾個步驟的。一次完整的轉換需要 24 個串行同步時鐘(DCLK)來完成。(看DCLK時序那,一共出現了3次8, 3*8=24) 
圖5-1 8位總線接口,無DCLK時鐘延遲,24時鐘周期轉換時序 
表5-2 制字的控制位命令 
表5-3 單端模式輸入配置
要想啟動選中該芯片,首先得給CS和CLK置0 (因為單片機默認引腳輸出1) ,然后開始寫入數據。 處理器和轉換器之間的的通信需要 8 個時鐘周期,可采用 SPI、SSI 和Microwire 等同步串行接口。前面8個時鐘就在進行通信。 前8個時鐘就是用來通過DIN引腳輸入控制字節。 先看DIN的時序(圖中有幾個英文單詞,Idle的意思是閑置的意思,aquire是獲取的意思,conversion是轉換的意思,個人理解是圖中把DIN在24個時鐘周期內的變化,分成了幾個段,閑置段,獲取段,轉換段)DIN的控制位有8位,下面開始從最高位開始介紹:
控制字節由 DIN 輸入的控制字如表所示,它用來啟動轉換,尋址,設置 ADC 分辨率,配置和對XPT2046 進行掉電控制。圖4-1和表4-2給出控制字的各控制位的詳細說明。 起始位——第一位,即 S 位。控制字的首位必須是 1,即 S=1。在 XPT2046 的 DIN 引腳檢測到起始位前,所有的輸入將被忽略。 地址——接下來的 3 位(A2、A1 和 A0)選擇多路選擇器的現行通道(見表4-3和圖4-1),觸摸屏驅動和參考源輸入。這里我參考我手里的開發板原理圖光敏電阻是接在AIN2通道,也就是AUX引腳,所以A1/A2/A3為010。 MODE——模式選擇位,用于設置 ADC 的分辨率。MODE=0,下一次的轉換將是 12 位模式;MODE=1,下一次的轉換將是 8 位模式。這里我們選擇12位模式,此位為0。 SER/ ——SER/ 位控制參考源模式,選擇單端模式(SER/ =1)或差分模式(SER/ =0),我們在這里選擇單端模式,此位為1。 PD0 和 和 PD1——低功率模式選擇位,若為11,器件處于供電狀態,若為00,器件在交換之間處于低功率模式。我們選擇PD0/PD1都為0。
綜上所述,最后我們給DIN的值為A4H。 
圖5-2 開發板XPT2046原理圖
(六)其他外圍元器件的介紹光敏電阻是一種無結器件 , 它是利用半導體的光致導電特性制成的 , 在受光時產生空穴和電子( 光生載流子) ,在復合前就由一電級到達另一電極 , 有效的參與導電 , 從而使光電導體的電阻率發生變化。光照強度越強 , 電阻越小 如右圖所示, 目前生產的光敏電阻主要是硫化鎘 (cdS) 光敏電阻 ( 占 90 % 以上 ) 。為提高硫化鎘光敏電阻的光靈敏度 , 在 cdS中摻入銅銀等雜質。 圖4-1
光敏電阻的應用在生活中也非常的廣泛,我們在這里應用是給光敏電阻串聯一個100k的普通電阻如下圖4-2,我們只需要測量普通電阻兩端的電壓,就可以間接收集到光照強度。再將測得的電壓值通過ADC傳送給單片機。
圖4-2 光敏電阻接線
我是用到的蜂鳴器是一個無源蜂鳴器,相比有源蜂鳴器的通電發聲簡單操作,無源蜂鳴器卻需要給特定的方波信號才能發聲。但是無源蜂鳴器卻能夠通過給定方波信號不同而發出不同頻率的聲音。 我的蜂鳴器發聲程序如下: uint n; for ( n = 0; n < 100; n++) //改變循環次數可以改變發聲時長 { BEEP=~BEEP; delay(20); //改變此處延時程序的值,可以改變蜂鳴器音調 }
(七)Protues仿真圖
在protues里面我找不到XPT2046芯片,就沒有畫它的電路。下面附上開發板的ADC電路圖 
《電子時鐘》 四、軟件設計(一)按鍵操作邏輯
1、LCD顯示區域劃分 
2、按鍵分布
3、按鍵操作邏輯
(1)、首先,這是正常的時鐘模式,右下角顯示TIME
(2)、長按設置鍵可以選擇模式,左下角模式調整區clock,按key1 OK鍵進入時鐘調整模式 
(3)、進入時鐘調整模式后,繼續按key1切換要調整的位,同時右上角功能顯示區域顯示字母c,功能調整區會顯示需要調整的位,同時會閃爍,按加減鍵會進行相應的加減。 
(4)、調整完畢后按設置/return鍵返回,在次長按設置/return鍵,進入鬧鐘調整界面,功能調整區會顯示alarm。

(5)、按切換/ok鍵進入鬧鐘調整模式,右上角功能顯示區域會顯示字母a,調整方式與是時鐘模式相同。 
(6)、鬧鐘設置完畢后,按設置/return鍵退出,再次長按進入鬧鐘開關界面,功能調整區域顯示onoff,按切換/ok鍵開關鬧鐘,鬧鐘打開后右上角功能顯示區域會顯示字母o。再次長按設置鍵就會退出。
2、設置掃描函數
(1)流程圖
3、按鍵掃描函數 (1)程序圖 
本人初學,僅供參考,存在錯誤和不足之處,請大家回帖多多指教,切勿照搬,文件下載:
Keil代碼與Proteus仿真下載:
電子時鐘文件.7z
(2.37 MB, 下載次數: 97)
2022-4-27 22:28 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
|