STM32安富莱BSP学习指南:按键驱动详解

目录

1 学习架构的设计

1.1 函数设计

1.1  宏定义

1.3 按键ID结构体

1.4 按键对象结构体定义

1.5 fifo对象结构体定义                                                                                                                        

1.6 按键码值定义

2 移植

2.1 硬件连接

2.2  宏与变量的分析

2.2.1  引脚宏定义

2.2.2 按键函数 

3 函数分析

3.1 判断按键是否按下函数

3.2 FIFO的原理

3.3 按键初始化变量

3.4 FIFO压入键值函数

3.5 FIFO读取函数

3.6 FIFO读取函数 单独指针

3.7 清空按键FIFO

3.8 按键GPIO初始化

3.9 扫描按键(很重要)

3.10 扫描按键函数


如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的

stm32学习笔记: stm32学习笔记源码 (gitee.com)(出差是公司电脑改的代码,有锁所以不能上传,只能回去了拿自己电脑上传了)

如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用

git的使用git的使用(下载及上传_gitcode怎么下载代码-CSDN博客

有错误的地方欢迎大家多多指出,方便我修改错误,以及提升自己
                        

1 学习架构的设计

首先看一下结构设计:写的安富莱BSP设计文章,基本都是我拿来学习架构设计思想和c函数写法的。

首先直接看.h文件吧,一般架构都是直接搭的,h文件,从上到下,每段代码都会写的

1.1 函数设计

先看一下函数有哪些

/* 供外部调用的函数声明 */
void bsp_InitKey(void);
void bsp_KeyScan(void);
void bsp_PutKey(uint8_t _KeyCode);
uint8_t bsp_GetKey(void);
uint8_t bsp_GetKey2(void);
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID);
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed);
void bsp_ClearKey(void);

1 、应该是init整个的初始化函数    2 、按键扫描函数 

3 、fifo压入键值函数  这里有个形参   ,应该是通过这个参数来判断是哪个按键被按下了

4 、读取按键函数  5 读取按键函数2 

 6 、获取按键状态   这个类型是一个结构体的形参,传入按键的ID号,将按键操作抽象为一个结构体,里面带有当前按键状态的参数,可以直接通过参数来判断是否按下。

7、设置按键参数通过id号来更改长按时间设置,以及连发设置

8、清除按键值

1.1  宏定义

首先是设定了一下按键的最大个数:这个个数用来判断是哪个按键的时候很好用。

#define KEY_COUNT    10	   					/* 按键个数, 8个独立建 + 2个组合键 */

之后是一些按键的宏定义,在判断哪个按键按下时,可以直接用宏来写,这样提高可读性


/* 根据应用程序的功能重命名按键宏 */
#define KEY_DOWN_K1		KEY_1_DOWN
#define KEY_UP_K1		KEY_1_UP
#define KEY_LONG_K1		KEY_1_LONG

#define KEY_DOWN_K2		KEY_2_DOWN
#define KEY_UP_K2		KEY_2_UP
#define KEY_LONG_K2		KEY_2_LONG

#define KEY_DOWN_K3		KEY_3_DOWN
#define KEY_UP_K3		KEY_3_UP
#define KEY_LONG_K3		KEY_3_LONG

#define JOY_DOWN_U		KEY_4_DOWN		/* 上 */
#define JOY_UP_U		KEY_4_UP
#define JOY_LONG_U		KEY_4_LONG

#define JOY_DOWN_D		KEY_5_DOWN		/* 下 */
#define JOY_UP_D		KEY_5_UP
#define JOY_LONG_D		KEY_5_LONG

#define JOY_DOWN_L		KEY_6_DOWN		/* 左 */
#define JOY_UP_L		KEY_6_UP
#define JOY_LONG_L		KEY_6_LONG

#define JOY_DOWN_R		KEY_7_DOWN		/* 右 */
#define JOY_UP_R		KEY_7_UP
#define JOY_LONG_R		KEY_7_LONG

#define JOY_DOWN_OK		KEY_8_DOWN		/* ok */
#define JOY_UP_OK		KEY_8_UP
#define JOY_LONG_OK		KEY_8_LONG

#define SYS_DOWN_K1K2	KEY_9_DOWN		/* K1 K2 组合键 */
#define SYS_UP_K1K2	    KEY_9_UP
#define SYS_LONG_K1K2	KEY_9_LONG

#define SYS_DOWN_K2K3	KEY_10_DOWN		/* K2 K3 组合键 */
#define SYS_UP_K2K3  	KEY_10_UP
#define SYS_LONG_K2K3	KEY_10_LONG

