单片机按键扫描

按键扫描

前言

最近因为工作原因使用了CA51的触摸按键功能,看了他们的按键扫描函数觉得函数比较好使用的,所以以他的框架写了一个使用32单片机的按键扫描函数。本代码也可以在51中使用,但如果你使用不到复合按键或者复合按键不超过2的话不建议使用,因为我使用的键值是32位的并且有更好的方式实现可以节省一些ram。后续如果有时间的话,再去为51优化这个代码。

一些知识点

因为我使用的是STM32F407的芯片,按键是共地的。我的开发板上只有三个按键,K0到K2分别连接的PE2到PE4。

为方便读取gpio口电平,根据位段算出的三个IO口的IDR的位段地址。

2025-11-30-14-51-48-image

2025-11-30-14-52-00-image

源码

头文件

#ifndef __KEY_SCANF_H__
#define __KEY_SCANF_H__


//需要自己定义uint8_t uint16_t uint32_t,其他地方已经定义了就注释掉并加入头文件
#define TYPEDEF_UINT_ENABLE

#ifdef TYPEDEF_UINT_ENABLE
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
#else
#error "加入定义uint8_t uint16_t uint32_t的头文件"
#endif

#define K1					(1UL)
#define K2					(2UL)
#define K3					(3UL)
#define K4					(4UL)
#define K5					(5UL)
#define K6					(6UL)
#define K7					(7UL)
#define K8					(8UL)
#define K9					(9UL)
#define K10					(10UL)
#define K11					(11UL)
#define K12					(12UL)
#define K13					(13UL)
#define K14					(14UL)
#define K15					(15UL)
#define K16					(16UL)
#define K17					(17UL)
#define K18					(18UL)
#define K19					(19UL)
#define K20					(20UL)
#define K21					(21UL)
#define K22					(22UL)
#define K23					(23UL)
#define K24					(24UL)
#define K25					(25UL)
#define K26					(26UL)
#define K27					(27UL)
#define K28					(28UL)
#define K29					(29UL)
#define K30					(30UL)
#define K31					(31UL)
#define KEY_LONG_BREAK		(1UL<<28)//BIT28
#define KEY_LONG_START		(1UL<<29)//BIT29
#define KEY_LONG			(1UL<<30)//BIT30
#define KEY_BREAK			(1UL<<31)//BIT31

#define COMPOUND_KEY_ENABLE	//如果使用复合按键则打开此宏 打开后按键数量不能超过15个,复合键最多支持6个
#define KEY_NUM 3	//按键个数,必须和实际相同

#ifdef COMPOUND_KEY_ENABLE
#define OFFSET_SIZE		(4)
#else
#define OFFSET_SIZE		(5)
#endif

enum
{
	NONE_KEY_PRESSED = 0,
	KEY_PRESSED,
};


void key_scanf_init(void);
uint32_t key_scanf_handler(void);






#endif /* __KEY_SCANF_H__ */

源文件

#include "key_scanf.h"
#include "string.h"
//#include "stm32f4xx.h"


/*
	按键驱动要自己写,如初始化函数等

	key_value_update函数要实现的功能
	1.更新key_dev的key_pressed_buf数组,记录按键是否按下,1按下,0松开
	2.pressed_key_num记录按下的按键数量
*/
//#error "根据按键不同,驱动的不同,实现对下面按键数据的更新,实现key_value_update函数"


typedef struct
{
    uint8_t key_pressed_buf[KEY_NUM];       //按键是否被按下,按键状态数组
    uint8_t pressed_key_num;     			//记录按键按下的个数,复合键最多支持3个按键符合键
}key_pressed_t;

static key_pressed_t key_dev;	//按键扫描结构体

// void key_gpio_init()
// {
//     GPIO_InitTypeDef GPIO_InitStructure;
//     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOA, ENABLE);
//     GPIO_InitStructure.GPIO_Pin			= GPIO_Pin_8 |GPIO_Pin_9;
// 	GPIO_InitStructure.GPIO_Mode		= GPIO_Mode_IN;
// 	GPIO_InitStructure.GPIO_Speed		= GPIO_Speed_50MHz;
// 	GPIO_InitStructure.GPIO_OType		= GPIO_OType_OD;
// 	GPIO_InitStructure.GPIO_PuPd		= GPIO_PuPd_UP;
//     GPIO_Init(GPIOB,&GPIO_InitStructure);
// 	GPIO_InitStructure.GPIO_Pin			= GPIO_Pin_0;
// 	GPIO_InitStructure.GPIO_PuPd		= GPIO_PuPd_DOWN;	
// 	GPIO_Init(GPIOA,&GPIO_InitStructure);
// }

