当前位置: 首页 > article >正文

基于51单片机和ESP8266(01S)、8X8点阵屏的二进制WiFi时钟

目录

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

系列文章目录


前言

按自己的想法制作一个独一无二的时钟,谁不喜欢呢?

有两个版本:
①普中开发板版本:6T@11.0592MHz,115200bps
②最小系统板版本:12T@12.0000MHz,4800bps

本文代码对应的是普中开发板版本。

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

所用的外设有:8X8点阵屏(普中开发板版本的是74HC595驱动,最小系统板版本的是MAX7219驱动)、DS1302时钟芯片(普中开发板版本没用到)、独立按键(普中开发板版本)、触摸按键(最小系统板版本)。

效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

(1)普中开发板版本
图中显示的时间为:25/1/27 17:23:11 星期一
在这里插入图片描述

(2)最小系统板版本
图中显示的时间为:25/1/27 17:32:26 星期一
在这里插入图片描述

二、原理分析

1、获取网络时间
ESP8266(01S)模块的使用、串口通信、截取包含时间信息的字符串等,可以看一下我的另一篇博客:八位数码管WiFi定时器时钟

2、8X8点阵屏的显示
普中开发板版本的是74HC595驱动,需要用定时器扫描显示,扫描的时间间隔设置为1ms即可。最小开发板版本的是MAX7219驱动,MAX7219芯片内置自动扫描电路,无需占用单片机的资源,把需要显示的数据送入MAX7219的寄存器即可。

3、走时

(1)普中开发板版本

普中开发板版本利用定时器走时,经过补偿后,走时还是比较准确的。(本来普中开发板版本的也想用板上的DS1302时钟芯片,但是DS1302时钟芯片的引脚和驱动点阵屏的74HC595芯片的引脚冲突了,都用到了P34、P35、P36)

可以按以下方法进行补偿。

计时器计时的时间和实际时间同步的时候(即都是整数秒),看一下计时器的计时的时间间隔和实际的时间间隔的秒数分别是多少,再通过计算修改定时器的初值。

例如,计时器计时260s,对应实际时间的265s,说明定时器计时较慢,需要减小定时器的初值,如果定时器定时1ms,即1000us,就需要将定时器定时的时间改为1000*260/265us≈987us,这样就准确了。

经过测试,补偿后,定时器计时2000s与实际的2000s相差不到1s。

代码中设置了每隔30min自动联网校时。其实可以将时间间隔设置长一些,因为补偿后的定时器的走时还是比较准的。

(2)最小系统板版本

最小系统板版本获取到网络时间后,将时间写入DS1302时钟芯片,利用DS1302走时。虽说DS1302精度不高,但走时一天也不会相差太多,代码中设置了每隔24h自动联网校时。

4、二进制显示时间
年、月、日、时、分、秒、星期,分别对应7个字节,每个字节对应8X8点阵屏的一列(也可以用一行来显示一个时间数据,感兴趣的道友可以尝试一下),直接把7个字节写入前7列就可以了。

注意高低位的方向,要让高位在上。普中开发板版本的可以通过修改74HC595芯片写入字节的时候先写高位还是先写低位来控制高位在上还是低位在上。MAX7219驱动的点阵屏模块则转动一下点阵屏的方向即可,或者将字节处理一下(调转高低位)再写入MAX7219的寄存器。

三、各模块代码

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、8X8点阵屏

h文件

#ifndef __MATRIXLED__
#define __MATRIXLED__

extern unsigned char DisplayBuffer[];	//声明数组,外部可使用
void MatrixLED_Init(void);
void MatrixLED_MoveLeft(unsigned char *Array,unsigned int Offset);
void MatrixLED_MoveUp(unsigned char *Array,unsigned int Offset);
void MatrixLED_Clear(void);
void MatrixLED_ShowNum(unsigned char Group);
void MatrixLED_Tick(void);

#endif

c文件

#include <REGX52.H>

//引脚定义

sbit _74HC595_DS=P3^4;	//串行数据输入
sbit _74HC595_STCP=P3^5;	//储存寄存器时钟输入,上升沿有效
sbit _74HC595_SHCP=P3^6;	//移位寄存器时钟输入,上升沿有效

