LCD1602 电子时钟(时间日期温度显示)

写在前面

  这个工程大致综合了前段时间学习到的大多数内容,用状态机实现的按键检测、LCD1602的读写、ds18b20的读写、LCD1602的自编字符,还有最基础的定时器启停。
  功能为:时间显示、日期显示、星期显示、温度显示及调节
  代码如下:

/**************************************************************************************
*		              LCD1602电子时钟												  *
实现现象:下载程序后,LCD屏上第一行会显示出日期,第二行显示时间和当前温度。按下K1,进入时间调节
		 模式,按下K2让光标往左移,按下K3让光标处的数字增1,按下K4让光标处的数字减1
注意事项:																				  
***************************************************************************************/
//--头文件声明,数据类型声明--//
#include <reg52.h>
#include <intrins.h>
typedef unsigned char uint8;
typedef unsigned int uint16;

#define key P3
#define no_key 0xff					//无按键按下
#define key_state0 0				//状态定义
#define key_state1 1
#define key_state2 2
//---------端口声明---------//
sbit lcden = P2^7;
sbit lcdrs = P2^6;
sbit lcdrw = P3^6;

sbit key1  = P3^2;
sbit key2  = P3^3;
sbit key3  = P3^4;
sbit key4  = P3^5;

sbit dq    = P3^7;					//DS18B20数据线
//---------函数声明---------//
void lcd_init();					//LCD屏幕初始化
void wcom();						//LCD写命令
void wdat();						//LCD写数据
void display();						//显示函数
void timeshow();					//时间显示
void dateshow();					//日期显示
void weekshow();					//星期显示
void keyscan();						//按键扫描
void key_disposal();				//键值处理
void day_jud();						//日期处理
void ds18b20_init();				//18B20模块初始化
uint8 byte_read();					//读去18B20一个字节
void byte_write();					//写入18B20一个字节
void temp_read();					//温度读取函数
void temp_display(uint8 position,uint16 tvalue1);		//温度显示函数
//---------变量定义---------//
uint8 time_1s,time_500ms;
uint8 second,minute = 59,hour = 23;
uint8 day = 1,month = 1,week = 1;			//设定初始
uint16 year = 2019,tvalue;
//uint8 temp;
uint8 cursor;								//要调节的位置
uint8 key_value;
bit f_twinkle,f_mode,f_pressing,f_set;

uint8 code table1[]={0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00};			//字符℃


//---------长延时---------//
void delay(uint16 k){
	uint16 i,j;
	for(i=k;i>0;i--)
		for(j=110;j>0;j--);
}

//---------短延时---------//
void delayus(uint8 time_1us){
	while(time_1us--);
}

//---------定时器初始化---------//
void timer_init(){
	EA = 0;
	TR0 = 0;
	TMOD = 0x01;
	TH0 = 0x4C;		//50ms
	TL0 = 0x00;

	ET0 = 1;		//允许Timer0中断
	ET1 = 1;
	EA  = 1;
	TR0 = 1;
}

//---------Timer0中断服务函数---------//
void isr_timer0 () interrupt 1 using 0 {
	TH0 = 0x4C;		//50ms
	TL0 = 0x00;
	keyscan();
	if(++time_500ms >= 10){
		time_500ms = 0;
		f_twinkle = ~f_twinkle;
	}
	if(!f_set){
		if(++time_1s >= 20){
			time_1s = 0;
			second++;
		}
	}
}

void ds18b20_init(){						//ds18b20初始化
	dq = 1;	
	_nop_();
	dq = 0;
	delayus(75);
	dq = 1;
	delayus(20);
	dq = 1;
	_nop_();
}

void wcom(uint8 com){						//LCD1602写命令
	lcdrw = 0;
	lcdrs = 0;
	P0 = com;
	lcden = 1;
	delay(5);
	lcden = 0;
}

void wdat(uint8 dat){						//LCD1602写数据
	lcdrw = 0;
	lcdrs = 1;
	P0 = dat;
	lcden = 1;
	delay(5);
	lcden = 0;	
}

void lcd_init(){							//LCD初始化
	wcom(0x38);
	wcom(0x01);
	wcom(0x06);
	wcom(0x0c);
	
	wcom(0x86);
	wdat(0x2d);								//"-"
	wcom(0x89);
	wdat(0x2d);								//"-"
	wcom(0x80+0x40+0x04);
	wdat(0x3a);								//":"
	wcom(0x80+0x40+0x07);
	wdat(0x3a);								//":"
}

