基于51单片机和ESP8266(01S)、LCD1602、DS1302、独立按键的WiFi时钟

目录

系列文章目录


前言

之前做了一个WiFi定时器时钟,用八位数码管进行显示,但是定时器时钟的精度较低,需要频繁校时。

这次做一个LCD1602版本的WiFi时钟,同样通过ESP8266(01S)从网络获取时间,获取时间后,将时间写入DS1302时钟芯片,每一次成功获取网络时间后,会每隔24小时自动校时(长时间之后ESP8266模块可能会与网络断开连接,但是这不影响,如果校时超时20s,会发送指令重启ESP8266模块,重新连接WiFi和网络,并校时)。不校时的时候,通过DS1302时钟芯片读取时间。

有三个版本(都是用普中A2开发板):

①八位数据接口,汉字显示星期

②八位数据接口,滚动显示时分秒

③I2C通信四位数据接口,汉字显示星期

本文代码对应的是版本②。

三个版本用到的单片机都是:STC89C52RC。

用到的外设有:ESP8266(01S)、LCD1602、DS1302、独立按键。

效果查看/操作演示:B站搜索"甘腾胜"或"gantengsheng"查看。

源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

二、原理分析

1、如何获取网络时间

ESP8266(01S)模块的使用和串口通信,可以看一下我的另一篇博客:八位数码管WiFi定时器时钟

2、如何显示汉字

LCD1602显示汉字的原理,可以看一下我的另一篇博客:LCD1602多汉字动态扫描显示

这次只需要显示一个汉字,不需要扫描显示,简单很多,只需要用到6个自定义字符就行了。

3、滚动显示时间

隔一段时间向上移动一个像素就行了,变化一个数字需要移动8个像素(因为LCD1602每个区域是5*8的点阵),代码中是隔70ms移动一个像素,隔8*70ms=560ms完成一个数字的滚动,停顿一下,再等待进行下一次的滚动显示。

4、版本③的LCD1602的I2C通信

I2C的通信协议可以看一下其他博主的介绍,这里说明一下指令的问题。需要先发一个0x02的指令设置为四线模式。因为用了6T(双倍速)模式,相当于晶振翻倍了,相当于变成了22.1184MHz,I2C通信需要加延时才行了,不然会超过PCF8574T允许的最大通信速率,导致显示不正常。

三、各模块代码

1、延时

h文件

c 复制代码
#ifndef	__DELAY_H__
#define	__DELAY_H__

void Delay(unsigned int xms);

#endif

c文件

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文件

c 复制代码
#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

c文件

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文件

c 复制代码
#ifndef __UART_H__
#define __UART_H__

void UART_Init();
void UART_SendByte(unsigned char Byte);
void UART_SendString(char *String);

#endif

c文件

c 复制代码
#include <REGX52.H>

/**
  * @brief  串口初始化,115200bps@11.0592MHz(6T模式),误差: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、DS1302

h文件

c 复制代码
#ifndef __DS1302_H__
#define __DS1302_H__

//外部可调用的时间数组,索引0~6分别对应年、月、日、时、分、秒、星期
extern char DS1302_Time[];

void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);

#endif

c文件

c 复制代码
#include <REGX52.H>

//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

#define DS1302_WP	0x8E	//写保护的地址

//DS1302写入时间的地址:年,月,日,时,分,秒,星期
unsigned char code DS1302_WriteAddress[7]={0x8c,0x88,0x86,0x84,0x82,0x80,0x8a,};
//DS1302读取时间的地址:年,月,日,时,分,秒,星期
unsigned char code DS1302_ReadAddress[7]={0x8d,0x89,0x87,0x85,0x83,0x81,0x8b,};
//时间数组:年,月,日,时,分,秒,星期
char DS1302_Time[]={25,1,10,18,12,53,5};	//时间的初始值

/**
  * @brief  DS1302初始化
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
	DS1302_CE=0;
	DS1302_SCLK=0;
}

/**
  * @brief  DS1302写一个字节
  * @param  Command 命令字/地址
  * @param  Data 要写入的数据
  * @retval 无
  */
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	for(i=0;i<8;i++)	//循环8次,每次写1位,先写低位再写高位
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;	//SCLK置1后立即置0,该时序操作需考虑时钟芯片是否可承受这个时钟的最快频率
		DS1302_SCLK=0;	//由于单片机没有这么快的频率,故可不加延时
	}
	for(i=0;i<8;i++)
	{
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;	//CLK由低到高产生一个上升沿,从而写入数据
		DS1302_SCLK=0;
	}
	DS1302_CE=0;
}

