基于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.0592MHzTH0=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHzTF0=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.0592MHzTH0=0xFC; //设置定时初值,定时1ms,晶振@11.0592MHzT0Count++;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,倍速后为115200bpsSCON =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,并获取了IPDelay(500);}else //如果WiFi不能连接{ //WiFi账号密码是保存到ESP8266的flash里的,如果在else中连接成功,//下次上电就可以直接连接WiFi并获得IP,就不会进入此else中WiFiDisconnectFlag=0;Nixie(2,0); //第二位数码管显示“0”,表示ESP8266不能连接WiFiDelay(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.5sOKFlag=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置1TimeOutCountFlag=0; //TimeOutCountFlag置0,不进行超时的计时TimeOutFlag=0; //超时标志清零UART_SendString("T\r\n"); //进入主循环前,先获取一次时间,防止刚进主循环时显示异常while(!GotTimeFlag);GotTimeFlag=0;ConvertTime();ShowGotTimeFlag=1; //成功校时后,显示“-.”,2sT0Count4=0;T0Count2=0;ProofTimeCount=0; //每次校对时间后,用于自动校时的计数清0Nixie_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置1TimeOutCountFlag=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; //成功校时后,显示“-.”,2sT0Count4=0;T0Count2=0;ProofTimeCount=0; //每次校对时间后,用于自动校时的计数清0}CheckTime(); //检查时间ShowTime(); //更新显示时间}
}void Timer0_Routine() interrupt 1 //定时器0中断函数
{//因使能了6T(双倍速)模式,所以定时器计算器中12T模式定时2ms对应的是6T模式的1msTL0=0xF0; //设置定时初值,定时1ms(补偿:改为了981us),晶振@11.0592MHzTH0=0xF8; //设置定时初值,定时1ms(补偿:改为了981us),晶振@11.0592MHz//实测定时器计时260s,实际时间为265s,所以将原来的定时1000us改为定时1000*260/265us(约等于981us)//补偿后,计时1000s与实际的1000s相差不到1sT0Count0++;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; //接收标志位清0TempChar=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模块就行了。
相关文章:

基于51单片机和ESP8266(01S)、八位数码管、独立按键的WiFi定时器时钟
目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、延时函数2、定时器03、串口4、数码管扫描5、独立按键扫描 四、主函数总结 系列文章目录 前言 有三个版本: ①普中开发板版本1:28800bps11.0592MHz,12T ②普中开发板版本2&am…...

Androidstudio 中,project下的.gitignore和module下的.gitignore有什么区别,生效优先级是什么
在 Android Studio 项目中,project 根目录下的 .gitignore 文件和 module 目录下的 .gitignore 文件作用和生效优先级是不同的,理解它们之间的区别非常重要,可以避免不必要的提交和冲突。 1. project 根目录下的 .gitignore: 作…...

python学习笔记3-字符串常用的方法
一、判断(9个): 二、查找和替换(8个) 三、⼤⼩写转换(5个) 四、⽂本对⻬(3个) 五、去除空⽩字符(3个) 六、拆分和连接 (6个࿰…...

提示词工程(Prompt Engineering)
1. Prompt 是什么? Prompt:提示词,是描述 AI 需要执行的任务的自然语言文本。 如上图所示,Prompt就是用户的提问。其实我们大家都用过Prompt,比如我们使用的ChatGPT、文心一言、豆包等AI产品时的提问就是Prompt&…...

后端开发Web
Maven Maven是apache旗下的一个开源项目,是一款用于管理和构建java项目的工具 Maven的作用 依赖管理 方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题 统一项目结构 提供标准、统一的项目结构 项目构建 标准跨平台(…...

set和map(二)详解
文章目录 mapoperator[ ]的底层operator[ ]使用的实例 multimapequal_range 两道题目题目解析算法原理代码题目解析算法原理代码 map map和set大部分都相似,只有insert插入键值对不同,insert要插入pair,pair中有key和value。erase和find只与key有关&…...

第4章:Python TDD消除重复与降低依赖实践
写在前面 这本书是我们老板推荐过的,我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后,我突然思考,对于测试开发工程师来说,什么才更有价值呢?如何让 AI 工具更好地辅助自己写代码,或许…...

【语言处理和机器学习】概述篇(基础小白入门篇)
前言 自学笔记,分享给语言学/语言教育学方向的,但对语言数据处理感兴趣但是尚未入门,却需要在论文中用到的小伙伴,欢迎大佬们补充或绕道。ps:本文不涉及公式讲解(文科生小白友好体质)ÿ…...

vue3+uniapp开发鸿蒙初体验
去年7月20号,uniapp官网就已经开始支持鸿蒙应用开发了,话不多说,按照现有规则进行配置实现一下鸿蒙开发效果; 本文基于macOS Monterey 版本 12.6.5实现 开发鸿蒙的前置准备 这里就直接说我的版本: DevEco Studio 5.…...

Android四种方式刷新View
Android四种方式刷新View 1.前言: 最近在切换主题时有个TextView是Gone的状态,切换主题后内容没有显示,于是排查代码,刚开始以为是textView没有设置内容,但是打印日志和排查发现有setText. 2.View.VISIBLE与View.GO…...

【数学建模美赛速成系列】O奖论文绘图复现代码
文章目录 引言折线图 带误差棒得折线图单个带误差棒得折线图立体饼图完整复现代码 引言 美赛的绘图是非常重要得,这篇文章给大家分享我自己复现2024年美赛O奖优秀论文得代码,基于Matalab来实现,可以直接运行出图。 折线图 % MATLAB 官方整理…...

【27】Word:徐雅雯-艺术史文章❗
目录 题目 NO1.2 NO3 NO4 NO5 NO6.7 NO8.9 NO10.11 注意:修改样式的字体颜色/字号,若中英文一致,选择所有脚本。格式相似的文本→检查多选/漏选格式刷F4重复上一步操作请❗每一步检查和保存 题目 NO1.2 F12另存为布局→行号布局…...

web端ActiveMq测试工具
如何用vue3创建简单的web端ActiveMq测试工具? 1、复用vue3模板框架 创建main.js,引入APP文件,createApp创建文件,并加载element插件,然后挂载dom节点 2、配置vue.config.js脚本配置 mport { defineConfig } from "vite&qu…...

2025年最新深度学习环境搭建:Win11+ cuDNN + CUDA + Pytorch +深度学习环境配置保姆级教程
本文目录 一、查看驱动版本1.1 查看显卡驱动1.2 显卡驱动和CUDA对应版本1.3 Pytorch和Python对应的版本1.4 Pytorch和CUDA对应的版本 二、安装CUDA三、安装cuDANN四、安装pytorch五、验证是否安装成功 一、查看驱动版本 1.1 查看显卡驱动 输入命令nvidia-smi可以查看对应的驱…...

FPGA中场战事
2023年10月3日,英特尔宣布由桑德拉里维拉(Sandra Rivera)担任“分拆”后独立运营的可编程事业部首席执行官。 从数据中心和人工智能(DCAI)部门总经理,转身为执掌该业务的CEO,对她取得像AMD掌门人苏姿丰博士类似的成功,无疑抱以厚望。 十年前,英特尔花费167亿美元真金白银…...

[Computer Vision]实验二:图像特征点提取
目录 一、实验内容 二、实验过程及结果 2.1 Harris角点检测 2.2 SIFT算法 三、实验小结 一、实验内容 采用Harris与SIFT分别提取特征点及对应的描述子,对比两者的区别(特征点数量、分布、描述子维度、图像变化对二者的影响等)利用特征匹…...

TCP状态转移图详解
状态 描述 LISTEN represents waiting for a connection request from any remote TCP and port. SYN-SENT represents waiting for a matching connection request after having sent a connection request. SYN-RECEIVED represents waiting for a confirming connect…...

curl简介与libcurl开源库的使用总结
curl工具和libcurl不是同一个东西,二者的关系主要体现在以下方面: 定义与性质 curl工具: 是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持多种协议,如HTTP、HTTPS、FTP、FTPS等,可用…...

Win10系统部署RabbitMQ Server
文章目录 版本说明依赖安装添加Erlang环境变量验证Erlang安装 RabbitMQ Server安装解压启动查看RabbitMQ插件安装rabbitmq_management插件再次启动设置RabbitMQ为系统服务 版本说明 ErlangRabbitMQ27.24.0.5 可以在Erlang官网和RabbitMQ官网下载安装包,安装已下载…...

uniapp APP端页面触发调用webview(页面为uniapp开发的H5)里的方法
原理: 使用 getCurrentInstance() 获取当前组件的 Vue 实例,通过 instance.proxy.$scope.$getAppWebview() 获取 Uniapp 的原生 WebView 对象。 使用 WebView 提供的 evalJS 方法,执行嵌入 H5 页面内的 JavaScript 代码 <template>&l…...

嵌入式知识点总结 C/C++ 专题提升(七)-位操作
针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。 目录 1.位操作基础 2.如何求解整型数的二进制表示中1的个数 ? 3.如何求解二进制中0的个数 4.交换两个变量的值,不使用第三个变量。即a3,b5,交换之后a5,b3: 5.给定一个…...

新星杯-ESP32智能硬件开发--ESP32的I/O组成
本博文内容导读📕🎉🔥 ESP32系统的基础外设开发:IO_MUX和GPIO矩阵 IO_MUX和GPIO矩阵 ESP32的I/O组成了与外部世界交互的基础,ESP32芯片有34个物理GPIO引脚。每个引脚都可用作一个通用I/O,或者连接一个内部…...

航空航天混合动力(7)航空航天分布式电推进系统
航空航天分布式电推进系统 1.概述2.分布式电推进系统组成3.关键技术4.分布式电推进系统优势5.国内外研究情况5.1 国外5.2 国内6.分布式电推进系统应用场景6.1 航空领域6.2 航天领域tips:资料来自网上,仅供参考学习使用 1.概述 分布式推进系统是指飞行器推力由位于整个航空器…...

AIGC视频生成明星——Emu Video模型
大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video,作为Meta发布的第二款视频生成模型,在视频生成领域发挥关键作用。 🌺优质专栏回顾&am…...

Cyber Security 101-Security Solutions-Firewall Fundamentals(防火墙基础)
了解防火墙并亲身体验 Windows 和 Linux 内置防火墙。 任务1:防火墙的用途是什么 我们看到商场、银行、 餐馆和房屋。这些警卫被安置在 这些区域用于检查进出人员。这 维护此检查的目的是确保没有人在没有 被允许。这个警卫充当了他所在区域和访客之间的一堵墙。 …...

备赛蓝桥杯之第十五届职业院校组省赛第一题:智能停车系统
提示:本篇文章仅仅是作者自己目前在备赛蓝桥杯中,自己学习与刷题的学习笔记,写的不好,欢迎大家批评与建议 由于个别题目代码量与题目量偏大,请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题࿰…...

Docker核心命令与Yocto项目的高效应用
随着软件开发逐渐向分布式和容器化方向演进,Docker 已成为主流的容器化技术之一。它通过标准化的环境配置、资源隔离和高效的部署流程,大幅提高了开发和构建效率。Yocto 项目作为嵌入式 Linux 系统构建工具,与 Docker 的结合进一步增强了开发…...

idea plugin插件开发——入门级教程(IntelliJ IDEA Plugin)
手打不易,如果转摘,请注明出处! 注明原文:idea plugin插件开发——入门级教程(IntelliJ IDEA Plugin)-CSDN博客 目录 前言 官方 官方文档 代码示例 开发前必读 Intellij、Gradle、JDK 版本关系 plu…...

61,【1】BUUCTF WEB BUU XSS COURSE 11
进入靶场 左边是吐槽,右边是登录,先登录试试 admin 123456 admiin# 123456 admin"# 123456 不玩了,先去回顾下xss 回顾完就很尴尬了,我居然用SQL的知识去做xss的题 重来 吐槽这里有一个输入框,容易出现存储型…...

开发环境搭建-1:配置 WSL (类 centos 的 oracle linux 官方镜像)
一些 Linux 基本概念 个人理解,并且为了便于理解,可能会存在一些问题,如果有根本上的错误希望大家及时指出 发行版 WSL 的系统是基于特定发行版的特定版本的 Linux 发行版 有固定组织维护的、开箱就能用的 Linux 发行版由固定的团队、社…...