1.3 按键ID结构体

然后定义了一个按键的结构体,里面写了各个按键的名称,用来检测按键是否按下。

/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum
{
	KID_K1 = 0,
	KID_K2,
	KID_K3,
	KID_JOY_U,
	KID_JOY_D,
	KID_JOY_L,
	KID_JOY_R,
	KID_JOY_OK
}KEY_ID_E;
/*
	按键滤波时间50ms, 单位10ms。
	只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
	即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
*/
#define KEY_FILTER_TIME   5
#define KEY_LONG_TIME     100			/* 单位10ms, 持续1秒,认为长按事件 */

之后写了两个define  一个是滤波时间,一个是判断长按时间(因为最后的按键函数是被10ns中断调用的,所以这里的单位是10ms)。

1.4 按键对象结构体定义

/*
	每个按键对应1个全局的结构体变量。
*/
typedef struct
{
	/* 下面是一个函数指针,指向判断按键手否按下的函数 */
	uint8_t (*IsKeyDownFunc)void); /* 按键按下的判断函数,1表示按下 */

	uint8_t  Count;			/* 滤波器计数器 */
	uint16_t LongCount;		/* 长按计数器 */
	uint16_t LongTime;		/* 按键按下持续时间, 0表示不检测长按 */
	uint8_t  State;			/* 按键当前状态(按下还是弹起) */
	uint8_t  RepeatSpeed;	/* 连续按键周期 */
	uint8_t  RepeatCount;	/* 连续按键计数器 */
}KEY_T;

上面是一个按键的结构体:里面有一些按键的基本特征(然后就是面向对象,把对象的特征描述出来,尽量可复用),

最开始是一个函数指针,用来存放每个按键的判断函数。

首先两个计数器:一个滤波的,一个长按计数的。之后是一个长按计数时间,当长按时间>=了长按时间后才能判断为长按了,之后是一个按键状态,按下为1弹起为0。

之后一个是连续周期,一个是连续按键计数器  :这两个东西我没理解用途,后面具体写代码的时候再说了。

1.5 fifo对象结构体定义                                                                                                                        

/* 按键FIFO用到变量 */
#define KEY_FIFO_SIZE	10
typedef struct
{
	uint8_t Buf[KEY_FIFO_SIZE];		/* 键值缓冲区 */
	uint8_t Read;					/* 缓冲区读指针1 */
	uint8_t Write;					/* 缓冲区写指针 */
	uint8_t Read2;					/* 缓冲区读指针2 */
}KEY_FIFO_T;

之后构体对象,一个是fifo的结构体,fifo原理后面写了哦不了解的可以跳到fifo的原理那一节看看。

1  就是一个值的缓冲区,后面写FIFO的时候大家对照看,2 读指针  3写指针 

4 读指针2   在某些情况下,可能有两个任务都需要访问按键缓冲区,为了避免键值被其中一个任务取空,我们添加了第2个读指针Read2。

1.6 按键码值定义

之后是按键的码值,把所有按键的码值枚举出来,通过轮训来判断是否有按键按下了,如果按下,就把变量直接压入fifo,弹起来了也是同理。

/*
	定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件

	推荐使用enum, 不用#define,原因:
	(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
	(2) 编译器可帮我们避免键值重复。
*/
typedef enum
{
	KEY_NONE = 0,			/* 0 表示按键事件 */

	KEY_1_DOWN,				/* 1键按下 */
	KEY_1_UP,				/* 1键弹起 */
	KEY_1_LONG,				/* 1键长按 */

	KEY_2_DOWN,				/* 2键按下 */
	KEY_2_UP,				/* 2键弹起 */
	KEY_2_LONG,				/* 2键长按 */

	KEY_3_DOWN,				/* 3键按下 */
	KEY_3_UP,				/* 3键弹起 */
	KEY_3_LONG,				/* 3键长按 */

	KEY_4_DOWN,				/* 4键按下 */
	KEY_4_UP,				/* 4键弹起 */
	KEY_4_LONG,				/* 4键长按 */

	KEY_5_DOWN,				/* 5键按下 */
	KEY_5_UP,				/* 5键弹起 */
	KEY_5_LONG,				/* 5键长按 */

	KEY_6_DOWN,				/* 6键按下 */
	KEY_6_UP,				/* 6键弹起 */
	KEY_6_LONG,				/* 6键长按 */

	KEY_7_DOWN,				/* 7键按下 */
	KEY_7_UP,				/* 7键弹起 */
	KEY_7_LONG,				/* 7键长按 */

	KEY_8_DOWN,				/* 8键按下 */
	KEY_8_UP,				/* 8键弹起 */
	KEY_8_LONG,				/* 8键长按 */

	/* 组合键 */
	KEY_9_DOWN,				/* 9键按下 */
	KEY_9_UP,				/* 9键弹起 */
	KEY_9_LONG,				/* 9键长按 */

	KEY_10_DOWN,			/* 10键按下 */
	KEY_10_UP,				/* 10键弹起 */
	KEY_10_LONG,			/* 10键长按 */
}KEY_ENUM;