/**
  * @brief  DS1302读一个字节
  * @param  Command	命令字/地址
  * @retval Data	读出的数据
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	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;	//要先1后0,否则全都是65
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	DS1302_CE=0;
	DS1302_IO=0;	//读取后将IO设置为0,否则读出的数据会出错
	return Data;
}

/**
  * @brief  DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
  * @param  无
  * @retval 无
  */
void DS1302_SetTime(void)
{
	unsigned char i;
	DS1302_WriteByte(DS1302_WP,0x00);	//设置前关闭写保护
	for(i=0;i<7;i++)	//依次写入:年,月,日,时,分,秒,星期
	{
		DS1302_WriteByte(DS1302_WriteAddress[i],DS1302_Time[i]/10*16+DS1302_Time[i]%10);	//十进制转换为BCD码
	}
	DS1302_WriteByte(DS1302_WP,0x80);	//设置后开启写保护
}

/**
  * @brief  DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
  * @param  无
  * @retval 无
  */
void DS1302_ReadTime(void)
{
	unsigned char Temp,i;
	for(i=0;i<7;i++)	//依次读取:年,月,日,时,分,秒,星期
	{
		Temp=DS1302_ReadByte(DS1302_ReadAddress[i]);
		DS1302_Time[i]=Temp/16*10+Temp%16;//BCD码转换为十进制
	}
}

5、LCD1602

h文件

c 复制代码
#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_SetCursor(unsigned char Line,unsigned char Column);
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_Clear(void);
void LCD_MoveLeft(void);
void LCD_MoveRight(void);
void LCD_ScrollNum(unsigned char Line,unsigned char Column,unsigned char Order,unsigned char Number,unsigned char Quantity,char Offset);

#endif

c文件

c 复制代码
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//阴码(亮点为1),横向取模,高位在左
unsigned char code NumberTable[]={	//5*7数字字模(低5位)
0x0E,0x11,0x13,0x15,0x19,0x11,0x0E,0x00,	//0
0x04,0x0C,0x04,0x04,0x04,0x04,0x0E,0x00,	//1
0x0E,0x11,0x01,0x02,0x04,0x08,0x1F,0x00,	//2
0x1F,0x02,0x04,0x02,0x01,0x11,0x0E,0x00,	//3
0x02,0x06,0x0A,0x12,0x1F,0x02,0x02,0x00,	//4
0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E,0x00,	//5
0x06,0x08,0x10,0x1E,0x11,0x11,0x0E,0x00,	//6
0x1F,0x01,0x02,0x04,0x08,0x08,0x08,0x00,	//7
0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E,0x00,	//8
0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C,0x00,	//9
};

//函数定义:
/**
  * @brief  LCD1602私有延时函数,11.0592MHz(6T)调用可延时40us
  * @param  无
  * @retval 无
  */
void LCD_Delay40us(void)
{
	unsigned char i;
	i=34;
	while(--i);
}

/**
  * @brief  LCD1602延时函数,11.0592MHz(6T)调用可延时2ms
  * @param  无
  * @retval 无
  */
void LCD_Delay2ms(void)
{
	unsigned char i, j;
	i=8;
	j=40;
	do
	{
		while(--j);
	}while(--i);
}

