IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> cubemx stm32 按键key 支持单、双、三、四击以及长按 链表实现 驱动代码 -> 正文阅读

[嵌入式]cubemx stm32 按键key 支持单、双、三、四击以及长按 链表实现 驱动代码

cubemx配置

  1. 打开串口
  2. 配置按键的 gpio口为 up input 模式
  3. 配置时钟树

代码

yxy_open_key.c

#include "yxy_open_key.h"
#include "stdlib.h"
#include "string.h"
#include "yxy_debug.h"



/* // 按键实现思路:
	
	用户需要使用某个按键的时候,
	把按键通过 key_add 添加到链表里面,
	key_add函数会把按键名称绑定到对应的按键端口和按键引脚上面去,
	当按键按下时,会在 key_scan 函数中跳转到对应的按键处理函数中去,
	并且如果 KEY_UART_DEBUG 为1 的话,
	则会把按键名称以及对应的按键方式打印到串口上面去。
	
	概念:
		时间基段:判断按键的击打状态的时间基准。
		
		单击判断条件:	按下时间在第一个时间基段以内,
						并且按下结束后在一个时间基段内没有按下的算单击。
		长按判断条件:	按下时间超过 4 个时间基段的则可以认为是长按
		
		双击判断条件:	第一次的按下时间在一个时间基段以内,
						第一次和第二次按下之间的时间不能大于一个时间基段,
						第二次无论按下多长,都算是双击,
						第二次按下结束后在一个时间基段内没有按下,则是双击。
	
		三击判断条件:	三击和双击的原理一样。
		四击判断条件:	四击和双击的原理一样。
	
	使用:
	
		创建一个 User_Key userVar 全局变量
		
		初始化:
			openKey_init();
			key_add(&userVar, (uint8_t *)"keyName", KEY_GPIO_Port, KEY_Pin);
			
		main主循环:
			while(1)
			{
				// 每1ms扫描一次 key_scan 函数(在while中用任意代码使他等待1ms)。
				key_scan();
				HAL_Delay(1);
			}
			
			重写回调函数,key_click、key_double、key_third、key_fourth、key_long
			在回调函数中if(key_id == userVar.keyID),判断是哪个按键的回调函数,
			并在对应的回调中写对应的用户代码。


	
*/

/*********************
 *    宏定义区域
 *********************/




/*********************
 *   内部变量区域
 *********************/

uint16_t keyNum = 0;	// 按键数量

struct Key_Link * p_kl = NULL;



/*********************
 *    回调弱函数
 *********************/

/* key中调函数(调用回调函数之前先调用中调函数)
 * 用来确认调用哪个回调函数
 */


__weak void key_click(uint16_t key_id)
{
	USER_PASS;
}


#if KEY_SUPPORT >= 2
__weak void key_double(uint16_t key_id)
{
	USER_PASS;
}

#endif
#if KEY_SUPPORT >= 3
__weak void key_third(uint16_t key_id)
{
	USER_PASS;
}

#endif
#if KEY_SUPPORT >= 4
__weak void key_fourth(uint16_t key_id)
{
	USER_PASS;
}

#endif

__weak void key_long(uint16_t key_id)
{
	USER_PASS;
}

/*********************
 *     内部函数
 *********************/

static uint16_t Radical_2(uint16_t x);

/* 连续开 2 次方根,计算某个数是2的多少次方跟 */
static uint16_t Radical_2(uint16_t x)
{
	uint16_t y = 0;
	
	while(x)
	{
		x >>= 1;
		y++;
	}
	y--;
	
	return y;
}


/*********************
 *   外部函数实现
 *********************/

/**
  * @brief  开源按键的初始化
  * @param  
  * @retval 返回 HAL_OK 则初始化成功
  */