/*
每一个B对应一个LED。缓存数组DisplayBuffer的8个字节分别对应这8列,高位在上
B7	B7	B7	B7	B7	B7	B7	B7
B6	B6	B6	B6	B6	B6	B6	B6
B5	B5	B5	B5	B5	B5	B5	B5
B4	B4	B4	B4	B4	B4	B4	B4
B3	B3	B3	B3	B3	B3	B3	B3
B2	B2	B2	B2	B2	B2	B2	B2
B1	B1	B1	B1	B1	B1	B1	B1
B0	B0	B0	B0	B0	B0	B0	B0
*/

//想要改变显示内容,改变数组DisplayBuffer的数据就行了,由定时器自动扫描
unsigned char DisplayBuffer[8];

//阴码(亮点为1),纵向取模,高位在上
unsigned char code Table[]={
0x00,0x12,0x3E,0x02,0x00,0x00,0x00,0x00,	//	1	表示等待ESP8266准备好
0x00,0x12,0x3E,0x02,0x00,0x02,0x00,0x00,	//	1.	表示ESP8266已准备好
0x00,0x2E,0x2A,0x3A,0x00,0x00,0x00,0x00,	//	2	表示等待WiFi连接
0x00,0x2E,0x2A,0x3A,0x00,0x3E,0x22,0x3E,	//	20	表示WiFi连接失败
0x00,0x2E,0x2A,0x3A,0x00,0x12,0x3E,0x02,	//	21	表示尝试连接第一个预设的WiFi
0x00,0x2E,0x2A,0x3A,0x00,0x2E,0x2A,0x3A,	//	22	表示尝试连接第二个预设的WiFi
0x00,0x2E,0x2A,0x3A,0x00,0x2A,0x2A,0x3E,	//	23	表示尝试连接第三个预设的WiFi
0x00,0x2E,0x2A,0x3A,0x00,0x02,0x00,0x00,	//	2.	表示已连接WiFi并获取了IP
0x00,0x2A,0x2A,0x3E,0x00,0x00,0x00,0x00,	//	3	表示准备建立TCP连接
0x00,0x2A,0x2A,0x3E,0x00,0x02,0x00,0x00,	//	3.	表示已建立TCP连接
0x00,0x38,0x08,0x3E,0x00,0x00,0x00,0x00,	//	4	表示准备设置传输模式
0x00,0x38,0x08,0x3E,0x00,0x02,0x00,0x00,	//	4.	表示已设置为透传模式
0x00,0x3A,0x2A,0x2E,0x00,0x00,0x00,0x00,	//	5	表示准备发送数据
0x00,0x3A,0x2A,0x2E,0x00,0x02,0x00,0x00,	//	5.	表示可以发送数据了
};

//函数定义

/**
  * @brief  LED点阵屏清空显示
  * @param  无
  * @retval 无
  */
void MatrixLED_Clear(void)
{
	unsigned char i;
	for(i=0;i<8;i++){DisplayBuffer[i]=0;}		
}

/**
  * @brief	MatrixLED初始化(即74HC595初始化)
  * @param	无
  * @retval	无
  */
void MatrixLED_Init(void)
{
	_74HC595_SHCP=0;	//移位寄存器时钟信号初始化
	_74HC595_STCP=0;	//储存寄存器时钟信号初始化
	MatrixLED_Clear();	//点阵屏清屏
}

/**
  * @brief	74HC595写入字节
  * @param	Byte 要写入的字节
  * @retval	无
  */
void _74HC595_WriteByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)	//循环8次(8位移位寄存器)
	{
		_74HC595_DS=Byte&(0x80>>i);	//高位先发
		_74HC595_SHCP=1;	//SHCP上升沿时,DS的数据写入移位寄存器
		_74HC595_SHCP=0;
	}
	_74HC595_STCP=1;	//STCP上升沿时,数据从移位寄存器转存到储存寄存器
	_74HC595_STCP=0;
}