/**
  * @brief  LCD1602写指令
  * @param  Command 要写入的指令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay40us();
	LCD_EN=0;
	LCD_Delay40us();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay40us();
	LCD_EN=0;
	LCD_Delay40us();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);	//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0C);	//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);	//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);	//光标复位,清屏
	LCD_Delay2ms();	//清屏指令执行需要较长时间,需要较长的延时
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

/**
  * @brief  LCD1602的光标复位,清屏
  * @param  无
  * @retval 无
  */
void LCD_Clear(void)
{
	LCD_WriteCommand(0x01);
	LCD_Delay2ms();
}

/**
  * @brief  LCD1602的屏幕向左移动一个字符位,光标不动
  * @param  无
  * @retval 无
  */
void LCD_MoveLeft(void)
{
	LCD_WriteCommand(0x18);
}

/**
  * @brief  LCD1602的屏幕向左移动一个字符位,光标不动
  * @param  无
  * @retval 无
  */
void LCD_MoveRight(void)
{
	LCD_WriteCommand(0x1C);
}

/**
  * @brief  LCD1602向上滚动显示数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Order 所用到的CGRAM的自定义字符的序号,范围:0~7
  * @param  Number 要显示的数字,范围:0~9
  * @param	Quantity 这一位置所显示数字的总数量,例如,秒的个位,可以显示0~9这十个数字,显示的数字的总数量为10
  * @param	Offset 滚动显示的偏移量,范围:-7~0
  * @retval 无
  */
void LCD_ScrollNum(unsigned char Line,unsigned char Column,unsigned char Order,unsigned char Number,unsigned char Quantity,char Offset)
{
	unsigned char i,j,k;

	LCD_SetCursor(Line,Column);
	LCD_WriteData(Order);

	k=8*Quantity;
	j=(8*Number+Offset+k)%k;
	
	LCD_WriteCommand(0x40+8*Order);
	for(i=0;i<8;i++)
	{
		LCD_WriteData(NumberTable[(j+i)%k]);
	}
}

6、独立按键

h文件

c 复制代码
#ifndef __KEYSCAN_H__
#define __KEYSCAN_H__

unsigned char Key(void);
void Key_Tick(void);

#endif

c文件

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;	//主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0
	return KeyTemp;
}

/**
  * @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>=10)	//按下超过200ms才被检测为长按(定时器中断函数中每隔20ms检测一次按键)
		{
			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

c 复制代码
/*

by甘腾胜@20250125
效果展示:可以在B站搜索"甘腾胜"或"gantengsheng"查看
单片机:STC89C52RC
晶振:6T@11.0592MHz
波特率:115200bps
外设:ESP8266(01S)模块、LCD1602、DS1302、独立按键
注意:
(1)ESP8266供电电压为3.3V,接5V会发热严重,RX和TX要交叉连接
(2)此版本不用更改ESP8266模块的默认波特率115200bps,但下载的时候需要勾选"使能6T(双倍速)模式"
(3)无需设置ESP8266模块默认的WiFi模式(AP模式),超时20s,运行时程序会自动设置为STA模式
(4)每隔24h会自动联网校时(如果校时超时(20s),会重启ESP8266模块,重新连接WiFi和网络并校时)
(5)串口中断的优先级要比定时器0的高,否则会影响通信
(6)代码末尾有月份、星期的英文,以及网站返回的时间数据的样例

强调1:ESP8266模块供电电压为3.3V,不能用5V
强调2:下载的时候需要勾选"使能6T(双倍速)模式"

操作说明:

	K1	K2	K3	K4	

【K4】手动联网校时

*/

