基于51单片机和ESP8266(01S)、八位数码管、独立按键的WiFi定时器时钟
目录
- 系列文章目录
- 前言
- 一、效果展示
- 二、原理分析
- 三、各模块代码
- 1、延时函数
- 2、定时器0
- 3、串口
- 4、数码管扫描
- 5、独立按键扫描
- 四、主函数
- 总结
系列文章目录
前言
有三个版本:
①普中开发板版本1:28800bps@11.0592MHz,12T
②普中开发板版本2:115200bps@11.0592MHz,6T
③最小系统板版本:4800bps@12.0000MHz,12T
本文代码对应的是普中开发板版本。
三个版本用到的单片机都是:STC89C52RC。
用到的外设都是:ESP8266(01S)、八位数码管、独立按键。
本文代码对应的是版本是:普中开发板版本2。
普中开发板版本2烧录程序的时候需要勾选“使能6T(双倍速)模式”
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
二、原理分析
1、拿到ESP8266(01S)模块,可以用电脑通过串口连接模块,测试一下模块能否正常使用。
2、ESP8266的指令很多,我们用到的只是其中几条。
【说明】
①发送AT指令需要回车换行,多字符串的发送的方框内无法回车,需要用转义字符 \r\n 来实现回车换行。
②单片机代码中发送的字符串如果有双引号 " , 也需要用反斜杠 \ 转义。
例如:
下面(5)中,单片机发送给ESP8266模块的字符串的代码应为:
AT+CWJAP=\“GTS\”,\“01234567\”\r\n
下面(6)中,单片机发送给ESP8266模块的字符串的代码应为:
AT+CIPSTART=\“TCP\”,\“www.beijing-time.org\”,80\r\n
(1)测试 AT 启动
AT\r\n
(2)重启模块
AT+RST\r\n
(3)查看AT固件版本信息
AT+GMR\r\n
(4)设置 Wi-Fi 模式 (STA/AP/STA+AP),保存到 Flash
1:设置模块为STA(Station)模式;
2:设置模块为AP(Access Point)模式;
3:同时启用STA和AP模式。
AT+CWMODE=1\r\n
(5)连接AP,保存到Flash(还可以设置其他参数,具体请查看手册)
AT+CWJAP=<ssid>,<pwd>
:⽬标 AP 的 SSID
:密码最⻓ 64 字节 ASCII
功能:设置 ESP8266 Station 需连接的 AP。
例:
AT+CWJAP=“GTS”,“01234567”\r\n
(6)建⽴ TCP 连接
AT+CIPSTART=“TCP”,“www.beijing-time.org”,80\r\n
(7)设置传输模式,1为透传模式,0为普通传输模式
AT+CIPMODE=1\r\n
(8)发送数据
AT+CIPSEND\r\n
(9)进⼊透传模式发送数据,当输⼊单独⼀包 +++ 时,返回普通 AT 指令模式。发送 +++ 退出透传时,要⾄少间隔 1 秒再发下⼀条 AT 指令。
+++
(10)设置 UART 配置,保存到 Flash。ESP8266模块和单片机波特率一样,才能正常通信。
AT+UART=28800,8,1,0,0\r\n
(11)补充:出现问题,可以用电脑通过串口与单片机相连,发送一下字符串,看看单片机是否运行正常。
ready
WIFI CONNECTED
WIFI GOT IP
WIFI DISCONNECT
OK
3、设置三个预设的WiFi账号和密码
目的是在这三个WiFi之间切换的时候不用修改代码和烧录程序,如果连接不上,就按顺序连接这三个WiFi账号,超时了就连接下一个。这里要注意的是,发送WiFi账号、密码给ESP8266模块,如果连接不上,ESP8266模块会在大概15s后返回错误信息,在返回错误信息之前发下一个WiFi账号、密码,就算能连上也会导致连接不了WiFi,所以我的代码中设置了20s的超时时间。
4、定时器时钟的精度
我们都知道,定时器时钟的精度是比较低的,需要自行测量并进行补偿,测量和补偿方法如下。
计时器计时的时间和实际时间同步的时候(即都是整数秒),看一下计时器的计时的时间间隔和实际的时间间隔的秒数分别是多少,再通过计算修改定时器的初值。
例如,计时器计时230s,对应实际时间的233s,说明定时器计时较慢,需要减小定时器的初值,如果定时器定时1ms,即1000us,就需要将定时器定时的时间改为1000*230/233us≈987us,这样就准确了。
同时也要减小自动校时的时间间隔,我在代码中设置为了15分钟。
5、获取不了时间的处理
可能长时间之后会导致ESP8266与网络断开,如果超时获取不了,需要重启一下模块。
如果由于触碰到模块导致杜邦线接触不良而重启的话,就只是会自动连接WiFi,不会自动建立TCP连接,检测到重启需要发送相关的AT指令重新连接网络。
这些都要在程序中进行处理。
三、各模块代码
1、延时函数
h文件
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
c文件
/**
* @brief 延时函数,延时xms毫秒
* @param xms 延时的时间,范围:0~65535
* @retval 无
*/
void Delay(unsigned int xms) //@11.0592MHz,6T(双倍速)模式
{
unsigned char i,j;
while(xms)
{
i=4;
j=146;
do
{
while(--j);
} while(--i);
xms--;
}
}
2、定时器0
h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
c文件
#include <REGX52.H>
/**
* @brief 定时器0初始化
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)
TMOD|=0x01; //设置定时器模式(通过低四位设为“定时器0工作方式1”的模式)
TL0=0x66; //设置定时初值,定时1ms,晶振@11.0592MHz
TH0=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHz
TF0=0; //清除TF0标志
TR0=1; //定时器0开始计时
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
PT0=0; //当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned int T0Count; //定义静态变量
TL0=0x66; //设置定时初值,定时1ms,晶振@11.0592MHz
TH0=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHz
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
3、串口
h文件
#ifndef __UART_H__
#define __UART_H__
void UART_Init();
void UART_SendByte(unsigned char Byte);
void UART_SendString(char *String);
#endif
c文件
#include <REGX52.H>
/**
* @brief 串口初始化,115200bps@11.0592MHz,误差:0.00%
* @param 无
* @retval 无
*/
void Uart_Init(void)
{
PCON|=0x80; //使能波特率倍速位SMOD,倍速后为115200bps
SCON =0x50; //8位数据,可变波特率
// AUXR&=0xBF; //定时器时钟12T模式(89C52芯片无需设置这个)
// AUXR&=0xFE; //串口1选择定时器1为波特率发生器(89C52芯片无需设置这个)
TMOD&=0x0F; //设置定时器模式
TMOD|=0x20; //设置定时器模式
TL1=0xFF; //设置定时初始值
TH1=0xFF; //设置定时重载值
ET1=0; //禁止定时器1中断
TR1=1; //定时器1开始计时
EA=1; //开启所有中断
ES=1; //开启串口中断
PS=1; //要设置串口中断的优先级比定时器的高,
//否则发送或接收数据的时候会被打断,影响数据发送和接收
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte;
while(TI==0);
TI=0;
}
/**
* @brief 串口发送字符串
* @param String 要发送的字符串
* @retval 无
*/
void UART_SendString(char *String)
{
while(*String)
{
UART_SendByte(*String);
String++;
}
}
/*串口中断函数模板
void UART_Routine() interrupt 4
{
if(RI==1)
{
RI=0;
}
}
*/
4、数码管扫描
h文件
#ifndef __NIXIELOOP_H__
#define __NIXIELOOP_H__
void Nixie(unsigned char Location,unsigned char Number);
void Nixie_Clear(void);
void Nixie_Tick(void);
#endif
c文件
#include <REGX52.H>
sbit _74HC138_A=P2^2;
sbit _74HC138_B=P2^3;
sbit _74HC138_C=P2^4;
#define Nixie_SegPort P0 //段码端口
//数码管显示缓存区
unsigned char Nixie_Buffer[8]={34,34,34,34,34,34,34,34,}; //默认无显示
//共阴数码管段码表
unsigned char code Nixie_SegCode[]={
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07, //0~7
0x7F,0x6F,0x77,0x7c,0x39,0x5e,0x79,0x71, //8~F
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87, //0~7(带小数点)
0xFF,0xEF,0xF7,0xFc,0xB9,0xDe,0xF9,0xF1, //8~F(带小数点)
0x40,0xC0,0x00, //“-”、“-”(带小数点)、不显示
};
/**
* @brief 设置显示缓存区
* @param Location 要设置的位置,范围:1~8
* @param Number 要设置的数字,范围:段码表索引范围
* @retval 无
*/
void Nixie(unsigned char Location,unsigned char Number)
{
Nixie_Buffer[Location-1]=Number;
}
/**
* @brief 输出段码和位码
* @param Location 数码管显示的位置,范围:1~8
* @param Number 数码管显示的数字,范围:0~34,内容:0~F、0~F(带小数点)、-、-(带小数点)、无显示
* @retval
*/
void Nixie_Scan(unsigned char Location,Number)
{
Nixie_SegPort=0x00; //段码清零,消影
switch(Location) //位码输出
{
case 1:_74HC138_C=1;_74HC138_B=1;_74HC138_A=1;break;
case 2:_74HC138_C=1;_74HC138_B=1;_74HC138_A=0;break;
case 3:_74HC138_C=1;_74HC138_B=0;_74HC138_A=1;break;
case 4:_74HC138_C=1;_74HC138_B=0;_74HC138_A=0;break;
case 5:_74HC138_C=0;_74HC138_B=1;_74HC138_A=1;break;
case 6:_74HC138_C=0;_74HC138_B=1;_74HC138_A=0;break;
case 7:_74HC138_C=0;_74HC138_B=0;_74HC138_A=1;break;
case 8:_74HC138_C=0;_74HC138_B=0;_74HC138_A=0;break;
}
Nixie_SegPort=Nixie_SegCode[Number]; //段码输出
}
/**
* @brief 数码管清空显示
* @param 无
* @retval 无
*/
void Nixie_Clear(void)
{
unsigned char i=0;
for(i=0;i<8;i++){Nixie_Buffer[i]=34;}
}
/**
* @brief 数码管驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Nixie_Tick(void)
{
static unsigned char i=0;
Nixie_Scan(i+1,Nixie_Buffer[i]);
i++;
i%=8;
}
5、独立按键扫描
h文件
#ifndef __KEYSCAN_H__
#define __KEYSCAN_H__
unsigned char Key(void);
void Key_Tick(void);
#endif
c文件
#include <REGX52.H>
sbit Key1=P3^1;
sbit Key2=P3^0;
sbit Key3=P3^2;
sbit Key4=P3^3;
unsigned char KeyNumber;
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围:0,1~12,0表示无按键按下
*/
unsigned char Key(void)
{
unsigned char KeyTemp=0;
KeyTemp=KeyNumber;
KeyNumber=0;
return KeyTemp;
} //主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0
/**
* @brief 获取当前按键的状态,无消抖及松手检测
* @param 无
* @retval 按下的按键,范围:0~4,无按键按下时返回值为0
*/
unsigned char Key_GetState()
{
unsigned char KeyValue=0;
if(Key1==0){KeyValue=1;}
if(Key2==0){KeyValue=2;}
if(Key3==0){KeyValue=3;}
if(Key4==0){KeyValue=4;}
return KeyValue;
}
/**
* @brief 按键驱动函数,在中断中调用
* @param 无
* @retval 无
*/
void Key_Tick(void)
{
static unsigned char NowState,LastState;
// static unsigned int KeyCount;
LastState=NowState; //按键状态更新
NowState=Key_GetState(); //获取当前按键状态
//如果上个时间点按键未按下,这个时间点按键按下,则是按下瞬间
if(LastState==0)
{
switch(NowState)
{
case 1:KeyNumber=1;break;
case 2:KeyNumber=2;break;
case 3:KeyNumber=3;break;
case 4:KeyNumber=4;break;
default:break;
}
}
// //如果上个时间点按键按下,这个时间点按键按下,则是一直按住按键
// if(LastState && NowState)
// {
// KeyCount++;
// if(KeyCount>=20) //按下超过200ms才被检测为长按(如果定时器定时10ms的话)
// {
// if(LastState==1 && NowState==1){KeyNumber=5;}
// if(LastState==2 && NowState==2){KeyNumber=6;}
// if(LastState==3 && NowState==3){KeyNumber=7;}
// if(LastState==4 && NowState==4){KeyNumber=8;}
// }
// }
// else
// {
// KeyCount=0;
// }
// //如果上个时间点按键按下,这个时间点按键未按下,则是松手瞬间
// if(NowState==0)
// {
// switch(LastState)
// {
// case 1:KeyNumber=9;break;
// case 2:KeyNumber=10;break;
// case 3:KeyNumber=11;break;
// case 4:KeyNumber=12;break;
// default:break;
// }
// }
}
四、主函数
main.c
/*
by甘腾胜@20250117
效果展示:可以在B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:6T@11.0592MHz
波特率:115200bps
外设:八位数码管、ESP8266(01S)模块、独立按键
注意:
(1)ESP8266供电电压为3.3V,接5V会发热严重,RX和TX要交叉连接
(2)此版本不用更改ESP8266模块的默认波特率115200bps,但下载的时候需要勾选“使能6T(双倍速)模式”
(3)无需设置ESP8266模块默认的WiFi模式(AP模式),超时20s,运行时程序会自动设置为STA模式
(4)每隔15min会自动联网校时(如果校时超时(20s),会重启ESP8266模块,重新连接WiFi和网络)
(5)串口中断的优先级要比定时器0的高,否则会影响通信
(6)代码末尾有月份、星期的英文,以及网站返回的时间数据的样例
(7)计时不准的话,可以自行测量并修改定时器0所赋的初值进行补偿
强调:下载的时候需要勾选“使能6T(双倍速)模式”
操作说明:
K1 K2 K3 K4
【K3】切换显示时分秒和年月日星期
【K4】手动联网校时
有什么问题可以在B站私信告知我,或者发邮件到邮箱:gantengsheng@qq.com
*/
#include <REGX52.H> //包含头文件
#include "Delay.h"
#include "UART.h"
#include "Timer0.h"
#include "KeyScan.h"
#include "NixieScan.h"
//预设三个WiFi账号,如果连接不上,超时20s会连接下一个,连接WiFi成功(账号和密码保存到了Flash)后,下次上电自动连接
/*例:(设置第一个预设账号)
如果WiFi账号是:abc
如果WiFi密码是:12345678
则应改成:char code WiFi1[]="AT+CWJAP=\"abc\",\"12345678\"\r\n";
*/
char code WiFi1[]="AT+CWJAP=\"ganpan\",\"01234567\"\r\n"; //发送的字符串中如果有双引号,需要用反斜杠转义
char code WiFi2[]="AT+CWJAP=\"gantengsheng\",\"01234567\"\r\n";
char code WiFi3[]="AT+CWJAP=\"GTS\",\"01234567\"\r\n";
unsigned char KeyNum; //存储获得的键码值
char Judge[5]; //用来判断是不是我们想要保存的字符串
char TimeBuffer[25]; //用来存储接收到的时间的字符型的信息
bit OKFlag=0; //接收到了ESP8266返回的OK文本的标志,1:接收到了,0:未接收到
bit ReadyFlag=0; //ESP8266准备好了的标志,1:准备好了,0:未准备好
bit WiFiGotIPFlag=0; //ESP8266连接WiFi且获取了IP的标志,1:已获取IP,0:未获取IP
bit WiFiDisconnectFlag=0; //未能连接WiFi的标志,1:未能连接,0:无
bit GetTimeFlag=0; //从网络获取时间的标志,1:获取,0:不获取
bit GotTimeFlag=0; //从网络获取了时间的标志,1:已获取,0:未获取到
char Time[7]; //存储接收到的字符型的时间数据转化之后的十进制数据,索引0~6分别对应年、月、日、时、分、秒、星期
bit ShowGotTimeFlag; //显示表示获取了网络时间的符号(“-”右下角加多一点)的标志,1:显示,0:不显示
unsigned int T0Count0,T0Count1,T0Count2,T0Count3,T0Count4,T0Count5; //定时器计数的变量
unsigned char ProofTimeCount; //定时器中隔一段时间自动校时的计数
bit TimeOutFlag; //连接WiFi超时的标志,1:超时,2:未超时
bit ShowDateFlag; //显示日期的标志,1:显示,2:不显示
bit TimeOutCountFlag=1; //启动超时计数的标志,1:启动,2:不启动
/**
* @brief ESP8266初始化
* @param 无
* @retval 无
*/
void ESP8266_Init(void)
{
Nixie_Clear(); //数码管清屏
Nixie(1,1); //第一位数码管显示“1”,表示等待ESP8266准备好
Delay(100); //适当延时,延时0.1s
//退出透传模式(如果ESP8266不断电,只让单片机复位的话,ESP8266就还处于透传模式,发送AT指令会无效)
UART_SendString("+++");
Delay(1000); //退出透传模式要1s之后才能发AT指令
if(ReadyFlag) //如果上电ESP8266直接返回“ready”
{
ReadyFlag=0;
Nixie(1,17); //第一位数码管显示“1.”,表示ESP8266已准备好
Delay(500);
}
else //如果不返回“ready”,则重启一下ESP8266模块
{
UART_SendString("AT+RST\r\n"); //复位
while(!ReadyFlag); //等待ESP8266返回"ready"
ReadyFlag=0;
Nixie(1,17); //第一位数码管显示“1.”,表示ESP8266已准备好
Delay(500);
}
Nixie(1,2); //第一位数码管显示“2”,表示等待ESP8266连接WiFi
//WiFi账号密码保存在ESP8266的Flash中,掉电不丢失
//如果上次已经成功连接,则上电自动按上次的网络名称和密码连接
T0Count3=0; //超时的计数清零
TimeOutFlag=0; //超时的标志清零
while(!WiFiGotIPFlag && !WiFiDisconnectFlag && !TimeOutFlag);
if(TimeOutFlag) //如果是因为超时退出上面的while循环,说明ESP8266处于AP模式
{
UART_SendString("AT+CWMODE=1\r\n"); //发送AT指令设置为STA(Station)模式
while(!OKFlag); //等待ESP8266返回“OK”
OKFlag=0;
}
if(WiFiGotIPFlag) //如果成功获取了IP
{
WiFiGotIPFlag=0;
Nixie(1,18); //第一位数码管显示“2.”,表示ESP8266已连接WiFi,并获取了IP
Delay(500);
}
else //如果WiFi不能连接
{ //WiFi账号密码是保存到ESP8266的flash里的,如果在else中连接成功,
//下次上电就可以直接连接WiFi并获得IP,就不会进入此else中
WiFiDisconnectFlag=0;
Nixie(2,0); //第二位数码管显示“0”,表示ESP8266不能连接WiFi
Delay(500);
Nixie(2,1); //第二位数码管显示“1”,表示ESP8266正在连接第一个预设的WiFi账号
T0Count3=0; //超时的计数清零
TimeOutFlag=0; //超时的标志清零
//如果WiFi连接不成功,就按下面的账号密码进行连接
UART_SendString(WiFi1);
while(!OKFlag && !WiFiGotIPFlag && !TimeOutFlag);
OKFlag=0;
WiFiGotIPFlag=0;
if(TimeOutFlag) //如果是因为超时退出上面的while循环,说明第一个预设的WiFi账号没连上
{
Nixie(2,2); //第二位数码管显示“2”,表示ESP8266正在连接第二个预设的WiFi账号
T0Count3=0; //超时的计数清零
TimeOutFlag=0; //超时的标志清零
//如果上面的WiFi连接不成功,超时(20s)了,就会尝试下面的账号密码进行连接
//超时的时间不能少于15s,否则会导致连接不成功
//如果上面的WiFi连接成功,就不会连接下面的WiFi账号
UART_SendString(WiFi2);
while(!OKFlag && !WiFiGotIPFlag && !TimeOutFlag);
OKFlag=0;
WiFiGotIPFlag=0;
if(TimeOutFlag) //如果是因为超时退出上面的while循环,说明第二个预设的WiFi账号没连上
{
Nixie(2,3); //第二位数码管显示“3”,表示ESP8266正在连接第三个预设的WiFi账号
//如果上面的WiFi连接不成功,超时(20s)了,就会尝试下面的账号密码进行连接
//如果上面的WiFi连接成功,就不会连接下面的WiFi账号
UART_SendString(WiFi3);
while(!OKFlag && !WiFiGotIPFlag); //如果第三个WiFi账号连接不上,就会在此处陷入死循环
OKFlag=0;
WiFiGotIPFlag=0;
}
}
Nixie(2,34); //第二位数码管清空显示
Nixie(1,18); //第一位数码管显示“2.”,表示ESP8266已连接WiFi,并获取了IP
//下面的处理不能少,如果是第一次连上WiFi,还会多返回一个“OK”(暂时未知原因,有大佬知道原因的,请私信告知我一下,谢谢!)
//如果不处理,会导致第一次连上WiFi后程序卡在数码管显示“5”
Delay(1500); //延时1.5s
OKFlag=0; //延时不能少于1s,要等ESP8266返回OK令OKFlag置1之后,再将OKFlag置零
}
Nixie(1,3); //第一位数码管显示“3”,表示ESP8266开始建立TCP连接
UART_SendString("AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n"); //建立TCP连接
while(!OKFlag);
OKFlag=0;
Nixie(1,19); //第一位数码管显示“3.”,表示ESP8266已建立TCP连接
Delay(500);
Nixie(1,4); //第一位数码管显示“4”,表示ESP8266开始设置传输模式
UART_SendString("AT+CIPMODE=1\r\n"); //设置传输模式(0为普通传输模式,1为透传模式)
while(!OKFlag);
OKFlag=0;
Nixie(1,20); //第一位数码管显示“4.”,表示ESP8266已经设置传输模式为透传模式
Delay(500);
Nixie(1,5); //第一位数码管显示“5”,表示ESP8266开始发送数据
//开始发送数据(在透传模式时),当输入单独一包 +++ 时,返回普通 AT 指令模式,要至少间隔 1 秒再发下一条 AT 指令
UART_SendString("AT+CIPSEND\r\n");
while(!OKFlag);
OKFlag=0;
Nixie(1,21); //第一位数码管显示“5.”,表示ESP8266已经准备好了,可以发送数据了
Delay(500);
}
/**
* @brief 将接收到的时间数据(字符型)转换为十进制的数据,保存到时间数组Time中
* @param 无
* @retval 无
*/
void ConvertTime(void)
{
Time[0]=(TimeBuffer[14]-'0')*10+(TimeBuffer[15]-'0'); //年
if(TimeBuffer[8]=='J' && TimeBuffer[9]=='a'){Time[1]=1;} //月
else if(TimeBuffer[8]=='F'){Time[1]=2;}
else if(TimeBuffer[8]=='M' && TimeBuffer[9]=='a' && TimeBuffer[10]=='r'){Time[1]=3;}
else if(TimeBuffer[8]=='A' && TimeBuffer[9]=='p'){Time[1]=4;}
else if(TimeBuffer[8]=='M' && TimeBuffer[9]=='a' && TimeBuffer[10]=='y'){Time[1]=5;}
else if(TimeBuffer[8]=='J' && TimeBuffer[9]=='u' && TimeBuffer[10]=='n'){Time[1]=6;}
else if(TimeBuffer[8]=='J' && TimeBuffer[9]=='u' && TimeBuffer[10]=='l'){Time[1]=7;}
else if(TimeBuffer[8]=='A' && TimeBuffer[9]=='u'){Time[1]=8;}
else if(TimeBuffer[8]=='S'){Time[1]=9;}
else if(TimeBuffer[8]=='O'){Time[1]=10;}
else if(TimeBuffer[8]=='N'){Time[1]=11;}
else if(TimeBuffer[8]=='D'){Time[1]=12;}
Time[2]=(TimeBuffer[5]-'0')*10+(TimeBuffer[6]-'0'); //日
Time[3]=(TimeBuffer[17]-'0')*10+(TimeBuffer[18]-'0'); //时
Time[4]=(TimeBuffer[20]-'0')*10+(TimeBuffer[21]-'0'); //分
Time[5]=(TimeBuffer[23]-'0')*10+(TimeBuffer[24]-'0'); //秒
if(TimeBuffer[0]=='M'){Time[6]=1;} //星期
else if(TimeBuffer[0]=='T' && TimeBuffer[1]=='u'){Time[6]=2;}
else if(TimeBuffer[0]=='W'){Time[6]=3;}
else if(TimeBuffer[0]=='T' && TimeBuffer[1]=='h'){Time[6]=4;}
else if(TimeBuffer[0]=='F'){Time[6]=5;}
else if(TimeBuffer[0]=='S' && TimeBuffer[1]=='a'){Time[6]=6;}
else if(TimeBuffer[0]=='S' && TimeBuffer[1]=='u'){Time[6]=7;}
//返回的是GMT,北京时间比格林威治时间(Greenwich Mean Time简称GMT)早8小时。
Time[3]+=8; //UTC/GMT +8.00 (东八区)
if(Time[3]/24) //如果加8小时后是第二天
{
Time[3]%=24;
Time[6]++; //星期增加
if(Time[6]>7){Time[6]=1;}
Time[2]++;
if(Time[2]>=32) //大月
{
Time[2]=1;
Time[1]++;
if(Time[1]>12)
{
Time[1]=1;
Time[0]++;
Time[0]%=100;
}
}
else if(Time[2]==31) //小月
{
if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11)
{
Time[2]=1;
Time[1]++;
}
}
else if(Time[2]==30) //闰年二月
{
if(Time[1]==2 && Time[0]%4==0)
{
Time[2]=1;
Time[1]++;
}
}
else if(Time[2]==29) //平年二月
{
if(Time[1]==2 && Time[0]%4)
{
Time[2]=1;
Time[1]++;
}
}
}
/*由于网络延迟、数据的处理等,导致处理后的时间慢一两秒,这里进行补偿,加多2秒*/
if(Time[3]<23 || Time[4]<59 || Time[5]<58) //如果加多2秒不会跳到第二天
{
Time[5]+=2;
if(Time[5]>=60)
{
Time[5]%=60;
Time[4]++;
if(Time[4]>=60)
{
Time[4]%=60;
Time[3]++;
}
}
}
}
/**
* @brief 检查时间,看是否越界
* @param 无
* @retval 无
*/
void CheckTime(void)
{
if(Time[5]>=60)
{
Time[5]=0;
Time[4]++;
if(Time[4]>=60)
{
Time[4]=0;
Time[3]++;
if(Time[3]>=24)
{
Time[3]=0;
Time[2]++;
if( Time[2]>=32 && (Time[1]==1 || Time[1]==3 || Time[1]==5 ||
Time[1]==7 || Time[1]==8 || Time[1]==10 || Time[1]==12) ) //大月
{
Time[2]=1;
Time[1]++;
}
if( Time[2]>=31 && (Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11) ) //小月
{
Time[2]=1;
Time[1]++;
}
if( Time[2]>=30 && !(Time[0]%4) ) //闰年二月
{
Time[2]=1;
Time[1]++;
}
if( Time[2]>=29 && (Time[0]%4) ) //平年二月
{
Time[2]=1;
Time[1]++;
}
if(Time[1]>=13)
{
Time[1]=1;
Time[0]++;
Time[0]%=100;
}
}
}
}
}
/**
* @brief 更新显示时间
* @param 无
* @retval 无
*/
void ShowTime(void)
{
if(ShowDateFlag) //显示:年月日星期
{
Nixie(1,Time[0]/10); //年
Nixie(2,Time[0]%10+16); //年
Nixie(3,Time[1]/10); //月
Nixie(4,Time[1]%10+16); //月
Nixie(5,Time[2]/10); //日
Nixie(6,Time[2]%10); //日
Nixie(7,34); //无显示
Nixie(8,Time[6]); //星期
}
else //显示:时分秒
{
Nixie(1,Time[3]/10); //时
Nixie(2,Time[3]%10); //时
Nixie(4,Time[4]/10); //分
Nixie(5,Time[4]%10); //分
Nixie(7,Time[5]/10); //秒
Nixie(8,Time[5]%10); //秒
if(ShowGotTimeFlag) //显示“-.”,表示已成功从网络获取了时间
{
Nixie(3,33);
Nixie(6,33);
}
else //显示“-”
{
Nixie(3,32);
Nixie(6,32);
}
}
}
void main()
{
P2_5=0; //防止开发板的蜂鸣器发出声音
Timer0_Init(); //定时器0初始化
UART_Init(); //串口初始化
ESP8266_Init(); //ESP8266初始化
WiFiGotIPFlag=0; //ESP8266初始化的时候,回显信息会让WiFiGotIPFlag置1
TimeOutCountFlag=0; //TimeOutCountFlag置0,不进行超时的计时
TimeOutFlag=0; //超时标志清零
UART_SendString("T\r\n"); //进入主循环前,先获取一次时间,防止刚进主循环时显示异常
while(!GotTimeFlag);
GotTimeFlag=0;
ConvertTime();
ShowGotTimeFlag=1; //成功校时后,显示“-.”,2s
T0Count4=0;
T0Count2=0;
ProofTimeCount=0; //每次校对时间后,用于自动校时的计数清0
Nixie_Clear(); //数码管清空显示
while(1)
{
KeyNum=Key(); //获取键码值
if(KeyNum) //如果有按键按下
{
if(KeyNum==3) //如果按下按K3
{
ShowDateFlag=!ShowDateFlag; //切换时分秒和年月日星期的显示
}
if(KeyNum==4) //如果按下按K4
{
GetTimeFlag=1; //手动校时
}
}
if(WiFiGotIPFlag) //如果不小心触碰到ESP8266模块,导致接触不良而重启模块的话,获取IP后重新连接网络
{
Nixie_Clear();
TimeOutCountFlag=1; //启动超时的计时(防止出错卡在while循环)
Nixie(1,3); //第一位数码管显示“3”,表示ESP8266开始建立TCP连接
UART_SendString("AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n"); //建立TCP连接
while(!OKFlag && !TimeOutFlag);
OKFlag=0;
TimeOutFlag=0;
Nixie(1,19); //第一位数码管显示“3.”,表示ESP8266已建立TCP连接
Delay(500);
Nixie(1,4); //第一位数码管显示“4”,表示ESP8266开始设置传输模式
UART_SendString("AT+CIPMODE=1\r\n"); //设置传输模式(0为普通模式,1为透传模式)
while(!OKFlag && !TimeOutFlag);
OKFlag=0;
TimeOutFlag=0;
Nixie(1,20); //第一位数码管显示“4.”,表示ESP8266已经设置传输模式为透传模式
Delay(500);
Nixie(1,5); //第一位数码管显示“5”,表示ESP8266开始发送数据
UART_SendString("AT+CIPSEND\r\n"); //开始发送数据(在透传模式时)
while(!OKFlag && !TimeOutFlag);
OKFlag=0;
TimeOutFlag=0;
Nixie(1,21); //第一位数码管显示“5.”,表示ESP8266已经准备好了,可以发送数据了
Delay(500);
WiFiGotIPFlag=0; //要放在最后,否则回显信息又会让WiFiGotIPFlag置1
TimeOutCountFlag=0; //停止超时的计时
}
if(GetTimeFlag) //从网络获取时间
{
TimeOutFlag=0; //超时的标志清零
TimeOutCountFlag=1; //启动超时的计时(如果获取时间超时,则重启ESP8266模块)
GetTimeFlag=0;
//透传模式下,向“www.beijing-time.org”随便发送点什么,就会返回时间信息
UART_SendString("T\r\n");
}
if(TimeOutFlag) //如果获取时间超时了(可能是ESP8266没接收到指令或者网络断开了)
{
TimeOutFlag=0; //超时的标志清零
TimeOutCountFlag=0; //停止超时的计时
UART_SendString("+++"); //退出透传模式
Delay(1000); //退出透传模式要1s后才能发AT指令
UART_SendString("AT+RST\r\n"); //重启一下模块
}
if(GotTimeFlag) //获取了时间
{
TimeOutFlag=0; //超时的标志清零
TimeOutCountFlag=0; //停止超时的计时
GotTimeFlag=0;
ConvertTime();
ShowGotTimeFlag=1; //成功校时后,显示“-.”,2s
T0Count4=0;
T0Count2=0;
ProofTimeCount=0; //每次校对时间后,用于自动校时的计数清0
}
CheckTime(); //检查时间
ShowTime(); //更新显示时间
}
}
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
//因使能了6T(双倍速)模式,所以定时器计算器中12T模式定时2ms对应的是6T模式的1ms
TL0=0xF0; //设置定时初值,定时1ms(补偿:改为了981us),晶振@11.0592MHz
TH0=0xF8; //设置定时初值,定时1ms(补偿:改为了981us),晶振@11.0592MHz
//实测定时器计时260s,实际时间为265s,所以将原来的定时1000us改为定时1000*260/265us(约等于981us)
//补偿后,计时1000s与实际的1000s相差不到1s
T0Count0++;
if(T0Count0>=1000) //定时器计时,每隔1s,Time[5]加1
{
T0Count0=0;
Time[5]++;
}
T0Count1++;
T0Count2++;
if(TimeOutCountFlag){T0Count3++;} //TimeOutCountFlag为1才开始超时的计时
else{T0Count3=0;}
T0Count4++;
T0Count5++;
if(T0Count1>=20) //每隔20ms检测一次按键
{
T0Count1=0;
Key_Tick();
}
if(T0Count2>=60000) //1min,即60s
{
T0Count2=0;
ProofTimeCount++;
ProofTimeCount%=15; //1min*15=15min,每隔15分钟自动联网校时
if(!ProofTimeCount){GetTimeFlag=1;}
}
if(T0Count3>=20000) //ESP8266连接WiFi的超时时间:20s
{
T0Count3=0;
TimeOutFlag=1;
}
if(T0Count4>=2000) //如果从网络获取了时间,显示“-.”2秒钟
{
T0Count4=0;
ShowGotTimeFlag=0;
}
if(T0Count5>=1) //数码管扫描的时间间隔为1ms
{
T0Count5=0;
Nixie_Tick();
}
}
void UART_Routine() interrupt 4 //串口中断函数
{
static unsigned char i,j;
char TempChar; //缓存变量
static bit ReceiveTimeFlag=0; //开始保存时间数据的标志,1:开始保存,0:不保存
if(RI==1) //如果接收标志位为1,接收到了数据
{
RI=0; //接收标志位清0
TempChar=SBUF; //用缓存变量取出SBUF的数据
//如果接收到的字符是下面四个之一,则从数组Judge的索引0的位置开始保存接下来的字符
if(TempChar=='O' || TempChar=='D' || TempChar=='r' || TempChar=='I'){i=0;}
//如果不小心触碰到ESP8266模块,导致接触不良而重启模块的话,获取IP后令WiFiGotIPFlag置1,再在主函数中重新连接网络
//返回的时间数据里有“PI”,会误使WiFiGotIPFlag置1,所以要有下面的处理
if(TempChar=='I'){Judge[1]='\0';}
Judge[i]=TempChar;
i++;
if(ReceiveTimeFlag) //开始接收包含时间信息的字符串
{
j++;
if(j>=4){TimeBuffer[j-4]=TempChar;}
if(j>=28){ReceiveTimeFlag=0;GotTimeFlag=1;}
}
//接收到“ready”,注意,下面的if中Judge[1]和Judge[2]的字符不能和Judge[0]的重复
if(Judge[0]=='r' && Judge[1]=='e' && Judge[2]=='a'){Judge[1]='\0';ReadyFlag=1;}
//接收到“DISCONNECT”
if(Judge[0]=='I' && Judge[1]=='S' && Judge[2]=='C'){Judge[1]='\0';WiFiDisconnectFlag=1;}
//接收到“GOT IP”
if(Judge[0]=='I' && Judge[1]=='P'){Judge[1]='\0';WiFiGotIPFlag=1;}
//接收到“OK”
if(Judge[0]=='O' && Judge[1]=='K'){Judge[1]='\0';OKFlag=1;}
//接收到“Date: ”,说明接下来的字符串包含时间信息
if(Judge[0]=='D' && Judge[1]=='a' && Judge[2]=='t'){Judge[1]='\0';ReceiveTimeFlag=1;j=0;}
i%=5; //Judge数组只有5个数据
}
}
/*月份和星期
January(一月)
February(二月)
March(三月)
April(四月)
May(五月)
June(六月)
July(七月)
August(八月)
September(九月)
October(十月)
November(十一月)
December(十二月)
Monday(星期一)
Tuesday(星期二)
Wednesday(星期三)
Thursday(星期四)
Friday(星期五)
Saturday(星期六)
Sunday(星期日)
*/
/*网站返回的时间数据(第四行)
HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 13 Jan 2025 08:27:07 GMT
Connection: close
Content-Length: 326
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Bad Request</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Bad Request - Invalid Verb</h2>
<hr><p>HTTP Error 400. The request verb is invalid.</p>
</BODY></HTML>
*/
总结
ESP8266模块的使用还是比较简单的,熟悉相关AT指令的功能,通过单片机发给ESP8266模块就行了。