51单片机+ESP01S网络时钟(做不出来你打我)
ESP01S和51单片机获取网络时间及其注意事项
一、起因:之前做过一个51单片机和DS1302的时钟,但是精度太差,加入了修正程序之后也会存在走时不准的情况,因此决定做一个wifi时钟,自动对时,永远是准的。
二、材料:ESP01S开发板一个,烧录器一个,51开发板一个,杜邦线若干,3.3V电源模块一个,5V电源插头一个,USB转DC5V圆头电源线一条。
三、思路:
1、利用ESP1S和51单片机的串口通讯,把事实时间数据发送给51单片机,再利用数码管显示。
2、ESP01S使用的是自带的AT固件,可以使用AT指令直接获取网络时间,无需编程,操作方便,可以使用airkiss配网(这是我使用AT指令的重要原因)。也可以使用arduino编写自己的程序,优点是功能丰富,自由度高。
3、使用AT指令获取到时间之后,串口发送给51单片机,单片机做数据处理,显示在数码管上。每隔一段时间单片机向ESP01S发送对时命令,ESP01s把最新的时间发给单片机,实现自动对时。
4、开发板上带有DS1302模块,每次对时之后都把最新的时间写入到DS1302中,不对时的时候,依靠DS1302进行走时。
5、设置了3个按键,分别是ESP01S重启按键,ESP01S进入airkiss配网模式按键和ESP01S手动获取网络时间按键。
四、接线:
1、51单片机P0口接8位数码管的数据口。
2、P2.0,P2.1,P2.2分别接38译码器的ABC输入端。
3、ESP01S模块串口接收端RX接51单片机的串口发送端TX,51的RX接ESP01的TX。
4、ESP01S模块的GND连接51单片机开发板GND,ESP01S的电源连接开发板3.3V电源输出端。
5、独立按键RST,AIRKISS,和手动对时分别接在P2.4,P2.6,P2.7。
五、程序设计:
1、需要用到的AT指令:
(1)重启:AT+RST
(2)Airkiss配网模式:AT+CWSTARTSMART=3
(3)连接授时网站:AT+CIPSTART="TCP","www.beijing-time.org",80
(4)工作在透传模式:AT+CIPMODE=1
(5)打开透传模式:AT+CIPSEND
2、AT指令执行注意事项:
(1)串口调试助手需要勾选“发送新行”。
(2)波特率要设置正确,ESP01S默认是115200的波特率,但是51单片机不支持这么高的波特率,所以要使用 AT+UART=4800,8,1,0,0 指令把波特率设置为4800(也可以是9600,我用的4800)。注意串口调试助手的波特率要和你设置的一致,不然会收到乱码。
3、AT指令执行结果:
(1)执行上述第一条指令之后如下。
(2)接着执行第二条指令:AT+CWSTARTSMART=3 即可进入配网模式。此时要确保你的手机连接的是2.4G无线网络,并且网络名称最好是英文(不是英文的我没试),然后打开微信小程序搜索“ESP配网”,会有很多小程序,我用的是下面这个:
(这里显示wifi disconnect是因为我之前连结果wifi,进入配网模式它会自动断开,所以显示这个,如果你是第一次配网的话,应该不显示这个。)
(3)进入小程序之后,他应该会自动帮你填上你正连着的wifi名称,只需要你输入密码,然后确定,等待几秒钟配网就会成功了。
(显示配网成功了)
(4)发送上述第3,4,5条指令,就连接到授时网站了。
(5)此时随便在发送框里输入什么字符,点击发送,他都会返回一段包含时间的代码。
>HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
Date: Tue, 17 Oct 2023 08:10:01 GMT
Connection: close
Content-Length: 326
*这一段代码的第四行就是包含时间的代码,只要提取出时间就好了,注意这的时间需要加上八小时才等于北京时间。因此我们使用51单片机让ESP01s进入当前这个状态,只要随便给他发个消息他就能返回给我们实时的时间了。
2、51程序设计
(1)AT指令的存储,注意有的指令本身就包括引号,因此需要在前面加上反斜杠,否则指令被51单片机发出后ESP01S无法识别;其次,每条指令后面都要加上\r\n,表示光标换行并转移到最左端,如果不加的话,命令也无法被ESP01S执行。
unsigned char Esp01sCmd_RST[] = "AT+RST\r\n";
unsigned char Esp01sCmd_RirKiss[] = "AT+CWSTARTSMART=3\r\n";
unsigned char ESP01sCmd_Online[] = "AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n"; //连接服务器
unsigned char ESP01sCmd_Cip[] = "AT+CIPMODE=1\r\n"; //工作在传透模式
unsigned char ESP01sCmd_OpenCip[] = "AT+CIPSEND\r\n"; //打开传透模式
unsigned char ESP01sCmd_GetTime[] = "H\r\n"; //任意命令,会返回当时前时间
(2)51单片机串口的使用
具体使用原理这就不说了,网上有很多教程,这里只介绍一下代码:
①串口初始化函数
#define FOSC 11059200L //单片机的时钟频率
#define BAUD 4800 //串口通讯的波特率
void Uart_Init()
{
SCON = 0x50; //SCON==0101 0000, 模式1,8 bit UART,接收使能
TMOD = 0x20; //定时器1设置为8位自动重装模式
TH1 = TL1 = -(FOSC / 12 / 32 / BAUD); //设定自动重装值
TR1 = 1; //定时器1开始运行
ES = 1; //串口中断使能
ET1 = 0; //定时器中断关闭
EA = 1; //中断总开关使能
}
②串口中断程序函数(有数据发送或者接受,会进入中断)
void Uart_Isr() interrupt 4
{
if(RI == 1)
{
/*由于接收到包含时间的字符串很长,并且单片机存储空间有限,不能全部保留下来,
因此观察时间字符的特点,时间字符是8位,00:00:00,包括小时十位,小时个位,冒
号,分钟十位,分钟个位,冒号,秒十位,秒个位。只需要创建一个8位字符空间大小
的数组作为队列,每次截取8个字符,再分析中间是否包含两个冒号就可以知道是不是
时间代码了*/
RI = 0;
//接收中断标志位清零
ReceivedData[0] = ReceivedData[1];
//利用队列,每次读取一个串口数据就加在队列的最后,队列第一个出去
ReceivedData[1] = ReceivedData[2];
ReceivedData[2] = ReceivedData[3];
ReceivedData[3] = ReceivedData[4];
ReceivedData[4] = ReceivedData[5];
ReceivedData[5] = ReceivedData[6];
ReceivedData[6] = ReceivedData[7];
ReceivedData[7] = SBUF;
if((ReceivedData[2] == ':') && (ReceivedData[5] == ':'))
//如果检测到两个冒号,证明这是时间代码
{
SegSend[0] = ReceivedData[0] - '0';
//串口发送的是字符型数据,需要减去字符0来转换为整数类型
SegSend[1] = ReceivedData[1] - '0';
TimeDataFlag = SegSend[0] * 10 + SegSend[1] + 8;
//相差八小时,所以要加上8,涉及到进位问题,下面代码进行处理
if(TimeDataFlag > 24)
{
SegSend[1] = TimeDataFlag - 24;
SegSend[0] = 0;
}
if(TimeDataFlag == 24)
{
SegSend[0] = 0;
SegSend[1] = 0;
}
if(TimeDataFlag < 24)
{
TimeDataFlag = SegSend[0] * 10 + SegSend[1] + 8;
SegSend[0] = TimeDataFlag / 10;
SegSend[1] = TimeDataFlag - (SegSend[0] * 10);
}
SegSend[3] = ReceivedData[3] - '0';
SegSend[4] = ReceivedData[4] - '0';
SegSend[6] = ReceivedData[6] - '0';
SegSend[7] = ReceivedData[7] - '0';
TIME[0] = ((SegSend[6] << 4) & 0xf0) | SegSend[7];
//把数字转换为BCD码存入DS1302
TIME[1] = ((SegSend[3] << 4) & 0xf0) | SegSend[4];
//把数字转换为BCD码存入DS1302
TIME[2] = ((SegSend[0] << 4) & 0xf0) | SegSend[1];
//把数字转换为BCD码存入DS1302
/*每次获取到正确的时间之后写入DS1302*/
Ds1302Init();
}
}
if(TI == 1)
{
TI = 0;
busy = 0;
}
}
(3)51单片机串口发送数据函数
void SendData(unsigned char dat)
{
while(busy);//等待前面的数据发送完成
busy = 1; //发送数据时,busy标志为1
SBUF = dat; //把数据发给SBUF
}
void SendString(char *s)
{
while(*s)
{
SendData(*s++);
}
}
unsigned char SendNByte(unsigned char *p, unsigned char n)
{
unsigned char i;
for(i = 0; i < n; i++)
{
SendData(p[i]);
}
return 1;
}
(4)DS1302操作函数
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
//---定义ds1302使用的IO口---//
sbit DSIO = P3 ^ 4;
sbit RST = P3 ^ 5;
sbit SCLK = P3 ^ 6;
//---定义全局函数---//
void Ds1302Write(uchar addr, uchar dat);
uchar Ds1302Read(uchar addr);
void Ds1302Init();
void Ds1302ReadTime();
//---加入全局变量--//
extern uchar TIME[7]; //加入全局变量
#endif
#include"ds1302.h"
//---DS1302写入和读取时分秒的地址命令---//
//---秒分时日月周年 最低位读写位;-------//
uchar code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
uchar code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//---DS1302时钟初始化2013年1月1日星期二12点00分00秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
uchar TIME[7] = {0, 0, 0x12, 0x01, 0x01, 0x02, 0x13};
/*******************************************************************************
* 函 数 名 : Ds1302Write
* 函数功能 : 向DS1302命令(地址+数据)
* 输 入 : addr,dat
* 输 出 : 无
*******************************************************************************/
void Ds1302Write(uchar addr, uchar dat)
{
uchar n;
RST = 0;
_nop_();
SCLK = 0;//先将SCLK置低电平。
_nop_();
RST = 1; //然后将RST(CE)置高电平。
_nop_();
for (n = 0; n < 8; n++) //开始传送八位地址命令
{
DSIO = addr & 0x01;//数据从低位开始传送
addr >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;
_nop_();
}
for (n = 0; n < 8; n++) //写入8位数据
{
DSIO = dat & 0x01;
dat >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;//传送数据结束
_nop_();
}
/*******************************************************************************
* 函 数 名 : Ds1302Read
* 函数功能 : 读取一个地址的数据
* 输 入 : addr
* 输 出 : dat
*******************************************************************************/
uchar Ds1302Read(uchar addr)
{
uchar n, dat, dat1;
RST = 0;
_nop_();
SCLK = 0;//先将SCLK置低电平。
_nop_();
RST = 1;//然后将RST(CE)置高电平。
_nop_();
for(n = 0; n < 8; n++) //开始传送八位地址命令
{
DSIO = addr & 0x01;//数据从低位开始传送
addr >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;//DS1302下降沿时,放置数据
_nop_();
}
_nop_();
for(n = 0; n < 8; n++) //读取8位数据
{
dat1 = DSIO;//从最低位开始接收
dat = (dat >> 1) | (dat1 << 7);
SCLK = 1;
_nop_();
SCLK = 0;//DS1302下降沿时,放置数据
_nop_();
}
RST = 0;
_nop_(); //以下为DS1302复位的稳定时间,必须的。
SCLK = 1;
_nop_();
DSIO = 0;
_nop_();
DSIO = 1;
_nop_();
return dat;
}
/*******************************************************************************
* 函 数 名 : Ds1302Init
* 函数功能 : 初始化DS1302.
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds1302Init()
{
uchar n;
Ds1302Write(0x8E, 0X00); //禁止写保护,就是关闭写保护功能
for (n = 0; n < 7; n++) //写入7个字节的时钟信号:分秒时日月周年
{
Ds1302Write(WRITE_RTC_ADDR[n], TIME[n]);
}
Ds1302Write(0x8E, 0x80); //打开写保护功能
}
/*******************************************************************************
* 函 数 名 : Ds1302ReadTime
* 函数功能 : 读取时钟信息
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds1302ReadTime()
{
uchar n;
for (n = 0; n < 7; n++) //读取7个字节的时钟信号:分秒时日月周年
{
TIME[n] = Ds1302Read(READ_RTC_ADDR[n]);
}
}
(5)扫描数码管代码比较简单,就不单独写出来了。
(6)完整代码:
/********************本程序可实现配网,使用airkiss微信小程序********************/
#include <reg51.h>
#include<intrins.h>
#define FOSC 11059200L //单片机的时钟频率
#define BAUD 4800 //串口通讯的波特率
unsigned char SegCode[10] = {0xc0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90};
unsigned char SegSend[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char ReceivedData[40]; //用于存储串口接收到的数据
unsigned int TimeDataFlag = 0; //串口接收数据计数器
bit busy = 0;
sbit LSA = P2 ^ 0;
sbit LSB = P2 ^ 1;
sbit LSC = P2 ^ 2;
sbit Key_AT_RST = P2 ^ 4; //重启按钮
sbit Key_AirKiss = P2 ^ 6; //配网按钮,注意,更换新的网络要先重启再配网。
sbit Key_GetOlineTime = P2 ^ 7; //连接时间服务器获取网络时间。
unsigned char Esp01sCmd_RST[] = "AT+RST\r\n";
unsigned char Esp01sCmd_RirKiss[] = "AT+CWSTARTSMART=3\r\n";
unsigned char ESP01sCmd_Online[] = "AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n"; //连接服务器
unsigned char ESP01sCmd_Cip[] = "AT+CIPMODE=1\r\n"; //工作在传透模式
unsigned char ESP01sCmd_OpenCip[] = "AT+CIPSEND\r\n"; //打开传透模式
unsigned char ESP01sCmd_GetTime[] = "H\r\n"; //任意命令,会返回当时前时间
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
//---定义ds1302使用的IO口---//
sbit DSIO = P3 ^ 4;
sbit RST = P3 ^ 5;
sbit SCLK = P3 ^ 6;
//---定义全局函数---//
void Ds1302Write(uchar addr, uchar dat);
uchar Ds1302Read(uchar addr);
void Ds1302Init();
void Ds1302ReadTime();
//---加入全局变量--//
extern uchar TIME[7]; //加入全局变量
#endif
#include"ds1302.h"
//---DS1302写入和读取时分秒的地址命令---//
//---秒分时日月周年 最低位读写位;-------//
uchar code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
uchar code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//---DS1302时钟初始化2013年1月1日星期二12点00分00秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
uchar TIME[7] = {0, 0, 0x12, 0x01, 0x01, 0x02, 0x13};
/*******************************************************************************
* 函 数 名 : Ds1302Write
* 函数功能 : 向DS1302命令(地址+数据)
* 输 入 : addr,dat
* 输 出 : 无
*******************************************************************************/
void Ds1302Write(uchar addr, uchar dat)
{
uchar n;
RST = 0;
_nop_();
SCLK = 0;//先将SCLK置低电平。
_nop_();
RST = 1; //然后将RST(CE)置高电平。
_nop_();
for (n = 0; n < 8; n++) //开始传送八位地址命令
{
DSIO = addr & 0x01;//数据从低位开始传送
addr >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;
_nop_();
}
for (n = 0; n < 8; n++) //写入8位数据
{
DSIO = dat & 0x01;
dat >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;
_nop_();
}
RST = 0;//传送数据结束
_nop_();
}
/*******************************************************************************
* 函 数 名 : Ds1302Read
* 函数功能 : 读取一个地址的数据
* 输 入 : addr
* 输 出 : dat
*******************************************************************************/
uchar Ds1302Read(uchar addr)
{
uchar n, dat, dat1;
RST = 0;
_nop_();
SCLK = 0;//先将SCLK置低电平。
_nop_();
RST = 1;//然后将RST(CE)置高电平。
_nop_();
for(n = 0; n < 8; n++) //开始传送八位地址命令
{
DSIO = addr & 0x01;//数据从低位开始传送
addr >>= 1;
SCLK = 1;//数据在上升沿时,DS1302读取数据
_nop_();
SCLK = 0;//DS1302下降沿时,放置数据
_nop_();
}
_nop_();
for(n = 0; n < 8; n++) //读取8位数据
{
dat1 = DSIO;//从最低位开始接收
dat = (dat >> 1) | (dat1 << 7);
SCLK = 1;
_nop_();
SCLK = 0;//DS1302下降沿时,放置数据
_nop_();
}
RST = 0;
_nop_(); //以下为DS1302复位的稳定时间,必须的。
SCLK = 1;
_nop_();
DSIO = 0;
_nop_();
DSIO = 1;
_nop_();
return dat;
}
/*******************************************************************************
* 函 数 名 : Ds1302Init
* 函数功能 : 初始化DS1302.
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds1302Init()
{
uchar n;
Ds1302Write(0x8E, 0X00); //禁止写保护,就是关闭写保护功能
for (n = 0; n < 7; n++) //写入7个字节的时钟信号:分秒时日月周年
{
Ds1302Write(WRITE_RTC_ADDR[n], TIME[n]);
}
Ds1302Write(0x8E, 0x80); //打开写保护功能
}
/*******************************************************************************
* 函 数 名 : Ds1302ReadTime
* 函数功能 : 读取时钟信息
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds1302ReadTime()
{
uchar n;
for (n = 0; n < 7; n++) //读取7个字节的时钟信号:分秒时日月周年
{
TIME[n] = Ds1302Read(READ_RTC_ADDR[n]);
}
}
/************************数码管显示函数*****************************/
void DigDisplay()
{
unsigned char i;
unsigned int j;
for(i = 0; i < 8; i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA = 0;
LSB = 0;
LSC = 0;
break;//显示第0位
case(1):
LSA = 1;
LSB = 0;
LSC = 0;
break;//显示第1位
case(2):
LSA = 0;
LSB = 1;
LSC = 0;
break;//显示第2位
case(3):
LSA = 1;
LSB = 1;
LSC = 0;
break;//显示第3位
case(4):
LSA = 0;
LSB = 0;
LSC = 1;
break;//显示第4位
case(5):
LSA = 1;
LSB = 0;
LSC = 1;
break;//显示第5位
case(6):
LSA = 0;
LSB = 1;
LSC = 1;
break;//显示第6位
case(7):
LSA = 1;
LSB = 1;
LSC = 1;
break;//显示第7位
}
if((i != 2) && (i != 5))
{
P0 = ~SegCode[SegSend[i]]; //发送段码
}
else //8个数码管,时分秒中间显示两条横线,为了美观
{
P0 = 0x40;
}
j = 50; //扫描间隔时间设定
while(j--);
P0 = 0x00; //消隐
}
}
void SendData(unsigned char dat);
void SendString(char *s);
unsigned char SendNByte(unsigned char *p, unsigned char n);
void Delay1000ms();
/********************串口初始化**********************/
void Uart_Init()
{
SCON = 0x50; //SCON==0101 0000, 模式1,8 bit UART,接收使能
TMOD = 0x20; //定时器1设置为8位自动重装模式
TH1 = TL1 = -(FOSC / 12 / 32 / BAUD); //设定自动重装值
TR1 = 1; //定时器1开始运行
ES = 1; //串口中断使能
ET1 = 0; //定时器中断关闭
EA = 1; //中断总开关使能
Key_AT_RST = 1; //ESP复位按键连接的单片机初始位高。
Key_AirKiss = 1; //配网按钮,注意,更换新的网络要先重启再配网。
Key_GetOlineTime = 1;
}
/*联网对时函数*/
void GetTime()
{
/*这里延时的时间长一些,为了等待ESP01S准备好*/
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
SendString(ESP01sCmd_Online); //连接对时网站
Delay1000ms();
Delay1000ms();
SendString(ESP01sCmd_Cip); //传透模式
Delay1000ms();
SendString(ESP01sCmd_OpenCip); //打开传透模式
Delay1000ms();
SendString(ESP01sCmd_GetTime); //随便发个东西获取时间
}
void main()
{
int time1, time2, GetTimeFlag = 0; //GetTimeFlag是对时标记,这里设置为每3600秒对时一次,喜欢的话自己改。
Uart_Init();
/****************上电自动对时******************/
/*对时成功之后会显示正确的时间,如果不显示,说
明联网失败,需要依次按下复位按键,再按下airkiss
按键进行配网,注意只能链接2.4G频率Wifi*/
GetTime();
/*********自动对时之后读取DS1302时间********/
Ds1302ReadTime();
SegSend[7] = TIME[0] & 0x0f; //获取秒的个位数字
time1 = SegSend[7];
while(1)
{
Ds1302ReadTime();
SegSend[0] = TIME[2] / 16; //时
SegSend[1] = TIME[2] & 0x0f;
SegSend[3] = TIME[1] / 16; //分
SegSend[4] = TIME[1] & 0x0f;
SegSend[6] = TIME[0] / 16; //秒
SegSend[7] = TIME[0] & 0x0f;
time2 = SegSend[7];
if((time2 - time1 == 1)||(time1 - time2 == 9))
{
GetTimeFlag ++;
if(GetTimeFlag == 3600) //3600秒对时一次,没必要间隔太短
{
GetTimeFlag = 0;
SendString(ESP01sCmd_GetTime);
}
}
DigDisplay();
if(Key_AT_RST == 0)
{
/*复位按键。由于开启了透传模式,因此要想复位,必须在
esp01s无法联网的状态下才能重启。*/
Delay1000ms();
Delay1000ms();
if(Key_AT_RST == 0)
{
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
SendString(Esp01sCmd_RST);
Key_AT_RST = 1; //按键之后对应引脚置为高
}
}
if(Key_AirKiss == 0) //airkiss配网按键
/*ESP01S无法联网的状态下重启之后才能按下此按键进行配网,否则不会成功*/
{
Delay1000ms();
Delay1000ms();
if(Key_AirKiss == 0)
{
Delay1000ms();
Delay1000ms();
Delay1000ms();
Delay1000ms();
SendString(Esp01sCmd_RirKiss);
Key_AirKiss = 1; //按键之后对应引脚置为高
}
}
}
}
void Uart_Isr() interrupt 4
{
if(RI == 1)
{
/*由于接收到包含时间的字符串很长,并且单片机存储空间有限,不能全部保留下来,因此观察时间字符的特点,时间字符是8位
小时十位,小时个位,冒号,分钟十位,分钟个位,冒号,秒十位,秒个位。只需要创建一个8位字符空间大小的数组作为队列,
每次截取8个字符,再分析中间是否包含两个冒号就可以知道是不是时间代码了*/
RI = 0; //接收中断标志位清零
ReceivedData[0] = ReceivedData[1]; //利用队列,每次读取一个串口数据就加在队列的最后,队列第一个出去
ReceivedData[1] = ReceivedData[2];
ReceivedData[2] = ReceivedData[3];
ReceivedData[3] = ReceivedData[4];
ReceivedData[4] = ReceivedData[5];
ReceivedData[5] = ReceivedData[6];
ReceivedData[6] = ReceivedData[7];
ReceivedData[7] = SBUF;
if((ReceivedData[2] == ':') && (ReceivedData[5] == ':'))
//如果检测到两个冒号之间,证明这是时间代码
{
SegSend[0] = ReceivedData[0] - '0';
//串口发送的是字符型数据,需要减去字符0来转换为整数类型
SegSend[1] = ReceivedData[1] - '0';
TimeDataFlag = SegSend[0] * 10 + SegSend[1] + 8;
//相差八小时,所以要加上8,涉及到进位问题,下面代码进行处理
if(TimeDataFlag > 24)
{
SegSend[1] = TimeDataFlag - 24;
SegSend[0] = 0;
}
if(TimeDataFlag == 24)
{
SegSend[0] = 0;
SegSend[1] = 0;
}
if(TimeDataFlag < 24)
{
TimeDataFlag = SegSend[0] * 10 + SegSend[1] + 8;
SegSend[0] = TimeDataFlag / 10;
SegSend[1] = TimeDataFlag - (SegSend[0] * 10);
}
SegSend[3] = ReceivedData[3] - '0';
SegSend[4] = ReceivedData[4] - '0';
SegSend[6] = ReceivedData[6] - '0';
SegSend[7] = ReceivedData[7] - '0';
TIME[0] = ((SegSend[6] << 4) & 0xf0) | SegSend[7]; //把数字转换为BCD码存入DS1302
TIME[1] = ((SegSend[3] << 4) & 0xf0) | SegSend[4]; //把数字转换为BCD码存入DS1302
TIME[2] = ((SegSend[0] << 4) & 0xf0) | SegSend[1]; //把数字转换为BCD码存入DS1302
/*每次获取到正确的时间之后写入DS1302*/
Ds1302Init();
}
}
if(TI == 1)
{
TI = 0;
busy = 0;
}
}
void SendData(unsigned char dat)
{
while(busy);//等待前面的数据发送完成
busy = 1; //发送数据时,busy标志为1
SBUF = dat; //把数据发给SBUF
}
void SendString(char *s)
{
while(*s)
{
SendData(*s++);
}
}
unsigned char SendNByte(unsigned char *p, unsigned char n)
{
unsigned char i;
for(i = 0; i < n; i++)
{
SendData(p[i]);
}
return 1;
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
(7)运行结果:程序上电自动获取一次网络时间,然后每小时获取一次网络时间,有其他需求的可以自己更改。
六、小结和注意事项
(1)虽然原理比较简单,但是涉及到几个芯片之间的通讯,还是第一次弄。
(2)中间遇到了很多问题,一个最大的问题就是程序无法正确运行,经过好几天的排查,终于发现是电源的问题,我是用开发板上的3.3V给ESP01S供电,一运行数码管就会无休止的跳动,能不能正确运行全靠运气,后来我使用了电源模块单独给ESP01S供电,再把电源模块和开发板共地,程序就能稳定正确运行了。
(3)可以把开发板插在电脑上,同时把ESP01S通过下载器接在电脑上,这样ESP01S只需要引出2根串口数据线和单片机连接就行了,供电靠电脑,这样做的好处是可以开2个串口调试助手,通过检测对应的COM口来观察程序是否正确运行。
(4)我设置了2个电源模块,因为之前出现过ESP01S电流不足工作异常的情况,所以为了保险起见,PCB板子上预留了2个电源模块,结果果然出现那种情况,只安装一个电源模块,ESP01S可以接受51单片机的串口数据,但是发送数据异常,接上2个电源模块之后一切正常。
(5)LED限流电阻我选的20欧,数码管限流电阻我选的10欧。LES限流电阻不能太小,否则电流太大会烧坏电源模块。
(6)驱动数码管我选的74HC595,驱动能力大,亮度高,并且占用IO口少。区别于扫描显示,这种静态显示无闪烁,效果更好。
(3)随后我会做出样板,把系统转移到PCB上进行测试。
七、实物:
(焊接技术一如既往的垃圾)
你可能注意到为啥这有一条橘黄色的短线,这是因为我画图的时候把2根线接反了,需要飞线再改过来。。
亮度取决于给数码管串联的电阻大小,实测不加电阻也不会坏,但是晚上特别亮,所以我加了个20欧的电阻,但是白天在强光下好像不是那么亮了,最好整个滑动变阻器自由调节。
以上就是wifi时钟的一个做法了,,再也不用调表了,肯定有更好的解决方案,还请多多讨论。
更多推荐
所有评论(0)