#include <REGX52.H>	//包含头文件
#include "Delay.h"
#include "UART.h"
#include "Timer0.h"
#include "KeyScan.h"
#include "DS1302.h"
#include "LCD1602.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=\"wulou\",\"199019911992\"\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 ShowOKFlag;	//成功获取网络时间后显示"OK"的标志,1:显示,0:不显示
unsigned int T0Count1,T0Count2,T0Count3,T0Count4,T0Count5,T0Count6;	//定时器计数的变量
unsigned int ProofTimeCount;	//定时器中隔一段时间自动校时的计数
bit TimeOutFlag;	//连接WiFi超时的标志,1:超时,2:未超时
bit TimeOutCountFlag=1;	//启动超时计数的标志,1:启动,2:不启动
bit ReadTimeFlag=1;	//从DS1302时钟芯片读取时间的标志,1:读取,2:不读取
char Offset1,Offset2,Offset3,Offset4,Offset5,Offset6;	//数字向上滚动的偏移量
//时十位,时个位,分十位,分个位,秒十位,秒个位
unsigned char LastHour_10,LastHour_1,LastMinute_10,LastMinute_1,LastSecond_10,LastSecond_1;
bit ShowTimeFlag;	//显示时间的标志,1:显示,2:不显示(用来控制上电获取到网络时间后再显示时间)

/**
  * @brief  ESP8266初始化
  * @param  无
  * @retval 无
  */
