写在前面
这个工程大致综合了前段时间学习到的大多数内容,用状态机实现的按键检测、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);
}
}
}