// enum 
// {
//     KEY_STATE_RELEASE = 0,
//     KEY_STATE_DEBOUNCE,
//     KEY_STATE_PRESS,
// };

// #define KEY3_PA0IDR_ADDR 			(0x42400200)
// #define KEY1_PB8IDR_ADDR 			(0x42408220)
// #define KEY2_PB9IDR_ADDR 			(0x42408224)


void key_value_update(void)
{
    //#error "根据按键不同,驱动的不同,实现对下面按键数据的更新,实现key_value_update函数"
    // static uint8_t key_state[KEY_NUM]; //按键状态
    // static const uint32_t key_pin[KEY_NUM] = {KEY1_PB8IDR_ADDR,KEY2_PB9IDR_ADDR,KEY3_PA0IDR_ADDR};
    // #define KEY_PIN(I) *((uint32_t *)(key_pin[I]))

    // //因为我打算10ms运行一次所以这么写
    // uint8_t i;
    // for(i=0;i<KEY_NUM-1;i++)
    // {
    //     switch(key_state[i])
    //     {
    //         case KEY_STATE_RELEASE:
    //         {
    //             if(KEY_PIN(i) == 0)
    //             {
    //                 key_dev.key_pressed_buf[i] = 0;
    //                 key_state[i] = KEY_STATE_DEBOUNCE;
    //             }
    //             break;
    //         }
    //         case KEY_STATE_DEBOUNCE:
    //         {
    //             if(KEY_PIN(i) == 0)//按键按下
    //             {
	// 				key_dev.pressed_key_num++;
	// 				key_dev.key_pressed_buf[i] = 1;
    //                 key_state[i] = KEY_STATE_PRESS;
    //             }
    //             break;
    //         }
    //         case KEY_STATE_PRESS:
    //         {
    //             if(KEY_PIN(i) == 1)
    //             {
    //                 key_dev.key_pressed_buf[i] = 0;
	// 				key_dev.pressed_key_num--;
    //                 key_state[i] = KEY_STATE_RELEASE;
    //             }
    //             break;
    //         }
    //         default:key_state[i] = KEY_STATE_RELEASE;break;
    //     }
    // }
	// i = 2;
	// switch(key_state[i])
	// {
	// 	case KEY_STATE_RELEASE:
	// 	{
	// 		if(KEY_PIN(i) == 1)
	// 		{
	// 			key_dev.key_pressed_buf[i] = 0;
	// 			key_state[i] = KEY_STATE_DEBOUNCE;
	// 		}
	// 		break;
	// 	}
	// 	case KEY_STATE_DEBOUNCE:
	// 	{
	// 		if(KEY_PIN(i) == 1)//按键按下
	// 		{
	// 			key_dev.pressed_key_num++;
	// 			key_dev.key_pressed_buf[i] = 1;
	// 			key_state[i] = KEY_STATE_PRESS;
	// 		}
	// 		break;
	// 	}
	// 	case KEY_STATE_PRESS:
	// 	{
	// 		if(KEY_PIN(i) == 0)
	// 		{
	// 			key_dev.key_pressed_buf[i] = 0;
	// 			key_dev.pressed_key_num--;
	// 			key_state[i] = KEY_STATE_RELEASE;
	// 		}
	// 		break;
	// 	}
	// 	default:key_state[i] = KEY_STATE_RELEASE;break;
	// }
    // #undef KEY_PIN
    
}

/*
函数功能:按键扫描初始化
参数:无
返回值:无
*/
void key_scanf_init(void)
{
    memset(&key_dev,0,sizeof(key_pressed_t));

    //加入你的按键初始化代码
	//#error "加入你的按键初始化代码"
    key_gpio_init();
}