HAL_StatusTypeDef openKey_init(void)
{
	p_kl = (struct Key_Link *)malloc(sizeof(struct Key_Link));
	
	if(p_kl == NULL)
	{
		yxy_DEBUG("open key initialization failed... \r\n");
		return HAL_ERROR;
	}
	
	p_kl->p_kN = NULL;
	strcpy((char *)p_kl->kC.keyName, "keyLinkHand");	//按键名字为"keyLinkHand"
	p_kl->kC.keyID = 0xffff;		// 给头结点赋值一个到不了的值
	
	p_kl->kC.keyDownCnt = 0;		// 按键按下计数为0
	p_kl->kC.keyUpCnt = 0;			// 按键抬起计数为0
	p_kl->kC.keyManyCliCnt = 0;		// 按键多击计数为0
	p_kl->kC.kDownLockState = KEY_NULL;// 当前无按下的解锁状态

//	yxy_DEBUG("%s\r\n", p_kl->kC.keyName);
	
	// 按键数量为零
	keyNum = 0;
	
	yxy_DEBUG("open key initialization success !\r\n");
	
	return HAL_OK;
}

/**
  * @brief  添加一个按键到按键链表里面
  * @param  按键的名字
  * @param  按键的端口
  * @param  按键的引脚
  * @retval 返回 HAL_OK 则添加按键成功
  */
HAL_StatusTypeDef key_add(User_Key * userKey, uint8_t * keyName, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
	struct Key_Link * p_keyLinkNode = p_kl;	// 创建一个中间变量保存p_kl的值
	uint8_t GPIOxName = (uint8_t)((((uint32_t)GPIOx - (uint32_t)GPIOA) / 0x00000400UL) + 'A');
	uint16_t PinName = Radical_2(GPIO_Pin);
	
	if(IS_GPIO_ALL_INSTANCE(GPIOx) != 1)
	{
		yxy_DEBUG("key add GPIOx error... \r\n");
		return HAL_ERROR;
	}

	if((IS_GPIO_PIN(GPIO_Pin) != 1) && (GPIO_Pin != GPIO_PIN_All))
	{
		yxy_DEBUG("key add GPIO_Pin error... \r\n");
		return HAL_ERROR;
	}
	
	// 找到链表的尾部分
	while(p_keyLinkNode->p_kN != NULL)
	{
		p_keyLinkNode = p_keyLinkNode->p_kN;
	}
	
	// 给新的按键创建一个空间
	p_keyLinkNode->p_kN = (struct Key_Link *)malloc(sizeof(struct Key_Link));
	
	p_keyLinkNode = p_keyLinkNode->p_kN;
	
	if(p_keyLinkNode == NULL)
	{
		yxy_DEBUG("key add failed... \r\n");
		return HAL_ERROR;
	}
	
	p_keyLinkNode->p_kN = NULL;

	// 按键名字赋值
	strcpy((char *)p_keyLinkNode->kC.keyName, (const char *)keyName);
	
	// 按键id随着按键的增加逐渐递加
	p_keyLinkNode->kC.keyID = keyNum;
	
	// 按键数量加1
	keyNum++;
	
	// 按键绑定硬件端口、引脚
	p_keyLinkNode->kC.GPIOx = GPIOx;
	p_keyLinkNode->kC.GPIO_Pin = GPIO_Pin;
	
	// 状态归位
	p_keyLinkNode->kC.keyDownCnt = 0;
	p_keyLinkNode->kC.keyUpCnt = 0;
	p_keyLinkNode->kC.keyManyCliCnt = 0;
	p_keyLinkNode->kC.kDownLockState = KEY_NULL;
	
	// 把初始化好的 key 给用户用
	userKey->GPIOx = GPIOx;
	userKey->GPIO_Pin = GPIO_Pin;
	userKey->keyID = p_keyLinkNode->kC.keyID;
	userKey->portname = GPIOxName;
	userKey->pinName = PinName;

	// 打印添加的按键信息
	yxy_DEBUG("New key :\r\n   name : \"%s\" \r\n", p_keyLinkNode->kC.keyName);
	yxy_DEBUG("   port : GPIO%c\r\n", GPIOxName);
	yxy_DEBUG("   Pin  : PIN_%d\r\n", PinName);
	yxy_DEBUG("\r\n");
	
	return HAL_OK;
}

