基于STM32HAL库的万年历系统

目录

前言

项目分析

CubeMX配置

工程文件结构

App文件夹

Lib文件夹

库文件代码

myrtc.c

myrtc.h

oled库&字符库

knob.c

knob.h

业务逻辑代码

task_main.c

task_main.h


前言

        本篇博客来做一个简易的万年历系统,需要用到旋转编码器0.96寸OLED屏幕,以及STM32内部的RTC时钟;旋转编码器和RTC时钟在我的HAL库教程中有讲——HAL库零基础入门


项目分析

        程序应当分为两种主要模式:普通模式与设置模式;普通模式下程序从RTC中获取当前的unix时间戳,然后将其按照一定的格式显示在OLED屏幕上;按下旋转编码器,可以进入设置模式,会有光标在数据下闪烁,顺时针旋转旋钮数据增加,逆时针减少;再次按下旋钮切换到下一个参数的修改,依次修改年、月、日、时、分、秒后再次按下旋钮就回到普通模式,将设置好的时间转换为戳写入到RTC中,同时自动设置好星期,并显示在OLED屏幕上。

        整体框架如下,十分简单:

        我们要写出三个模块的库,且模块之间相互独立,减少耦合性;不过模块库的代码依旧会与其需要的硬件操作耦合,尽可能让其具有方便的移植性;最后应用层代码调用各个库,实现最终的逻辑。


CubeMX配置

        先来到System Core中的SYS,将Debug设置为Serial Wire:

        然后来到RCC,将两个时钟源都设置为晶振,RTC需要用到LSE(低速外部时钟):

        来到Timers里的RTC,勾选Activate Clock Source,开启RTC:

        来到顶部的时钟设置,Clock Configuration,选择主频为72MHz,然后将RTC的时钟源设置为LSE:

        我们的旋钮接在了TIM1的TI1FP1和TI2FP2,即PA8和PA9,因此回到主界面,进入Timers里的TIM1,选择组合通道(Combined Channels)为编码器模式(Encoder Mode)

        然后来到下面的详细设置,由于编码器一个脉冲,计数器会计数两次,因此设置为2分频;为了让其顺时针旋转时计数值增加,反转时计数值减少,将通道二的极性反转一下,波形翻转(注意:有些编码器可能正转的时候计数值会增加,那就保持默认即可):

        旋钮的按键引脚我接在PB15,设置为GPIO的上拉输入模式即可,这里不演示了。

        我们还要用到OLED屏幕,来到Connectivity的I2C1设置,开启I2C,并在下面的详细设置中将I2C速度设置为快速模式(Fast Mode)避免发送时间太长影响按钮的轮询判断:

        为了调试还可以自行开启串口,打印调试信息,使用最简单的轮询模式发送即可。

        最后来到Project Manager中的Code Generator,勾选为每个外设生成单独的.c/.h文件:

        一切就绪就可以生成代码了。


工程文件结构

        在工程文件夹下新建App文件夹以及Lib文件夹:

App文件夹

        该文件夹里写的是应用层代码,以后大型工程可能会用到FreeRTOS等操作系统,通常会将业务逻辑解耦分成一个个Task任务,这次我们只有一个主任务,因此创建task_main.c以及task_main.h文件:


Lib文件夹

        该文件夹用来放置我们的库代码,,如下:


库文件代码

myrtc.c

        这在我的HAL库教程博客里有详细的解释,请移步相关博客,这里不再赘述:

#include "myrtc.h"

#define RTC_INIT_FLAG 0xAAAA

// 进入RTC配置模式(关闭写保护)
static HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef *hrtc)
{
  uint32_t tickstart = 0U;

  tickstart = HAL_GetTick();
  // 等待RTC就绪
  while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
  {
    if ((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE)
    {
      return HAL_TIMEOUT;
    }
  }

  // 解除寄存器写保护
  __HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);


  return HAL_OK;
}

// 退出RTC配置模式(恢复写保护)
static HAL_StatusTypeDef RTC_ExitInitMode(RTC_HandleTypeDef *hrtc)
{
  uint32_t tickstart = 0U;

  // 恢复寄存器写保护
  __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);

  tickstart = HAL_GetTick();
  // 等待配置完成
  while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
  {
    if ((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE)
    {
      return HAL_TIMEOUT;
    }
  }

  return HAL_OK;
}