void ESP8266_Init(void)
{	
	LCD_Clear();	//LCD清屏
	LCD_ShowString(1,1,"ESP8266");	//第一行显示"ESP8266",表示等待ESP8266准备好
	
	Delay(100);	//适当延时,延时0.1s
	
	//退出透传模式(如果ESP8266不断电,只让单片机复位的话,ESP8266就还处于透传模式,发送AT指令会无效)
	UART_SendString("+++");
	
	Delay(1000);	//退出透传模式要1s之后才能发AT指令

	if(ReadyFlag)	//如果上电直接返回"ready"
	{
		ReadyFlag=0;
		LCD_ShowString(2,1,"ready");	//LCD第二行显示"ready",表示ESP8266已准备好
		Delay(500);
	}
	else	//如果不返回"ready",则重启一下ESP8266模块
	{
		UART_SendString("AT+RST\r\n");	//复位
		while(!ReadyFlag);	//等待ESP8266返回"ready"
		ReadyFlag=0;
		LCD_ShowString(2,1,"ready");
		Delay(500);
	}

	LCD_Clear();
	LCD_ShowString(1,1,"WIFI");	//LCD第一行显示"WIFI",表示等待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;
		LCD_ShowString(2,1,"GOT IP");	//表示ESP8266已连接WiFi,并获取了IP
		Delay(500);
	}
	else	//如果WiFi不能连接
	{	//WiFi账号密码是保存到ESP8266的flash里的,如果在else中连接成功,
		//下次上电就可以直接连接WiFi并获得IP,就不会进入此else中
		WiFiDisconnectFlag=0;
		LCD_ShowString(2,1,"DISCONNECT");	//表示ESP8266不能连接WiFi
		Delay(500);

		LCD_ShowString(2,1,"CONNECTING1");	//表示ESP8266正在连接第一个预设的WiFi账号
		T0Count3=0;	//超时的计数清零
		TimeOutFlag=0;	//超时的标志清零
		//如果WiFi连接不成功,就按下面的账号密码进行连接
		UART_SendString(WiFi1);
		while(!OKFlag && !WiFiGotIPFlag && !TimeOutFlag);
		OKFlag=0;
		WiFiGotIPFlag=0;
		
		if(TimeOutFlag)	//如果是因为超时退出上面的while循环,说明第一个预设的WiFi账号没连上
		{
			LCD_ShowString(2,1,"CONNECTING2");	//表示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账号没连上
			{
				LCD_ShowString(2,1,"CONNECTING3");	//第二位数码管显示"3",表示ESP8266正在连接第三个预设的WiFi账号
				//如果上面的WiFi连接不成功,超时(20s)了,就会尝试下面的账号密码进行连接
				//如果上面的WiFi连接成功,就不会连接下面的WiFi账号
				UART_SendString(WiFi3);
				while(!OKFlag && !WiFiGotIPFlag);	//如果第三个WiFi账号连接不上,就会在此处陷入死循环
				OKFlag=0;
				WiFiGotIPFlag=0;
			}
		}
		
		LCD_ShowString(2,1,"           ");
		LCD_ShowString(2,1,"GOT IP");
		
		//下面的处理不能少,如果是第一次连上WiFi,还会多返回一个"OK"(暂时未知原因,有大佬知道原因的,请私信告知我一下,谢谢!)
		//如果不处理,会导致第一次连上WiFi后程序卡在数码管显示"5"
		Delay(1500);	//延时1.5s
		OKFlag=0;	//延时不能少于1s,要等ESP8266返回OK令OKFlag置1之后,再将OKFlag置零
	}

	LCD_Clear();
	LCD_ShowString(1,1,"CIPSTART");	//表示ESP8266开始建立TCP连接
	UART_SendString("AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n");	//建立 TCP 连接
	while(!OKFlag);
	OKFlag=0;
	LCD_ShowString(2,1,"CONNECT OK");	//表示ESP8266已建立TCP连接
	Delay(500);

	LCD_Clear();
	LCD_ShowString(1,1,"CIPMODE=1");	//表示ESP8266开始设置传输模式
	UART_SendString("AT+CIPMODE=1\r\n");	//设置传输模式(0为普通模式,1为透传模式)
	while(!OKFlag);
	OKFlag=0;
	LCD_ShowString(2,1,"OK");	//表示ESP8266已经设置传输模式为透传模式
	Delay(500);

	LCD_Clear();
	LCD_ShowString(1,1,"CIPSEND");	//表示ESP8266开始发送数据
	//开始发送数据(在透传模式时),当输入单独一包 +++ 时,返回普通 AT 指令模式,要至少间隔 1 秒再发下一条 AT 指令
	UART_SendString("AT+CIPSEND\r\n");
	while(!OKFlag );
	OKFlag=0;
	LCD_ShowString(2,1,"OK");	//表示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 ShowTime(void)
{
	LCD_ShowChar(1,8,'-');
	LCD_ShowChar(1,11,'-');
	LCD_ShowChar(2,6,':');
	LCD_ShowChar(2,9,':');
	LCD_ShowString(1,1,"   ");
	LCD_ShowString(2,1,"   ");
	LCD_ShowChar(1,4,'2');
	LCD_ShowChar(1,5,'0');
	LCD_ShowNum(1,6,DS1302_Time[0],2);	//年
	LCD_ShowNum(1,9,DS1302_Time[1],2);	//月
	LCD_ShowNum(1,12,DS1302_Time[2],2);	//日
	LCD_ShowNum(2,13,DS1302_Time[6],1);	//星期
	LCD_ScrollNum(2,4,0,DS1302_Time[3]/10,3,Offset1);	//时(十位)
	if(DS1302_Time[3]/10==0)
	{
		LCD_ScrollNum(2,5,1,DS1302_Time[3]%10,4,Offset2);	//时(个位)
	}
	else
	{
		LCD_ScrollNum(2,5,1,DS1302_Time[3]%10,10,Offset2);	//时(个位)
	}	
	LCD_ScrollNum(2,7,2,DS1302_Time[4]/10,6,Offset3);	//分(十位)
	LCD_ScrollNum(2,8,3,DS1302_Time[4]%10,10,Offset4);	//分(个位)
	LCD_ScrollNum(2,10,4,DS1302_Time[5]/10,6,Offset5);	//秒(十位)
	LCD_ScrollNum(2,11,5,DS1302_Time[5]%10,10,Offset6);	//秒(个位)
	
	if(ShowOKFlag)
	{
		LCD_ShowChar(1,16,'O');
		LCD_ShowChar(2,16,'K');
	}
	else
	{
		LCD_ShowChar(1,16,20);	//无显示
		LCD_ShowChar(2,16,20);	//无显示
	}
}

