基于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的通信速率比较慢,如果星期的显示(耗时较长)实时更新,会导致走时的显示不流畅,即看起来会有卡顿的现象,所以星期的显示在检测到星期发生变化再进行更新显示。

相关推荐
勿忘初心913 小时前
Android车机DIY开发之学习篇(七)NDK交叉工具构建
arm开发·单片机·嵌入式硬件·学习
漫无目的行走的月亮4 小时前
51单片机开发:定时器中断
单片机·嵌入式硬件·51单片机
weixin_4612594116 小时前
TL494方案开关电源方案
单片机·嵌入式硬件
Zevalin爱灰灰18 小时前
FreeRTOS从入门到精通 第十二章(FreeRTOS消息队列)
stm32·单片机·嵌入式硬件·操作系统·freertos
Uitwaaien5420 小时前
51单片机——定时器时钟
单片机·嵌入式硬件·51单片机
honey ball1 天前
磁珠的选型以及变压器气隙问题
单片机·嵌入式硬件·机器人
esmember1 天前
电路研究9.2——合宙Air780EP使用AT指令
嵌入式硬件·at指令
7yewh2 天前
嵌入式知识点总结 Linux驱动 (三)-文件系统
linux·运维·驱动开发·stm32·嵌入式硬件·mcu·物联网
放我去玩单片机2 天前
基于STM32的数字多重仪表教学
单片机·嵌入式硬件
7yewh2 天前
嵌入式知识点总结 Linux驱动 (四)-中断-软硬中断-上下半部-中断响应
linux·驱动开发·stm32·嵌入式硬件·mcu·物联网·硬件工程