STM32江科大学习笔记,全功能按键非阻塞式实现,按键点击,双击,长按

目录

一、前言

二、关于实现非阻塞的办法

2.1 中断类型的选择

2.2 定时器中断

二、程序流程图

2.1 状态S=0空闲状态

2.2 状态S=1按键判断长按还是其他的事件

2.3 状态S=2按键判断双击或者单击

2.4 状态S=3按键已双击状态

2.5 状态S=4长按状态

三、编写代码

3.1 按键初始化

3.2 获取按键当前的状态

3.3 按键状态机处理函数

四、完整代码

4.1 定时器中断初始化

4.2 按键初始化


一、前言

由于普通Delay延时函数会出现阻塞主函数的运行,会发生主函数变慢或者停止的情况,在没有使用RTOS的情况应该如何实现非阻塞呢,以下为个人学习经验分享,学习来自江科大。

二、关于实现非阻塞的办法

2.1 中断类型的选择

肯定是中断的办法来实现非阻塞的作用,但是我们使用的不是普通的IO口接收到什么信号就进入中断,这个办法进入的中断难免会有一些按键抖动,因此,我们使用定时器中断

2.2 定时器中断

那么定时器中断为什么能够实现非阻塞呢?我们是用这个办法来解决的,如下图:

我们设置为每20ms进入定时中断读取一次引脚的值,然后读取本次的引脚值和保存上一次的引脚值,然后进行两次的值的分析:


两次都为1,则为松开按键状态


上次是1,本次是0,则为刚按下按键状态


上次是0,本次是1,则为刚松开按键状态


由于我们使用了定时器隔开读数的原因,所以我们避免了使用Delay延迟函数,所以我们实现了不阻塞主函数!

二、程序流程图

2.1 状态S=0空闲状态

状态:空闲状态,也就是我们按键未按下的状态

功能:检测按键是否按下(跳出当前状态的判断)

状态转移:按键按下,然后转去状态1,并设定长按的时间

2.2 状态S=1按键判断长按还是其他的事件

状态:按键已按下状态,也就是我们按键刚按下去的状态

功能:检测按键是否松开(跳出当前状态的判断)

功能:等待上面设计的长按时间是否结束(跳出当前状态的判断)

状态转移:按键松开,然后转去状态2,并设定双击等待的时间

状态转移:长按时间到,然后转去状态4,并设定重复的时间(多久重复一次),设置长按标志位LONG

2.3 状态S=2按键判断双击或者单击

状态:按键松开状态,也就是我们按键刚松开的状态

功能:检测按键是否按下(跳出当前状态的判断)

功能:等待上面设计的双击时间是否结束(跳出当前状态的判断)

状态转移:按键按下,然后转去状态3,并设置双击标志位DOUBLE

状态转移:双击时间到,然后重置状态,回到状态0并设置单击标志位SINGLE

2.4 状态S=3按键已双击状态

状态:按键已双击状态,也就是已经判断完双击事件

功能:检测按键是否松开(跳出当前状态的判断)

状态转移:按键松开,然后重置状态,返回初始状态

2.5 状态S=4长按状态

状态:按键长按状态,一直按着就一直循环,然后置重复事件REPEAT

功能:检测按键是否松开(跳出当前状态的判断)

功能:等待重复时间(当时间到后重复执行当前状态的功能)

状态转移:重复时间到了,然后再设置重复时间,将当前状态再执行一次

状态转移:按键松开,然后重置状态,返回初始状态

三、编写代码

3.1 按键初始化

/*** @brief 按键GPIO初始化函数* @param 无* @retval 无* @description 配置按键对应的GPIO引脚*              KEY_1(PB1)、KEY_2(PB11): 上拉输入,按下时读取为0*              KEY_3(PB13)、KEY_4(PB15): 下拉输入,按下时读取为1*/
void Key_Init(void)
{// 使能GPIOB时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;// 配置KEY_1(PB1)和KEY_2(PB11)为上拉输入GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置KEY_3(PB13)和KEY_4(PB15)为下拉输入GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;		// 下拉输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
}

初始化就不多说啦,正常输入模式就可以。

3.2 获取按键当前的状态