/**
  * @brief  按键链表删除一个按键节点
  * @param  按键的端口
  * @param  按键的引脚
  * @retval 返回 HAL_OK 则添加按键成功
  */
HAL_StatusTypeDef key_clear(User_Key * userKey)
{
	struct Key_Link * p_keyLinkNode = p_kl;	// 创建一个中间变量保存p_kl的值
	struct Key_Link * p_keyNode_temp = NULL;
	
	if(IS_GPIO_ALL_INSTANCE(userKey->GPIOx) != 1)
	{
		yxy_DEBUG("key clear GPIOx error... \r\n");
		return HAL_ERROR;
	}

	if(IS_GPIO_PIN(userKey->GPIO_Pin) != 1)
	{
		yxy_DEBUG("key clear GPIO_Pin error... \r\n");
		return HAL_ERROR;
	}
	
	// 在整个链表寻找按键
	while(p_keyLinkNode->p_kN != NULL)
	{
		// 寻找对应的按键,一但下一个节点是对应的按键,则把当前的节点连接到后面的节点上,并删除按键节点
		if((p_keyLinkNode->p_kN->kC.GPIOx == userKey->GPIOx) && (p_keyLinkNode->p_kN->kC.GPIO_Pin == userKey->GPIO_Pin))
		{
			p_keyNode_temp = p_keyLinkNode->p_kN;
			p_keyLinkNode->p_kN = p_keyNode_temp->p_kN;
			
			free(p_keyNode_temp);
			p_keyNode_temp = NULL;
			
			userKey->GPIOx = NULL;
			userKey->GPIO_Pin = 0;
			userKey->keyID = 0xff;
			
			yxy_DEBUG("key clear ok!\r\n");
			return HAL_OK;
		}
		
		p_keyLinkNode = p_keyLinkNode->p_kN;
	}
	
	return HAL_ERROR;
}
	
/**
  * @brief  扫描按键的状态,并跳转到对应的按键处理函数中
  * @param  
  * @retval 
  */