/**
  * @brief  8X8LED点阵屏向左滚动显示数组内容(要求数组数据逐列式取模,高位在上)
  * @param  Array 传递过来的数组的指针(地址),数组名就是数组的首地址
  * @param  Offset 偏移量,向左偏移Offset个像素
  * @retval 无
  */
void MatrixLED_MoveLeft(unsigned char *Array,unsigned int Offset)
{
	unsigned char i;
	Array+=Offset;
	for(i=0;i<8;i++)
	{
		DisplayBuffer[i]=*Array;
		Array++;	//地址自增
	}
}

/**
  * @brief  8X8LED点阵屏向上滚动显示数组内容(要求数组数据逐列式取模,高位在上)
  * @param  *Array Array为传递过来的数组的首地址(数组名对应的就是数组的首地址)
  * @param  Offset 显示数组数据的偏移量,向上偏移Offset个像素
  * @retval 无
  */
void MatrixLED_MoveUp(unsigned char *Array,unsigned int Offset)
{
	unsigned char i,m,n;
	m=Offset/8;
	n=Offset%8;
	Array+=m*8;
	for(i=0;i<8;i++)
	{
		DisplayBuffer[i]=(*Array>>n)|(*(Array+8)<<(8-n));
		Array++;
	}	
}

/**
  * @brief  LED点阵屏显示“数字”
  * @param  Group  Table中数据的组号(每8个数据为一组),范围:0~13
  * @retval 无
  */
void MatrixLED_ShowNum(unsigned char Group)
{
	unsigned char i,j;
	j=8*Group;
	for(i=0;i<8;i++){DisplayBuffer[i]=Table[j+i];}		
}

/**
  * @brief	LED点阵屏驱动函数,中断中调用
  * @param	无
  * @retval	无
  */
void MatrixLED_Tick(void)
{
	static unsigned char i=0;	//定义静态变量
	P0=0xFF;	//消影
	_74HC595_WriteByte(DisplayBuffer[i]);	//将数据写入到74HC595中锁存
	P0=~(0x80>>i);	//位选,低电平选中
	i++;	//下次进中断后扫描下一列
	i%=8;	//显示完第八列后,又从第一列开始显示
}

5、独立按键

h文件

#ifndef __KEYSCAN_H__
#define __KEYSCAN_H__

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

#endif

c文件

#include <REGX52.H>

sbit Key1=P3^1;
sbit Key2=P3^0;
sbit Key3=P3^2;
sbit Key4=P3^3;

unsigned char KeyNumber;

/**
  * @brief  获取独立按键的键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~12,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char KeyTemp=0;
	KeyTemp=KeyNumber;
	KeyNumber=0;	//主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回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甘腾胜@20250126
效果展示:可以在B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:6T@11.0592MHz
波特率:115200bps
外设:ESP8266(01S)模块、8X8LED点阵屏(74HC595驱动)、独立按键
注意:
(1)ESP8266供电电压为3.3V,接5V会发热严重,RX和TX要交叉连接
(2)此版本不用更改ESP8266模块的默认波特率115200bps,但下载的时候需要勾选“使能6T(双倍速)模式”
(3)无需设置ESP8266模块默认的WiFi模式(AP模式),超时20s,运行时程序会自动设置为STA模式
(4)每隔30min会自动联网校时(如果校时超时(20s),会重启ESP8266模块,重新连接WiFi和网络,并校时)
(5)串口中断的优先级要比定时器0的高,否则会影响通信
(6)代码末尾有月份、星期的英文,以及网站返回的时间数据的样例
(7)计时不准的话,可以自行测量并修改定时器0所赋的初值进行补偿
(8)因引脚冲突,用不了板上的DS1302时钟芯片,所以用定时器来走时(DS1302和74HC595均用到了P34、P35、P36)

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

操作说明:

	K1	K2	K3	K4	

【K4】手动联网校时

接线:
(1)ESP8266模块的TX、RX分别接单片机的P30、P31
(2)普中A2开发板点阵屏旁边的短路帽接左边两个针

*/

