基于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定时器时钟,用八位数码管进行显示,但是定时器时钟的精度较低࿰…...
AI 浪潮席卷中国年,开启科技新春新纪元
在这博主提前祝大家蛇年快乐呀!!! 随着人工智能(AI)技术的飞速发展,其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间,AI 技术也展现出了巨大的潜力,为中国年带…...
STM32 LED呼吸灯
接线图: 这里将正极接到PA0引脚上,负极接到GND,这样就高电平点亮LED,低电平熄灭。 占空比越大,LED越亮,占空比越小,LED越暗 PWM初始化配置 输出比较函数介绍: 用这四个函数配置输…...
机器学习day4
自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测 import numpy as np import torch import torch.nn as nn import torch.optim as optimizer import matplotlib.pyplot as pltclass1_points np.array([[2.1, 1.8],[1.9, 2…...
《哈佛家训》
《哈佛家训》是一本以教育为主题的书籍,旨在通过一系列富有哲理的故事和案例,传递积极的人生观、价值观和教育理念。虽然它并非直接由哈佛大学官方出版,但其内容深受读者喜爱,尤其是在家庭教育和个人成长领域。 以下是《哈佛家训…...
为什么redis会开小差?Redis 频繁异常的深度剖析与解决方案
文章目录 导读为什么redis会开小差?1.连接数过多2.bigkey3.慢命令操作4.内存策略不合理5.外部数据双写一致性6.保护机制未开启7. 数据集中过期8. CPU饱和9. 持久化阻塞10. 网络问题结论 导读 提起分布式缓存,想必大多数同学脑海中都会浮出redis这个名字…...
window中80端口被占用问题
1,查看报错信息 可以看到在启动项目的时候,8081端口被占用了,导致项目无法启动。 2,查看被占用端口的pid #语法 netstat -aon|findstr :被占用端口#示例 netstat -aon|findstr :8080 3,杀死进程 #语法 taikkill /pid…...
< OS 有关 > 阿里云:轻量应用服务器 的使用 :轻量化 阿里云 vpm 主机
原因: < OS 有关 > 阿里云:轻量应用服务器 的使用 :从新开始 配置 SSH 主机名 DNS Tailscale 更新OS安装包 最主要是 清除阿里云客户端这个性能杀手-CSDN博客 防止 I/O 祸害系统 操作: 查看进程&#x…...
解决报错“The layer xxx has never been called and thus has no defined input shape”
解决报错“The layer xxx has never been called and thus has no defined input shape”(这里写自定义目录标题) 报错显示 最近在跑yolo的代码时遇到这样一个错误,显示“the layer {self.name} has never been called”.这个程序闲置了很久,每次一遇到…...
Microsoft Visual Studio 2022 主题修改(补充)
Microsoft Visual Studio 2022 透明背景修改这方面已经有很多佬介绍过了,今天闲来无事就补充几点细节。 具体的修改可以参考:Microsoft Visual Studio 2022 透明背景修改(快捷方法)_material studio怎么把背景弄成透明-CSDN博客文…...
PyCharm接入DeepSeek实现AI编程
目录 效果演示 创建API key 在PyCharm中下载CodeGPT插件 配置Continue DeepSeek 是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的 AI 模型。DeepSeek-V3 是 DeepSeek 公司推出的最新一代 AI 模型。其前身是 DeepSeek-V2.5,经过持续的…...
C++ 包装器与绑定器的应用之回调函数的实现
回调函数的实现 在消息队列和网络库的框架中,当接收到消息(报文)时,回调用户自定义的函数对象,把消息(报文)参数传给它,由它决定如何处理。 queue参考文章:C queue(STL queue&…...
一文读懂DeepSeek-R1论文
目录 论文总结 摘要 1. 引言 1.1. 贡献 1.2. 评估结果总结 2. 方法 2.1. 概述 2.2. DeepSeek-R1-Zero:在基础模型上进行强化学习 2.2.1. 强化学习算法 2.2.2. 奖励建模 2.2.3. 训练模板 2.2.4. DeepSeek-R1-Zero 的性能、自我进化过程和顿悟时刻 2.3. …...
文献阅读 250128-Tropical forests are approaching critical temperature thresholds
Tropical forests are approaching critical temperature thresholds 来自 <Tropical forests are approaching critical temperature thresholds | Nature> 热带森林正在接近临界温度阈值 ## Abstract: The critical temperature beyond which photosynthetic machinery…...
【python】python基于机器学习与数据分析的二手手机特性关联与分类预测(源码+数据集)【独一无二】
👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 python基于机器学习与数据分析的二手手机特性关联与…...
构建可靠的时间序列预测模型:数据泄露检测、前瞻性偏差消除与因果关系验证
在时间序列分析领域中,存在多种可能影响分析结果有效性的技术挑战。其中,数据泄露、前瞻性偏差和因果关系违反是最为常见且具有显著影响的问题。 数据泄露:隐蔽的系统性错误 数据泄露是指在预测时理论上无法获取的信息,通过某种…...
Spring Boot - 数据库集成05 - 集成MongoDB
Spring Boot集成MongoDB 文章目录 Spring Boot集成MongoDB一:使用前的准备1:依赖导入 & 配置2:实体类创建 二:核心 - MongoRepository三:核心 - MongoTemplate1:集合操作2:文档操作(重点)3&…...
计算机网络之运输层
本文章目录结构出自于《王道计算机考研 计算机网络_哔哩哔哩_bilibili》 04 传输层 在网上看到其他人做了相关笔记,就不再多余写了,直接参考着学习吧。 王道考研 计算机网络笔记 第五章:传输层_王道计算机网络传输层_Baret-H的博客-CSDN博…...
DeepSeekMoE:迈向混合专家语言模型的终极专业化
一、结论写在前面 论文提出了MoE语言模型的DeepSeekMoE架构,目的是实现终极的专家专业化(expert specialization)。通过细粒度的专家分割和共享专家隔离,DeepSeekMoE相比主流的MoE架构实现了显著更高的专家专业化和性能。从较小的2B参数规模开始&#x…...
知识库管理驱动企业知识流动与工作协同创新模式
内容概要 知识库管理在现代企业中扮演着至关重要的角色,其价值不仅体现在知识的积累,还在于通过优质的信息流动促进协作与创新。有效的知识库能够将分散的信息整合为有序、易于访问的资源,为员工提供实时支持,进而提升整体工作效…...
二叉树-堆(补充)
二叉树-堆 1.二叉树的基本特性2.堆2.1.堆的基本概念2.2.堆的实现2.2.1.基本结构2.2.2.堆的初始化2.2.3.堆的销毁2.2.4.堆的插入2.2.5.取出堆顶的数据2.2.6.堆的删除2.2.7.堆的判空2.2.8.堆的数据个数2.2.9.交换2.2.10.打印堆数据2.2.11.堆的创建2.2.12.堆排序2.2.13.完整代码 3…...
Java面试题2025-并发编程基础(多线程、锁、阻塞队列)
并发编程 一、线程的基础概念 一、基础概念 1.1 进程与线程A 什么是进程? 进程是指运行中的程序。 比如我们使用钉钉,浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源)。 …...
【方法论】ChatGPT与DeepSeek的联合应用,提升工作效率的新解决方案
标题:ChatGPT与DeepSeek的联合应用,提升工作效率的新解决方案 【表格】ChatGPT与DeepSeek联合应用流程 阶段工具主要任务优势备注初稿生成ChatGPT基于用户输入生成初步内容高效、快速生成内容,适应多种主题适合生成长篇文章、报告、分析等验…...
RoboMaster- RDK X5能量机关实现案例(一)识别
作者:SkyXZ CSDN:https://blog.csdn.net/xiongqi123123 博客园:https://www.cnblogs.com/SkyXZ 在RoboMaster的25赛季,我主要负责了能量机关的视觉方案开发,目前整体算法已经搭建完成,实际方案上我使用的上…...
5分钟带你获取deepseek api并搭建简易问答应用
目录 1、获取api 2、获取base_url和chat_model 3、配置模型参数 方法一:终端中临时将加入 方法二:创建.env文件 4、 配置client 5、利用deepseek大模型实现简易问答 deepseek-v3是截止博文撰写之日,无论是国内还是国际上发布的大模型中…...
Ikigai是什么
Ikigai(生き甲斐) 是一个日语词语,意思是“生活的意义”或“生命的价值所在”。它是一种关于人生意义的哲学概念,源自日本文化,强调通过找到自己热爱、擅长、社会需要以及能带来经济回报的交集来实现幸福和满足感。 I…...
基于PyQt设计的智能停车管理系统
文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】设计意义【4】国内外研究现状【6】摘要1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】VSCODE【2】python【3】ptqt【4】HyperLPR31.5 参考文献二、安装Python环境1.1 环境介绍**1.2 Python版本介…...
Flink (十二) :Table API SQL (一) 概览
Apache Flink 有两种关系型 API 来做流批统一处理:Table API 和 SQL。Table API 是用于 Scala 和 Java 语言的查询API,它可以用一种非常直观的方式来组合使用选取、过滤、join 等关系型算子。Flink SQL 是基于 Apache Calcite 来实现的标准 SQL。无论输入…...
MySQL知识点总结(十三)
执行逻辑备份要具备哪些条件,其优缺点在哪。 逻辑备份是温备,创建逻辑备份文件时,MySQL服务器必须处于运行状态,其他应用程序在逻辑备份期间不能修改但可以执行读取操作。逻辑备份会把表结构和数据转换为SQL语句保存。 逻辑备份…...
ACL-2024 | 具身智能空间理解能力几何?EmbSpatial-Bench:视觉语言大模型在具身任务中空间理解水平测试基准
作者:Mengfei Du, Binhao Wu, Zejun Li, Xuanjing Huang, Zhongyu Wei 单位:复旦大学数据科学学院,复旦大学计算机科学学院 论文标题:EmbSpatial-Bench: Benchmarking Spatial Understanding for Embodied Tasks with Large Vis…...
动手学图神经网络(6):利用图神经网络进行点云分类
利用图神经网络进行点云分类 引言 在本教程中,大家将学习使用图神经网络(Graph Neural Networks, GNN)进行点云分类的基本工具。给定一组对象或点集的数据集,将这些对象嵌入到一个特征空间中,使得它们在特定任务下能够分类。将原始点云作为神经网络的输入,让网络学习捕…...
Ollama+DeepSeek本地大模型部署
1、Ollama 官网:https://ollama.com/ Ollama可以干什么? 可以快速在本地部署和管理各种大语言模型,操作命令和dokcer类似。 mac安装ollama: # 安装ollama brew install ollama# 启动ollama服务(默认11434端口…...
docker安装Redis:docker离线安装Redis、docker在线安装Redis、Redis镜像下载、Redis配置、Redis命令
一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull redis:7.4.0 2、离线包下载 两种方式: 方式一: -)在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -)导出 # 导出镜像…...
HTML 标题
HTML 标题 引言 HTML(超文本标记语言)是构建网页的基础,而标题则是网页中不可或缺的元素。标题不仅能够帮助用户快速了解网页内容,还能够对搜索引擎优化(SEO)产生重要影响。本文将详细介绍HTML标题的用法…...
记录 | MaxKB创建本地AI智能问答系统
目录 前言一、重建MaxKBStep1 复制路径Step2 删除MaxKBStep3 创建数据存储文件夹Step4 重建 二、创建知识库Step1 新建知识库Step2 下载测试所用的txtStep3 上传本地文档Step4 选择模型补充智谱的API Key如何获取 Step5 查看是否成功 三、创建应用Step1 新建应用Step2 配置AI助…...
Linux 非阻塞IO
Linux 非阻塞IO 1. fcntl() 在Linux操作系统中,fcntl() 是一个用于操作文件描述符的系统调用。它提供了多种功能,包括控制文件描述符的属性、管理文件锁定、设置文件的非阻塞模式等。 本文只截取了用于IO模型的 fcntl() 部分内容, fcntl() …...
美国本科申请文书PS写作中的注意事项
在完成了introduction之后,便可进入到main body的写作之中。美国本科申请文书PS的写作不同于学术论文写作,要求你提出论点进行论证之类。PS更多的注重对你自己的经历或者motivation的介绍和描述。而这一描述过程只能通过对你自己的过往的经历的展现才能体…...
Qt文件操作
目录 一、文件操作相关类 1.QFile 2.QFileInfo 3.QTextStream 4.QDataStream 5.QDir 6.QFileSystemWatcher 7.QTemporaryFile 二、文件操作示例 1.文本文件操作 2.目录操作 3.二进制文件操作 一、文件操作相关类 1.QFile QFile类用于文件的创建、读写、复制、删除…...
赚钱的究极认识
1、赚钱的本质是提供了价值或者价值想象 价值: 比如小米手机靠什么?“性价比”,什么饥饿营销,创新,用户参与,生态供应链,品牌这些不能说不重要,但是加在一起都没有“性价比”这3字重…...
【项目】基于Qt开发的音乐播放软件
目录 项目介绍 项目概述 界面开发 界面分析 创建工程 主界面布局设计 窗口主框架设计 界面美化 主窗口设定 添加图片资源 head处理 播放控制区处理 自定义控件 BtForm 推荐页面 自定义CommonPage 自定义ListItemBox 自定义MusicSlider 自定义VolumeTool 音…...
week08_文本匹配任务
1、文本匹配任务概述 狭义: 给定一组文本,判断其是否语义相似 今天天气不错 match 今儿个天不错呀 √ 今天天气不错 match 你的代码有bug 以分值形式给出相似度 今天天气不错 match 今儿个天不错呀 0.9 今天天气不错 match…...
01-01 五元组
[外链图片转存中…(img-8JR8fhPZ-1737855365022)] 01-01 五元组 网络中的五元组(5-Tuple) 是用于唯一标识一个网络连接或数据流的五个关键参数组合。这五个参数共同定义了数据包的来源、目的地以及传输方式,是网络设备(如防火墙…...
5.2 软件需求分析
文章目录 需求分析的意义软件需求的组成需求分析的5个方面需求分析方法 需求分析的意义 需求分析解决软件“做什么”的问题。由于开发人员比较熟悉计算机而不熟悉领域业务,用户比较熟悉领域业务而不熟悉计算机,双方需要通过交流,制定出完整、…...
OpenCV:在图像中添加噪声(瑞利、伽马、脉冲、泊松)
目录 简述 1. 瑞利噪声 2. 伽马噪声 3. 脉冲噪声 4. 泊松噪声 总结 相关阅读 OpenCV:在图像中添加高斯噪声、胡椒噪声-CSDN博客 OpenCV:高通滤波之索贝尔、沙尔和拉普拉斯-CSDN博客 OpenCV:图像处理中的低通滤波-CSDN博客 OpenCV&…...
进程池的制作(linux进程间通信,匿名管道... ...)
目录 一、进程间通信的理解 1.为什么进程间要通信 2.如何进行通信 二、匿名管道 1.管道的理解 2.匿名管道的使用 3.管道的五种特性 4.管道的四种通信情况 5.管道缓冲区容量 三、进程池 1.进程池的理解 2.进程池的制作 四、源码 1.ProcessPool.hpp 2.Task.hpp 3…...
C++:多继承习题3
题目内容: 声明一个时间类Time,时间类中有3个私有数据成员(Hour,Minute,Second)和两个公有成员函数(SetTime和PrintTime)。要求: (1) SetTime根据传递的3个参数为对象设置时间; &a…...
数论问题75
命题,证明:存在K∈N,使得对于每个n∈N,Kx2^n1都是合数。 证明:设n2^m,当m0,1,2,3,4时,a(m)2^(2^m)1都是素数。 a(0)213,a(1)2^215,a(2)2^4117&…...
Baklib引领数字化内容管理转型提升企业运营效率
内容概要 在数字化迅速发展的背景下,企业正面临着前所未有的内容管理挑战。传统的内容管理方式已难以适应如今的信息爆炸,企业需要更加高效、智能的解决方案以应对复杂的数据处理需求。Baklib作为行业的先锋,以其创新技术对数字化内容管理进…...
2025年AI手机集中上市,三星Galaxy S25系列上市
2025年被认为是AI手机集中爆发的一年,各大厂商都会推出搭载人工智能的智能手机。三星Galaxy S25系列全球上市了。 三星Galaxy S25系列包含S25、S25和S25 Ultra三款机型,起售价为800美元(约合人民币5800元)。全系搭载骁龙8 Elite芯…...
Vue2官网教程查漏补缺学习笔记 - 3Vue实例4模板语法5计算属性监听器
3 Vue实例 3.1 创建一个 Vue 实例 每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的: var vm new Vue({// 选项 })虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变…...