void key_scan(void)
{
	struct Key_Link * p_keyLinkNode = p_kl->p_kN;	// 创建一个中间变量保存p_kl的值

	// 扫描每一个节点看看,有没有按键的状态发生变化
	while(p_keyLinkNode != NULL)
	{
		// 若是当前节点的按键按下了
		if((p_keyLinkNode->kC.GPIOx->IDR & p_keyLinkNode->kC.GPIO_Pin) == (uint32_t)GPIO_PIN_RESET) // 按下不一定是低电平,后期可以适配
		{
			p_keyLinkNode->kC.keyUpCnt = 0;
			p_keyLinkNode->kC.keyDownCnt++;
			
			switch(p_keyLinkNode->kC.kDownLockState)
			{
				case KEY_NULL:
				{
					// 消抖处理
					if(p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination)
					{
						p_keyLinkNode->kC.keyDownCnt = 0;
						p_keyLinkNode->kC.keyManyCliCnt = 0;
						
						// 中间状态晋升为单击
						p_keyLinkNode->kC.kDownLockState = KEY_CLICK;
					}
				}
				break;
				case KEY_CLICK:
				{
					// 消抖处理
					if((p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination) && (p_keyLinkNode->kC.keyManyCliCnt == 1))
					{
						p_keyLinkNode->kC.keyDownCnt = 0;
						p_keyLinkNode->kC.keyManyCliCnt = 0;
						
#if KEY_SUPPORT >= 2
						// 中间状态晋升为双击
						p_keyLinkNode->kC.kDownLockState = KEY_DOUBLE;
#else
						// 无法判断为双击,则执行单击
						p_keyLinkNode->kC.kDownLockState = KEY_NULL;
						key_click(p_keyLinkNode->kC.keyID);
#endif
					}
					
					// 当预判断为单击的时候,仍按下2秒就是长按
					if((p_keyLinkNode->kC.keyDownCnt >= TIME_BASE_SLOT * 4) && (p_keyLinkNode->kC.keyManyCliCnt < 1))
					{
						p_keyLinkNode->kC.keyDownCnt = 0;
						p_keyLinkNode->kC.kDownLockState = KEY_NULL;
						
						key_long(p_keyLinkNode->kC.keyID);
						
						// 等待长按结束
						while((p_keyLinkNode->kC.GPIOx->IDR & p_keyLinkNode->kC.GPIO_Pin) == (uint32_t)GPIO_PIN_RESET);
					}
				}
				break;
#if KEY_SUPPORT >= 2
				case KEY_DOUBLE:
				{
					// 消抖处理
					if((p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination) && (p_keyLinkNode->kC.keyManyCliCnt == 2))
					{
						p_keyLinkNode->kC.keyDownCnt = 0;
						p_keyLinkNode->kC.keyManyCliCnt = 0;
						
#if KEY_SUPPORT >= 3
						// 中间状态晋升为三击
						p_keyLinkNode->kC.kDownLockState = KEY_THIRD;
#else
						// 无法判断为三击,则执行双击
						p_keyLinkNode->kC.kDownLockState = KEY_NULL;
						key_double(p_keyLinkNode->kC.keyID);
#endif
					}
				}
				break;
#endif
#if KEY_SUPPORT >= 3
				case KEY_THIRD:
				{
					// 消抖处理
					if((p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination) && (p_keyLinkNode->kC.keyManyCliCnt == 3))
					{
						p_keyLinkNode->kC.keyDownCnt = 0;
						p_keyLinkNode->kC.keyManyCliCnt = 0;
						
#if KEY_SUPPORT >= 4
						// 中间状态晋升为四击
						p_keyLinkNode->kC.kDownLockState = KEY_FOURTH;
#else
						// 无法判断为四击,则执行三击
						p_keyLinkNode->kC.kDownLockState = KEY_NULL;
						key_third(p_keyLinkNode->kC.keyID);
#endif
					}
				}
				break;
#endif
#if KEY_SUPPORT >= 4
				case KEY_FOURTH:
				{
					
				}
				break;
#endif		
				default:
					
				break;
			}
		}
		else // 若是当前节点的按键没按下
		{
			if(p_keyLinkNode->kC.kDownLockState != NULL)
			{
				p_keyLinkNode->kC.keyDownCnt = 0;
				p_keyLinkNode->kC.keyUpCnt++;
				
				switch(p_keyLinkNode->kC.kDownLockState)
				{
					case KEY_CLICK:
					{
						// 击打次数为 1
						p_keyLinkNode->kC.keyManyCliCnt = 1;
					}
					break;
#if KEY_SUPPORT >= 2				
					case KEY_DOUBLE:
					{
						// 击打次数为 2
						p_keyLinkNode->kC.keyManyCliCnt = 2;
					}
					break;
#endif
#if KEY_SUPPORT >= 3
					case KEY_THIRD:
					{
						// 击打次数为 3
						p_keyLinkNode->kC.keyManyCliCnt = 3;
					}
					break;
#endif
#if KEY_SUPPORT >= 4
					case KEY_FOURTH:
					{
						// 击打次数为 4
						p_keyLinkNode->kC.keyManyCliCnt = 4;
					}
					break;
#endif
					default:
					break;
				}
				
				if(p_keyLinkNode->kC.keyUpCnt >= TIME_BASE_SLOT)
				{
					p_keyLinkNode->kC.keyUpCnt = 0;
					p_keyLinkNode->kC.keyManyCliCnt = 0;
					
					switch(p_keyLinkNode->kC.kDownLockState)
					{
						case KEY_CLICK:
							key_click(p_keyLinkNode->kC.keyID);
							break;
#if KEY_SUPPORT >= 2
						case KEY_DOUBLE:
							key_double(p_keyLinkNode->kC.keyID);
							break;
#endif
#if KEY_SUPPORT >= 3
						case KEY_THIRD:
							key_third(p_keyLinkNode->kC.keyID);
							break;
#endif
#if KEY_SUPPORT >= 4
						case KEY_FOURTH:
							key_fourth(p_keyLinkNode->kC.keyID);
							break;
#endif
						default:
							break;
					}
					p_keyLinkNode->kC.kDownLockState = KEY_NULL;
				}
			}
		}
		
		p_keyLinkNode = p_keyLinkNode->p_kN;
	}
}