// 原子读取32位计数器(处理跨16位读取时的翻转)
static uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef *hrtc)
{
  uint16_t high1 = 0U, high2 = 0U, low = 0U;
  uint32_t timecounter = 0U;

  high1 = READ_REG(hrtc->Instance->CNTH & RTC_CNTH_RTC_CNT);
  low   = READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT);
  high2 = READ_REG(hrtc->Instance->CNTH & RTC_CNTH_RTC_CNT);

  // 高位变化时重新读取低位
  if (high1 != high2)
  {
    /* In this case the counter roll over during reading of CNTL and CNTH registers,
       read again CNTL register then return the counter value */
    timecounter = (((uint32_t) high2 << 16U) | READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT));
  }
  else
  {
    /* No counter roll over during reading of CNTL and CNTH registers, counter
       value is equal to first value of CNTL and CNTH */
    timecounter = (((uint32_t) high1 << 16U) | low);
  }

  return timecounter;
}

// 原子写入32位计数器
static HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef *hrtc, uint32_t TimeCounter)
{
  HAL_StatusTypeDef status = HAL_OK;

  /* Set Initialization mode */
  if (RTC_EnterInitMode(hrtc) != HAL_OK)
  {
    status = HAL_ERROR;
  }
  else
  {
    // 写入高16位
    WRITE_REG(hrtc->Instance->CNTH, (TimeCounter >> 16U));
    // 写入低16位
    WRITE_REG(hrtc->Instance->CNTL, (TimeCounter & RTC_CNTL_RTC_CNT));

    // 退出时自动同步
    if (RTC_ExitInitMode(hrtc) != HAL_OK)
    {
      status = HAL_ERROR;
    }
  }

  return status;
}

// 设置RTC时间(Unix时间戳格式)
HAL_StatusTypeDef sakabu_RTC_SetTime(struct tm *time)
{
	uint32_t unixTime = mktime(time);
	return RTC_WriteTimeCounter(&hrtc, unixTime);
}

// 获取RTC时间(返回tm结构体指针)
struct tm* sakabu_RTC_GetTime(void)
{
	time_t unixTime = RTC_ReadTimeCounter(&hrtc);
	return gmtime(&unixTime);
}

// RTC初始化(首次上电加载默认时间)
void sakabu_RTC_Init(void)
{
						//读取备份寄存器的函数,10个寄存器
	uint32_t initFlag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
	if(initFlag == RTC_INIT_FLAG) return;
	
	if (HAL_RTC_Init(&hrtc) != HAL_OK)
    {
        Error_Handler();
    }
	
    // 设置默认时间:2025-01-01 23:59:55
	struct tm time = {
		//年要存储的是年份和1900年的差值
		.tm_year = 2025 - 1900,//2025
		//月份的取值是0~11,代表1到12月
		.tm_mon = 1-1,//1月
		.tm_mday = 1,
		.tm_hour = 23,
		.tm_min = 59,
		.tm_sec = 55,
	};
	sakabu_RTC_SetTime(&time);
    // 设置初始化标记
	HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, RTC_INIT_FLAG);
}

myrtc.h

#ifndef __MYRTC_H__
#define __MYRTC_H__

#include "stm32f1xx_hal.h"
#include "rtc.h"
#include "time.h"

HAL_StatusTypeDef sakabu_RTC_SetTime(struct tm *time);
struct tm* sakabu_RTC_GetTime(void);
void sakabu_RTC_Init(void);

#endif

oled库&字符库

        我是用的别人写好的驱动库,我上传到我的资源了,把oled和font的.c/.h文件放入Lib文件夹下即可,使用方法如下:

        ● STM32初始化IIC完成后调用OLED_Init()初始化OLED. 注意STM32启动比OLED上电快, 可等待20ms再初始化OLED
        ● 调用OLED_NewFrame()开始绘制新的一帧
        ● 调用OLED_DrawXXX()系列函数绘制图形到显存 调用OLED_Printxxx()系列函数绘制文本到显存
        ● 调用OLED_ShowFrame()将显存内容显示到OLED