void main()
{
	unsigned char i;
	
	P2_5=0;	//防止开发板的蜂鸣器发声
	
	LCD_Init();	//LCD1602初始化
	Timer0_Init();	//定时器0初始化
	DS1302_Init();	//DS1302初始化
	UART_Init();	//串口初始化
	ESP8266_Init();	//ESP8266初始化
	
	WiFiGotIPFlag=0;	//ESP8266初始化的时候,回显信息会让WiFiGotIPFlag置1
	TimeOutCountFlag=0;	//TimeOutCountFlag置0,不进行超时的计时
	TimeOutFlag=0;	//超时标志清零

	LCD_Clear();
	
	GetTimeFlag=1;	//上电获取一次网络时间

	while(1)
	{
		KeyNum=Key();	//获取键码值
		
		if(KeyNum)	//如果有按键按下
		{
			if(KeyNum==12)	//如果按下K4(松手瞬间)
			{
				GetTimeFlag=1;	//手动校时
			}
		}
		
		if(WiFiGotIPFlag)	//如果不小心触碰到ESP8266模块,导致接触不良而重启模块的话,获取IP后重新连接网络
		{
			TimeOutCountFlag=1;	//启动超时的计时(防止出错卡在while循环)

			LCD_Clear();
			LCD_ShowString(1,1,"CIPSTART");	//表示ESP8266开始建立TCP连接
			UART_SendString("AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n");	//建立 TCP 连接
			while(!OKFlag && !TimeOutFlag);
			OKFlag=0;
			TimeOutFlag=0;
			LCD_ShowString(2,1,"CONNECT OK");	//表示ESP8266已建立TCP连接
			Delay(500);

			LCD_Clear();
			LCD_ShowString(1,1,"CIPMODE=1");	//表示ESP8266开始设置传输模式
			UART_SendString("AT+CIPMODE=1\r\n");	//设置传输模式(0为普通模式,1为透传模式)
			while(!OKFlag && !TimeOutFlag);
			OKFlag=0;
			TimeOutFlag=0;
			LCD_ShowString(2,1,"OK");	//表示ESP8266已经设置传输模式为透传模式
			Delay(500);

			LCD_Clear();
			LCD_ShowString(1,1,"CIPSEND");	//表示ESP8266开始发送数据
			//开始发送数据(在透传模式时),当输入单独一包 +++ 时,返回普通 AT 指令模式,要至少间隔 1 秒再发下一条 AT 指令
			UART_SendString("AT+CIPSEND\r\n");
			while(!OKFlag && !TimeOutFlag);
			OKFlag=0;
			TimeOutFlag=0;
			LCD_ShowString(2,1,"OK");	//表示ESP8266已经准备好了,可以发送数据了
			Delay(500);
			
			WiFiGotIPFlag=0;	//要放在最后,否则回显信息又会让WiFiGotIPFlag置1
			TimeOutCountFlag=0;	//停止超时的计时
			GetTimeFlag=1;
		}
		
		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)	//如果获取了时间
		{
			GotTimeFlag=0;
			TimeOutFlag=0;	//超时的标志清零
			TimeOutCountFlag=0;	//停止超时的计时
			
			ConvertTime();
			for(i=0;i<7;i++){DS1302_Time[i]=Time[i];}
			DS1302_SetTime();	//将获取到的网络时间写入DS1302时钟芯片
			
			LastHour_10=DS1302_Time[3]/10;	//成功获取时间后,更新变量的值
			LastHour_1=DS1302_Time[3]%10;
			LastMinute_10=DS1302_Time[4]/10;
			LastMinute_1=DS1302_Time[4]%10;
			LastSecond_10=DS1302_Time[5]/10;
			LastSecond_1=DS1302_Time[5]%10;
			
			ShowOKFlag=1;	//校时后,1行16列显示"O",2行16列显示"K",显示2s
			T0Count4=0;	//显示"OK"2s的计数清零

			T0Count2=0;	//每次成功校对时间后,用于自动校时的计数清0
			ProofTimeCount=0;	//每次成功校对时间后,用于自动校时的计数清0
			
			ShowTimeFlag=1;
		}
		
		if(ReadTimeFlag && ShowTimeFlag)	//从DS1302芯片中读取时间
		{
			ReadTimeFlag=0;
			
			DS1302_ReadTime();	//读取时间
			
			if(LastHour_10 != DS1302_Time[3]/10){Offset1=-8;T0Count6=0;}
			if(LastHour_1 != DS1302_Time[3]%10){Offset2=-8;T0Count6=0;}
			if(LastMinute_10 != DS1302_Time[4]/10){Offset3=-8;T0Count6=0;}
			if(LastMinute_1 != DS1302_Time[4]%10){Offset4=-8;T0Count6=0;}
			if(LastSecond_10 != DS1302_Time[5]/10){Offset5=-8;T0Count6=0;}
			if(LastSecond_1 != DS1302_Time[5]%10){Offset6=-8;T0Count6=0;}					

			LastHour_10=DS1302_Time[3]/10;
			LastHour_1=DS1302_Time[3]%10;
			LastMinute_10=DS1302_Time[4]/10;
			LastMinute_1=DS1302_Time[4]%10;
			LastSecond_10=DS1302_Time[5]/10;
			LastSecond_1=DS1302_Time[5]%10;
			
			ShowTime();	//更新显示时间
		}
		
	}
}