/**
  * @brief  获取已经添加的按键数量
  * @retval 返回添加的按键数量
  */
uint16_t get_key_Num(void)
{
	return keyNum;
}


/**
  * @brief  获取按键绑定的名字
  * @param  获取按键名字的buff(buff大小至少需要20个字节)
  * @param  按键的端口
  * @param  按键的引脚号
  * @retval 无
  */
HAL_StatusTypeDef get_key_Name(User_Key * userKey, uint8_t * nameReadBuff)
{
	struct Key_Link * p_keyLinkNode = p_kl->p_kN;	// 创建一个中间变量保存p_kl的值
	
	while(p_keyLinkNode != NULL)
	{
		// 寻找对应的按键
		if((p_keyLinkNode->kC.GPIOx == userKey->GPIOx) && (p_keyLinkNode->kC.GPIO_Pin == userKey->GPIO_Pin))
		{
			strcpy((char *)nameReadBuff, (const char *)p_keyLinkNode->kC.keyName);
			return HAL_OK;
		}
		
		p_keyLinkNode = p_keyLinkNode->p_kN;
	}
	
	return HAL_ERROR;
}



yxy_open_key.h

#ifndef __KEY_H_
#define __KEY_H_

/* 支持C++调用。
 * 用extern"C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。
 */
#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/
 
#include "gpio.h"

/*Error checking*/
#ifdef YXY
//	#error "故意给你一个错误,啦啦啦!"
	#warning "这个警告要害死强迫症!"
#endif



/*********************
 *      DEFINES
 *********************/


/* 按键多击支持(默认支持单击和长按)
 *
 * >=2 则可支持2击
 * >=3 则可支持2、3击
 * >=4 则可支持2、3、4击
*/
#define KEY_SUPPORT 4

/* 最大支持的按键数量 */
#define KEY_MAX_NUM 10

/* 默认按键名字最大长度,默认值为20 */
#define KEY_NUME_LEN_MAX 20

/* 消抖时间 - 20ms */
#define KEY_Jitters_Elimination 20

/* 时间基段 - 100ms */
#define TIME_BASE_SLOT 100



/**********************
 *      TYPEDEFS
 **********************/
 
 
/* 按键状态值KEY_枚举 */
typedef enum
{
	KEY_NULL = 0	, // 按键无动作
	
	KEY_CLICK = 1	, // 默认支持单击
	
#if KEY_SUPPORT >= 2
	KEY_DOUBLE = 2	,
#endif
#if KEY_SUPPORT >= 3
	KEY_THIRD  = 3	,
#endif
#if KEY_SUPPORT >= 4
	KEY_FOURTH  = 4	,
#endif
	
	KEY_MAX_VAL = 0xffff, // 按键状态最大值(永远无法超越的值)
	
	KEY_LONG_PRESS = KEY_MAX_VAL-1, // 长按
	
	KEY_LAST_NULL_VAL = 0xffff // 按键最后的无效值
}KEY_STATE;


/*  按键类结构体 */
typedef struct
{
	/* 按键的名称、代号 */
	uint8_t keyName[KEY_NUME_LEN_MAX];	// 按键名字,长度可由 KEY_NUME_LEN 设置
	uint16_t keyID;						// 按键id,由代码自动分配
	
	/* 按键硬件 */
	GPIO_TypeDef *GPIOx;				// 按键的端口
	uint16_t GPIO_Pin;					// 按键的引脚
	
	/* 按键的状态 */
	uint16_t keyDownCnt;				// 按键按下的计数值
	uint16_t keyUpCnt;					// 按键抬起的计数值
	uint16_t keyManyCliCnt;				// 按键多击计数
	KEY_STATE kDownLockState;			// 被按下的按键当前已解锁的状态
}key_Class;

