核心内容摘要
大模型推理优化秘籍:小白也能学会的收藏指南
输入子系统结构体设计在这个项目中有使用一个按键仿照高手代码进行编程抽象对应的结构体如下input_system.h#ifndef __INPUT_SYSTEM_H #define __INPUT_SYSTEM_H #ifndef NULL #define NULL (void *)0 #endif #define TIME_T int #define INPUT_BUF_LEN 20 /* 事件类型 */ typedef enum { INPUT_EVENT_TYPE_KEY, INPUT_EVENT_TYPE_TOUCH, INPUT_EVENT_TYPE_NET, INPUT_EVENT_TYPE_STDIO } INPUT_EVENT_TYPE; /* 按键状态*/ typedef enum { KEY_STATE_PRESSED, /* 按下 */ KEY_STATE_RELEASED, /* 弹起 */ KEY_STATE_LONG_PRESS, /* 长按 */ KEY_STATE_LONG_RELEASED, /* 长按弹起 */ KEY_STATE_REPEAT, /* 长按连发 */ KEY_STATE_DOUBLE_CLICK, /* 双击 */ KEY_STATE_MULTI_CLICK /* 多击 */ } KEY_STATE; /* 输入事件结构体扩展 */ typedef struct InputEvent { TIME_T time; /* 事件时间戳 */ INPUT_EVENT_TYPE eType; /* 事件类型 */ /* 通用事件数据 */ union { /* 按键事件数据 */ struct { int iKey; /* 按键代码 */ KEY_STATE eState; /* 按键状态 */ int iDuration; /* 持续时间(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; typedef struct InputDevice { char *name; int (*GetInputEvent)(PInputEvent ptInputEvent); int (*DeviceInit)(void); int (*DeviceExit)(void); struct InputDevice *pNext; } InputDevice, *PInputDevice; /********************************************************************** * 函数名称 AddInputDevices * 功能描述 注册多个输入设备 * 输入参数 无 * 输出参数 无 * 返 回 值 无 ***********************************************************************/ void AddInputDevices(void); /********************************************************************** * 函数名称 InitInputDevices * 功能描述 初始化所有的输入设备 * 输入参数 无 * 输出参数 无 * 返 回 值 无 ***********************************************************************/ void InitInputDevices(void); /********************************************************************** * 函数名称 InputDeviceRegister * 功能描述 注册一个输入设备 * 输入参数 ptInputDevice-输入设备 * 输出参数 无 * 返 回 值 无 ***********************************************************************/ void InputDeviceRegister(PInputDevice ptInputDevice); #endif /* __INPUT_SYSTEM_H */使用共用体设计在上述的代码片段中使用了union共用体进行设计主要原因是节省内存空间输入事件在某个时刻只能是一种类型 不可能同时是按键事件和触摸事件不可能同时是网络事件和标准输入事件 这种互斥性非常适合使用共用体因为同一时间只需要存储一种事件的数据。
输入子系统按键驱动测试
硬件原理图设计思路找到自己积累的按键驱动代码进行移植即可移植过程略。
设计一些c函数代码片段如下
扫描任务/* ********************************************************************************************************* * 函 数 名: key_scan_task * 功能说明: 按键扫描任务每10ms调用一次bsp_KeyScan10ms * 形 参: pvParameters - 任务参数 * 返 回 值: 无 ********************************************************************************************************* */ static void key_scan_task(void *pvParameters) { while (
{ /* 每10ms调用一次按键扫描函数 */ bsp_KeyScan10ms(); /* 处理按键事件 */ key_event_process(); /* 延时10ms */ vTaskDelay(
; } }
按键处理部分/* ********************************************************************************************************* * 函 数 名: key_event_process * 功能说明: 处理按键事件转换为InputEvent并发送到队列 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void key_event_process(void) { 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 xTaskGetTickCount(); 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 xTaskGetTickCount(); 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 xTaskGetTickCount(); 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 xTaskGetTickCount(); 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 xTaskGetTickCount(); 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 xTaskGetTickCount(); event.data.key.iClickCount 2; break; default: return; } /* 发送事件到队列 */ xQueueSend(xKeyQueue, event,
; } }
接收队列/* ********************************************************************************************************* * 函 数 名: input_test_task * 功能说明: 按键测试任务 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void input_test_task(void *pvParameters) { InputEvent event; while (
{ /* 从队列中获取按键事件 */ 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); } } }
函数入口/* ********************************************************************************************************* * 函 数 名: input_test_start * 功能说明: 启动按键扫描任务 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void input_test_start(void) { BaseType_t xReturn pdPASS; /* 初始化按键测试 */ input_test_init(); /* 创建按键扫描任务 */ xReturn xTaskCreate( (TaskFunction_t)key_scan_task, (const char *)key_scan, (uint16_t)128, (void *)NULL, (UBaseType_t)1, (TaskHandle_t *)xKeyScanTask ); if (xReturn ! pdPASS) { DBG_log([ERROR] Create key scan task failed\n); return; } /* 创建按键测试任务 */ xReturn xTaskCreate( (TaskFunction_t)input_test_task, (const char *)input_test, (uint16_t)128, (void *)NULL, (UBaseType_t)2, (TaskHandle_t *)xInputTestTask ); if (xReturn ! pdPASS) { DBG_log([ERROR] Create input test task failed\n); return; } DBG_log([INFO] Input test started\n); }实验结果可以看到按键驱动可以正常驱动起来日志比较正常。
总结本文设计了一个按键扫描任务将按键的键值放进队列中在接收队列中拿出来进行处理算是一个freertos的队列实战。
按键输入事件管理系统设计与实现设计思路如下从输入事件得到数据将数据放入对应的buffer进行管理方便供上层代码进行调用管理。
系统架构按键输入事件管理系统采用分层设计主要包括以下几个层次底层驱动层负责硬件按键的扫描和状态检测设备抽象层将硬件按键抽象为输入设备输入系统层管理所有输入设备提供统一的事件获取接口应用层从输入系统获取事件并进行处理
核心组件
1 输入事件结构体在input_system.h中定义了InputEvent结构体用于表示各种输入事件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 输入设备结构体在input_system.h中定义了InputDevice结构体用于表示输入设备typedef struct InputDevice { char *name; /* 设备名称 */ int (*GetInputEvent)(PInputEvent ptInputEvent); /* 获取输入事件的函数 */ int (*DeviceInit)(void); /* 设备初始化函数 */ int (*DeviceExit)(void); /* 设备退出函数 */ struct InputDevice *pNext; /* 指向下一个设备的指针 */ } InputDevice, *PInputDevice;
3 输入缓冲区在input_system.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;
调用流程
1 系统初始化调用AddInputDevices()注册所有输入设备调用InitInputDevices()初始化所有输入设备创建key_scan_task任务每10ms进行一次按键扫描
2 按键扫描与事件处理key_scan_task每10ms调用一次bsp_KeyScan10ms()进行按键扫描bsp_KeyScan10ms()检测按键状态并将按键事件放入按键FIFO调用GetInputEvent()从所有输入设备中获取输入事件调用InputBufferPut()将输入事件放入输入缓冲区
3 事件获取与处理应用层调用InputBufferGet()从输入缓冲区中获取输入事件根据事件类型和事件数据进行相应的处理
代码实现
1 输入系统层实现
4.
1 设备注册/********************************************************************** * 函数名称 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.
2 设备初始化/********************************************************************** * 函数名称 InitInputDevices * 功能描述 初始化所有已注册的输入设备 ***********************************************************************/ void InitInputDevices(void) { PInputDevice pDev g_ptInputDevices; while (pDev) { if (pDev-DeviceInit ! NULL) { pDev-DeviceInit(); } pDev pDev-pNext; } }
4.
3 事件获取/********************************************************************** * 函数名称 GetInputEvent * 功能描述 从所有输入设备中获取输入事件 * 输入参数 ptInputEvent - 输入事件指针 * 输出参数 无 * 返 回 值 0 - 成功非0 - 失败 ***********************************************************************/ int GetInputEvent(PInputEvent ptInputEvent) { PInputDevice pDev g_ptInputDevices; while (pDev) { if (pDev-GetInputEvent ! NULL) { if (pDev-GetInputEvent(ptInputEvent)
{ return 0; } } pDev pDev-pNext; } return -1; }
4.
4 输入缓冲区管理/********************************************************************** * 函数名称 InputBufferPut * 功能描述 向输入缓冲区中放入一个输入事件 * 输入参数 ptInputEvent - 输入事件指针 * 输出参数 无 * 返 回 值 0 - 成功非0 - 失败 ***********************************************************************/ int InputBufferPut(PInputEvent ptInputEvent) { int iNextWrite (g_iInputBufferWrite
% 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
% INPUT_BUFFER_SIZE; return 0; } /********************************************************************** * 函数名称 InputBufferClear * 功能描述 清空输入缓冲区 * 输入参数 无 * 输出参数 无 * 返 回 值 无 ***********************************************************************/ void InputBufferClear(void) { g_iInputBufferRead 0; g_iInputBufferWrite 0; }
2 设备抽象层实现
4.
1 按键设备初始化static int GPIOKeyInit(void) { KAL_GPIOKkeyInit(); /* 创建按键事件队列 */ xKeyQueue xQueueCreate(10, sizeof(InputEvent)); if (xKeyQueue NULL) { return -1; } /* 初始化按键驱动 */ bsp_InitKey(); return 0; }
4.
2 按键事件获取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.
3 按键设备注册static InputDevice g_tKeyDevice { gpio_key, GPIOKeyGetInputEvent, GPIOKeyInit, NULL, }; void AddInputDeviceKey(void) { InputDeviceRegister(g_tKeyDevice); }
3 应用层实现
4.
1 按键扫描任务/********************************************************************** * 函数名称 key_scan_task * 功能描述 按键扫描任务每10ms调用一次bsp_KeyScan10ms * 输入参数 pvParameters - 任务参数 * 输出参数 无 * 返 回 值 无 ***********************************************************************/ void key_scan_task(void *pvParameters) { InputEvent event; while (
{ /* 每10ms调用一次按键扫描函数 */ bsp_KeyScan10ms(); /* 获取按键事件并放入输入缓冲区 */ if (GetInputEvent(event)
{ InputBufferPut(event); } /* 延时10ms */ vTaskDelay(
; } }
4.
2 输入测试任务static void input_test_task(void *pvParameters) { InputEvent event; while (
{ /* 从队列中获取按键事件 */ 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 输入系统单元测试/********************************************************************** * 函数名称 input_test * 功能描述 输入系统单元测试函数 * 输入参数 无 * 输出参数 无 * 返 回 值 无 ***********************************************************************/ void input_test(void) { InputEvent event; AddInputDevices(); InitInputDevices(); DBG_log(Input system unit test start...\n); while (
{ if (GetInputEvent(event)
{ /* 打印按键事件信息 */ 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)
{ DBG_log([INFO] Event put into buffer success\n); } else { DBG_log([ERROR] Event put into buffer failed\n); } } /* 尝试从输入缓冲区获取事件 */ if (InputBufferGet(event)
{ 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(
; } }
5.
使用方法
1 初始化输入系统/* 注册并初始化输入设备 */ AddInputDevices(); InitInputDevices(); /* 创建按键扫描任务 */ xTaskCreate( key_scan_task, key_scan, 128, NULL, 1, xKeyScanTask );
2 获取并处理输入事件/* 从输入缓冲区中获取输入事件 */ InputEvent event; if (InputBufferGet(event)
{ /* 根据事件类型进行处理 */ 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; /* 处理其他类型的事件... */ } }
扩展建议添加更多输入设备可以添加触摸屏幕、网络输入等其他类型的输入设备优化输入缓冲区可以根据实际需求调整输入缓冲区的大小添加事件过滤可以添加事件过滤机制过滤掉不需要的事件添加事件回调可以添加事件回调机制当有输入事件时自动调用回调函数添加事件优先级可以为不同类型的事件添加优先级优先处理重要的事件
7.
总结按键输入事件管理系统采用分层设计通过输入设备抽象和输入缓冲区管理提供了一个统
高效的输入事件处理接口。
系统支持多种按键状态的检测包括按下、弹起、长按、双击等可以满足各种应用场景的需求。
任务优先级优化划分前言在本次开发空气检测仪项目中划分了一些任务有些中任务优先级没处理好故本篇文章进行记录与修复下。
现象分析uart_test任务未能运行怀疑是优先级设置不当导致。
原系统所有任务KeyScan, InputTest, UartTest, LogPrint优先级均为2且LedTest为1。
虽然理论上开启时间片轮转Time Slicing后同优先级任务应轮流执行但若配置缺失或某些任务占用过多尽管检查发现都有 Delay可能导致响应迟缓或饿死风险。
优先级重新规划为了保证系统响应性和实时性采用Rate Monotonic Scheduling (RMS)的思想按照任务的重要性和实时性要求重新分配优先级。
FreeRTOS 优先级范围: 0 (Low) - 4 (High) [configMAX_PRIORITIES 5]任务名称原优先级新优先级说明KeyScan24(最高)硬件扫描任务需要严格的 10ms 周期抖动会影响按键体验。
uart_test23(高)通信任务需要及时响应接收中断和处理数据避免缓冲区溢出。
input_test22(中)业务逻辑/事件处理对实时性要求稍低只要能处理完 buffer 即可。
LogPrint21(低)后台日志打印不应抢占业务资源。
LedTest11(低)后台状态指示低优先级。
修改记录
1 FreeRTOSConfig.h显式定义#define configUSE_TIME_SLICING 1确保同优先级任务如 LogPrint 和 LedTest能公平共享 CPU。
2 User/main.c将vTaskLogPrint优先级从 2 降为1。
3 uart_test.c将uart_test_task优先级从 2 升为3。
4 gpio_key.c将vTaskKeyScan优先级从 2 升为4。
5 input_test.c保持input_test_task优先级为2或明确注释为 Medium。
预期效果KeyScan将获得最高优先权确保按键扫描不丢失。
uart_test其次保证串口通信流畅。
input_test在空闲时处理事件。
LogPrint和LedTest在系统空闲时运行不会影响关键业务。
这种梯队设计能有效避免关键任务被饿死同时保证系统的实时响应能力。