/* 按键编号定义 */
#define KEY_1					0			// 按键1编号 (对应PB1,上拉输入)
#define KEY_2					1			// 按键2编号 (对应PB11,上拉输入)
#define KEY_3					2			// 按键3编号 (对应PB13,下拉输入)
#define KEY_4					3			// 按键4编号 (对应PB15,下拉输入)/*** @brief 获取按键物理状态* @param n 按键编号 (KEY_1, KEY_2, KEY_3, KEY_4)* @retval KEY_PRESSED: 按键被按下, KEY_UNPRESSED: 按键未被按下* @description 读取指定按键的当前物理状态*              KEY_1(PB1)、KEY_2(PB11): 低电平有效*              KEY_3(PB13)、KEY_4(PB15): 高电平有效*/
uint8_t Key_GetState(uint8_t n)
{if (n == KEY_1){// KEY_1上拉输入,按下时为低电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){return KEY_PRESSED;}}else if (n == KEY_2){// KEY_2上拉输入,按下时为低电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0){return KEY_PRESSED;}}else if (n == KEY_3){// KEY_3下拉输入,按下时为高电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1){return KEY_PRESSED;}}else if (n == KEY_4){// KEY_4下拉输入,按下时为高电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) == 1){return KEY_PRESSED;}}return KEY_UNPRESSED;
}

获取指定按键的物理状态(也就是高低电平),然后来识别是否按下之类的,用来变成之后代码的合成材料

3.3 按键状态机处理函数

/* 按键数量配置 */
#define KEY_COUNT				4			// 系统支持的按键总数/* 按键状态标志位定义 */
#define KEY_HOLD				0x01		// 按键保持按下状态
#define KEY_DOWN				0x02		// 按键刚按下瞬间
#define KEY_UP					0x04		// 按键刚松开瞬间
#define KEY_SINGLE				0x08		// 按键单击
#define KEY_DOUBLE				0x10		// 按键双击
#define KEY_LONG				0x20		// 按键长按
#define KEY_REPEAT				0x40		// 按键连按(长按后的重复触发)/*** @brief 按键状态机处理函数* @param 无* @retval 无* @description 按键扫描和状态处理的核心函数,需要定时调用(建议1ms调用一次)*              实现了完整的按键状态机,支持以下功能:*              - KEY_HOLD: 按键保持按下状态*              - KEY_DOWN: 按键刚按下瞬间*              - KEY_UP: 按键刚松开瞬间*              - KEY_SINGLE: 按键单击(在双击检测时间内只按一次)*              - KEY_DOUBLE: 按键双击(在指定时间内按两次)*              - KEY_LONG: 按键长按(超过长按时间阈值)*              - KEY_REPEAT: 按键连按(长按后的重复触发)* * @note 状态机说明:*       S[i] = 0: 空闲状态,等待按键按下*       S[i] = 1: 第一次按下状态,等待松开或长按超时*       S[i] = 2: 第一次松开状态,等待第二次按下或双击超时*       S[i] = 3: 双击确认状态,等待第二次松开*       S[i] = 4: 长按状态,等待松开或重复触发*/
void Key_Tick(void)
{static uint8_t Count, i;						// Count: 扫描计数器, i: 循环变量static uint8_t CurrState[KEY_COUNT], PrevState[KEY_COUNT];	// 当前状态和前一次状态static uint8_t S[KEY_COUNT];					// 状态机状态数组static uint16_t Time[KEY_COUNT];				// 定时器数组// 所有按键的定时器递减for (i = 0; i < KEY_COUNT; i ++){if (Time[i] > 0){Time[i] --;}}// 每20ms扫描一次按键状态(假设本函数每1ms调用一次)Count ++;if (Count >= 20){Count = 0;// 遍历所有按键for (i = 0; i < KEY_COUNT; i ++){// 更新按键状态PrevState[i] = CurrState[i];CurrState[i] = Key_GetState(i);// 处理KEY_HOLD状态if (CurrState[i] == KEY_PRESSED){Key_Flag[i] |= KEY_HOLD;		// 按键按下时置位HOLD标志}else{Key_Flag[i] &= ~KEY_HOLD;		// 按键松开时清除HOLD标志}// 处理KEY_DOWN状态(上升沿检测)if (CurrState[i] == KEY_PRESSED && PrevState[i] == KEY_UNPRESSED){Key_Flag[i] |= KEY_DOWN;}// 处理KEY_UP状态(下降沿检测)if (CurrState[i] == KEY_UNPRESSED && PrevState[i] == KEY_PRESSED){Key_Flag[i] |= KEY_UP;}// 按键状态机处理if (S[i] == 0)		// 状态0: 空闲状态{if (CurrState[i] == KEY_PRESSED){Time[i] = KEY_TIME_LONG;	// 设置长按计时器S[i] = 1;					// 转到状态1}}else if (S[i] == 1)	// 状态1: 第一次按下状态{if (CurrState[i] == KEY_UNPRESSED){Time[i] = KEY_TIME_DOUBLE;	// 设置双击检测计时器S[i] = 2;					// 转到状态2}else if (Time[i] == 0)		// 长按时间到{Time[i] = KEY_TIME_REPEAT;	// 设置重复触发计时器Key_Flag[i] |= KEY_LONG;	// 置位长按标志S[i] = 4;					// 转到状态4}}else if (S[i] == 2)	// 状态2: 第一次松开状态{if (CurrState[i] == KEY_PRESSED){Key_Flag[i] |= KEY_DOUBLE;	// 置位双击标志S[i] = 3;					// 转到状态3}else if (Time[i] == 0)		// 双击检测时间到{Key_Flag[i] |= KEY_SINGLE;	// 置位单击标志S[i] = 0;					// 回到状态0}}else if (S[i] == 3)	// 状态3: 双击确认状态{if (CurrState[i] == KEY_UNPRESSED){S[i] = 0;					// 回到状态0}}else if (S[i] == 4)	// 状态4: 长按状态{if (CurrState[i] == KEY_UNPRESSED){S[i] = 0;					// 回到状态0}else if (Time[i] == 0)		// 重复触发时间到{Time[i] = KEY_TIME_REPEAT;	// 重新设置重复触发计时器Key_Flag[i] |= KEY_REPEAT;	// 置位重复触发标志S[i] = 4;					// 保持状态4}}}}
}

该代码是放在定时器中断函数里面的!!这个记住了,定时器是每隔1ms调用一次这个函数;

由于Count ++,每1ms加一次,记到20次则为20ms,所以每隔20ms处理一次状态机;

然后和上述的一样,进行当前和之前电平状态的对比,就可以知道当前的按键的物理状态;

然后根据流程图进行代码的编写,比如状态0通过按下按键跳到状态1,然后设置长按定时,也就是这样

if (S[i] == 0)       // 状态0: 空闲状态

            {

                if (CurrState[i] == KEY_PRESSED)

                {

                    Time[i] = KEY_TIME_LONG;    // 设置长按计时器

                    S[i] = 1;                   // 转到状态1

                }

            }

以此类推即可!!

四、完整代码

4.1 定时器中断初始化

#include "stm32f10x.h"                  // Device headervoid Timer_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_InternalClockConfig(TIM2);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 1000 - 1;TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2, TIM_FLAG_Update);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);TIM_Cmd(TIM2, ENABLE);
}/*
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
*/

