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 是被滴答定时器调用的,这个等放在中断和定时器的章节写了。
作者:是小刘不是刘