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

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.dtcms.com/a/317531.html

相关文章:

  • 思途AOP学习笔记 0806
  • 网安学习no.22
  • Zookeeper集群在Kubernetes上的高可用部署方案
  • 什么是VR全景图?VR全景图有什么优势?
  • vite项目中集成vditor文档编辑器
  • 金融风控实战:Spring Boot + LightGBM 贷款预测模型服务化(超详细版)
  • 多链钱包开发指南:MPC无助记词方案+60+公链支持
  • 问题定位排查手记1 | 从Windows端快速检查连接状态
  • STM32的PWR
  • 阿里云polardb-x 2.0迁移至华为云taurusdb
  • VSCode:基础使用 / 使用积累
  • react16 umi3 快速刷新配置
  • 从技术角度看React和Vue:性能、生态与开发体验对比
  • 猎板视角下的 PCB 翘曲:成因、检测、工艺优化及解决措施热点解析
  • C++ Primer Plus 14.4.10 模板别名
  • 下载 | Windows Server 2019最新原版ISO映像!(集成7月更新、标准版、数据中心版、17763.7558)
  • ref存储对象和reactive深度响应式递归地对对象的嵌套属性进行响应式处理
  • 纯血鸿蒙(HarmonyOS NEXT)应用开发完全指南
  • Baumer相机如何通过YoloV8深度学习模型实现农作物水稻病虫害的检测识别(C#代码UI界面版)
  • 机器学习----随机森林(Random Forest)详解
  • MonoFusion 与 Genie 3
  • imx6ull-驱动开发篇10——pinctrl 子系统
  • Apollo中三种相机外参的可视化分析
  • ipv6学习
  • CVE-2020-24557
  • 【LayUI】数据表格监听事件
  • 界面规范的其他框架实现-列表-layui实现
  • 最新教程 | CentOS 7 下 MySQL 8 离线部署完整手册(含自动部署脚本)
  • 【后端】java 抽象类和接口的介绍和区别
  • PromptPilot 与豆包新模型:从图片到视频,解锁 AI 新玩法