freertos开发空气检测仪之按键输入事件管理系统设计与实现
在本次项目中,我的设计思路如下,从输入事件得到数据,将数据放入对应的buffer进行管理,方便供上层代码进行调用管理。
1. 系统架构
按键输入事件管理系统采用分层设计,主要包括以下几个层次:
- 底层驱动层:负责硬件按键的扫描和状态检测
- 设备抽象层:将硬件按键抽象为输入设备
- 输入系统层:管理所有输入设备,提供统一的事件获取接口
- 应用层:从输入系统获取事件并进行处理
2. 核心组件
2.1 输入事件结构体
在 input_system.h 中定义了 InputEvent 结构体,用于表示各种输入事件:
c
typedef struct InputEvent {
TIME_T time; /* 事件时间戳 */
INPUT_EVENT_TYPE eType; /* 事件类型 */
/* 通用事件数据 */
union {
/* 按键事件数据 */
struct {
int iKey; /* 按键代码 */
KEY_STATE eState; /* 按键状态 */
int iTime; /* 时间ms */
int iClickCount; /* 点击次数,用于多击判断 */
} key;
/* 触摸事件数据 */
struct {
int iX;
int iY;
int iPressure;
} touch;
/* 网络事件数据 */
struct {
int iEventCode;
char strData[INPUT_BUF_LEN];
} net;
/* 标准输入事件数据 */
struct {
char strInput[INPUT_BUF_LEN];
} stdio;
} data;
} InputEvent, *PInputEvent;
2.2 输入设备结构体
在 input_system.h 中定义了 InputDevice 结构体,用于表示输入设备:
c
typedef struct InputDevice {
char *name; /* 设备名称 */
int (*GetInputEvent)(PInputEvent ptInputEvent); /* 获取输入事件的函数 */
int (*DeviceInit)(void); /* 设备初始化函数 */
int (*DeviceExit)(void); /* 设备退出函数 */
struct InputDevice *pNext; /* 指向下一个设备的指针 */
} InputDevice, *PInputDevice;
2.3 输入缓冲区
在 input_system.c 中实现了一个环形缓冲区,用于存储输入事件:
c
#define INPUT_BUFFER_SIZE 10
static InputEvent g_tInputBuffer[INPUT_BUFFER_SIZE];
static volatile int g_iInputBufferRead = 0;
static volatile int g_iInputBufferWrite = 0;
3. 调用流程
3.1 系统初始化
- 调用
AddInputDevices()注册所有输入设备 - 调用
InitInputDevices()初始化所有输入设备 - 创建
key_scan_task任务,每10ms进行一次按键扫描
3.2 按键扫描与事件处理
key_scan_task每10ms调用一次bsp_KeyScan10ms()进行按键扫描bsp_KeyScan10ms()检测按键状态并将按键事件放入按键FIFO- 调用
GetInputEvent()从所有输入设备中获取输入事件 - 调用
InputBufferPut()将输入事件放入输入缓冲区
3.3 事件获取与处理
- 应用层调用
InputBufferGet()从输入缓冲区中获取输入事件 - 根据事件类型和事件数据进行相应的处理
4. 代码实现
4.1 输入系统层实现
4.1.1 设备注册
c
/**********************************************************************
* 函数名称: InputDeviceRegister
* 功能描述: 注册一个输入设备(头插法)
***********************************************************************/
void InputDeviceRegister(PInputDevice ptInputDevice)
{
if (ptInputDevice == NULL)
return;
ptInputDevice->pNext = g_ptInputDevices;
g_ptInputDevices = ptInputDevice;
}
/**********************************************************************
* 函数名称: AddInputDevices
* 功能描述: 注册所有需要的输入设备
***********************************************************************/
void AddInputDevices(void)
{
/* 注册按键设备 */
extern void AddInputDeviceKey(void);
AddInputDeviceKey();
/* 未来扩展:触摸、网络等设备 */
// extern void AddInputDeviceTouch(void);
// AddInputDeviceTouch();
}
4.1.2 设备初始化
c
/**********************************************************************
* 函数名称: InitInputDevices
* 功能描述: 初始化所有已注册的输入设备
***********************************************************************/
void InitInputDevices(void)
{
PInputDevice pDev = g_ptInputDevices;
while (pDev)
{
if (pDev->DeviceInit != NULL)
{
pDev->DeviceInit();
}
pDev = pDev->pNext;
}
}
4.1.3 事件获取
c
/**********************************************************************
* 函数名称: GetInputEvent
* 功能描述: 从所有输入设备中获取输入事件
* 输入参数: ptInputEvent - 输入事件指针
* 输出参数: 无
* 返 回 值: 0 - 成功,非0 - 失败
***********************************************************************/
int GetInputEvent(PInputEvent ptInputEvent)
{
PInputDevice pDev = g_ptInputDevices;
while (pDev)
{
if (pDev->GetInputEvent != NULL)
{
if (pDev->GetInputEvent(ptInputEvent) == 0)
{
return 0;
}
}
pDev = pDev->pNext;
}
return -1;
}
4.1.4 输入缓冲区管理
c
/**********************************************************************
* 函数名称: InputBufferPut
* 功能描述: 向输入缓冲区中放入一个输入事件
* 输入参数: ptInputEvent - 输入事件指针
* 输出参数: 无
* 返 回 值: 0 - 成功,非0 - 失败
***********************************************************************/
int InputBufferPut(PInputEvent ptInputEvent)
{
int iNextWrite = (g_iInputBufferWrite + 1) % INPUT_BUFFER_SIZE;
if (iNextWrite == g_iInputBufferRead)
{
/* 缓冲区已满 */
return -1;
}
g_tInputBuffer[g_iInputBufferWrite] = *ptInputEvent;
g_iInputBufferWrite = iNextWrite;
return 0;
}
/**********************************************************************
* 函数名称: InputBufferGet
* 功能描述: 从输入缓冲区中获取一个输入事件
* 输入参数: ptInputEvent - 输入事件指针
* 输出参数: 无
* 返 回 值: 0 - 成功,非0 - 失败
***********************************************************************/
int InputBufferGet(PInputEvent ptInputEvent)
{
if (g_iInputBufferRead == g_iInputBufferWrite)
{
/* 缓冲区为空 */
return -1;
}
*ptInputEvent = g_tInputBuffer[g_iInputBufferRead];
g_iInputBufferRead = (g_iInputBufferRead + 1) % INPUT_BUFFER_SIZE;
return 0;
}
/**********************************************************************
* 函数名称: InputBufferClear
* 功能描述: 清空输入缓冲区
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void InputBufferClear(void)
{
g_iInputBufferRead = 0;
g_iInputBufferWrite = 0;
}
4.2 设备抽象层实现
4.2.1 按键设备初始化
c
static int GPIOKeyInit(void)
{
KAL_GPIOKkeyInit();
/* 创建按键事件队列 */
xKeyQueue = xQueueCreate(10, sizeof(InputEvent));
if (xKeyQueue == NULL)
{
return -1;
}
/* 初始化按键驱动 */
bsp_InitKey();
return 0;
}
4.2.2 按键事件获取
c
static int GPIOKeyGetInputEvent(PInputEvent ptInputEvent)
{
uint8_t key_code;
InputEvent event;
/* 获取按键事件 */
key_code = bsp_GetKey();
if (key_code != KEY_NONE)
{
/* 填充InputEvent结构 */
event.time = xTaskGetTickCount();
event.eType = INPUT_EVENT_TYPE_KEY;
/* 根据按键代码解析按键信息 */
switch (key_code)
{
case KEY_1_DOWN:
event.data.key.iKey = 1;
event.data.key.eState = KEY_STATE_PRESSED;
event.data.key.iTime = event.time;
event.data.key.iClickCount = 0;
break;
case KEY_1_UP:
event.data.key.iKey = 1;
event.data.key.eState = KEY_STATE_RELEASED;
event.data.key.iTime = event.time;
event.data.key.iClickCount = 1;
break;
case KEY_1_LONG_DOWN:
event.data.key.iKey = 1;
event.data.key.eState = KEY_STATE_LONG_PRESS;
event.data.key.iTime = event.time;
event.data.key.iClickCount = 0;
break;
case KEY_1_LONG_UP:
event.data.key.iKey = 1;
event.data.key.eState = KEY_STATE_LONG_RELEASED;
event.data.key.iTime = event.time;
event.data.key.iClickCount = 0;
break;
case KEY_1_AUTO_UP:
event.data.key.iKey = 1;
event.data.key.eState = KEY_STATE_REPEAT;
event.data.key.iTime = event.time;
event.data.key.iClickCount = 0;
break;
case KEY_1_DB_UP:
event.data.key.iKey = 1;
event.data.key.eState = KEY_STATE_DOUBLE_CLICK;
event.data.key.iTime = event.time;
event.data.key.iClickCount = 2;
break;
default:
return -1;
}
/* 复制事件到输出参数 */
*ptInputEvent = event;
return 0;
}
return -1;
}
4.2.3 按键设备注册
c
static InputDevice g_tKeyDevice = {
"gpio_key",
GPIOKeyGetInputEvent,
GPIOKeyInit,
NULL,
};
void AddInputDeviceKey(void)
{
InputDeviceRegister(&g_tKeyDevice);
}
4.3 应用层实现
4.3.1 按键扫描任务
c
/**********************************************************************
* 函数名称: key_scan_task
* 功能描述: 按键扫描任务,每10ms调用一次bsp_KeyScan10ms
* 输入参数: pvParameters - 任务参数
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void key_scan_task(void *pvParameters)
{
InputEvent event;
while (1)
{
/* 每10ms调用一次按键扫描函数 */
bsp_KeyScan10ms();
/* 获取按键事件并放入输入缓冲区 */
if (GetInputEvent(&event) == 0)
{
InputBufferPut(&event);
}
/* 延时10ms */
vTaskDelay(10);
}
}
4.3.2 输入测试任务
c
static void input_test_task(void *pvParameters)
{
InputEvent event;
while (1)
{
/* 从队列中获取按键事件 */
if (xQueueReceive(xKeyQueue, &event, portMAX_DELAY) == pdTRUE)
{
/* 打印按键事件信息 */
DBG_log("[INFO] Key event: type=%d, key=%d, state=%d, iTime=%d, click_count=%d\n",
event.eType, event.data.key.iKey, event.data.key.eState,
event.data.key.iTime, event.data.key.iClickCount);
}
}
}
4.3.3 输入系统单元测试
c
/**********************************************************************
* 函数名称: input_test
* 功能描述: 输入系统单元测试函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void input_test(void)
{
InputEvent event;
AddInputDevices();
InitInputDevices();
DBG_log("Input system unit test start...\n");
while (1)
{
if (GetInputEvent(&event) == 0)
{
/* 打印按键事件信息 */
DBG_log("[INFO] Key event: type=%d, key=%d, state=%d, iTime=%d, click_count=%d\n",
event.eType, event.data.key.iKey, event.data.key.eState,
event.data.key.iTime, event.data.key.iClickCount);
/* 将事件放入输入缓冲区 */
if (InputBufferPut(&event) == 0)
{
DBG_log("[INFO] Event put into buffer success\n");
}
else
{
DBG_log("[ERROR] Event put into buffer failed\n");
}
}
/* 尝试从输入缓冲区获取事件 */
if (InputBufferGet(&event) == 0)
{
DBG_log("[INFO] Event get from buffer: type=%d, key=%d, state=%d\n",
event.eType, event.data.key.iKey, event.data.key.eState);
}
/* 延时10ms */
vTaskDelay(10);
}
}
5. 使用方法
5.1 初始化输入系统
c
/* 注册并初始化输入设备 */
AddInputDevices();
InitInputDevices();
/* 创建按键扫描任务 */
xTaskCreate(
key_scan_task,
"key_scan",
128,
NULL,
1,
&xKeyScanTask
);
5.2 获取并处理输入事件
c
/* 从输入缓冲区中获取输入事件 */
InputEvent event;
if (InputBufferGet(&event) == 0)
{
/* 根据事件类型进行处理 */
switch (event.eType)
{
case INPUT_EVENT_TYPE_KEY:
/* 处理按键事件 */
printf("Key event: key=%d, state=%d\n",
event.data.key.iKey, event.data.key.eState);
break;
/* 处理其他类型的事件... */
}
}
6. 扩展建议
- 添加更多输入设备:可以添加触摸屏幕、网络输入等其他类型的输入设备
- 优化输入缓冲区:可以根据实际需求调整输入缓冲区的大小
- 添加事件过滤:可以添加事件过滤机制,过滤掉不需要的事件
- 添加事件回调:可以添加事件回调机制,当有输入事件时自动调用回调函数
- 添加事件优先级:可以为不同类型的事件添加优先级,优先处理重要的事件
7. 总结
按键输入事件管理系统采用分层设计,通过输入设备抽象和输入缓冲区管理,提供了一个统一、高效的输入事件处理接口。系统支持多种按键状态的检测,包括按下、弹起、长按、双击等,可以满足各种应用场景的需求。