knob.c

        为旋钮封装一个模块库,代码思路如下:

        注意这里的正转反转任务,旋钮具体要做什么工作,应该是由负责具体业务逻辑的主任务规定的,层次较低的模块库,只能被更高层调用,而且业务逻辑实现在模块库的话,模块库就没有任何可移植性了,因此我们自己写一个回调函数,通过指针注册的方式,在库内提供一个函数指针变量(或数组),上层在使用对应库时,将此函数指针指向某函数,当模块库发生某事件时需要通知上层代码时,就调用此函数指针指向的函数即可

#include "knob.h"

#define COUNTER_INIT_VALUE 65535/2  // 编码器中点值(防溢出设计)
#define BTN_DEBOUNCE_TICKS 10       // 按键消抖时间阈值(单位:ms)

typedef enum {Pressed, Unpressed} BtnState;

// 设置编码器计数器值
void setCounter(int value)
{
	__HAL_TIM_SetCounter(&htim1, value);  // 直接操作硬件定时器
}

// 获取当前编码器计数值
uint32_t getCounter(void)
{
	return __HAL_TIM_GetCounter(&htim1);  // 读取硬件定时器
}

// 获取按键状态(带电平转换)
BtnState getBtnState(void)
{
	return HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_15) == GPIO_PIN_RESET ? Pressed : Unpressed;
}

// 获取系统时钟基准
uint32_t getTick(void)
{
	return HAL_GetTick();  // 用于时间间隔计算
}

// 回调函数指针容器
KnobCallback onForwardCallback = NULL;   // 正转事件回调
KnobCallback onBackwardCallback = NULL;  // 反转事件回调
KnobCallback onPressedCallback = NULL;   // 按键事件回调

// 注册正转回调
void Knob_SetForwardCallback(KnobCallback callback)
{
	onForwardCallback = callback;  // 绑定用户逻辑
}

// 注册反转回调
void Knob_SetBackwardCallback(KnobCallback callback)
{
	onBackwardCallback = callback; // 绑定用户逻辑
}

// 注册按键回调
void Knob_SetPressedCallback(KnobCallback callback)
{
	onPressedCallback = callback;  // 绑定用户逻辑
}

// 编码器初始化(启动捕获)
void Knob_Init(void)
{
	HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);  // 启动编码器模式
	setCounter(COUNTER_INIT_VALUE);                  // 初始化到中间位置
}

// 主检测循环(需周期性调用)
void Knob_Loop(void)
{
	/* 旋转检测逻辑 */
	uint32_t counter = getCounter();
	// 与初始值比较判断方向
	if(counter > COUNTER_INIT_VALUE)        // 正转条件
	{
		if(onForwardCallback != NULL) onForwardCallback();  // 触发正转事件
	}
	else if(counter < COUNTER_INIT_VALUE)   // 反转条件
	{
		if(onBackwardCallback != NULL) onBackwardCallback();// 触发反转事件
	}
	setCounter(COUNTER_INIT_VALUE);         // 重置计数器(相对式编码器模式)

	/* 按键检测逻辑 */
	BtnState btnState = getBtnState();
	static uint8_t callbackState = 0;  // 防重复触发标记(0-待触发 1-已触发)
	static uint32_t pressedTime = 0;   // 按下时刻记录
	
	if(btnState == Pressed)
	{
		if(pressedTime == 0)  // 首次按下记录时间
			pressedTime = getTick();
		// 达到消抖时间且未触发过回调
		else if(callbackState == 0 && getTick() - pressedTime > BTN_DEBOUNCE_TICKS)
		{
			if(onPressedCallback != NULL) onPressedCallback();  // 触发按键事件
			callbackState = 1;  // 标记已触发
		}
	}
	else  // 按键松开
	{
		pressedTime = 0;       // 重置时间记录
		callbackState = 0;     // 重置触发标记
	}
}

knob.h

#ifndef __KNOB_H__
#define __KNOB_H__

#include "tim.h"

typedef void (*KnobCallback)(void);

void Knob_Init(void);

void Knob_Loop(void);

void Knob_SetForwardCallback(KnobCallback callback);

void Knob_SetBackwardCallback(KnobCallback callback);

void Knob_SetPressedCallback(KnobCallback callback);