2 移植

2.1 硬件连接

确定自己板子的硬件连接,到时候好更改代码

我这里两个按键都是高有效的。 引脚为PA6 与PC13

2.2  宏与变量的分析

2.2.1  引脚宏定义

/*
	安富莱STM32-V4 按键口线分配:
		K1 键      : PC13   (低电平表示按下)
		K2 键      : PA0    ( --- 高电平表示按下)
		K3 键      : PG8    (低电平表示按下)
		摇杆UP键   : PG15   (低电平表示按下)
		摇杆DOWN键 : PD3    (低电平表示按下)
		摇杆LEFT键 : PG14   (低电平表示按下)
		摇杆RIGHT键: PG13   (低电平表示按下)
		摇杆OK键   : PG7    (低电平表示按下)
*/

/* 按键口对应的RCC时钟 */
#define RCC_ALL_KEY 	(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOG)

#define GPIO_PORT_K1    GPIOC
#define GPIO_PIN_K1	    GPIO_Pin_13

#define GPIO_PORT_K2    GPIOA
#define GPIO_PIN_K2	    GPIO_Pin_0

#define GPIO_PORT_K3    GPIOG
#define GPIO_PIN_K3	    GPIO_Pin_8

#define GPIO_PORT_K4    GPIOG
#define GPIO_PIN_K4	    GPIO_Pin_15

#define GPIO_PORT_K5    GPIOD
#define GPIO_PIN_K5	    GPIO_Pin_3

#define GPIO_PORT_K6    GPIOG
#define GPIO_PIN_K6	    GPIO_Pin_14

#define GPIO_PORT_K7    GPIOG
#define GPIO_PIN_K7	    GPIO_Pin_13

#define GPIO_PORT_K8    GPIOG
#define GPIO_PIN_K8	    GPIO_Pin_7

static KEY_T s_tBtn[KEY_COUNT];
static KEY_FIFO_T s_tKey;		/* 按键FIFO变量,结构体 */

static void bsp_InitKeyVar(void);
static void bsp_InitKeyHard(void);
static void bsp_DetectKey(uint8_t i);

首先是按键GPIO宏与时钟开关的宏:这个很简单就是将后面的替换成为前面的,提高可读性。

移植:这里更改起来也很简单,直接把按键替换成自己的硬件即可,时钟也记得换,没用的定义可以删一下。

2.2.2 按键函数 

按键函数需要注意一下低有效和高有效的区别,具体的请看3.1的按键判断函数分析哦

static uint8_t IsKeyDown1(void)
{
	if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) == 0 && (GPIO_PORT_K2->IDR & GPIO_PIN_K2) == 0
		&& (GPIO_PORT_K3->IDR & GPIO_PIN_K3) != 0)
		return 1;
	else 
		return 0;
}

3 函数分析

3.1 判断按键是否按下函数

/*
*********************************************************************************************************
*	函 数 名: IsKeyDownX
*	功能说明: 判断按键是否按下
*	形    参: 无
*	返 回 值: 返回值1 表示按下,0表示未按下
*********************************************************************************************************
*/
/* 安富莱 STM32-V4 开发板 */
#if 1	/* 为了区分3个事件: K1单独按下, K2单独按下, K1和K2同时按下 */
static uint8_t IsKeyDown1(void)
{
	if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) == 0 && (GPIO_PORT_K2->IDR & GPIO_PIN_K2) == 0
		&& (GPIO_PORT_K3->IDR & GPIO_PIN_K3) != 0)
		return 1;
	else 
		return 0;
}
static uint8_t IsKeyDown2(void)
{
	if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) != 0 && (GPIO_PORT_K2->IDR & GPIO_PIN_K2) != 0
		&& (GPIO_PORT_K3->IDR & GPIO_PIN_K3) != 0)
		return 1;
	else 
		return 0;
}
static uint8_t IsKeyDown3(void)
{
	if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) != 0 && (GPIO_PORT_K2->IDR & GPIO_PIN_K2) == 0
		&& (GPIO_PORT_K3->IDR & GPIO_PIN_K3) == 0)
		return 1;
	else 
		return 0;
}
static uint8_t IsKeyDown9(void)	/* K1 K2组合键 */
{
	if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) == 0 && (GPIO_PORT_K2->IDR & GPIO_PIN_K2) != 0
		&& (GPIO_PORT_K3->IDR & GPIO_PIN_K3) != 0)
		return 1;
	else 
		return 0;
}
static uint8_t IsKeyDown10(void)	/* K2 K3组合键 */
{
	if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) != 0 && (GPIO_PORT_K2->IDR & GPIO_PIN_K2) != 0
		&& (GPIO_PORT_K3->IDR & GPIO_PIN_K3) == 0)
		return 1;
	else 
		return 0;
}

