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

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

目录

  • 系列文章目录
  • 前言
  • 一、效果展示
  • 二、原理分析
  • 三、各模块代码
    • 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.0592MHzTH0=0xFC;	//设置定时初值,定时1ms,晶振@11.0592MHzTF0=0;	//清除TF0标志TR0=1;	//定时器0开始计时ET0=1;	//打开定时器0中断允许EA=1;	//打开总中断PT0=0;	//当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}/*定时器中断函数模板
void Timer0_Routine() interrupt 1	//定时器0中断函数
{static unsigned int T0Count;	//定义静态变量TL0=0x66;	//设置定时初值,定时1ms,晶振@11.0592MHzTH0=0xFC;	//设置定时初值,定时1ms,晶振@11.0592MHzT0Count++;if(T0Count>=1000){T0Count=0;}
}
*/

3、串口通信

h文件

#ifndef __UART_H__
#define __UART_H__void UART_Init();
void UART_SendByte(unsigned char Byte);
void UART_SendString(char *String);#endif

c文件

#include <REGX52.H>/*** @brief  串口初始化,115200bps@11.0592MHz(6T模式),误差:0.00%* @param  无* @retval 无*/
void Uart_Init(void)
{PCON|=0x80;	//使能波特率倍速位SMOD,倍速后为115200bpsSCON =0x50;	//8位数据,可变波特率
//	AUXR&=0xBF;	//定时器时钟12T模式(89C52芯片无需设置这个)
//	AUXR&=0xFE;	//串口1选择定时器1为波特率发生器(89C52芯片无需设置这个)TMOD&=0x0F;	//设置定时器模式TMOD|=0x20;	//设置定时器模式TL1=0xFF;	//设置定时初始值TH1=0xFF;	//设置定时重载值ET1=0;	//禁止定时器1中断TR1=1;	//定时器1开始计时EA=1;	//开启所有中断ES=1;	//开启串口中断PS=1;	//要设置串口中断的优先级比定时器的高,//否则发送或接收数据的时候会被打断,影响数据发送和接收
}/*** @brief	串口发送一个字节数据* @param	Byte 要发送的一个字节数据* @retval	无*/
void UART_SendByte(unsigned char Byte)
{SBUF=Byte;while(TI==0);TI=0;
}/*** @brief	串口发送字符串* @param	String 要发送的字符串* @retval	无*/
void UART_SendString(char *String)
{while(*String){UART_SendByte(*String);String++;}
}/*串口中断函数模板
void UART_Routine() interrupt 4
{if(RI==1){RI=0;}
}
*/