void timeshow(uint8 position,uint8 time){		//时间显示,position为显示位置
	uint8 i,j;
	i = time/10;				//shiwei
	j = time%10;				//gewei
	wcom(0x80+0x40+position);
	wdat(0x30+i);				//shiwei
	wdat(0x30+j);				//gewei
}

void dateshow(uint8 position,uint8 date1){		//日期显示(月、日)
	uint8 i,j;
	i = date1/10;				//shiwei
	j = date1%10;				//gewei
	wcom(0x80+position);
	wdat(0x30+i);				//shiwei
	wdat(0x30+j);				//gewei
}

void yearshow(uint8 position,uint16 year1){		//年份显示
	uint8 i,j,m,n;
	i = year1/1000;				//qianwei
	j = year1/100%10;			//baiwei
	m = year1/10%10;
	n = year1%10;
	wcom(0x80+position);
	wdat(0x30+i);				//qianwei
	wdat(0x30+j);				//baiwei
	wdat(0x30+m);				//shiwei
	wdat(0x30+n);				//gewei
}

void weekshow(uint8 position,uint8 week1){		//星期显示
	wcom(0x80+position);
	switch(week1){
		case 1:wdat(0x4d);wdat(0x4f);wdat(0x4e);break;
		case 2:wdat(0x54);wdat(0x55);wdat(0x45);break;
		case 3:wdat(0x57);wdat(0x45);wdat(0x44);break;
		case 4:wdat(0x54);wdat(0x48);wdat(0x55);break;
		case 5:wdat(0x46);wdat(0x52);wdat(0x49);break;
		case 6:wdat(0x53);wdat(0x41);wdat(0x54);break;
		case 7:wdat(0x53);wdat(0x55);wdat(0x4e);break;
		default:break;
	}
}

void day_jud(){									//日期处理(秒、分、时、日、月进位)
	if(second > 59){
		second = 0;
		if(++minute > 59){
			minute = 0;
			if(++hour > 23){
				hour = 0;
				if(++week >= 8)	week = 1;
				day++;
			}
		}
	}
	
	if(minute > 59){
		minute = 0;
		if(++hour > 23){
			hour = 0;
			if(++week > 7)	week = 1;
			day++;
		}
	}
	
	if(hour > 23){
		hour = 0;
		if(++week > 7)	week = 1;
		day++;
	}
	
	if(day > 31 && (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12)){
		day = 1;
		month++;
		if(month >= 12){
			month = 1;
			year++;
		}
	}
	else if(day > 30 && (month == 4 || month == 6 || month == 9 || month == 11)){
		day = 1;
		month++;
	}
	else if(day > 29 && month == 2 && ((year%4 == 0 && year%100 != 0) || year%400 == 0)){
		day = 1;
		month++;
	}
	else if(day > 28 && month == 2){
		day = 1;
		month++;
	}
}

void display(){										//显示函数
	if(!f_mode){									//时间显示模式
		yearshow(2,year);
		dateshow(7,month);
		dateshow(0x0a,day);
		weekshow(0x0d,week);
		
		timeshow(2,hour);
		timeshow(5,minute);
		timeshow(0x08,second);
		temp_read();								//读取温度
		temp_display(0x0b,tvalue);					//温度显示
	}
	else{											//时间调节模式
		wcom(0x80+0x40+0x0d);
		wdat(0x53);									//"S"
		wdat(0x45);									//"E"
		wdat(0x54);									//"T"

		if(f_twinkle && !f_pressing){				//闪烁标志位,0.5秒取反一次,为1则相应位置显示空白
			if(cursor == 0) {						//按压标志位,按键按下时为1,不闪烁
				wcom(0x82);							//要显示空白的位置
				wdat(0x20);							//4位空白(实现闪烁效果)
				wdat(0x20);
				wdat(0x20);
				wdat(0x20);
			}
			if(cursor == 1) {
				wcom(0x87);							//要显示空白的位置
				wdat(0x20);							//2位空白
				wdat(0x20);
			}
			if(cursor == 2) {
				wcom(0x8a);
				wdat(0x20);
				wdat(0x20);
			}
			if(cursor == 3) {
				wcom(0x8d);
				wdat(0x20);
				wdat(0x20);
				wdat(0x20);
			}
			if(cursor == 4) {
				wcom(0x80+0x42);
				wdat(0x20);
				wdat(0x20);
			}
			if(cursor == 5) {
				wcom(0x80+0x45);
				wdat(0x20);
				wdat(0x20);
			}
			if(cursor == 6) {
				wcom(0x80+0x48);
				wdat(0x20);
				wdat(0x20);
			}
		}
		else {										//正常显示
			yearshow(2,year);
			dateshow(7,month);
			dateshow(0x0a,day);
			weekshow(0x0d,week);
			timeshow(2,hour);
			timeshow(5,minute);
			timeshow(0x08,second);
		}
	}
}