这个和前面的判断LED灯是否点亮类似:还是判断输入寄存器:输入寄存器IDR,输入低就为0输入高就为1

这里代码太长了,没显示全,截图分析一下

举例一个:首先判断K1是否单独按下,这里K1是低触发 K2是高  K3是低(这个是板子电路图决定的)首先就是IDR&GPIO_PIN(IDR就是输入  GPIOPIN就是默认的位)

首先如果K1 & GPIO == 0 这就代表K!的IDR为为0  就是K1按下    按下为真这一段值就为1

K2 & GPIO == 0  这代表K2的IDR为0  所以K2没有按下   没按下这一段值为1

K3 & GPIO !=0   那就是&为1  K3也就是1  所有K3也就没有按下  没有按下这一段值为1

之后3个括号内的数据逻辑与(假设按下的为K1)那这个if判断就为真  则返回1

其他的同理了:

后面还写了一种

直接判断IDR值  之后通过返回值相与判断组合键

#else	
static uint8_t IsKeyDown1(void) {if ((GPIO_PORT_K1->IDR & GPIO_PIN_K1) == 0) return 1;else return 0;}
static uint8_t IsKeyDown2(void) {if ((GPIO_PORT_K2->IDR & GPIO_PIN_K2) != 0) return 1;else return 0;}
static uint8_t IsKeyDown3(void) {if ((GPIO_PORT_K3->IDR & GPIO_PIN_K3) == 0) return 1;else return 0;}

static uint8_t IsKeyDown9(void) {if (IsKeyDown1() && IsKeyDown2()) return 1;else return 0;}		/* K1 K2组合键 */
static uint8_t IsKeyDown10(void) {if (IsKeyDown2() && IsKeyDown3()) return 1;else return 0;}	/* K2 K3组合键 */
#endif

之后代码是一个五向摇杆 这些都和之前举例的差不多,可以自己看看

/* 5方向摇杆 */
static uint8_t IsKeyDown4(void) {if ((GPIO_PORT_K4->IDR & GPIO_PIN_K4) == 0) return 1;else return 0;}
static uint8_t IsKeyDown5(void) {if ((GPIO_PORT_K5->IDR & GPIO_PIN_K5) == 0) return 1;else return 0;}
static uint8_t IsKeyDown6(void) {if ((GPIO_PORT_K6->IDR & GPIO_PIN_K6) == 0) return 1;else return 0;}
static uint8_t IsKeyDown7(void) {if ((GPIO_PORT_K7->IDR & GPIO_PIN_K7) == 0) return 1;else return 0;}
static uint8_t IsKeyDown8(void) {if ((GPIO_PORT_K8->IDR & GPIO_PIN_K8) == 0) return 1;else return 0;}

3.2 FIFO的原理

在了解下面的部分是需要了解一下FIFO的,安富莱的按键是写了个FIFO的

FIFO是First Input First Output的缩写,先入先出队列。我们这里以5个字节的FIFO空间进行说明。Write变量表示写位置,Read变量表示读位置。

初始状态时,Read = Write = 0。

假设依次按下了K1 K2 那么就会写入4个数据 变为1按下、 1谈起  、2按下 、2弹起

此时write与read变量就不相等了

为什么要用FIFO来写按键操作

3.3 按键初始化变量

/*
*********************************************************************************************************
*	函 数 名: bsp_InitKey
*	功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitKey(void)
{
	bsp_InitKeyVar();		/* 初始化按键变量 */
	bsp_InitKeyHard();		/* 初始化按键硬件 */
}

先看  bsp_InitKeyVar();        /* 初始化按键变量 */