#include <REGX52.H>	//包含头文件
#include "Delay.h"
#include "Timer0.h"
#include "UART.h"
#include "KeyScan.h"
#include "MatrixLED.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 T0Count0,T0Count1,T0Count2,T0Count3,T0Count4;	//定时器计数的变量
unsigned int ProofTimeCount;	//定时器中隔一段时间自动校时的计数
bit TimeOutFlag;	//连接WiFi超时的标志,1:超时,2:未超时
bit TimeOutCountFlag=1;	//启动超时计数的标志,1:启动,2:不启动
bit ReadTimeFlag=1;	//从DS1302时钟芯片读取时间的标志,1:读取,2:不读取
bit ShowTimeFlag;	//显示时间的标志,1:显示,2:不显示(用来控制上电获取到网络时间后再显示时间)
bit ShowGotTimeFlag;	//成功获取网络时间后最后一列全亮显示的标志,1:显示,0:不显示

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

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

	MatrixLED_ShowNum(2);	//显示“2”,表示等待ESP8266连接WiFi
	//WiFi账号密码保存在ESP8266的Flash中,掉电不丢失
	//如果上次已经成功连接,则上电自动按上次的网络名称和密码连接
	T0Count3=0;	//超时的计数清零
	TimeOutFlag=0;	//超时的标志清零
	while(!WiFiGotIPFlag && !WiFiDisconnectFlag && !TimeOutFlag);

	if(TimeOutFlag)	//如果是因为超时退出上面的while循环,说明ESP8266处于AP模式
	{
		UART_SendString("AT+CWMODE=1\r\n");	//发送AT指令设置为STA(Station)模式
		while(!OKFlag);	//等待ESP8266返回“OK”
		OKFlag=0;
	}

	if(WiFiGotIPFlag)	//如果成功获取了IP
	{
		WiFiGotIPFlag=0;
		MatrixLED_ShowNum(7);	//显示“2.”,表示ESP8266已连接WiFi,并获取了IP
		Delay(500);
	}
	else	//如果WiFi不能连接
	{	//WiFi账号密码是保存到ESP8266的flash里的,如果在else中连接成功,
		//下次上电就可以直接连接WiFi并获得IP,就不会进入此else中
		WiFiDisconnectFlag=0;
		MatrixLED_ShowNum(3);	//显示“20”,表示ESP8266连接WiFi失败
		Delay(500);

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

	MatrixLED_ShowNum(8);	//显示“3”,表示ESP8266开始建立TCP连接
	UART_SendString("AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n");	//建立 TCP 连接
	while(!OKFlag);
	OKFlag=0;
	MatrixLED_ShowNum(9);	//显示“3.”,表示ESP8266已建立TCP连接
	Delay(500);

	MatrixLED_ShowNum(10);	//显示“4”,表示ESP8266开始设置传输模式
	UART_SendString("AT+CIPMODE=1\r\n");	//设置传输模式(0为普通模式,1为透传模式)
	while(!OKFlag);
	OKFlag=0;
	MatrixLED_ShowNum(11);	//显示“4.”,表示ESP8266已经设置传输模式为透传模式
	Delay(500);

	MatrixLED_ShowNum(12);	//显示“5”,表示ESP8266准备发送数据
	//开始发送数据(在透传模式时),当输入单独一包 +++ 时,返回普通 AT 指令模式,要至少间隔 1 秒再发下一条 AT 指令
	UART_SendString("AT+CIPSEND\r\n");
	while(!OKFlag );
	OKFlag=0;
	MatrixLED_ShowNum(13);	//显示“5.”,表示ESP8266可以发送数据了
	Delay(500);
}

/**
  * @brief  将接收到的时间数据(字符型)转换为十进制的数据,保存到时间数组Time中
  * @param  无
  * @retval 无
  */