void keyscan(){										//按键扫描
	static uint8 key_state;
	
	switch(key_state){
		case key_state0:							//未按下
			if(!key1 || !key2 || !key3 || !key4){
				key_state = key_state1;
			}
		break;
		case key_state1:							//有键按下
			f_pressing = 1;
			if(!key1){
				f_set = ~f_set;						//秒走时停止
				f_mode = ~f_mode;					//模式切换
				key_value = 1;
				key_state = key_state2;
			}
			else if(!key2){
				key_value = 2;
				key_state = key_state2;
			}
			else if(!key3){
				key_value = 3;
				key_state = key_state2;
			}
			else if(!key4){
				key_value = 4;
				key_state = key_state2;
			}
			else{
				key_state = key_state0;
				f_pressing = 0;						//如果不清零,假如按键是无效按下,调节模式时就会停止闪烁
			}
		break;
		case key_state2:							//松开按键
			if(key1 && key2 && key3 && key4){
				f_pressing = 0;
				key_state = key_state0;
			}
		break;
	}
}

void key_disposal(){								//键值处理
	if(!f_pressing){								//松开按键才处理
		if(key_value == 1){
			key_value = 0;
			cursor = 0;								//光标位置清零
			lcd_init();								//清屏
		}
		else if(key_value == 2){
			key_value = 0;
			cursor++;								//光标+(光标即闪烁的位置,实际上LCD上并无光标)
			if(cursor > 6){
				cursor = 0;
			}
		}
		else if((key_value == 3) && f_mode){		//光标所处位置值+
			key_value = 0;
			if(cursor == 0){
				year++;
			}
			else if (cursor == 1){
				if(++month > 12) month = 1;
			}
			else if(cursor == 2){
				if(++day > 31) day = 1;
			}
			else if(cursor == 3){
				if(++week > 7) week = 1;
			}
			else if(cursor == 4){
				if(++hour > 23) hour = 0;
			}
			else if(cursor == 5){
				if(++minute > 59) minute = 0;
			}
			else if(cursor == 6){
				if(++second > 59) second = 0;
			}
		}
		else if((key_value == 4) && f_mode){		//光标所处位置值-
			key_value = 0;
			if(cursor == 0 && year > 0){
				year--;
			}
			else if (cursor == 1 && month > 1){
				month--;
			}
			else if(cursor == 2 && day > 1){
				day--;
			}
			else if(cursor == 3 && week > 1){
				week--;
			}
			else if(cursor == 4 && hour > 0){
				hour--;
			}
			else if(cursor == 5 && minute > 0){
				minute--;
			}
			else if(cursor == 6 && second > 0){
				second--;
			}
		}
	}
}

uint8 byte_read(){									//ds18b20 读字节
	uint8 i,j,dat;
	for(i = 0; i < 8 ; i++){
		dq = 0;
		_nop_();
		dq = 1;
		_nop_();
		j = dq;
		delayus(10);
		dq = 1;
		_nop_();
		dat = (j << 7 | dat >> 1);
	}
	return(dat);
}

void byte_write(uint8 data1){						//ds18b20 写字节
	uint8 i;
	for(i = 0; i < 8 ; i++){
		dq = 0;
		_nop_();
		dq = data1&0x01;
		delayus(10);
		dq = 1;
		_nop_();
		data1 >>= 1;
	}
}

void temp_read(){									//读温度
	float data1;
	uint8 i,j;
	ds18b20_init();
	byte_write(0xcc);								//向DS18B20发跳过读ROM命令
	byte_write(0x44);								//启动DS18B20进行温度转换命令,转换结果存入内部RAM
	delayus(80);
	ds18b20_init();
	byte_write(0xcc);								//向DS18B20发跳过读ROM命令
	byte_write(0xbe);								//读取温度寄存器等(共可读9个寄存器) 前两个就是温度
	delayus(80);
	i = byte_read();								//内部RAM 低位
	j = byte_read();								//内部RAM 高位
	data1 = (j*256 + i) * 6.25;						//取出温度值
	tvalue = (uint16)data1;							//强制转换
}