/*
*********************************************************************************************************
*	函 数 名: bsp_InitKeyVar
*	功能说明: 初始化按键变量
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyVar(void)
{
	uint8_t i;

	/* 对按键FIFO读写指针清零 */
	s_tKey.Read = 0;
	s_tKey.Write = 0;
	s_tKey.Read2 = 0;

	/* 给每个按键结构体成员变量赋一组缺省值 */
	for (i = 0; i < KEY_COUNT; i++)
	{
		s_tBtn[i].LongTime = KEY_LONG_TIME;			/* 长按时间 0 表示不检测长按键事件 */
		s_tBtn[i].Count = KEY_FILTER_TIME / 2;		/* 计数器设置为滤波时间的一半 */
		s_tBtn[i].State = 0;							/* 按键缺省状态,0为未按下 */
		//s_tBtn[i].KeyCodeDown = 3 * i + 1;				/* 按键按下的键值代码 */
		//s_tBtn[i].KeyCodeUp   = 3 * i + 2;				/* 按键弹起的键值代码 */
		//s_tBtn[i].KeyCodeLong = 3 * i + 3;				/* 按键被持续按下的键值代码 */
		s_tBtn[i].RepeatSpeed = 0;						/* 按键连发的速度,0表示不支持连发 */
		s_tBtn[i].RepeatCount = 0;						/* 连发计数器 */
	}

	/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
	/* 比如,我们希望按键1按下超过1秒后,自动重发相同键值 */
	s_tBtn[KID_JOY_U].LongTime = 100;
	s_tBtn[KID_JOY_U].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */

	s_tBtn[KID_JOY_D].LongTime = 100;
	s_tBtn[KID_JOY_D].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */

	s_tBtn[KID_JOY_L].LongTime = 100;
	s_tBtn[KID_JOY_L].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */

	s_tBtn[KID_JOY_R].LongTime = 100;
	s_tBtn[KID_JOY_R].RepeatSpeed = 5;	/* 每隔50ms自动发送键值 */

	/* 判断按键按下的函数 */
	s_tBtn[0].IsKeyDownFunc = IsKeyDown1;
	s_tBtn[1].IsKeyDownFunc = IsKeyDown2;
	s_tBtn[2].IsKeyDownFunc = IsKeyDown3;
	s_tBtn[3].IsKeyDownFunc = IsKeyDown4;
	s_tBtn[4].IsKeyDownFunc = IsKeyDown5;
	s_tBtn[5].IsKeyDownFunc = IsKeyDown6;
	s_tBtn[6].IsKeyDownFunc = IsKeyDown7;
	s_tBtn[7].IsKeyDownFunc = IsKeyDown8;

	/* 组合键 */
	s_tBtn[8].IsKeyDownFunc = IsKeyDown9;
	s_tBtn[9].IsKeyDownFunc = IsKeyDown10;
}

首先初始化前面说的按键FIFO的值

	/* 对按键FIFO读写指针清零 */
	s_tKey.Read = 0;
	s_tKey.Write = 0;
	s_tKey.Read2 = 0;

直接将read  wwrite  read2  清零

这里初始化结构体:1 长按时间:计算多少ms以上算长按 这里长按计算为1000ms也就是1s  2、这里滤波时间为50ms  设置滤波计数器为25 为了避免主板上电的瞬间,检测到一个无效的按键按下或弹起事件。我们将这个滤波计数器的初值设置为正常值的1/2

因为这个按键检测函数是10ms调用一次,所以这里的值单位都是10ms哦

 3、按键状态为0 这个就是检测是否有按键按下了,按下置1,谈起重新写0 

 4、按键连发速度  0的话就是不支持连发 

5、连发计算初始化为0

之后就是自定义程序==重新赋值和赋值函数进去了,这一些都是

3.4 FIFO压入键值函数