#endif

业务逻辑代码

        我们最终的main.c函数很简单,都是调用我们创建的task_main.c里面封装好的函数,这样函数结构清晰明确:

        MainTaskInit里面是各个模块的初始化函数,后面MainTask就是各个模块需要循环检测的函数。我们在写task_main.c之前,先来到main.c的MX_RTC_Init()函数,这是CubeMX自动生成的RTC初始化函数,在我的HAL库教程里说过,里面有个小小的bug,导致我们复位的时候,RTC会停止运行一小会,修改如下:

        将下面的初始化代码移到上面的注释对中,然后直接return跳出函数,下面的if分支就是导致RTC停止运行的原因,我把它放在了自己封装的RTC库里,在myrtc.c里的sakabu_RTC_Init中,上电先判断备份寄存器里面是否有我们写入的标志数据,如果没有表示我们从来没有设置过RTC,或者是因为断电(包括VBAT引脚)导致备份寄存器清零;没有的话就初始化RTC,这个if分支调用一次就好了。


task_main.c

#include "task_main.h"

#define CURSOR_FLASH_INTERVAL 500  // 光标闪烁周期(单位:ms)

// 星期显示文本缓存
char weeks[7][10] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// 日历状态机
typedef enum {
	CalendarState_Normal, // 正常显示模式
	CalendarState_Setting // 时间设置模式
} CalendarState;

// 可调节时间参数枚举
typedef enum {
	Year,    // 年
	Month,   // 月
	Day,     // 日
	Hour,    // 时
	Minute,  // 分
	Second   // 秒
} SettingState;

// 光标位置结构体(包含起点终点坐标)
typedef struct {uint8_t x1; uint8_t y1; uint8_t x2; uint8_t y2;} CursorPosition;

// 各时间参数对应的光标位置(基于字符尺寸计算)
CursorPosition cursorPosition[6] = {
	{24 + 0 * 8, 17, 24 + 4 * 8, 17},  // Year(年)坐标
	{24 + 5 * 8, 17, 24 + 7 * 8, 17},  // Month(月)坐标
	{24 + 8 * 8, 17, 24 + 10 * 8, 17}, // Day(日)坐标
	{16 + 0 * 12, 45, 16 + 2 * 12, 45},// Hour(时)坐标
	{16 + 3 * 12, 45, 16 + 5 * 12, 45},// Minute(分)坐标
	{16 + 6 * 12, 45, 16 + 8 * 12, 45},// Second(秒)坐标
};

CalendarState calendarState = CalendarState_Normal;  // 当前日历状态
SettingState settingState = Year;                     // 当前设置项
struct tm settingTime;                                // 时间设置缓冲区

// 旋钮正转回调(增加值)
void onKnobForward(void)
{
	if(calendarState == CalendarState_Setting)
	{
		switch(settingState)
		{
			case Year:  // 年+1(基准1900)
				settingTime.tm_year++;
				break;
			case Month: // 月+1(0-11循环)
				settingTime.tm_mon++;
				if(settingTime.tm_mon > 11)
					settingTime.tm_mon = 0;
				break;
			case Day:   // 日+1(1-31循环)
				settingTime.tm_mday++;
				if(settingTime.tm_mday > 31)
					settingTime.tm_mday = 1;
				break;
			case Hour:  // 时+1(0-23循环)
				settingTime.tm_hour++;
				if(settingTime.tm_hour > 23)
					settingTime.tm_hour = 0;
				break;
			case Minute:// 分+1(0-59循环)
				settingTime.tm_min++;
				if(settingTime.tm_min > 59)
					settingTime.tm_min = 0;
				break;
			case Second:// 秒+1(0-59循环)
				settingTime.tm_sec++;
				if(settingTime.tm_sec > 59)
					settingTime.tm_sec = 0;
				break;
		}
	}
}