/* key_press_TimeCnt 根据函数调用频率来确定计时时间,我打算10ms运行一次所以这样时间常量如下设置 */
#define LONG_START_TIMECNT			(100)
#define LONG_KEY_TIMECNT			(25)
/*
函数功能:按键扫描处理函数
参数:无
返回值:键值

注意!
若按键符合按键按下后,得所有按键松开后才产生松开事件。

*/
uint32_t key_scanf_handler(void)
{
	static uint32_t key_press_TimeCnt,key_value_old=0;
	static uint8_t key_scanf_state = NONE_KEY_PRESSED;
	static uint8_t key_num_old = 0,longflag = 0;
	uint32_t key_value_reply = 0;
	uint8_t i,offset=0;
	key_value_update();	

	/* 将key_dev.key_pressed_buf数据转换成键值 */
	if(key_dev.pressed_key_num != 0)
	{
		for(i=0;i<KEY_NUM;i++)
		{
			if(key_dev.key_pressed_buf[i] == 1)
			{
				key_value_reply |= ((uint32_t)(i+1)<<offset);
				offset += OFFSET_SIZE;
			}
		}
	}
	else
	{
		if(key_scanf_state == NONE_KEY_PRESSED)
			return 0;
	}
	
	
	switch(key_scanf_state)
	{
		case NONE_KEY_PRESSED://无按键按下,等待按键按下
		{
			key_num_old = key_dev.pressed_key_num;
			key_scanf_state = KEY_PRESSED;
			key_value_old = key_value_reply;
			key_press_TimeCnt = 0;
			longflag = 0;
			return key_value_reply;
		}
		case KEY_PRESSED://按键按下中,等待按键松开或者另外按键按下形成复合键,超时到长按状态
		{
			if(key_dev.pressed_key_num == 0)
			{
				if(key_press_TimeCnt >= LONG_START_TIMECNT)
					key_value_reply = key_value_old | KEY_LONG_BREAK;
				else
					key_value_reply = key_value_old | KEY_BREAK;
				key_scanf_state = NONE_KEY_PRESSED;
				return key_value_reply;
			}else if(key_dev.pressed_key_num < key_num_old)
			{
				//null
			}else if(key_dev.pressed_key_num == key_num_old)
			{
				if(key_value_old == key_value_reply)
				{
					key_press_TimeCnt++;
					if(longflag == 0)
					{
						if(key_press_TimeCnt >= LONG_START_TIMECNT)
						{
							longflag = 1;
							key_value_reply = key_value_old | KEY_LONG_START;
							return key_value_reply;
						}
					}
					else
					{
						if((key_press_TimeCnt%LONG_KEY_TIMECNT)==0)
						{
							key_value_reply = key_value_old | KEY_LONG;
							return key_value_reply;
						}
					}
				}
				else
				{
					if(key_press_TimeCnt >= LONG_START_TIMECNT)
						key_value_reply = key_value_old | KEY_LONG_BREAK;
					else
						key_value_reply = key_value_old | KEY_BREAK;
					key_scanf_state = NONE_KEY_PRESSED;
					return key_value_reply;
				}
			}else if(key_dev.pressed_key_num <= KEY_NUM)
			{
				key_value_old = key_value_reply;
				key_num_old = key_dev.pressed_key_num;
				longflag = 0;
				key_press_TimeCnt = 0;
				return key_value_reply;
			}
		}
	}
	return 0;
	
}

使用介绍

结构体介绍

    /*
    按键驱动要自己写,如初始化函数等

    key_value_update函数要实现的功能
    1.更新key_dev的key_pressed_buf数组,记录按键是否按下,1按下,0松开
    2.pressed_key_num记录按下的按键数量
*/
//#error "根据按键不同,驱动的不同,实现对下面按键数据的更新,实现key_value_update函数"

typedef struct
{
    uint8_t key_pressed_buf[KEY_NUM];       //按键是否被按下,按键状态数组
    uint8_t pressed_key_num;                 //记录按键按下的个数,复合键最多支持3个按键符合键
}key_pressed_t;

key_pressed_buf数组索引对应K1~K28,pressed_key_num记录当前按键按下的个数。

移植使用

  1. KEY_NUM 此宏应为你的按键数量

  2. 如果需要使用复合按键则打开宏#define COMPOUND_KEY_ENABLE

  3. 你得自己实现key_value_update函数,此函数需要更新key_dev结构体的值。key_scanf_handler定期调用此函数,调用频率关系到长按和持续长按事件的发生。并且此函数会调用key_value_update函数,所以你实现时也可利用此调用周期完成按键消抖等逻辑。LONG_START_TIMECNT和LONG_KEY_TIMECNT因为我的调用周期是10ms所以我长按1s产生长按事件并且从这后每250ms产生一次持续长按事件。

函数逻辑

我的key_value_update函数

此函数只适用于我的开发版,但可以提供参考。我的调用周期是10ms,所以我的消抖直接就是转换按键状态了。

下面是代码逻辑图

2025-11-30-16-09-32-image

key_scanf_handler函数

此函数不用修改,可直接使用。不过若是51可以修改逻辑以节省ram。

代码逻辑图

2025-11-30-17-00-47-image

posted @ 2025-11-30 17:24  洛神入菜园  阅读(39)  评论(0)    收藏  举报