void temp_display(uint8 position,uint16 tvalue1){	//温度显示
	uint8 i,j,m;
	i = tvalue1/1000;
	j = tvalue1%1000/100;
	m = tvalue1%100/10;								//取出十位 个位 十分位
	wcom(0x80 + 0x40 + position);
	wdat(0x30 + i);
	wdat(0x30 + j);
	wdat(0x2e);										//"."
	wdat(0x30 + m);
	wdat(0x00);										//自编字符
}

void main(){
	uint8 i;
	wcom(0x40);
	for(i = 0; i < 8 ; i++){
		wdat(table1[i]);							//写入自编字符
	}
	
	timer_init();
	lcd_init();
	while(1){
		// if(++temp > 2){
			// temp = 0;
		// }
		// switch(temp){
			// case 0:day_jud();break;
			// case 1:key_disposal();break;
			// case 2:display();break;
		// }
		day_jud();
		key_disposal();
		display();
	}
}

显示的效果如下:
在这里插入图片描述
开发板的电路图如下,读者可以使用proteus搭建类似环境试验(仿真大概率温度传感器有问题),建议还是找个类似的开发板,把端口对应上,一个个功能逐个实现再整合再一起,上来就全复制,不仅不利于自己的消化学习,出了问题还不知道应该从哪着手去调试。
在这里插入图片描述
  这个电子时钟在调节时间日期的模式时,我一直想要做一个光标在要调节的参数底下闪烁的效果,但是由于LCD1602开光标的命令我打开之后,就算给光标设定了位置,光标还是会满屏跑,无法实现,是目前比较遗憾的。想了下如果把显示空白那部分改为让LCD每个大格的最下面一行的小格子亮,也能实现一个伪光标的效果。

uint8 code table2[]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff};			//字符__ 用来实现伪光标效果

//主函数中添加
	wcom(0x48);										//声明自编字符第一行的位置
	for(i = 0; i < 8 ; i++){
		wdat(table2[i]);							//写入自编字符
	}												//连续写入数据时,应该是先写入第一行的八位,在写入第二行的八位。
													//类似wcom(0x48);wdat(table2[0]);wcom(0x49);wdat(table2[1]);
													//只不过这个wcom(0x49)应该会在写完第一个八位数据即wdat(table2[0])后,
													//在写第二个八位数据时就自动匹配好了,不需要程序员再写出。
													
//把display()函数中闪烁部分的wdat(0x20)替换为wdat(0x01);即可实现一个伪光标效果,不过这个光标的闪烁跟数字的闪烁是互补的,
//数字闪的时候光标常亮,光标闪的时候数字常亮。忘记实物是怎么样的逻辑了,暂时就先这样凑合吧。
void display(){										//显示函数
	if(!f_mode){									//时间显示模式
		yearshow(2,year);
		dateshow(7,month);
		dateshow(0x0a,day);
		weekshow(0x0d,week);
		
		timeshow(2,hour);
		timeshow(5,minute);
		timeshow(0x08,second);
		temp_read();								//读取温度
		temp_display(0x0b,tvalue);					//温度显示
	}
	else{											//时间调节模式
		wcom(0x80+0x40+0x0d);
		wdat(0x53);									//"S"
		wdat(0x45);									//"E"
		wdat(0x54);									//"T"

		if(f_twinkle && !f_pressing){				//闪烁标志位,0.4秒取反一次,为1则相应位置显示空白
			if(cursor == 0) {						//按压标志位,按键按下时为1,不闪烁
				wcom(0x82);							//要显示空白的位置
				wdat(0x01);							//4位最下面一行亮,类似光标,伪光标
				wdat(0x01);
				wdat(0x01);
				wdat(0x01);
			}
			if(cursor == 1) {
				wcom(0x87);							//要显示空白的位置
				wdat(0x01);							//2位空白(实现闪烁效果)
				wdat(0x01);
			}
			if(cursor == 2) {
				wcom(0x8a);
				wdat(0x01);
				wdat(0x01);
			}
			if(cursor == 3) {
				wcom(0x8d);
				wdat(0x01);
				wdat(0x01);
				wdat(0x01);
			}
			if(cursor == 4) {
				wcom(0x80+0x42);
				wdat(0x01);
				wdat(0x01);
			}
			if(cursor == 5) {
				wcom(0x80+0x45);
				wdat(0x01);
				wdat(0x01);
			}
			if(cursor == 6) {
				wcom(0x80+0x48);
				wdat(0x01);
				wdat(0x01);
			}
		}
		else {										//正常显示
			yearshow(2,year);
			dateshow(7,month);
			dateshow(0x0a,day);
			weekshow(0x0d,week);
			timeshow(2,hour);
			timeshow(5,minute);
			timeshow(0x08,second);
		}
	}
}
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值