4、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,否则全都是65if(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;	//主程序中获取键码值之后键码值清零,在下一次定时器扫描按键之前再次获取键码值,一定会返回0return 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,并获取了IPDelay(500);}else	//如果WiFi不能连接{	//WiFi账号密码是保存到ESP8266的flash里的,如果在else中连接成功,//下次上电就可以直接连接WiFi并获得IP,就不会进入此else中WiFiDisconnectFlag=0;LCD_ShowString(2,1,"DISCONNECT");	//表示ESP8266不能连接WiFiDelay(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.5sOKFlag=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置1TimeOutCountFlag=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置1TimeOutCountFlag=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”,显示2sT0Count4=0;	//显示“OK”2s的计数清零T0Count2=0;	//每次成功校对时间后,用于自动校时的计数清0ProofTimeCount=0;	//每次成功校对时间后,用于自动校时的计数清0ShowTimeFlag=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模式的10msTL0=0x00;	//设置定时初值,定时10ms,晶振@11.0592MHzTH0=0xB8;	//设置定时初值,定时10ms,晶振@11.0592MHzT0Count1++;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;	//接收标志位清0TempChar=SBUF;	//用缓存变量取出SBUF的数据//如果接收到的字符是下面四个之一,则从数组Judge的索引0的位置开始保存接下来的字符if(TempChar=='O' || TempChar=='D' || TempChar=='r' || TempChar=='I'){i=0;}//如果不小心触碰到ESP8266模块,导致接触不良而重启模块的话,获取IP后令WiFiGotIPFlag置1,再在主函数中重新连接网络//返回的时间数据里有“PI”,会误使WiFiGotIPFlag置1,所以要有下面的处理if(TempChar=='I'){Judge[1]='\0';}Judge[i]=TempChar;i++;if(ReceiveTimeFlag)	//开始接收包含时间信息的字符串{j++;if(j>=4){TimeBuffer[j-4]=TempChar;}if(j>=28){ReceiveTimeFlag=0;GotTimeFlag=1;}}//接收到“ready”,注意,下面的if中Judge[1]和Judge[2]的字符不能和Judge[0]的重复if(Judge[0]=='r' && Judge[1]=='e' && Judge[2]=='a'){Judge[1]='\0';ReadyFlag=1;}//接收到“DISCONNECT”if(Judge[0]=='I' && Judge[1]=='S' && Judge[2]=='C'){Judge[1]='\0';WiFiDisconnectFlag=1;}//接收到“GOT IP”if(Judge[0]=='I' && Judge[1]=='P'){Judge[1]='\0';WiFiGotIPFlag=1;}//接收到“OK”if(Judge[0]=='O' && Judge[1]=='K'){Judge[1]='\0';OKFlag=1;}//接收到“Date: ”,说明接下来的字符串包含时间信息if(Judge[0]=='D' && Judge[1]=='a' && Judge[2]=='t'){Judge[1]='\0';ReceiveTimeFlag=1;j=0;}i%=5;	//Judge数组只有5个数据}
}/*月份和星期January(一月)
February(二月)
March(三月)
April(四月)
May(五月)
June(六月)
July(七月)
August(八月)
September(九月)
October(十月)
November(十一月)
December(十二月)Monday(星期一)
Tuesday(星期二)
Wednesday(星期三)
Thursday(星期四)
Friday(星期五)
Saturday(星期六)
Sunday(星期日)*//*网站返回的时间数据(第四行)HTTP/1.1 400 Bad Request
Content-Type: text/html; charset=us-ascii
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 13 Jan 2025 08:27:07 GMT
Connection: close
Content-Length: 326<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Bad Request</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Bad Request - Invalid Verb</h2>
<hr><p>HTTP Error 400. The request verb is invalid.</p>
</BODY></HTML>*/

总结

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

相关文章:

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

目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、延时2、定时器03、串口通信4、DS13025、LCD16026、独立按键 四、主函数总结 系列文章目录 前言 之前做了一个WiFi定时器时钟&#xff0c;用八位数码管进行显示&#xff0c;但是定时器时钟的精度较低&#xff0…...

机器学习 ---逻辑回归

逻辑回归是属于机器学习里面的监督学习&#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 介…...

【C++基础】多线程并发场景下的同步方法

如果在多线程程序中对全局变量的访问没有进行适当的同步控制&#xff08;例如使用互斥锁、原子变量等&#xff09;&#xff0c;会导致多个线程同时访问和修改全局变量时发生竞态条件&#xff08;race condition&#xff09;。这种竞态条件可能会导致一系列不确定和严重的后果。…...

Linux常见问题解决方法--1

常见安全工具、设备 工具 端口及漏洞扫描&#xff1a;Namp、Masscan 抓包&#xff1a;Wireshark&#xff0c;Burpsuite、Fiddler、HttpCanary Web自动化安全扫描&#xff1a;Nessus、Awvs、Appscan、Xray 信息收集&#xff1a;Oneforall、hole 漏洞利用&#xff1a;MSF、…...

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

银行卡三要素验证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…...

Kafka 入门与应用实战:吞吐量优化与与 RabbitMQ、RocketMQ 的对比

前言 在现代微服务架构和分布式系统中&#xff0c;消息队列作为解耦组件&#xff0c;承担着重要的职责。它不仅提供了异步处理的能力&#xff0c;还能确保系统的高可用性、容错性和扩展性。常见的消息队列包括 Kafka、RabbitMQ 和 RocketMQ&#xff0c;其中 Kafka 因其高吞吐量…...

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

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

数据结构与算法再探(六)动态规划

目录 动态规划 (Dynamic Programming, DP) 动态规划的基本思想 动态规划的核心概念 动态规划的实现步骤 动态规划实例 1、爬楼梯 c 递归&#xff08;超时&#xff09;需要使用记忆化递归 循环 2、打家劫舍 3、最小路径和 4、完全平方数 5、最长公共子序列 6、0-1背…...

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

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

Python爬虫获取custom-1688自定义API操作接口

一、引言 在电子商务领域&#xff0c;1688作为国内领先的B2B平台&#xff0c;提供了丰富的API接口&#xff0c;允许开发者获取商品信息、店铺信息等。其中&#xff0c;custom接口允许开发者进行自定义操作&#xff0c;获取特定的数据。本文将详细介绍如何使用Python调用1688的…...

