基于51单片机的多功能数字时钟设计利用AT89C51为主控,LCD12864为显示器件,使用DS1302作为时间单元,DHT11温湿度传感器获取环境温湿度,蜂鸣器和LED作为提示器件,使用按键进行模式选择等一系列功能。
一、硬件设计
1、LCD12864
本次设计有中文显示的需求,所以普通的LCD1602、LCD1604已经无法再满足需求,目前在仿真中还剩下两个方案,一个是利用OLED,一个是LCD12864,综合考虑51单片机的性能等各方面因素,本次设计使用LCD12864实现中文显示。在Proteus中,LCD12864叫做 AMPIRE128X64。电路如下:
2、DHT11
DHT11是一款经典的温湿度传感器,它利用单总线节省了IO口,在实际使用中大大节省了硬件资源。Proteus中提供有DHT11,如下图:
3、DS1302
DS1302为本次系统的时间单元器件,DS1302由于在仿真系统中,如果不设置其时间,这个DS1302的时间是会和电脑时间同步的,显然会非常方便本次项目的使用,DS1302使用 SPI 的通信协议,在实际使用中我们利用SPI读写寄存器,便可以完成时间的获取。
4、串口终端
由于Proteus并没有语音模块,所以这次决定使用串口终端打印中文来表示语音播报。
二、软件设计
1、硬件驱动设计
(1)DS1302使用 SPI 协议进行驱动,通过对年、月、日、时、分、秒的寄存器的读寄存器进行读操作,便可以方便的获得其值,参考代码如下:
DS1302.c
#include <REGX52.H>
sbit DS1302_SCLK=P1^0;
sbit DS1302_IO=P1^1;
sbit DS1302_CE=P1^2;
//Write:
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATA 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8a
#define DS1302_YEAR 0x8c
#define DS1302_WP 0x8e
char DS1302_Time[]={22,1,24,20,01,40,1};
void DS1302_Init()
{
DS1302_CE=0;
DS1302_SCLK=0;
}
void DS1302_WriteByte(unsigned char Command,Data)
{
unsigned char i;
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
for(i=0;i<8;i++)
{
DS1302_IO=Data&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_CE=0;
}
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i,Data=0x00;
Command|=0x01;
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=0;
DS1302_SCLK=1;
}
for(i=0;i<8;i++)
{
DS1302_SCLK=1;
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_IO=0;
DS1302_CE=0;
return Data;
}
void DS1302_SetTime()
{
DS1302_WriteByte(DS1302_WP,0x00);
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
DS1302_WriteByte(DS1302_DATA,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
DS1302_WriteByte(DS1302_WP,0x80);
}
void DS1302_ReadTime()
{
unsigned char temp;
temp=DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_MONTH);
DS1302_Time[1]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_DATA);
DS1302_Time[2]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[3]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[4]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[5]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_DAY);
DS1302_Time[6]=temp/16*10+temp%16;
}
DS1302.h
#ifndef __DS1302_H__
#define __DS1302_H__
extern char DS1302_Time[];
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime();
void DS1302_SetTime();
#endif
LCD12864有两个CS引脚,分别控制着显示左边、右边两个部分,所以在程序运行时候,也可以看到,LCD1602会左右依次向下显示,直到显示完最后一行。LCD12864代码如下:
LCD12864.c
#include "12864.h"
#include "ziku.h"
void Delay_ms(unsigned int xms) //@11.0592MHz
{
unsigned int i,j;
for(i=xms;i>0;i--)
for(j=112;j>0;j--);
}
static void LCD12864_Write(bit COM_Data,unsigned char dat) //12864操作函数
{
LCD12864_RS=COM_Data; //指令 0 数据 1
LCDData_Pro=dat; //数据传输至P0扣
LCD12864_EN=1; //置1
_nop_();
LCD12864_EN=0; //置0 产生下降沿
}
void LCD12864_Init(void) //12864初始化
{
Delay_ms(300); //等待系统上电稳定
/******************************************************************************/
LCD12864_EN=1;
LCD12864_RS=1;
LCD12864_CS1=1; //仿真 0 实物 1
LCD12864_CS2=1; //仿真 0 实物 1
Delay_ms(10);
/******************************************************************************/
LCD12864_Write(LCD12864_COM,0x3f); //开显示
/*
LCD12864_Write(LCD12864_COM,0xc0); //这段不写也可以正常显示
LCD12864_Write(LCD12864_COM,0xb8);
LCD12864_Write(LCD12864_COM,0x40);
*/
LCD12864_Write(LCD12864_COM,0x30); //打开基本指令
LCD_Clear(); //清屏
}
/*****************************************************************************
函数功能:清平函数
入口参数:空
说 明:清空整个屏幕数据
版 本:V1.0
时 间:2020年5月9日
*****************************************************************************/
void LCD_Clear(void)
{
unsigned char i,j;
#if LCD_Mode
LCD12864_CS1=1; //选中左半屏
LCD12864_CS2=1; //选中右半屏
#else
LCD12864_CS1=0; //选中左半屏
LCD12864_CS2=0; //选中右半屏
#endif
LCD12864_Write(LCD12864_COM,0xc0); //行
for(i=0;i<8;i++)
{
LCD12864_Write(LCD12864_COM,0xb8+i); //页
for(j=0;j<64;j++)
{
LCD12864_Write(LCD12864_COM,0x40+j); //列
LCD12864_Write(LCD12864_DATA,0x00);
Delay_ms(5); //方便看清屏效果
}
}
}
/*****************************************************************************
函数功能:地址写入函数
入口参数:X,Y
说 明:根据地址自动切换左、右屏
版 本:V1.0
时 间:2020年5月9日
*****************************************************************************/
void LCD12864_X_Y(unsigned char X,unsigned char Y)
{
Y=Y&0x7f; //限定范围,列不能超过127
X=X&0x07; //限定范围,行不能超过7
if(Y<64)
{
#if LCD_Mode
LCD12864_CS1=1; //选中左半屏
LCD12864_CS2=0; //关闭右半屏
#else
LCD12864_CS1=0; //选中左半屏
LCD12864_CS2=1; //关闭右半屏
#endif
LCD12864_Write(LCD12864_COM,0x40+Y); //选择列基地址+Y 一共64行
if(Y==63) Y=64;
}
else
{
#if LCD_Mode
LCD12864_CS1=0; //关闭左半屏
LCD12864_CS2=1; //选中右半屏
#else
LCD12864_CS1=1; //关闭左半屏
LCD12864_CS2=0; //选中右半屏
#endif
Y&=0x3f;
LCD12864_Write(LCD12864_COM,0x40+Y); //选择列基地址+Y 一共64列
}
LCD12864_Write(LCD12864_COM,0xb8+X); //选择页基地址+X 一共7页
}
/*****************************************************************************
函数功能:6*16数值写入函数
入口参数:X,Y,num,*dat
说 明:X_横坐标 Y_纵坐标 num_数组的第num个数 *day_需要显示的数组
版 本:V1.5
修改时间:2022年7月10日
新 增:可实现跨左右屏
*****************************************************************************/
void LCD12864_Write_Number(unsigned char X,unsigned char Y,unsigned char num,unsigned char *dat)
{
unsigned char i;
for(i=0;i<6;i++)
{