void ConvertTime(void)
{
	Time[0]=(TimeBuffer[14]-'0')*10+(TimeBuffer[15]-'0');	//年
	if(TimeBuffer[8]=='J' && TimeBuffer[9]=='a'){Time[1]=1;}	//月
	else if(TimeBuffer[8]=='F'){Time[1]=2;}
	else if(TimeBuffer[8]=='M' && TimeBuffer[9]=='a' && TimeBuffer[10]=='r'){Time[1]=3;}
	else if(TimeBuffer[8]=='A' && TimeBuffer[9]=='p'){Time[1]=4;}
	else if(TimeBuffer[8]=='M' && TimeBuffer[9]=='a' && TimeBuffer[10]=='y'){Time[1]=5;}
	else if(TimeBuffer[8]=='J' && TimeBuffer[9]=='u' && TimeBuffer[10]=='n'){Time[1]=6;}
	else if(TimeBuffer[8]=='J' && TimeBuffer[9]=='u' && TimeBuffer[10]=='l'){Time[1]=7;}
	else if(TimeBuffer[8]=='A' && TimeBuffer[9]=='u'){Time[1]=8;}
	else if(TimeBuffer[8]=='S'){Time[1]=9;}
	else if(TimeBuffer[8]=='O'){Time[1]=10;}
	else if(TimeBuffer[8]=='N'){Time[1]=11;}
	else if(TimeBuffer[8]=='D'){Time[1]=12;}
	Time[2]=(TimeBuffer[5]-'0')*10+(TimeBuffer[6]-'0');	//日
	Time[3]=(TimeBuffer[17]-'0')*10+(TimeBuffer[18]-'0');	//时
	Time[4]=(TimeBuffer[20]-'0')*10+(TimeBuffer[21]-'0');	//分
	Time[5]=(TimeBuffer[23]-'0')*10+(TimeBuffer[24]-'0');	//秒
	if(TimeBuffer[0]=='M'){Time[6]=1;}	//星期
	else if(TimeBuffer[0]=='T' && TimeBuffer[1]=='u'){Time[6]=2;}
	else if(TimeBuffer[0]=='W'){Time[6]=3;}
	else if(TimeBuffer[0]=='T' && TimeBuffer[1]=='h'){Time[6]=4;}
	else if(TimeBuffer[0]=='F'){Time[6]=5;}
	else if(TimeBuffer[0]=='S' && TimeBuffer[1]=='a'){Time[6]=6;}
	else if(TimeBuffer[0]=='S' && TimeBuffer[1]=='u'){Time[6]=7;}

	//返回的是GMT,北京时间比格林威治时间(Greenwich Mean Time简称GMT)早8小时。
	Time[3]+=8;	//UTC/GMT +8.00 (东八区)
	
	if(Time[3]/24)	//如果加8小时后是第二天
	{
		Time[3]%=24;
		
		Time[6]++;	//星期增加
		if(Time[6]>7){Time[6]=1;}
		
		Time[2]++;
		
		if(Time[2]>=32)	//大月
		{
			Time[2]=1;
			Time[1]++;
			if(Time[1]>12)
			{
				Time[1]=1;
				Time[0]++;
				Time[0]%=100;
			}
		}
		else if(Time[2]==31)	//小月
		{
			if(Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11)
			{
				Time[2]=1;
				Time[1]++;
			}
		}
		else if(Time[2]==30)	//闰年二月
		{
			if(Time[1]==2 && Time[0]%4==0)
			{
				Time[2]=1;
				Time[1]++;
			}
		}
		else if(Time[2]==29)	//平年二月
		{
			if(Time[1]==2 && Time[0]%4)
			{
				Time[2]=1;
				Time[1]++;
			}
		}
	}

	/*由于网络延迟、数据的处理等,导致处理后的时间慢一两秒,这里进行补偿,加多2秒*/
	if(Time[3]<23 ||  Time[4]<59 || Time[5]<58)	//如果加多2秒不会跳到第二天
	{
		Time[5]+=2;
		if(Time[5]>=60)
		{
			Time[5]%=60;
			Time[4]++;
			if(Time[4]>=60)
			{
				Time[4]%=60;
				Time[3]++;
			}
		}
	}
	
}

/**
  * @brief	检查时间,看是否越界
  * @param  无
  * @retval 无
  */