/* 按键链表结构体 */
struct Key_Link
{
	key_Class kC;					// 按键类
	
	struct Key_Link * p_kN;			// 指向下一个节点的指针
};

/* 用户按键结构体 */
typedef struct
{
	/* 代号 */
	uint16_t keyID;				// 按键id,由代码自动分配
	
	/* 按键硬件 */
	GPIO_TypeDef *GPIOx;		// 按键的端口
	uint16_t GPIO_Pin;			// 按键的引脚
	
	/* 硬件引脚打印用值 */
	uint8_t portname;			// 端口的名称
	uint16_t pinName;			// 引脚的名称
}User_Key;



/**********************
 * GLOBAL PROTOTYPES
 **********************/

HAL_StatusTypeDef openKey_init(void);
HAL_StatusTypeDef key_add(User_Key * userKey, uint8_t * keyName, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
HAL_StatusTypeDef key_clear(User_Key * userKey);
void key_scan(void);


uint16_t get_key_Num(void);
HAL_StatusTypeDef get_key_Name(User_Key * userKey, uint8_t * nameReadBuff);


/* 弱函数外部引用(可以重写,不用调用) */
void key_click(uint16_t key_id);
#if KEY_SUPPORT >= 2
void key_double(uint16_t key_id);
#endif
#if KEY_SUPPORT >= 3
void key_third(uint16_t key_id);
#endif
#if KEY_SUPPORT >= 4
void key_fourth(uint16_t key_id);
#endif
void key_long(uint16_t key_id);


#ifdef __cplusplus
} /*extern "C"*/
#endif

#endif /* __KEY_H_ */

使用

初次使用

在 yxy_open_key.h 中更改对应的宏配置

image-20221017102632789

KEY_SUPPORT

  • 按键多击支持(默认支持单击和长按)
  • >=2 则可支持2击
  • >=3 则可支持2、3击
  • >=4 则可支持2、3、4击

KEY_MAX_NUM

  • 可添加按键的最大数量。

KEY_NUME_LEN_MAX

  • 用户自定义的按键名字的最大长度。

KEY_Jitters_Elimination

  • 用户可定义的按键消抖时间,单位ms。

TIME_BASE_SLOT

  • 时间段落的长度,用于判断按键的不同击打模式的时间基准,单位ms。

main.c

main函数外需要添加的部分

头文件


#include "O_redirect.h"
#include "yxy_debug.h"
#include "yxy_open_key.h"

需要定义的全局变量


// 用户的按键对象,用于初始化按键,以及在按键回调中,匹配按键id,
// 或者用于获取按键的打印信息
User_Key key0;

重写按键回调函数


// 单击回调函数
void key_click(uint16_t key_id)
{
	if(key_id == key0.keyID)
	{
		U_Printf("key0 click\r\n");
	}
}

// 双击回调函数
void key_double(uint16_t key_id)
{
	if(key_id == key0.keyID)
	{
		U_Printf("key0 double\r\n");
	}
}

// 三击回调函数
void key_third(uint16_t key_id)
{
	if(key_id == key0.keyID)
	{
		U_Printf("key0 third\r\n");
	}
}

// 四击回调函数
void key_fourth(uint16_t key_id)
{
	if(key_id == key0.keyID)
	{
		U_Printf("key0 fourth\r\n");
	}
}

// 长按回调函数
void key_long(uint16_t key_id)
{
	if(key_id == key0.keyID)
	{
		U_Printf("key0 long\r\n");
	}
}

main函数非循环部分

// 开源按键初始化,这个是必须的
openKey_init();

// 根据自己的按键情况,添加按键进来
key_add(&Key0, (uint8_t *)"first key", KEY0_GPIO_Port, KEY0_Pin);

while循环中

// 对应的按键扫描函数,提供时间基准,时间基准最好在 1ms 左右
// 以及扫描按键链表上各个按键的状态,并调用对应的按键事件回调函数
key_scan();

HAL_Delay(1);


完整代码

开源按键完整工程代码点这里

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 21:31:28  更:2022-10-22 21:34:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:18:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码