/*
*********************************************************************************************************
*	函 数 名: bsp_PutKey
*	功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
*	形    参:  _KeyCode : 按键代码
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_PutKey(uint8_t _KeyCode)
{
	s_tKey.Buf[s_tKey.Write] = _KeyCode;

	if (++s_tKey.Write  >= KEY_FIFO_SIZE)
	{
		s_tKey.Write = 0;
	}
}

这个函数就是往FIFO压入一个按键值了的操作了:当检测到按键事件发生后,可以调用 bsp_PutKey函数将键值压入FIFO,输入参数是按键代码

第一句就是在这个结构体的,键值缓冲区写入数据,写到数组的第几位数就是write的值。

如果write+1 >=了key_FIFO的值,就让write为0,后面具体使用这个函数的时候就会更加明白

键值代码是通过一个枚举来定义的

/*
	定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件

	推荐使用enum, 不用#define,原因:
	(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
	(2) 编译器可帮我们避免键值重复。
*/
typedef enum
{
	KEY_NONE = 0,			/* 0 表示按键事件 */

	KEY_1_DOWN,				/* 1键按下 */
	KEY_1_UP,				/* 1键弹起 */
	KEY_1_LONG,				/* 1键长按 */

	KEY_2_DOWN,				/* 2键按下 */
	KEY_2_UP,				/* 2键弹起 */
	KEY_2_LONG,				/* 2键长按 */

	KEY_3_DOWN,				/* 3键按下 */
	KEY_3_UP,				/* 3键弹起 */
	KEY_3_LONG,				/* 3键长按 */

	KEY_4_DOWN,				/* 4键按下 */
	KEY_4_UP,				/* 4键弹起 */
	KEY_4_LONG,				/* 4键长按 */

	KEY_5_DOWN,				/* 5键按下 */
	KEY_5_UP,				/* 5键弹起 */
	KEY_5_LONG,				/* 5键长按 */

	KEY_6_DOWN,				/* 6键按下 */
	KEY_6_UP,				/* 6键弹起 */
	KEY_6_LONG,				/* 6键长按 */

	KEY_7_DOWN,				/* 7键按下 */
	KEY_7_UP,				/* 7键弹起 */
	KEY_7_LONG,				/* 7键长按 */

	KEY_8_DOWN,				/* 8键按下 */
	KEY_8_UP,				/* 8键弹起 */
	KEY_8_LONG,				/* 8键长按 */

	/* 组合键 */
	KEY_9_DOWN,				/* 9键按下 */
	KEY_9_UP,				/* 9键弹起 */
	KEY_9_LONG,				/* 9键长按 */

	KEY_10_DOWN,			/* 10键按下 */
	KEY_10_UP,				/* 10键弹起 */
	KEY_10_LONG,			/* 10键长按 */
}KEY_ENUM;

3.5 FIFO读取函数

/*
*********************************************************************************************************
*	函 数 名: bsp_GetKey
*	功能说明: 从按键FIFO缓冲区读取一个键值。
*	形    参:  无
*	返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey(void)
{
	uint8_t ret;

	if (s_tKey.Read == s_tKey.Write)
	{
		return KEY_NONE;
	}
	else
	{
		ret = s_tKey.Buf[s_tKey.Read];

		if (++s_tKey.Read >= KEY_FIFO_SIZE)
		{
			s_tKey.Read = 0;
		}
		return ret;
	}
}

输入参数:无  变量有一个八位无符号整形

首先如果 读取等于写入值 那就代表FIFO里面没有按键了,这个从前面写的FIFO原来那里应该也看得出来。

如果不等:就用ret接收结构体buf内的值(从0开始的,所以读取第一个值)

如果read>= 了fifo size最大值就让read重新指到头结点去,之后返回ret接收的值。

3.6 FIFO读取函数 单独指针

/*
*********************************************************************************************************
*	函 数 名: bsp_GetKey2
*	功能说明: 从按键FIFO缓冲区读取一个键值。独立的读指针。
*	形    参:  无
*	返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey2(void)
{
	uint8_t ret;

	if (s_tKey.Read2 == s_tKey.Write)
	{
		return KEY_NONE;
	}
	else
	{
		ret = s_tKey.Buf[s_tKey.Read2];

		if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
		{
			s_tKey.Read2 = 0;
		}
		return ret;
	}
}

这里的代码就是上面说的第二个read指针,和第一个度的read是一样的哈。

7 读取按键状态

/*
*********************************************************************************************************
*	函 数 名: bsp_GetKeyState
*	功能说明: 读取按键的状态
*	形    参:  _ucKeyID : 按键ID,从0开始
*	返 回 值: 1 表示按下, 0 表示未按下
*********************************************************************************************************
*/
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID)
{
	return s_tBtn[_ucKeyID].State;
}

输入值就是键值:如果键值的state被赋值了 就是1 返回1就是按键被按下了

8 单独设置按键的参数

/*
*********************************************************************************************************
*	函 数 名: bsp_SetKeyParam
*	功能说明: 设置按键参数
*	形    参:_ucKeyID : 按键ID,从0开始
*			_LongTime : 长按事件时间
*			 _RepeatSpeed : 连发速度
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t  _RepeatSpeed)
{
	s_tBtn[_ucKeyID].LongTime = _LongTime;			/* 长按时间 0 表示不检测长按键事件 */
	s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed;			/* 按键连发的速度,0表示不支持连发 */
	s_tBtn[_ucKeyID].RepeatCount = 0;						/* 连发计数器 */
}

