仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)
单片机源程序如下:
- /*设计思路
- 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)
|