基于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定时器时钟,用八位数码管进行显示,但是定时器时钟的精度较低࿰…...
机器学习 ---逻辑回归
逻辑回归是属于机器学习里面的监督学习,它是以回归的思想来解决分类问题的一种非常经典的二分类分类器。由于其训练后的参数有较强的可解释性,在诸多领域中,逻辑回归通常用作 baseline 模型,以方便后期更好的挖掘业务相关信息或提…...
拟合损失函数
文章目录 拟合损失函数一、线性拟合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++基础】多线程并发场景下的同步方法
如果在多线程程序中对全局变量的访问没有进行适当的同步控制(例如使用互斥锁、原子变量等),会导致多个线程同时访问和修改全局变量时发生竞态条件(race condition)。这种竞态条件可能会导致一系列不确定和严重的后果。…...
Linux常见问题解决方法--1
常见安全工具、设备 工具 端口及漏洞扫描:Namp、Masscan 抓包:Wireshark,Burpsuite、Fiddler、HttpCanary Web自动化安全扫描:Nessus、Awvs、Appscan、Xray 信息收集:Oneforall、hole 漏洞利用:MSF、…...
银行卡三要素验证接口:方便快捷地实现银行卡核验功能
银行卡三要素验证API:防止欺诈交易的有力武器 随着互联网的发展,电子支付方式也越来越普及。在支付过程中,银行卡是最常用的支付工具之一。然而,在一些支付场景中,需要对用户的银行卡信息进行验证,以确保支…...
利用JSON数据类型优化关系型数据库设计
利用JSON数据类型优化关系型数据库设计 前言 在关系型数据库中,传统的结构化存储方式要求预先定义好所有的列及其数据类型。 然而,随着业务的发展,这种设计可能会显得不够灵活,尤其是在需要扩展单个列的描述功能时。 JSON数据…...
极简壁纸js逆向
首先抓包,翻页可以看到数据储存在该包 可以看到随着页面变化,只有current在变化 而且载荷都没有加密,看来不用js逆向了 爬取代码 import os import asyncio import aiohttp import jsonheaders {"accept": "application/j…...
Kafka 入门与应用实战:吞吐量优化与与 RabbitMQ、RocketMQ 的对比
前言 在现代微服务架构和分布式系统中,消息队列作为解耦组件,承担着重要的职责。它不仅提供了异步处理的能力,还能确保系统的高可用性、容错性和扩展性。常见的消息队列包括 Kafka、RabbitMQ 和 RocketMQ,其中 Kafka 因其高吞吐量…...
JAVA 接口、抽象类的关系和用处 详细解析
接口 - Java教程 - 廖雪峰的官方网站 一个 抽象类 如果实现了一个接口,可以只选择实现接口中的 部分方法(所有的方法都要有,可以一部分已经写具体,另一部分继续保留抽象),原因在于: 抽象类本身…...
数据结构与算法再探(六)动态规划
目录 动态规划 (Dynamic Programming, DP) 动态规划的基本思想 动态规划的核心概念 动态规划的实现步骤 动态规划实例 1、爬楼梯 c 递归(超时)需要使用记忆化递归 循环 2、打家劫舍 3、最小路径和 4、完全平方数 5、最长公共子序列 6、0-1背…...
使用PC版本剪映制作照片MV
目录 制作MV模板时长调整拖动边缘缩短法分割删除法变速法整体调整法 制作MV 导入音乐 导入歌词 点击歌词 和片头可以修改字体: 还可以给字幕添加动画效果: 导入照片,自动创建照片轨: 修改片头字幕:增加两条字幕轨&…...
Python爬虫获取custom-1688自定义API操作接口
一、引言 在电子商务领域,1688作为国内领先的B2B平台,提供了丰富的API接口,允许开发者获取商品信息、店铺信息等。其中,custom接口允许开发者进行自定义操作,获取特定的数据。本文将详细介绍如何使用Python调用1688的…...
Autogen_core: Reflection
目录 代码代码逻辑解释:数据类定义:CoderAgent 类:ReviewerAgent 类:主程序: 完成的功能: 代码 from dataclasses import dataclassdataclass class CodeWritingTask:task: strdataclass class CodeWritin…...
GitHub 仓库的 Archived 功能详解:中英双语
GitHub 仓库的 Archived 功能详解 一、什么是 GitHub 仓库的 “Archived” 功能? 在 GitHub 上,“Archived” 是一个专门用于标记仓库状态的功能。当仓库被归档后,它变为只读模式,所有的功能如提交代码、创建 issue 和 pull req…...
.NET Core缓存
目录 缓存的概念 客户端响应缓存 cache-control 服务器端响应缓存 内存缓存(In-memory cache) 用法 GetOrCreateAsync 缓存过期时间策略 缓存的过期时间 解决方法: 两种过期时间策略: 绝对过期时间 滑动过期时间 两…...
Ubuntu 20.04安装Protocol Buffers 2.5.0
个人博客地址:Ubuntu 20.04安装Protocol Buffers 2.5.0 | 一张假钞的真实世界 安装过程 Protocol Buffers 2.5.0源码下载:https://github.com/protocolbuffers/protobuf/tree/v2.5.0。下载并解压。 将autogen.sh文件中以下内容: curl htt…...
【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G
2025 - 01 - 21 - 第 45 篇 【洛谷】贪心算法题单 -【 贪心算法】 - 【学习笔记】 作者(Author): 郑龙浩 / 仟濹(CSND账号名) 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 【贪心算法】 文章目录 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06…...
14.模型,纹理,着色器
模型、纹理和着色器是计算机图形学中的三个核心概念,用通俗易懂的方式来解释: 1. 模型:3D物体的骨架 通俗解释: 模型就像3D物体的骨架,定义了物体的形状和结构。 比如,一个房子的模型包括墙、屋顶、窗户等…...
【微服务与分布式实践】探索 Dubbo
核心组件 服务注册与发现原理 服务提供者启动时,会将其服务信息(如服务名、版本、所在节点的网络地址等)注册到注册中心。服务消费者则可以从注册中心发现可用的服务提供者列表,并与之通信。注册中心会存储服务的信息,…...
Scale AI 创始人兼 CEO采访
Scale AI 创始人兼 CEO 亚历山大王(Alexander Wang)首次亮相节目接受采访。他的公司专注于为人工智能工具提供准确标注的数据。早在 2022 年,王成为世界上最年轻的白手起家亿万富翁。 美国在全球人工智能竞赛中的地位,以及它与中…...
Java 大视界 -- Java 大数据在生物信息学中的应用与挑战(67)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
NeuIPS 2024 | CoT推理的新突破:推理边界框架(RBF)
近年来,大型语言模型(LLMs)在推理任务上的能力不断提升,尤其是 思维链(Chain-of-Thought, CoT) 技术,使得模型可以逐步推演逻辑,提高预测准确率。然而,当前的CoT推理仍然…...
【C】memory 详解
<memory.h> 是一个 C 标准库头文件,提供了一组内存管理函数,用于分配、释放和操作动态内存。这些函数主要操作的是未初始化的内存块,是早期 C 编程中常用的内存操作工具。 尽管在现代 C 编程中更推荐使用<cstring>或<memory&…...
linux——进程树的概念和示例
一些程序进程运行后,会调用其他进程,这样就组成了一个进程树。 比如,在Windows XP的“运行”对话框中输入“cmd”启动命令行控制台,然后在命令行中输入“notepad”启动记事本,那么命令行控制台进程“cmd.exe”和记事本进程“note…...
分布式系统相关面试题收集
目录 什么是分布式系统,以及它有哪些主要特性? 分布式系统中如何保证数据的一致性? 解释一下CAP理论,并说明在分布式系统中如何权衡CAP三者? 什么是分布式事务,以及它的实现方式有哪些? 什么是…...
CSAPP学习:前言
前言 本书简称CS:APP。 背景知识 一些基础的C语言知识 如何阅读 Do-做系统 在真正的系统上解决具体的问题,或是编写和运行程序。 章节 2025-1-27 个人认为如下章节将会对学习408中的操作系统与计算机组成原理提供帮助,于是先凭借记忆将其简单…...
kaggle比赛入门 - House Prices - Advanced Regression Techniques(第三部分)
本文承接上一篇。 1. 数据预处理流水线(pipelines) 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操作系统,其内核由林纳斯本纳第克特托瓦兹(Linus Benedict Torvalds)于1991年10月5日首次发布。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户…...
从 GShard 到 DeepSeek-V3:回顾 MoE 大模型负载均衡策略演进
作者:小天狼星不来客 原文:https://zhuanlan.zhihu.com/p/19117825360 故事要从 GShard 说起——当时,人们意识到拥有数十亿甚至数万亿参数的模型可以通过某种形式的“稀疏化(sparsified)”来在保持高精度的同时加速训…...