// 旋钮反转回调(减少值)
void onKnobBackward(void)
{
	if(calendarState == CalendarState_Setting)
	{
		switch(settingState)
		{
			case Year:  // 年-1(最低1970年)
				settingTime.tm_year--;
				if(settingTime.tm_year < 70)
						settingTime.tm_year = 70;
				break;
			case Month: // 月-1(0-11循环)
				settingTime.tm_mon--;
				if(settingTime.tm_mon < 0)
					settingTime.tm_mon = 11;
				break;
			case Day:   // 日-1(1-31循环)
				settingTime.tm_mday--;
				if(settingTime.tm_mday < 0)
					settingTime.tm_mday = 31;
				break;
			case Hour:  // 时-1(0-23循环)
				settingTime.tm_hour--;
				if(settingTime.tm_hour < 0)
					settingTime.tm_hour = 23;
				break;
			case Minute:// 分-1(0-59循环)
				settingTime.tm_min--;
				if(settingTime.tm_min < 0)
					settingTime.tm_min = 59;
				break;
			case Second:// 秒-1(0-59循环)
				settingTime.tm_sec--;
				if(settingTime.tm_sec < 0)
					settingTime.tm_sec = 59;
				break;
		}
	}
}

// 旋钮按压回调(状态切换)
void onKnobPressed(void)
{
	if(calendarState == CalendarState_Normal)
	{
		settingTime = *sakabu_RTC_GetTime(); // 载入当前时间到缓冲区
		settingState = Year;                 // 重置设置项
		calendarState = CalendarState_Setting;// 进入设置模式
	}
	else
	{
		if(settingState == Second)            // 最后一个设置项
		{
			sakabu_RTC_SetTime(&settingTime); // 提交设置到RTC
			calendarState = CalendarState_Normal;// 返回正常模式
		}
		else
			settingState++;                   // 切换到下一设置项
	}
}