Autogen_core: Reflection

目录 代码代码逻辑解释&#xff1a;数据类定义&#xff1a;CoderAgent 类&#xff1a;ReviewerAgent 类&#xff1a;主程序&#xff1a; 完成的功能&#xff1a; 代码 from dataclasses import dataclassdataclass class CodeWritingTask:task: strdataclass class CodeWritin…...

GitHub 仓库的 Archived 功能详解:中英双语

GitHub 仓库的 Archived 功能详解 一、什么是 GitHub 仓库的 “Archived” 功能&#xff1f; 在 GitHub 上&#xff0c;“Archived” 是一个专门用于标记仓库状态的功能。当仓库被归档后&#xff0c;它变为只读模式&#xff0c;所有的功能如提交代码、创建 issue 和 pull req…...

.NET Core缓存

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

Ubuntu 20.04安装Protocol Buffers 2.5.0

个人博客地址&#xff1a;Ubuntu 20.04安装Protocol Buffers 2.5.0 | 一张假钞的真实世界 安装过程 Protocol Buffers 2.5.0源码下载&#xff1a;https://github.com/protocolbuffers/protobuf/tree/v2.5.0。下载并解压。 将autogen.sh文件中以下内容&#xff1a; curl htt…...

【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G

2025 - 01 - 21 - 第 45 篇 【洛谷】贪心算法题单 -【 贪心算法】 - 【学习笔记】 作者(Author): 郑龙浩 / 仟濹(CSND账号名) 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 【贪心算法】 文章目录 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06…...

14.模型,纹理,着色器

模型、纹理和着色器是计算机图形学中的三个核心概念&#xff0c;用通俗易懂的方式来解释&#xff1a; 1. 模型&#xff1a;3D物体的骨架 通俗解释&#xff1a; 模型就像3D物体的骨架&#xff0c;定义了物体的形状和结构。 比如&#xff0c;一个房子的模型包括墙、屋顶、窗户等…...

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

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

Scale AI 创始人兼 CEO采访

Scale AI 创始人兼 CEO 亚历山大王&#xff08;Alexander Wang&#xff09;首次亮相节目接受采访。他的公司专注于为人工智能工具提供准确标注的数据。早在 2022 年&#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推理仍然…...

【C】memory 详解

<memory.h> 是一个 C 标准库头文件&#xff0c;提供了一组内存管理函数&#xff0c;用于分配、释放和操作动态内存。这些函数主要操作的是未初始化的内存块&#xff0c;是早期 C 编程中常用的内存操作工具。 尽管在现代 C 编程中更推荐使用<cstring>或<memory&…...

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

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

分布式系统相关面试题收集

目录 什么是分布式系统&#xff0c;以及它有哪些主要特性&#xff1f; 分布式系统中如何保证数据的一致性&#xff1f; 解释一下CAP理论&#xff0c;并说明在分布式系统中如何权衡CAP三者&#xff1f; 什么是分布式事务&#xff0c;以及它的实现方式有哪些&#xff1f; 什么是…...

CSAPP学习:前言

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

kaggle比赛入门 - House Prices - Advanced Regression Techniques(第三部分)

本文承接上一篇。 1. 数据预处理流水线&#xff08;pipelines&#xff09; from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.impute import SimpleImputer from sklearn.preprocessing import StandardScaler, OneHotEnc…...

Linux 命令之技巧(Tips for Linux Commands)

Linux 命令之技巧 简介 Linux ‌是一种免费使用和自由传播的类Unix操作系统&#xff0c;其内核由林纳斯本纳第克特托瓦兹&#xff08;Linus Benedict Torvalds&#xff09;于1991年10月5日首次发布。Linux继承了Unix以网络为核心的设计思想&#xff0c;是一个性能稳定的多用户…...

从 GShard 到 DeepSeek-V3:回顾 MoE 大模型负载均衡策略演进

作者&#xff1a;小天狼星不来客 原文&#xff1a;https://zhuanlan.zhihu.com/p/19117825360 故事要从 GShard 说起——当时&#xff0c;人们意识到拥有数十亿甚至数万亿参数的模型可以通过某种形式的“稀疏化&#xff08;sparsified&#xff09;”来在保持高精度的同时加速训…...