void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	//因使能了6T(双倍速)模式,所以定时器计算器中12T模式定时20ms对应的是6T模式的10ms
	TL0=0x00;	//设置定时初值,定时10ms,晶振@11.0592MHz
	TH0=0xB8;	//设置定时初值,定时10ms,晶振@11.0592MHz
	T0Count1++;
	T0Count2++;
	if(TimeOutCountFlag){T0Count3++;}	//TimeOutCountFlag为1才开始超时的计时
	else{T0Count3=0;}
	T0Count4++;
	T0Count5++;
	T0Count6++;
	if(T0Count1>=2)	//每隔20ms检测一次按键
	{
		T0Count1=0;
		Key_Tick();
	}
	if(T0Count2>=6000)	//1min,即60s
	{
		T0Count2=0;
		ProofTimeCount++;
		ProofTimeCount%=1440;	//60*1440s=24h,每隔24小时自动联网校时
		if(!ProofTimeCount){GetTimeFlag=1;}
	}
	if(T0Count3>=2000)	//ESP8266连接WiFi的超时时间:20s
	{
		T0Count3=0;
		TimeOutFlag=1;
	}
	if(T0Count4>=200)	//如果从网络获取了时间,显示"OK"2秒钟
	{
		T0Count4=0;
		ShowOKFlag=0;
	}
	if(T0Count5>=10)	//每隔100ms从DS1302时钟芯片读取一次时间
	{
		T0Count5=0;
		ReadTimeFlag=1;
	}
	if(T0Count6>=7)	//每隔70ms滚动一个像素
	{
		T0Count6=0;
		Offset1++;
		Offset2++;
		Offset3++;
		Offset4++;
		Offset5++;
		Offset6++;
		if(Offset1>0){Offset1=0;}
		if(Offset2>0){Offset2=0;}
		if(Offset3>0){Offset3=0;}
		if(Offset4>0){Offset4=0;}
		if(Offset5>0){Offset5=0;}
		if(Offset6>0){Offset6=0;}
	}
}

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>

*/

总结

LCD1602滚动显示时分秒的实现没用多少时间,因为之前做过一个32X8点阵屏的时钟,原理是差不多的,不过LCD1602由于硬件原因,会拖影现象。I2C版本的LCD1602的通信速率比较慢,如果星期的显示(耗时较长)实时更新,会导致走时的显示不流畅,即看起来会有卡顿的现象,所以星期的显示在检测到星期发生变化再进行更新显示。

相关推荐
智商偏低2 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen4 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
白鱼不小白6 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D6 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术9 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt10 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘10 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
几个几个n12 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件
Despacito0o15 小时前
ESP32-s3摄像头驱动开发实战:从零搭建实时图像显示系统
人工智能·驱动开发·嵌入式硬件·音视频·嵌入式实时数据库
门思科技16 小时前
设计可靠 LoRaWAN 设备时需要考虑的关键能力
运维·服务器·网络·嵌入式硬件·物联网