// 时间显示格式化(OLED输出)
void ShowTime(struct tm *time)
{
	char str[30];
	// 日期显示:年-月-日(年基准1900,月+1显示)
	sprintf(str, "%d-%d-%d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday);
	OLED_PrintASCIIString(24, 0, str, &afont16x8, OLED_COLOR_NORMAL);

	// 时间显示:HH:MM:SS(两位数格式)
	sprintf(str, "%02d:%02d:%02d", time->tm_hour, time->tm_min, time->tm_sec);
	OLED_PrintASCIIString(16, 20, str, &afont24x12, OLED_COLOR_NORMAL);

	// 星期显示(居中布局)
	char *week = weeks[time->tm_wday];
	uint8_t x_week = (128 - (strlen(week) * 8)) / 2;  // 计算居中位置
	OLED_PrintASCIIString(x_week, 48, week, &afont16x8, OLED_COLOR_NORMAL);
}

// 光标闪烁效果(仅设置模式显示)
void showCursor(void)
{
	static uint32_t startTime = 0;
	uint32_t diffTime = HAL_GetTick() - startTime;
	
	if(diffTime > 2 * CURSOR_FLASH_INTERVAL)  // 重置计时周期
		startTime = HAL_GetTick();
	else if(diffTime > CURSOR_FLASH_INTERVAL) // 后半周期显示光标
	{
		CursorPosition position = cursorPosition[settingState];
		OLED_DrawLine(position.x1, position.y1, position.x2, position.y2, OLED_COLOR_NORMAL);
	}
}

// 系统初始化(外设启动)
void MainTaskInit(void)
{
	HAL_Delay(20);  // 等待硬件稳定
	OLED_Init();     // 显示模块初始化
	sakabu_RTC_Init();// RTC初始化
	Knob_Init();     // 编码器初始化
	// 绑定回调函数
	Knob_SetForwardCallback(onKnobForward);
	Knob_SetBackwardCallback(onKnobBackward);
	Knob_SetPressedCallback(onKnobPressed);
}

// 主循环任务(持续执行)
void MainTask(void)
{
	Knob_Loop();       // 处理编码器事件
	OLED_NewFrame();   // 准备新显示帧
	
	if(calendarState == CalendarState_Normal)  // 正常模式
	{
		struct tm *now = sakabu_RTC_GetTime(); // 获取实时时间
		ShowTime(now);                         // 显示当前时间
	}
	else  // 设置模式
	{
		ShowTime(&settingTime);  // 显示设置中的时间
		showCursor();            // 显示闪烁光标
	}
	
	OLED_ShowFrame();  // 刷新整屏显示
}

task_main.h

#ifndef __TASK_MAIN_H__
#define __TASK_MAIN_H__

#include "myrtc.h"
#include "oled.h"
#include "usart.h"
#include "knob.h"

#include <stdio.h>
#include <string.h>

void MainTask(void);

void MainTaskInit(void);

#endif // __TASK_MAIN_H__

        至此这个简易的万年历系统就结束了,还有一些地方可以优化的:例如不同月份有不同的天数,我只判断大于31日可能会导致设置的时间与真实时间有出入,包括闰年的判断等等;但更多的是提供模块封装的思路,包括给大家提供封装好的RTC库和旋钮knob库,供大家移植在自己的项目上。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Matlab优化算法-第14期】基于智能优化算法的VMD信号去噪项目实践

基于智能优化算法的VMD信号去噪项目实践 一、前言 在信号处理领域&#xff0c;噪声去除是一个关键问题&#xff0c;尤其是在处理含有高斯白噪声的复杂信号时。变分模态分解&#xff08;VMD&#xff09;作为一种新兴的信号分解方法&#xff0c;因其能够自适应地分解信号而受到…

蓝耘智算平台与DeepSeek R1模型:推动深度学习发展

公主请阅 前言何为DeepSeek R1DeepSeek R1 的特点DeepSeek R1 的应用领域DeepSeek R1 与其他模型的对比 何为蓝耘智算平台使用蓝耘智算平台深度使用DeepSeek R1代码解释&#xff1a;处理示例输入&#xff1a;输出结果&#xff1a; 前言 在深度学习领域&#xff0c;创新迭代日新…

5、大模型的记忆与缓存

文章目录 本节内容介绍记忆Mem0使用 mem0 实现长期记忆 缓存LangChain 中的缓存语义缓存 本节内容介绍 本节主要介绍大模型的缓存思路&#xff0c;通过使用常见的缓存技术&#xff0c;降低大模型的回复速度&#xff0c;下面介绍的是使用redis和mem0&#xff0c;当然redis的语义…

windows蓝牙驱动开发-调试及支持的HCI和事件

调试蓝牙配置文件驱动程序 开发蓝牙配置文件驱动程序时&#xff0c;可以使用驱动程序验证程序来协助其调试。 若要启用验证检查&#xff0c;必须为 Bthusb.sys 启用驱动程序验证程序。 如果不执行此操作&#xff0c;将禁用验证检查。 若要完全利用验证检查&#xff0c;请确保…

深度求索(DeepSeek)的AI革命:NLP、CV与智能应用的技术跃迁

Deepseek官网&#xff1a;DeepSeek 引言&#xff1a;AI技术浪潮中的深度求索 近年来&#xff0c;人工智能技术以指数级速度重塑全球产业格局。在这场技术革命中&#xff0c;深度求索&#xff08;DeepSeek&#xff09;凭借其前沿的算法研究、高效的工程化能力以及对垂直场景的…

xxl-job使用nginx代理https后,访问出现403异常问题解决

在nginx代理为https之前&#xff0c;xxl-job使用http访问是没有问题的&#xff0c;但是换为https后&#xff0c;访问就有以下报错&#xff1a; 很多接口都出现了403异常 DataTables warning: table idjob_list - Ajax error. For more information about this error, please s…

kafka 3.5.0 raft协议安装

前言 最近做项目&#xff0c;需要使用kafka进行通信&#xff0c;且只能使用kafka&#xff0c;笔者没有测试集群&#xff0c;就自己搭建了kafka集群&#xff0c;实际上笔者在很早之前就搭建了&#xff0c;因为当时还是zookeeper&#xff08;简称ZK&#xff09;注册元数据&#…

Python 鼠标轨迹 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

爬虫技巧汇总

一、UA大列表 USER_AGENT_LIST 是一个包含多个用户代理字符串的列表&#xff0c;用于模拟不同浏览器和设备的请求。以下是一些常见的用户代理字符串&#xff1a; USER_AGENT_LIST [Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; Hot Lingo 2.0),Mozilla…

Microsoft Word xml 字符非法解决

如图&#xff0c;word能正常打开&#xff0c;复制内容到另外一个word时候出错&#xff0c;显示&#xff1a; Microsoft Word很抱歉,无法打开文档,因为内容有问题。确定详细信息(D)详细信息xml 字符非法。位置&#xff1a;行&#xff1a;3&#xff0c;列&#xff1a;2439 解决…

现代神经网络QA(LeNet/AlexNet/VGG/NiN/GooleNet/ResNet)-----一篇搞懂

现代神经网络Q&A-----一篇搞懂 LeNet核心架构 经典卷积神经网络的包括&#xff1a; 带填充以保持分辨率的卷积层&#xff1b;非线性激活函数&#xff0c;如ReLU&#xff1b;汇聚层&#xff0c;如最大汇聚层。 pooling时&#xff0c;使用avg还是max&#xff1f; max&…

数据结构与算法(test2)

五、串 1. 串是由___零___个或___多____个字符组成的有限序列, 又称为___字符串________。 一般记为 S“a1a2.....an” (n > 0), 串中的字符数目n称为串的__长度_____&#xff0c;零个字符的串称为___空串_____. 定义中谈到的"有限"是指长度 n 是一个有限的数值…

Matplotlib基础01( 基本绘图函数/多图布局/图形嵌套/绘图属性)

Matplotlib基础 Matplotlib是一个用于绘制静态、动态和交互式图表的Python库&#xff0c;广泛应用于数据可视化领域。它是Python中最常用的绘图库之一&#xff0c;提供了多种功能&#xff0c;可以生成高质量的图表。 Matplotlib是数据分析、机器学习等领域数据可视化的重要工…

六种负载均衡算法

六种负载均衡算法对比&#xff1a;原理、优缺点及适用场景 负载均衡是分布式系统的核心技术之一&#xff0c;通过合理分配请求流量&#xff0c;确保服务器资源高效利用&#xff0c;提升系统的可用性和响应速度。不同的负载均衡算法适用于不同的场景&#xff0c;以下是六种常见…

公司配置内网穿透方法笔记

一、目的 公司内部有局域网&#xff0c;局域网上有ftp服务器&#xff0c;有windows桌面服务器&#xff1b; 在内网环境下&#xff0c;是可以访问ftp服务器以及用远程桌面登录windows桌面服务器的&#xff1b; 现在想居家办公时&#xff0c;也能访问到公司内网的ftp服务器和win…

Citespace之关键词爆发检测分析(进阶分析)

在开始citespace进行关键词爆发检测分析之前&#xff0c;如果不会使用citespace的&#xff0c;可以参考我之前这一篇博客&#xff1a; https://blog.csdn.net/m0_56184997/article/details/145536095?spm1001.2014.3001.5501 一、创建工程后进行设置 在创建好工程后&#xf…

【文献讲解】《Non-local Neural Networks》

一、引言 传统的深度学习方法(如卷积神经网络CNN和循环神经网络RNN)在捕捉长距离依赖关系时存在局限性。CNN主要关注局部邻域的特征,而RNN则依赖于序列的递归计算,无法直接捕捉全局信息。为了解决这一问题,本文提出了一种非局部神经网络(Non-local Neural Networks),通…

基于 Spring Cloud + Spring AI + VUE 的知识助理平台介绍以及问题

前言&#xff08;一些废话&#xff09; 在看这篇文章的各位大佬&#xff0c;感谢你们留出几分钟时间&#xff0c;来看这个产品介绍&#xff0c;其实重点说实话&#xff0c;不是这个产品怎么样。而是在最后有一个郁结在心里的几个问题&#xff0c;希望大佬们能给出一些建议。万…

IDEA安装离线插件(目前提供了MavenHelper安装包)

目录 1、离线安装方式2、Maven Helper 1、离线安装方式 首先访问 IDEA插件网站 下载离线插件安装包&#xff0c;操作如下&#xff1a; 然后打开IDEA的Settings配置&#xff0c;点击Plugins&#xff0c;点击右侧设置按钮&#xff08;齿轮&#xff09;&#xff0c;选择Install P…

JVM的性能优化

1.方法内联 方法内联,是指 JVM在运行时将调用次数达到一定阈值的方法调用替换为方法体本身 ,从而消除调用成本,并为接下来进一步的代码性能优化提供基础,是JVM的一个重要优化手段之一。 注: C++的inline属于编译后内联,但是java是运行时内联 简单通俗的讲就是把方法内部调…