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

news2025/1/30 4:59:06

目录

  • 系列文章目录
  • 前言
  • 一、效果展示
  • 二、原理分析
  • 三、各模块代码
    • 1、延时
    • 2、定时器0
    • 3、串口通信
    • 4、DS1302
    • 5、LCD1602
    • 6、独立按键
  • 四、主函数
  • 总结

系列文章目录


前言

之前做了一个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文件

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

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

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

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

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

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

/*

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2284561.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习 ---逻辑回归

逻辑回归是属于机器学习里面的监督学习&#xff0c;它是以回归的思想来解决分类问题的一种非常经典的二分类分类器。由于其训练后的参数有较强的可解释性&#xff0c;在诸多领域中&#xff0c;逻辑回归通常用作 baseline 模型&#xff0c;以方便后期更好的挖掘业务相关信息或提…

拟合损失函数

文章目录 拟合损失函数一、线性拟合1.1 介绍1.2 代码可视化1.2.1 生成示例数据1.2.2 损失函数1.2.3 绘制三维图像1.2.4 绘制等高线1.2.5 损失函数关于斜率的函数 二、 多变量拟合2.1 介绍2.2 代码可视化2.2.1 生成示例数据2.2.2 损失函数2.2.3 绘制等高线 三、 多项式拟合3.1 介…

银行卡三要素验证接口:方便快捷地实现银行卡核验功能

银行卡三要素验证API&#xff1a;防止欺诈交易的有力武器 随着互联网的发展&#xff0c;电子支付方式也越来越普及。在支付过程中&#xff0c;银行卡是最常用的支付工具之一。然而&#xff0c;在一些支付场景中&#xff0c;需要对用户的银行卡信息进行验证&#xff0c;以确保支…

利用JSON数据类型优化关系型数据库设计

利用JSON数据类型优化关系型数据库设计 前言 在关系型数据库中&#xff0c;传统的结构化存储方式要求预先定义好所有的列及其数据类型。 然而&#xff0c;随着业务的发展&#xff0c;这种设计可能会显得不够灵活&#xff0c;尤其是在需要扩展单个列的描述功能时。 JSON数据…

极简壁纸js逆向

首先抓包&#xff0c;翻页可以看到数据储存在该包 可以看到随着页面变化&#xff0c;只有current在变化 而且载荷都没有加密&#xff0c;看来不用js逆向了 爬取代码 import os import asyncio import aiohttp import jsonheaders {"accept": "application/j…

JAVA 接口、抽象类的关系和用处 详细解析

接口 - Java教程 - 廖雪峰的官方网站 一个 抽象类 如果实现了一个接口&#xff0c;可以只选择实现接口中的 部分方法&#xff08;所有的方法都要有&#xff0c;可以一部分已经写具体&#xff0c;另一部分继续保留抽象&#xff09;&#xff0c;原因在于&#xff1a; 抽象类本身…

使用PC版本剪映制作照片MV

目录 制作MV模板时长调整拖动边缘缩短法分割删除法变速法整体调整法 制作MV 导入音乐 导入歌词 点击歌词 和片头可以修改字体&#xff1a; 还可以给字幕添加动画效果&#xff1a; 导入照片&#xff0c;自动创建照片轨&#xff1a; 修改片头字幕&#xff1a;增加两条字幕轨&…

.NET Core缓存

目录 缓存的概念 客户端响应缓存 cache-control 服务器端响应缓存 内存缓存&#xff08;In-memory cache&#xff09; 用法 GetOrCreateAsync 缓存过期时间策略 缓存的过期时间 解决方法&#xff1a; 两种过期时间策略&#xff1a; 绝对过期时间 滑动过期时间 两…

【微服务与分布式实践】探索 Dubbo

核心组件 服务注册与发现原理 服务提供者启动时&#xff0c;会将其服务信息&#xff08;如服务名、版本、所在节点的网络地址等&#xff09;注册到注册中心。服务消费者则可以从注册中心发现可用的服务提供者列表&#xff0c;并与之通信。注册中心会存储服务的信息&#xff0c…

Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

NeuIPS 2024 | CoT推理的新突破:推理边界框架(RBF)

近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在推理任务上的能力不断提升&#xff0c;尤其是 思维链&#xff08;Chain-of-Thought, CoT&#xff09; 技术&#xff0c;使得模型可以逐步推演逻辑&#xff0c;提高预测准确率。然而&#xff0c;当前的CoT推理仍然…

linux——进程树的概念和示例

一些程序进程运行后&#xff0c;会调用其他进程&#xff0c;这样就组成了一个进程树。 比如,在Windows XP的“运行”对话框中输入“cmd”启动命令行控制台&#xff0c;然后在命令行中输入“notepad”启动记事本&#xff0c;那么命令行控制台进程“cmd.exe”和记事本进程“note…

CSAPP学习:前言

前言 本书简称CS&#xff1a;APP。 背景知识 一些基础的C语言知识 如何阅读 Do-做系统 在真正的系统上解决具体的问题&#xff0c;或是编写和运行程序。 章节 2025-1-27 个人认为如下章节将会对学习408中的操作系统与计算机组成原理提供帮助&#xff0c;于是先凭借记忆将其简单…

【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节课我们不学习新的知识&#xff0c;我们来做一个扫雷小游戏 目录 扫雷小游戏概述一、扫雷游戏分析…

【反悔堆】力扣1642. 可以到达的最远建筑

给你一个整数数组 heights &#xff0c;表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程&#xff0c;不断向后面的建筑物移动&#xff0c;期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1&#xff08;下标 从 0 开始 &#xff09;…

电力晶体管(GTR)全控性器件

电力晶体管&#xff08;Giant Transistor&#xff0c;GTR&#xff09;是一种全控性器件&#xff0c;以下是关于它的详细介绍&#xff1a;&#xff08;模电普通晶体管三极管进行对比学习&#xff09; 基本概念 GTR是一种耐高电压、大电流的双极结型晶体管&#xff08;BJT&am…

Cursor 帮你写一个小程序

Cursor注册地址 首先下载客户端 点击链接下载 1 打开微信开发者工具创建一个小程序项目 选择TS-基础模版 官方 2 然后使用Cursor打开小程序创建的项目 3 在CHAT聊天框输入自己的需求 比如 小程序功能描述&#xff1a;吃什么助手 项目名称&#xff1a; 吃什么小程序 功能目标…

【shell工具】编写一个批量扫描IP地址的shell脚本

批量扫描某个网段中的主机&#xff08;并发&#xff09; 创建目录编写脚本文件 mkdir /root/ip_scan_shell/ touch /root/ip_scan_shell/online_server.txt touch /root/ip_scan_shell/offline_server.txt touch /root/ip_scan_shell/ip_scan.sh写入下面shell到脚本文件中…

vim如何设置制表符表示的空格数量

:set tabstop4 设置制表符表示的空格数量 制表符就是tab键&#xff0c;一般默认是四个空格的数量 示例&#xff1a; &#xff08;vim如何使设置制表符表示的空格数量永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;

LangChain:使用表达式语言优化提示词链

在 LangChain 里&#xff0c;LCEL 即 LangChain Expression Language&#xff08;LangChain 表达式语言&#xff09;&#xff0c;本文为你详细介绍它的定义、作用、优势并举例说明&#xff0c;从简单示例到复杂组合示例&#xff0c;让你快速掌握LCEL表达式语言使用技巧。 定义 …