void CheckTime(void)
{
	if(Time[5]>=60)
	{
		Time[5]=0;
		Time[4]++;
		if(Time[4]>=60)
		{
			Time[4]=0;
			Time[3]++;
			if(Time[3]>=24)
			{
				Time[3]=0;
				Time[2]++;
				if( Time[2]>=32 && (Time[1]==1 || Time[1]==3 || Time[1]==5 || 
					Time[1]==7 || Time[1]==8 || Time[1]==10 || Time[1]==12) )	//大月
				{
					Time[2]=1;
					Time[1]++;
				}
				if( Time[2]>=31 && (Time[1]==4 || Time[1]==6 || Time[1]==9 || Time[1]==11) )	//小月
				{
					Time[2]=1;
					Time[1]++;
				}
				if( Time[2]>=30 && !(Time[0]%4) )	//闰年二月
				{
					Time[2]=1;
					Time[1]++;
				}
				if( Time[2]>=29 && (Time[0]%4) )	//平年二月
				{
					Time[2]=1;
					Time[1]++;
				}
				if(Time[1]>=13)
				{
					Time[1]=1;
					Time[0]++;
					Time[0]%=100;
				}
			}
		}
	}
}

/**
  * @brief	更新显示时间
  * @param  无
  * @retval 无
  */
void ShowTime(void)
{
	unsigned char i;
	for(i=0;i<7;i++){DisplayBuffer[i]=Time[i];}
	if(ShowGotTimeFlag)	//最后一列全亮,表示已成功从网络获取了时间
	{
		DisplayBuffer[7]=0xFF;
	}
	else	//无显示
	{
		DisplayBuffer[7]=0;
	}
}

void main()
{
	P2_5=0;	//防止开发板的蜂鸣器发声
	
	MatrixLED_Init();	//点阵屏初始化
	Timer0_Init();	//定时器0初始化
	UART_Init();	//串口初始化
	ESP8266_Init();	//ESP8266初始化
	
	WiFiGotIPFlag=0;	//ESP8266初始化的时候,回显信息会让WiFiGotIPFlag置1
	TimeOutCountFlag=0;	//TimeOutCountFlag置0,不进行超时的计时
	TimeOutFlag=0;	//超时标志清零
	
	GetTimeFlag=1;	//上电获取一次网络时间

	while(1)
	{
		KeyNum=Key();	//获取键码值
		
		if(KeyNum)	//如果有按键按下
		{
			if(KeyNum==12)	//如果按下K4(松手瞬间)
			{
				GetTimeFlag=1;	//手动校时
			}
		}
		
		if(WiFiGotIPFlag)	//如果ESP8266模块重启,并获取了IP
		{
			TimeOutCountFlag=1;	//启动超时的计时(防止出错卡在while循环)

			MatrixLED_ShowNum(8);	//显示“3”,表示ESP8266开始建立TCP连接
			UART_SendString("AT+CIPSTART=\"TCP\",\"www.beijing-time.org\",80\r\n");	//建立 TCP 连接
			while(!OKFlag && !TimeOutFlag);
			OKFlag=0;
			TimeOutFlag=0;
			MatrixLED_ShowNum(9);	//显示“3.”,表示ESP8266已建立TCP连接
			Delay(500);

			MatrixLED_ShowNum(10);	//显示“4”,表示ESP8266开始设置传输模式
			UART_SendString("AT+CIPMODE=1\r\n");	//设置传输模式(0为普通模式,1为透传模式)
			while(!OKFlag && !TimeOutFlag);
			OKFlag=0;
			TimeOutFlag=0;
			MatrixLED_ShowNum(11);	//显示“4.”,表示ESP8266已经设置传输模式为透传模式
			Delay(500);

			MatrixLED_ShowNum(12);	//显示“5”,表示ESP8266准备发送数据
			//开始发送数据(在透传模式时),当输入单独一包 +++ 时,返回普通 AT 指令模式,要至少间隔 1 秒再发下一条 AT 指令
			UART_SendString("AT+CIPSEND\r\n");
			while(!OKFlag && !TimeOutFlag);
			OKFlag=0;
			TimeOutFlag=0;
			MatrixLED_ShowNum(13);	//显示“5.”,表示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();
			
			ShowGotTimeFlag=1;	//成功校时后,最后一列全亮,2s
			T0Count4=0;	//最后一列全亮的计数清零

			T0Count2=0;	//每次成功校对时间后,用于自动校时的计数清0
			ProofTimeCount=0;	//每次成功校对时间后,用于自动校时的计数清0
			
			ShowTimeFlag=1;
		}
		
		if(ShowTimeFlag)	//上电后第一次成功获取网络时间后,才显示二进制时间
		{
			CheckTime();	//检查时间
			ShowTime();	//更新显示时间
		}
	}
}

