51单片机+ESP01S网络时钟(做不出来你打我)

一、起因:之前做过一个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时钟的一个做法了,,再也不用调表了,肯定有更好的解决方案,还请多多讨论。

  • 6
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值