4.2 按键初始化

/*** @file Key.c* @brief 全功能按键非阻塞式实现源文件* @author STM32江科大* @date 2025年8月6日* @description 实现多种按键检测功能,包括按下、松开、单击、双击、长按、连按等*              采用状态机方式实现,支持非阻塞式检测*/#include "stm32f10x.h"                  // Device header
#include "Key.h"/* 按键物理状态定义 */
#define KEY_PRESSED				1		// 按键被按下的物理状态
#define KEY_UNPRESSED			0		// 按键未被按下的物理状态/* 按键时间参数定义 (单位: ms) */
#define KEY_TIME_DOUBLE			200		// 双击间隔时间阈值
#define KEY_TIME_LONG			2000	// 长按时间阈值
#define KEY_TIME_REPEAT			100		// 连按重复间隔时间/* 按键状态标志数组 */
uint8_t Key_Flag[KEY_COUNT];			// 存储各按键的状态标志位/*** @brief 按键GPIO初始化函数* @param 无* @retval 无* @description 配置按键对应的GPIO引脚*              KEY_1(PB1)、KEY_2(PB11): 上拉输入,按下时读取为0*              KEY_3(PB13)、KEY_4(PB15): 下拉输入,按下时读取为1*/
void Key_Init(void)
{// 使能GPIOB时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;// 配置KEY_1(PB1)和KEY_2(PB11)为上拉输入GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);// 配置KEY_3(PB13)和KEY_4(PB15)为下拉输入GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;		// 下拉输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
}/*** @brief 获取按键物理状态* @param n 按键编号 (KEY_1, KEY_2, KEY_3, KEY_4)* @retval KEY_PRESSED: 按键被按下, KEY_UNPRESSED: 按键未被按下* @description 读取指定按键的当前物理状态*              KEY_1(PB1)、KEY_2(PB11): 低电平有效*              KEY_3(PB13)、KEY_4(PB15): 高电平有效*/
uint8_t Key_GetState(uint8_t n)
{if (n == KEY_1){// KEY_1上拉输入,按下时为低电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){return KEY_PRESSED;}}else if (n == KEY_2){// KEY_2上拉输入,按下时为低电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0){return KEY_PRESSED;}}else if (n == KEY_3){// KEY_3下拉输入,按下时为高电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1){return KEY_PRESSED;}}else if (n == KEY_4){// KEY_4下拉输入,按下时为高电平if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) == 1){return KEY_PRESSED;}}return KEY_UNPRESSED;
}/*** @brief 检测按键状态* @param n 按键编号 (KEY_1, KEY_2, KEY_3, KEY_4)* @param Flag 要检测的状态标志位 (KEY_HOLD, KEY_DOWN, KEY_UP, KEY_SINGLE, KEY_DOUBLE, KEY_LONG, KEY_REPEAT)* @retval 1: 指定状态发生, 0: 指定状态未发生* @description 检测指定按键的指定状态是否发生*              除了KEY_HOLD状态会持续保持外,其他状态在检测后会自动清除*/
uint8_t Key_Check(uint8_t n, uint8_t Flag)
{// 检查指定按键的指定标志位是否置位if (Key_Flag[n] & Flag){// 除了KEY_HOLD外,其他标志位检测后自动清除if (Flag != KEY_HOLD){Key_Flag[n] &= ~Flag;	// 清除对应标志位}return 1;}return 0;
}/*** @brief 按键状态机处理函数* @param 无* @retval 无* @description 按键扫描和状态处理的核心函数,需要定时调用(建议1ms调用一次)*              实现了完整的按键状态机,支持以下功能:*              - KEY_HOLD: 按键保持按下状态*              - KEY_DOWN: 按键刚按下瞬间*              - KEY_UP: 按键刚松开瞬间*              - KEY_SINGLE: 按键单击(在双击检测时间内只按一次)*              - KEY_DOUBLE: 按键双击(在指定时间内按两次)*              - KEY_LONG: 按键长按(超过长按时间阈值)*              - KEY_REPEAT: 按键连按(长按后的重复触发)* * @note 状态机说明:*       S[i] = 0: 空闲状态,等待按键按下*       S[i] = 1: 第一次按下状态,等待松开或长按超时*       S[i] = 2: 第一次松开状态,等待第二次按下或双击超时*       S[i] = 3: 双击确认状态,等待第二次松开*       S[i] = 4: 长按状态,等待松开或重复触发*/
void Key_Tick(void)
{static uint8_t Count, i;						// Count: 扫描计数器, i: 循环变量static uint8_t CurrState[KEY_COUNT], PrevState[KEY_COUNT];	// 当前状态和前一次状态static uint8_t S[KEY_COUNT];					// 状态机状态数组static uint16_t Time[KEY_COUNT];				// 定时器数组// 所有按键的定时器递减for (i = 0; i < KEY_COUNT; i ++){if (Time[i] > 0){Time[i] --;}}// 每20ms扫描一次按键状态(假设本函数每1ms调用一次)Count ++;if (Count >= 20){Count = 0;// 遍历所有按键for (i = 0; i < KEY_COUNT; i ++){// 更新按键状态PrevState[i] = CurrState[i];CurrState[i] = Key_GetState(i);// 处理KEY_HOLD状态if (CurrState[i] == KEY_PRESSED){Key_Flag[i] |= KEY_HOLD;		// 按键按下时置位HOLD标志}else{Key_Flag[i] &= ~KEY_HOLD;		// 按键松开时清除HOLD标志}// 处理KEY_DOWN状态(上升沿检测)if (CurrState[i] == KEY_PRESSED && PrevState[i] == KEY_UNPRESSED){Key_Flag[i] |= KEY_DOWN;}// 处理KEY_UP状态(下降沿检测)if (CurrState[i] == KEY_UNPRESSED && PrevState[i] == KEY_PRESSED){Key_Flag[i] |= KEY_UP;}// 按键状态机处理if (S[i] == 0)		// 状态0: 空闲状态{if (CurrState[i] == KEY_PRESSED){Time[i] = KEY_TIME_LONG;	// 设置长按计时器S[i] = 1;					// 转到状态1}}else if (S[i] == 1)	// 状态1: 第一次按下状态{if (CurrState[i] == KEY_UNPRESSED){Time[i] = KEY_TIME_DOUBLE;	// 设置双击检测计时器S[i] = 2;					// 转到状态2}else if (Time[i] == 0)		// 长按时间到{Time[i] = KEY_TIME_REPEAT;	// 设置重复触发计时器Key_Flag[i] |= KEY_LONG;	// 置位长按标志S[i] = 4;					// 转到状态4}}else if (S[i] == 2)	// 状态2: 第一次松开状态{if (CurrState[i] == KEY_PRESSED){Key_Flag[i] |= KEY_DOUBLE;	// 置位双击标志S[i] = 3;					// 转到状态3}else if (Time[i] == 0)		// 双击检测时间到{Key_Flag[i] |= KEY_SINGLE;	// 置位单击标志S[i] = 0;					// 回到状态0}}else if (S[i] == 3)	// 状态3: 双击确认状态{if (CurrState[i] == KEY_UNPRESSED){S[i] = 0;					// 回到状态0}}else if (S[i] == 4)	// 状态4: 长按状态{if (CurrState[i] == KEY_UNPRESSED){S[i] = 0;					// 回到状态0}else if (Time[i] == 0)		// 重复触发时间到{Time[i] = KEY_TIME_REPEAT;	// 重新设置重复触发计时器Key_Flag[i] |= KEY_REPEAT;	// 置位重复触发标志S[i] = 4;					// 保持状态4}}}}
}
/*** @file Key.h* @brief 全功能按键非阻塞式实现头文件* @author STM32江科大* @date 2025年8月6日* @description 实现多种按键检测功能,包括按下、松开、单击、双击、长按、连按等*/#ifndef __KEY_H
#define __KEY_H/* 按键数量配置 */
#define KEY_COUNT				4			// 系统支持的按键总数/* 按键编号定义 */
#define KEY_1					0			// 按键1编号 (对应PB1,上拉输入)
#define KEY_2					1			// 按键2编号 (对应PB11,上拉输入)
#define KEY_3					2			// 按键3编号 (对应PB13,下拉输入)
#define KEY_4					3			// 按键4编号 (对应PB15,下拉输入)/* 按键状态标志位定义 */
#define KEY_HOLD				0x01		// 按键保持按下状态
#define KEY_DOWN				0x02		// 按键刚按下瞬间
#define KEY_UP					0x04		// 按键刚松开瞬间
#define KEY_SINGLE				0x08		// 按键单击
#define KEY_DOUBLE				0x10		// 按键双击
#define KEY_LONG				0x20		// 按键长按
#define KEY_REPEAT				0x40		// 按键连按(长按后的重复触发)/*** @brief 按键初始化函数* @param 无* @retval 无* @description 初始化按键对应的GPIO引脚,配置为输入模式*/
void Key_Init(void);/*** @brief 按键状态检测函数* @param n 按键编号 (KEY_1, KEY_2, KEY_3, KEY_4)* @param Flag 要检测的按键状态标志位* @retval 1: 指定状态发生, 0: 指定状态未发生* @description 检测指定按键的指定状态是否发生,除KEY_HOLD外,其他状态检测后会自动清除*/
uint8_t Key_Check(uint8_t n, uint8_t Flag);/*** @brief 按键扫描处理函数* @param 无* @retval 无* @description 按键状态机处理函数,需要定时调用(建议1ms调用一次)*              处理所有按键的状态转换和事件检测*/
void Key_Tick(void);#endif

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态代理常用的两种方式?

口语化回答好的&#xff0c;面试官&#xff0c;动态常见的两种&#xff0c;一种是 jdk 动态代理&#xff0c;一种是 cglib 动态代理&#xff0c;两者的最主要区别是 jdk 动态代理主要是依赖于接口创建代理对象&#xff0c;cglib 是通过生成子类的方式&#xff0c;不需要接口&am…

StarRocks vs ClickHouse:2025 年 OLAP 引擎终极对比指南

StarRocks 与 ClickHouse&#xff1a;高性能 OLAP 引擎的两种选择在当今数据驱动的商业环境中&#xff0c;选择合适的分析型数据库对于企业数据战略至关重要。StarRocks 和 ClickHouse 作为两款领先的 OLAP&#xff08;在线分析处理&#xff09;引擎&#xff0c;各自拥有独特的…

RuoYi-Cloud 微服务本地部署详细流程实录(IDEA + 本地 Windows 环境)

本文以 RuoYi-Cloud 3.x 版本为例&#xff0c;开发工具用的是 IntelliJ IDEA&#xff0c;数据库为 MySQL 8.x&#xff0c;注册中心选用本地 Nacos 2.2.3&#xff0c;Redis 为 3.x/5.x 均可。亲测全流程可用&#xff0c;细节与官方文档略有不同&#xff0c;避免新手踩坑。 目录 …

2025年了,程序员转行还这么难?别愁!大模型这趟“顺风车”,你搭不搭?

在“大龄程序员的未来在何方”这篇文章里比较乐观地介绍了程序员保持竞争力的几个方向&#xff0c;但现实依然是残酷的&#xff1a;很多人将不得不离开软件开发工作&#xff0c;转型去从事其他职业。 当你要这么做时&#xff0c;就会感慨&#xff1a;想不到一切竟如此艰难&…

CEH、OSCP、CISP、CISSP 四大网络安全认证攻略

以下是 CEH、OSCP、CISP、CISSP 四大网络安全认证的详细对比&#xff0c;涵盖认证定位、考试难度、适用场景及职业方向&#xff0c;帮助你快速选择适合自己的证书&#xff1a;1. 核心区别速览认证发证机构定位 考试形式适合人群国际认可度CEHEC-Council道德黑客渗透测试基础选择…

SnapDevelop支持uni-app开发:跨平台与原生体验的完美融合

随着移动互联网的迅速发展&#xff0c;开发者面临着多平台需求和技术挑战。传统开发模式要求为每个平台编写独立代码&#xff0c;不仅浪费时间&#xff0c;还增加了维护难度。作为一款强大的低代码开发工具&#xff0c;SnapDevelop打破了这一局限&#xff0c;通过对uni-app的支…

海康威视相机,MVS连接成功,但无图像怎么办?

问题&#xff1a;准备一个常见的海康 相机去海康机器视觉官网下载MVS软件。打开软件&#xff0c;连接相机。显示连接成功&#xff0c;并能看到相机的信息。点击开始采集发现没有图像解决&#xff1a;右侧找到触发。1. 触发模式 ON2.选择 软触发3 启用自动触发点击采集有图像但一…

Linux systemd 系统管理:systemctl 控制服务与守护进程

Linux systemd 系统管理&#xff1a;systemctl 控制服务与守护进程系统启动过程 Linux 系统的启动过程遵循以下步骤&#xff1a; 开机自检&#xff1a;计算机启动后&#xff0c;BIOS/UEFI 进行硬件自检&#xff0c;确认硬件设备正常MBR 引导&#xff1a;从指定的启动设备读取主…

《Day2-PyTorch Tensor 从入门到实践:核心操作与避坑指南》

一、Tensor的创建 在Torch中张量以 "类" 的形式封装起来&#xff0c;对张量的一些运算、处理的方法被封装在类中&#xff0c;官方文档&#xff1a; torch — PyTorch 2.7 documentation 1. 基本创建方式 以下讲的创建tensor的函数中有两个有默认值的参数dtype和d…

两种格式数据介绍——bin 、 yuv文件

一、场景存储 通常指的是用于存储摄像头或传感器原始采集数据的文件格式&#xff0c;尤其是在自动驾驶、机器人、安防、工业视觉等需要记录真实世界场景的应用中。格式存储内容用途场景特点.binLiDAR点云、毫米波雷达数据、IMU、GPS、原始传感器帧自动驾驶仿真、SLAM建图、数据…

【网络运维】Linux:SELinux简介和配置

SELinux 介绍 SELinux 概述 文件权限控制了哪些用户或用户组可以访问哪些特定文件&#xff0c;但未限定用户访问文件的方式。 例如&#xff1a;对于文件的写入权限而言&#xff0c; 结构化数据文件是否应当设计为只能使用特定的程序写入&#xff0c;但其他编辑器仍可以打开和修…

GaussDB SQL执行计划详解

1 问题现象SQL执行计划是GaussDB性能分析及调优的核心&#xff0c;它输出三个关键信息&#xff1a;访问路径:扫描表数据的路径。连接顺序&#xff1a;多表连接顺序。连接方式&#xff1a;多表连接方式。2 技术背景GaussDB SQL语句执行计划是数据库为运行SQL语句而执行的操作步骤…

02.【数据结构-C语言】顺序表(线性表概念、顺序表实现:增删查、前向声明、顺序表实现通讯录项目:增删改查、通讯录数据导入及保存到本地文件)

目录 1. 线性表 2. 顺序表概念及分类 2.1 顺序表的概念 2.2 顺序表分类 2.3 动静态顺序表对比 3. 顺序表的实现&#xff08;附完整版代码&#xff09; 3.1 顺序表结构体声明 3.2 初始化&销毁 3.3 插入&#xff08;尾插、头插、指定位置之前插入&#xff09; 3.4 …

MyBatis核心配置深度解析:从XML到映射的完整技术指南

&#x1f527; MyBatis核心配置深度解析&#xff1a;从XML到映射的完整技术指南 &#x1f680; 引言&#xff1a;MyBatis作为Java生态中最受欢迎的持久层框架之一&#xff0c;其强大的配置体系是实现灵活数据访问的核心。本文将深入解析MyBatis的配置文件架构、映射机制以及高级…

OpenCV HSV与RGB颜色模型的区别

HSV与RGB颜色模型的区别 HSV&#xff08;Hue, Saturation, Value&#xff09;和 RGB&#xff08;Red, Green, Blue&#xff09;是两种不同的颜色表示方式&#xff0c;主要区别如下&#xff1a;对比项RGBHSV定义基于红、绿、蓝三原色的混合基于色相&#xff08;H&#xff09;、饱…

具有柔性关节的机械臂matlab仿真

柔性关节机械臂MATLAB仿真方案&#xff0c;包含动力学建模、控制器设计和可视化分析。该方案基于拉格朗日方程建立柔性关节模型&#xff0c;并实现了PD控制、滑模控制和自适应控制三种控制策略。 MATLAB仿真 %% 柔性关节机械臂仿真 - 完整系统 % 作者: MATLAB技术助手 % 日期: …

数据结构—队列和栈

1.二级指针的使用二级指针&#xff1a; 1. 在被调函数中&#xff0c;想要修改主调函数中的指针变量&#xff0c;需要传递该指针变量的地址&#xff0c;形参用二级指针接收。 2.指针数组的数组名是一个二级指针&#xff0c;指针数组的数组名作为参数传递时&#xff0c;可用二级指…

均线:从市场脉搏到量子计算的时空密码

一部跨越百年的技术分析进化史,揭示金融市场的数学本质 引言:金融市场的永恒罗盘 在华尔街百年风云中,一个简单的数学工具始终闪耀着智慧光芒——移动平均线(Moving Average)。从杰西利弗莫尔的手绘图表到文艺复兴科技的量子模型,均线系统完成了从经验工具到科学框架的惊…

Python 通过Playwright+OpenCV破解滑动验证码 实例

由于公司最近需要对接某业务系统&#xff0c;涉及到部分数据需要提交至其它平台业务系统&#xff0c;只有其它平台账户&#xff0c;没有接口&#xff0c;因此做此开发。首先通过OpenCV计算出验证验证码滑块距离&#xff0c;根据距离&#xff0c;使用 Playwright 利用滑动距离模…

山东省天地图API申请并加载到QGIS和ArcGIS Pro中

目的&#xff1a;在QGIS/ArcGIS Pro中加载山东省不同时期的历史影像1、申请API 山东省天地图的API和国家天地图的API不通用&#xff0c;需要单独申请。 https://shandong.tianditu.gov.cn/ 打开本地服务资源找到影像的详情页 点击申请地址按照下面的步骤一步一步来&#xff0c;…