需要更改参数的可以自己直接调用,单独更改键值的参数。和初始化的时候一样

往结构体

1 键值  2 长按时间  3 连发速度

3.7 清空按键FIFO

/*
*********************************************************************************************************
*	函 数 名: bsp_ClearKey
*	功能说明: 清空按键FIFO缓冲区
*	形    参:无
*	返 回 值: 按键代码
*********************************************************************************************************
*/
void bsp_ClearKey(void)
{
	s_tKey.Read = s_tKey.Write;
}

直接read=write  这样就等于清空了,让read和write相等  下一次写入之后,我们再去读就是刚刚写入的值。

3.8 按键GPIO初始化

/*
*********************************************************************************************************
*	函 数 名: bsp_InitKeyHard
*	功能说明: 配置按键对应的GPIO
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyHard(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	/* 第1步:打开GPIO时钟 */
	RCC_APB2PeriphClockCmd(RCC_ALL_KEY, ENABLE);

	/* 第2步:配置所有的按键GPIO为浮动输入模式(实际上CPU复位后就是输入状态) */
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	/* 输入浮空模式 */
	
	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K1;
	GPIO_Init(GPIO_PORT_K1, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K2;
	GPIO_Init(GPIO_PORT_K2, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K3;
	GPIO_Init(GPIO_PORT_K3, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K4;
	GPIO_Init(GPIO_PORT_K4, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K5;
	GPIO_Init(GPIO_PORT_K5, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K6;
	GPIO_Init(GPIO_PORT_K6, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K7;
	GPIO_Init(GPIO_PORT_K7, &GPIO_InitStructure);

	GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K8;
	GPIO_Init(GPIO_PORT_K8, &GPIO_InitStructure);
}

GPIO初始化嘛,首先还是确定GPIO在哪个时钟总线上,然后先开启对应的时钟,然后初始化为浮空输入:具体输入模式看这个链接(暂时还没加过两天弄完会加链接进来的):

之后写入对应GPIO引脚,然后调用初始化的库函数,这个函数会把GPIO结构体里的数据帮你写到对应的寄存器去,有兴趣的可以去看看。

这里只用第一个写速度与模式:因为结构体数据写进去是不会变的,你第二个GPIO写入的时候,依旧可以沿用第一个GOIO的值,所以可以不改,但是你想每一个GPIO都写一个也是可以的。

3.9 扫描按键(很重要)

/*
*********************************************************************************************************
*	函 数 名: bsp_DetectKey
*	功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
*	形    参:  按键结构变量指针
*	返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectKey(uint8_t i)
{
	KEY_T *pBtn;

	/*
		如果没有初始化按键函数,则报错
		if (s_tBtn[i].IsKeyDownFunc == 0)
		{
			printf("Fault : DetectButton(), s_tBtn[i].IsKeyDownFunc undefine");
		}
	*/

	pBtn = &s_tBtn[i];
	if (pBtn->IsKeyDownFunc())
	{
		if (pBtn->Count < KEY_FILTER_TIME)
		{
			pBtn->Count = KEY_FILTER_TIME;
		}
		else if(pBtn->Count < 2 * KEY_FILTER_TIME)
		{
			pBtn->Count++;
		}
		else
		{
			if (pBtn->State == 0)
			{
				pBtn->State = 1;

				/* 发送按钮按下的消息 */
				bsp_PutKey((uint8_t)(3 * i + 1));
			}

			if (pBtn->LongTime > 0)
			{
				if (pBtn->LongCount < pBtn->LongTime)
				{
					/* 发送按钮持续按下的消息 */
					if (++pBtn->LongCount == pBtn->LongTime)
					{
						/* 键值放入按键FIFO */
						bsp_PutKey((uint8_t)(3 * i + 3));
					}
				}
				else
				{
					if (pBtn->RepeatSpeed > 0)
					{
						if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
						{
							pBtn->RepeatCount = 0;
							/* 常按键后,每隔10ms发送1个按键 */
							bsp_PutKey((uint8_t)(3 * i + 1));
						}
					}
				}
			}
		}
	}
	else
	{
		if(pBtn->Count > KEY_FILTER_TIME)
		{
			pBtn->Count = KEY_FILTER_TIME;
		}
		else if(pBtn->Count != 0)
		{
			pBtn->Count--;
		}
		else
		{
			if (pBtn->State == 1)
			{
				pBtn->State = 0;

				/* 发送按钮弹起的消息 */
				bsp_PutKey((uint8_t)(3 * i + 2));
			}
		}

		pBtn->LongCount = 0;
		pBtn->RepeatCount = 0;
	}
}

首先创建一个结构图指针,类型就是KEYT 之后,将我们创建的S_TBTN(i)的值赋值给这个结构体指针,这个i就是传入的参数。

下面这个 if 语句主要是用于按键滤波前给 Count 设置一个初值,前面说按键初始化的时候
已经设置了 Count = KEY_FILTER_TIME/2,

else  if  这里实现 KEY_FILTER_TIME 时间长度的延迟

继续往下  一个全部的else,

滤波结束,通过了滤波的就进这,

这个 State 变量是有其实际意义的,如果按键按下了,这里就将其设置为 1 ,如果没有按下这个
变量的值就会一直是 0 ,这样设置的目的可以有效的防止一种情况的出现:比如按键 K1 在某个
时刻检测到了按键有按下,那么它就会做进一步的滤波处理,但是在滤波的过程中,这个按键
按下的状态消失了,这个时候就会进入到上面第二步 else 语句里面,然后再做按键松手检测滤波
,滤波结束后判断这个 State 变量,如果前面就没有检测到按下,这里就不会记录按键弹起。

之后我们继续if(判断按键长按是否打开)若长按时间>0就进判断

之后++计数值,再次判断长按计数是不是等于长按时间,等于的话再次调用fifo压入函数,将3号位的键值写入fifo。

再下面又是一个else  这个else是上面判断长按计算小于长按时间的else

就是长按时间大于长按时间的设定值(之后++计数值,再判断连续计数有没有开启,如果开启,再次进入if,判断连续计数是否大于连续按键周期)

进入之后将连续计数赋值为0,还是将键值压入。

最后一个else(上面if检测到按键按下的 else,等于是松开了)

这里没有检测到按键:首先判断滤波计数器是不是大于了滤波计数的设定值

之后判断count是不是为0 不是的话滤波就-1,把滤波剪到0位置

最后else  再if如果按键为1则将其赋值为0(为1只有前面按键按下赋值过1 还记得吧,这里等于就是如果前面赋值为1过,这里就会进入将其重新为0,并且压入一个弹起值给fifo)

最后 将长按计算和连续计数重新赋值为0.

3.10 扫描按键函数

/*
*********************************************************************************************************
*	函 数 名: bsp_KeyScan
*	功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan(void)
{
	uint8_t i;

	for (i = 0; i < KEY_COUNT; i++)
	{
		bsp_DetectKey(i);
	}
}

循环扫描10个按键 

这个函数是被中断循环调用的,每10ms检测一次

/*
*********************************************************************************************************
*	函 数 名: bsp_RunPer10ms
*	功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求不严格的
*			任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
*	形    参:无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
	bsp_KeyScan();		/* 按键扫描 */
}

首先被runper10ms 这个函数调用,这个函数会10ms被中断调用,里面可以加很多函数。

void SysTick_ISR(void)
{
	static uint8_t s_count = 0;
	uint8_t i;

	/* 每隔1ms进来1次 (仅用于 bsp_DelayMS) */
	if (s_uiDelayCount > 0)
	{
		if (--s_uiDelayCount == 0)
		{
			s_ucTimeOutFlag = 1;
		}
	}

	/* 每隔1ms,对软件定时器的计数器进行减一操作 */
	for (i = 0; i < TMR_COUNT; i++)
	{
		bsp_SoftTimerDec(&s_tTmr[i]);
	}

	/* 全局运行时间每1ms增1 */
	g_iRunTime++;
	if (g_iRunTime == 0x7FFFFFFF)	/* 这个变量是 int32_t 类型,最大数为 0x7FFFFFFF */
	{
		g_iRunTime = 0;
	}

	bsp_RunPer1ms();		/* 每隔1ms调用一次此函数,此函数在 bsp.c */

	if (++s_count >= 10)
	{
		s_count = 0;

		bsp_RunPer10ms();	/* 每隔10ms调用一次此函数,此函数在 bsp.c */
	}
}

s_count++ 加到10,就会执行一次bsp_RunPer10ms() 

这一整个函数SysTick_ISR 是被滴答定时器调用的,这个等放在中断和定时器的章节写了。

作者:是小刘不是刘

物联沃分享整理
物联沃-IOTWORD物联网 » STM32安富莱BSP学习指南:按键驱动详解

发表评论