void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	//因使能了6T(双倍速)模式,所以定时器计算器中12T模式定时2ms对应的是6T模式的1ms
	TL0=0xF0;	//设置定时初值,定时1ms(补偿:改为了981us),晶振@11.0592MHz
	TH0=0xF8;	//设置定时初值,定时1ms(补偿:改为了981us),晶振@11.0592MHz
				//实测定时器计时260s,实际时间为265s,所以将原来的定时1000us改为定时1000*260/265us(约等于981us)
				//补偿后,计时2000s与实际的2000s相差不到1s

	MatrixLED_Tick();	//每隔1ms切换显示点阵屏的下一列
	
	T0Count0++;
	if(T0Count0>=1000)	//定时器计时,每隔1s,Time[5]加1
	{
		T0Count0=0;
		Time[5]++;
	}
	
	T0Count1++;
	T0Count2++;
	if(TimeOutCountFlag){T0Count3++;}	//TimeOutCountFlag为1才开始超时的计时
	else{T0Count3=0;}
	T0Count4++;
	if(T0Count1>=20)	//每隔20ms检测一次按键
	{
		T0Count1=0;
		Key_Tick();
	}
	if(T0Count2>=60000)	//1min,即60s
	{
		T0Count2=0;
		ProofTimeCount++;
		ProofTimeCount%=30;	//1min*30=30min,每隔30分钟自动联网校时
		if(!ProofTimeCount){GetTimeFlag=1;}
	}
	if(T0Count3>=20000)	//ESP8266连接WiFi的超时时间:20s
	{
		T0Count3=0;
		TimeOutFlag=1;
	}
	if(T0Count4>=2000)	//如果从网络获取了时间,最后一列全亮2秒钟
	{
		T0Count4=0;
		ShowGotTimeFlag=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>

*/

总结

用二进制来显示时间,用到的LED较少,而且也可以让自己更加熟悉二进制和十进制之间的转换。


http://www.kler.cn/a/522916.html

相关文章:

  • AI大模型开发原理篇-2:语言模型雏形之词袋模型
  • 【Redis】List 类型的介绍和常用命令
  • 分布式版本控制系统:Git
  • 设计模式面试题
  • PPT自动化 python-pptx -7: 占位符(placeholder)
  • 08.OSPF 特殊区域及其他特性
  • 什么是循环神经网络?
  • python.tkinter设计标记语言(渲染7-动态呈现标签) - 副本
  • 1.2第1章DC/DC变换器的动态建模-1.2Buck-Boost 变换器的交流模型--电力电子系统建模及控制 (徐德鸿)--读书笔记
  • game101 环节搭建 windows 平台 vs2022
  • doris:STRUCT
  • 【阅读笔记】New Edge Diected Interpolation,NEDI算法,待续
  • 跨域问题解释及前后端解决方案(SpringBoot)
  • 接口技术-第2次作业
  • Gradle配置指南:深入解析settings.gradle.kts(Kotlin DSL版)
  • AAAI2024论文合集解读|Multi-dimensional Fair Federated Learning-water-merged
  • IBMSamllPower服务器监控指标解读
  • 【数据库初阶】表的查询语句和聚合函数
  • leetcode 2920. 收集所有金币可获得的最大积分
  • 10 款《医学数据库和期刊》查阅网站
  • Lesson 119 A true story
  • 蓝桥杯模拟算法:多项式输出
  • 【Prometheus】Prometheus如何监控Haproxy
  • 菜鸟之路Day09一一集合进阶(二)
  • 【公因数匹配——暴力、(质)因数分解、哈希】
  • Github 2025-01-27 开源项目周报 Top15