From c17f41b2679efa20e3561db809af95ef1216de49 Mon Sep 17 00:00:00 2001 From: yxming66 <2389287465@qq.com> Date: Mon, 15 Dec 2025 22:25:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clangd | 3 +- User/module/cmd_v2/cmd.c | 295 ++++++++++++++++++++++++++++++ User/module/cmd_v2/cmd.h | 173 ++++++++++++++++++ User/module/cmd_v2/cmd_adapter.c | 181 ++++++++++++++++++ User/module/cmd_v2/cmd_adapter.h | 111 +++++++++++ User/module/cmd_v2/cmd_behavior.c | 174 ++++++++++++++++++ User/module/cmd_v2/cmd_behavior.h | 69 +++++++ User/module/cmd_v2/cmd_example.c | 153 ++++++++++++++++ User/module/cmd_v2/cmd_types.h | 177 ++++++++++++++++++ 9 files changed, 1334 insertions(+), 2 deletions(-) create mode 100644 User/module/cmd_v2/cmd.c create mode 100644 User/module/cmd_v2/cmd.h create mode 100644 User/module/cmd_v2/cmd_adapter.c create mode 100644 User/module/cmd_v2/cmd_adapter.h create mode 100644 User/module/cmd_v2/cmd_behavior.c create mode 100644 User/module/cmd_v2/cmd_behavior.h create mode 100644 User/module/cmd_v2/cmd_example.c create mode 100644 User/module/cmd_v2/cmd_types.h diff --git a/.clangd b/.clangd index cfc956a..89d4e90 100644 --- a/.clangd +++ b/.clangd @@ -2,7 +2,7 @@ CompileFlags: Add: - '-ferror-limit=0' - '-Wno-implicit-int' - - "-I/usr/lib/arm-none-eabi/include" + - '-I/usr/lib/arm-none-eabi/include' CompilationDatabase: build/Debug Diagnostics: Suppress: @@ -10,4 +10,3 @@ Diagnostics: - unknown_typename - unknown_typename_suggest - typename_requires_specqual - diff --git a/User/module/cmd_v2/cmd.c b/User/module/cmd_v2/cmd.c new file mode 100644 index 0000000..82dbc1f --- /dev/null +++ b/User/module/cmd_v2/cmd.c @@ -0,0 +1,295 @@ +/* + * CMD 模块 V2 - 主控制模块实现 + */ +#include "cmd.h" +#include "bsp/time.h" +#include +#include + +/* ========================================================================== */ +/* 内部辅助函数 */ +/* ========================================================================== */ + +/* 从RC输入生成底盘命令 */ +static void CMD_RC_BuildChassisCmd(CMD_Context_t *ctx, const CMD_RawInput_t *input) { + CMD_RCModeMap_t *map = &ctx->config->rc_mode_map; + + /* 根据左拨杆位置选择模式 */ + switch (input->sw[0]) { + case CMD_SW_UP: + ctx->chassis.cmd.mode = map->sw_left_up; + break; + case CMD_SW_MID: + ctx->chassis.cmd.mode = map->sw_left_mid; + break; + case CMD_SW_DOWN: + ctx->chassis.cmd.mode = map->sw_left_down; + break; + default: + ctx->chassis.cmd.mode = CHASSIS_MODE_RELAX; + break; + } + + /* 摇杆控制移动 */ + ctx->chassis.cmd.ctrl_vec.vx = input->joy_right.x; + ctx->chassis.cmd.ctrl_vec.vy = input->joy_right.y; +} + +/* 从RC输入生成云台命令 */ +static void CMD_RC_BuildGimbalCmd(CMD_Context_t *ctx, const CMD_RawInput_t *input) { + CMD_RCModeMap_t *map = &ctx->config->rc_mode_map; + + /* 根据拨杆选择云台模式 */ + switch (input->sw[0]) { + case CMD_SW_UP: + ctx->gimbal.cmd.mode = map->gimbal_sw_up; + break; + case CMD_SW_MID: + ctx->gimbal.cmd.mode = map->gimbal_sw_mid; + break; + case CMD_SW_DOWN: + ctx->gimbal.cmd.mode = map->gimbal_sw_down; + break; + default: + ctx->gimbal.cmd.mode = GIMBAL_MODE_RELAX; + break; + } + + /* 左摇杆控制云台 */ + ctx->gimbal.cmd.delta_yaw = -input->joy_left.x * 2.0f; + ctx->gimbal.cmd.delta_pit = -input->joy_left.y * 1.5f; +} + +/* 从RC输入生成射击命令 */ +static void CMD_RC_BuildShootCmd(CMD_Context_t *ctx, const CMD_RawInput_t *input) { + if (input->online) { + ctx->shoot.cmd.mode = SHOOT_MODE_SINGLE; + } else { + ctx->shoot.cmd.mode = SHOOT_MODE_SAFE; + } + + /* 根据右拨杆控制射击 */ + switch (input->sw[1]) { + case CMD_SW_DOWN: + ctx->shoot.cmd.ready = true; + ctx->shoot.cmd.firecmd = true; + break; + case CMD_SW_MID: + ctx->shoot.cmd.ready = true; + ctx->shoot.cmd.firecmd = false; + break; + case CMD_SW_UP: + default: + ctx->shoot.cmd.ready = false; + ctx->shoot.cmd.firecmd = false; + break; + } +} + +/* 从PC输入生成底盘命令 */ +static void CMD_PC_BuildChassisCmd(CMD_Context_t *ctx, const CMD_RawInput_t *input) { + CMD_Sensitivity_t *sens = &ctx->config->sensitivity; + + if (!input->online) { + ctx->chassis.cmd.mode = CHASSIS_MODE_RELAX; + return; + } + + ctx->chassis.cmd.mode = CHASSIS_MODE_FOLLOW_GIMBAL; + + /* WASD控制移动 */ + ctx->chassis.cmd.ctrl_vec.vx = 0.0f; + ctx->chassis.cmd.ctrl_vec.vy = 0.0f; + + if (CMD_KEY_PRESSED(&input->keyboard, CMD_KEY_W)) { + ctx->chassis.cmd.ctrl_vec.vy += sens->move_sens; + } + if (CMD_KEY_PRESSED(&input->keyboard, CMD_KEY_S)) { + ctx->chassis.cmd.ctrl_vec.vy -= sens->move_sens; + } + if (CMD_KEY_PRESSED(&input->keyboard, CMD_KEY_A)) { + ctx->chassis.cmd.ctrl_vec.vx -= sens->move_sens; + } + if (CMD_KEY_PRESSED(&input->keyboard, CMD_KEY_D)) { + ctx->chassis.cmd.ctrl_vec.vx += sens->move_sens; + } + + /* 加速/减速 */ + if (CMD_KEY_PRESSED(&input->keyboard, CMD_KEY_SHIFT)) { + ctx->chassis.cmd.ctrl_vec.vx *= sens->move_fast_mult; + ctx->chassis.cmd.ctrl_vec.vy *= sens->move_fast_mult; + } + if (CMD_KEY_PRESSED(&input->keyboard, CMD_KEY_CTRL)) { + ctx->chassis.cmd.ctrl_vec.vx *= sens->move_slow_mult; + ctx->chassis.cmd.ctrl_vec.vy *= sens->move_slow_mult; + } +} + +/* 从PC输入生成云台命令 */ +static void CMD_PC_BuildGimbalCmd(CMD_Context_t *ctx, const CMD_RawInput_t *input) { + CMD_Sensitivity_t *sens = &ctx->config->sensitivity; + + if (!input->online) { + ctx->gimbal.cmd.mode = GIMBAL_MODE_RELAX; + return; + } + + ctx->gimbal.cmd.mode = GIMBAL_MODE_ABSOLUTE; + + /* 鼠标控制云台 */ + ctx->gimbal.cmd.delta_yaw = (float)-input->mouse.x * ctx->timer.dt * sens->mouse_sens; + ctx->gimbal.cmd.delta_pit = (float)input->mouse.y * ctx->timer.dt * sens->mouse_sens * 1.5f; +} + +/* 从PC输入生成射击命令 */ +static void CMD_PC_BuildShootCmd(CMD_Context_t *ctx, const CMD_RawInput_t *input) { + if (!input->online) { + ctx->shoot.cmd.mode = SHOOT_MODE_SAFE; + return; + } + + ctx->shoot.cmd.ready = true; + ctx->shoot.cmd.firecmd = input->mouse.l_click; +} + +/* 离线安全模式 */ +static void CMD_SetOfflineMode(CMD_Context_t *ctx) { + ctx->chassis.cmd.mode = CHASSIS_MODE_RELAX; + ctx->gimbal.cmd.mode = GIMBAL_MODE_RELAX; + ctx->shoot.cmd.mode = SHOOT_MODE_SAFE; +} + +/* ========================================================================== */ +/* 公开API实现 */ +/* ========================================================================== */ + +int8_t CMD_Init(CMD_Context_t *ctx, CMD_Config_t *config) { + if (ctx == NULL || config == NULL) { + return CMD_ERR_NULL; + } + + memset(ctx, 0, sizeof(CMD_Context_t)); + ctx->config = config; + + /* 初始化适配器 */ + CMD_Adapter_InitAll(); + + /* 初始化行为处理器 */ + CMD_Behavior_Init(); + + return CMD_OK; +} + +int8_t CMD_UpdateInput(CMD_Context_t *ctx) { + if (ctx == NULL) { + return CMD_ERR_NULL; + } + + /* 保存上一帧输入 */ + memcpy(ctx->last_input, ctx->input, sizeof(ctx->input)); + + /* 更新所有输入源 */ + for (int i = 0; i < CMD_SRC_NUM; i++) { + CMD_Adapter_GetInput((CMD_InputSource_t)i, &ctx->input[i]); + } + + return CMD_OK; +} + +int8_t CMD_Arbitrate(CMD_Context_t *ctx) { + if (ctx == NULL) { + return CMD_ERR_NULL; + } + + /* 智能仲裁算法:优先级 PC > RC > NUC */ + CMD_InputSource_t candidates[] = {CMD_SRC_PC, CMD_SRC_RC, CMD_SRC_NUC}; + const int num_candidates = sizeof(candidates) / sizeof(candidates[0]); + + /* 如果当前输入源仍然在线且有效,保持使用 */ + if (ctx->active_source < CMD_SRC_NUM && + ctx->active_source != CMD_SRC_REF && + ctx->input[ctx->active_source].online) { + goto seize; + } + + /* 否则选择第一个可用的控制输入源 */ + for (int i = 0; i < num_candidates; i++) { + CMD_InputSource_t src = candidates[i]; + if (ctx->input[src].online) { + ctx->active_source = src; + } + } + ctx->chassis.source = ctx->active_source; + ctx->gimbal.source = ctx->active_source; + ctx->shoot.source = ctx->active_source; + + seize: + + + + + /* 没有可用输入源 */ + ctx->active_source = CMD_SRC_NUM; + return CMD_OK; +} + +int8_t CMD_GenerateCommands(CMD_Context_t *ctx) { + if (ctx == NULL) { + return CMD_ERR_NULL; + } + + /* 更新时间 */ + uint64_t now_us = BSP_TIME_Get_us(); + ctx->timer.now = now_us / 1000000.0f; + ctx->timer.dt = (now_us - ctx->timer.last_us) / 1000000.0f; + ctx->timer.last_us = now_us; + + /* 没有有效输入源 */ + if (ctx->active_source >= CMD_SRC_NUM) { + CMD_SetOfflineMode(ctx); + return CMD_ERR_NO_INPUT; + } + + const CMD_RawInput_t *active_input = &ctx->input[ctx->active_source]; + const CMD_RawInput_t *last_input = &ctx->last_input[ctx->active_source]; + + /* 根据输入源类型生成命令 */ + switch (ctx->active_source) { + case CMD_SRC_RC: + CMD_RC_BuildChassisCmd(ctx, active_input); + CMD_RC_BuildGimbalCmd(ctx, active_input); + CMD_RC_BuildShootCmd(ctx, active_input); + break; + + case CMD_SRC_PC: + CMD_PC_BuildChassisCmd(ctx, active_input); + CMD_PC_BuildGimbalCmd(ctx, active_input); + CMD_PC_BuildShootCmd(ctx, active_input); + + /* 处理行为 */ + CMD_Behavior_ProcessAll(ctx, active_input, last_input, CMD_MODULE_ALL); + break; + + case CMD_SRC_NUC: + /* TODO: NUC输入处理 */ + break; + + default: + CMD_SetOfflineMode(ctx); + return CMD_ERR_SOURCE; + } + + return CMD_OK; +} + +int8_t CMD_Update(CMD_Context_t *ctx) { + int8_t ret; + + ret = CMD_UpdateInput(ctx); + if (ret != CMD_OK) return ret; + + CMD_Arbitrate(ctx); + + ret = CMD_GenerateCommands(ctx); + return ret; +} diff --git a/User/module/cmd_v2/cmd.h b/User/module/cmd_v2/cmd.h new file mode 100644 index 0000000..a0abc43 --- /dev/null +++ b/User/module/cmd_v2/cmd.h @@ -0,0 +1,173 @@ +/* + * CMD 模块 V2 - 主控制模块 + * 统一的命令控制接口 + */ +#pragma once + +#include "cmd_types.h" +#include "cmd_adapter.h" +#include "cmd_behavior.h" + +/* 引入输出模块的命令类型 */ +#include "module/chassis.h" +#include "module/gimbal.h" +#include "module/shoot.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================== */ +/* 输出命令结构 */ +/* ========================================================================== */ + +/* 每个模块的输出包含源信息和命令 */ +typedef struct { + CMD_InputSource_t source; + Chassis_CMD_t cmd; +} CMD_ChassisOutput_t; + +typedef struct { + CMD_InputSource_t source; + Gimbal_CMD_t cmd; +} CMD_GimbalOutput_t; + +typedef struct { + CMD_InputSource_t source; + Shoot_CMD_t cmd; +} CMD_ShootOutput_t; + +/* ========================================================================== */ +/* 配置结构 */ +/* ========================================================================== */ + +/* 灵敏度配置 */ +typedef struct { + float mouse_sens; /* 鼠标灵敏度 */ + float move_sens; /* 移动灵敏度 */ + float move_fast_mult; /* 快速移动倍率 */ + float move_slow_mult; /* 慢速移动倍率 */ +} CMD_Sensitivity_t; + +/* RC模式映射配置 - 定义开关位置到模式的映射 */ +typedef struct { + /* 左拨杆映射 - 底盘模式 */ + Chassis_Mode_t sw_left_up; + Chassis_Mode_t sw_left_mid; + Chassis_Mode_t sw_left_down; + + /* 右拨杆映射 - 云台/射击模式 */ + Gimbal_Mode_t gimbal_sw_up; + Gimbal_Mode_t gimbal_sw_mid; + Gimbal_Mode_t gimbal_sw_down; +} CMD_RCModeMap_t; + +/* 整体配置 */ +typedef struct { + /* 输入源优先级,索引越小优先级越高 */ + CMD_InputSource_t source_priority[CMD_SRC_NUM]; + + /* 灵敏度设置 */ + CMD_Sensitivity_t sensitivity; + + /* RC模式映射 */ + CMD_RCModeMap_t rc_mode_map; + + /* 是否启用PC输入 */ + bool enable_pc_input; +} CMD_Config_t; + +/* ========================================================================== */ +/* 主控制上下文 */ +/* ========================================================================== */ + +typedef struct { + float now; + float dt; + uint64_t last_us; +} CMD_Timer_t; + +typedef struct CMD_Context { + /* 配置 */ + CMD_Config_t *config; + + /* 时间 */ + CMD_Timer_t timer; + + /* 当前帧和上一帧的原始输入 */ + CMD_RawInput_t input[CMD_SRC_NUM]; + CMD_RawInput_t last_input[CMD_SRC_NUM]; + + /* 仲裁后的活跃输入源 */ + CMD_InputSource_t active_source; + + /* 输出 */ + CMD_ChassisOutput_t chassis; + CMD_GimbalOutput_t gimbal; + CMD_ShootOutput_t shoot; + +} CMD_Context_t; + +/* ========================================================================== */ +/* 主API接口 */ +/* ========================================================================== */ + +/** + * @brief 初始化CMD模块 + * @param ctx CMD上下文 + * @param config 配置指针 + * @return CMD_OK成功,其他失败 + */ +int8_t CMD_Init(CMD_Context_t *ctx, CMD_Config_t *config); + +/** + * @brief 更新所有输入源的数据 + * @param ctx CMD上下文 + * @return CMD_OK成功 + */ +int8_t CMD_UpdateInput(CMD_Context_t *ctx); + +/** + * @brief 执行仲裁,决定使用哪个输入源 + * @param ctx CMD上下文 + * @return 选中的输入源 + */ +int8_t CMD_Arbitrate(CMD_Context_t *ctx); + +/** + * @brief 生成所有模块的控制命令 + * @param ctx CMD上下文 + * @return CMD_OK成功 + */ +int8_t CMD_GenerateCommands(CMD_Context_t *ctx); + +/** + * @brief 一键更新(包含UpdateInput + Arbitrate + GenerateCommands) + * @param ctx CMD上下文 + * @return CMD_OK成功 + */ +int8_t CMD_Update(CMD_Context_t *ctx); + +/* ========================================================================== */ +/* 输出获取接口 */ +/* ========================================================================== */ + +/* 获取底盘命令 */ +static inline Chassis_CMD_t* CMD_GetChassisCmd(CMD_Context_t *ctx) { + return &ctx->chassis.cmd; +} + +/* 获取云台命令 */ +static inline Gimbal_CMD_t* CMD_GetGimbalCmd(CMD_Context_t *ctx) { + return &ctx->gimbal.cmd; +} + +/* 获取射击命令 */ +static inline Shoot_CMD_t* CMD_GetShootCmd(CMD_Context_t *ctx) { + return &ctx->shoot.cmd; +} + +#ifdef __cplusplus +} +#endif diff --git a/User/module/cmd_v2/cmd_adapter.c b/User/module/cmd_v2/cmd_adapter.c new file mode 100644 index 0000000..4ca382b --- /dev/null +++ b/User/module/cmd_v2/cmd_adapter.c @@ -0,0 +1,181 @@ +/* + * CMD 模块 V2 - 输入适配器实现 + */ +#include "cmd_adapter.h" +#include + +/* ========================================================================== */ +/* 适配器存储 */ +/* ========================================================================== */ +static CMD_InputAdapter_t *g_adapters[CMD_SRC_NUM] = {0}; + +/* ========================================================================== */ +/* DR16 适配器实现 */ +/* ========================================================================== */ +#if CMD_RC_DEVICE_TYPE == 0 + +int8_t CMD_DR16_Init(void *data) { + DR16_t *dr16 = (DR16_t *)data; + return DR16_Init(dr16); +} + +int8_t CMD_DR16_RC_GetInput(void *data, CMD_RawInput_t *output) { + DR16_t *dr16 = (DR16_t *)data; + + memset(output, 0, sizeof(CMD_RawInput_t)); + + output->online = dr16->header.online; + + /* 遥控器摇杆映射 */ + output->joy_left.x = dr16->data.rc.ch_l_x; + output->joy_left.y = dr16->data.rc.ch_l_y; + output->joy_right.x = dr16->data.rc.ch_r_x; + output->joy_right.y = dr16->data.rc.ch_r_y; + + /* 拨杆映射 */ + switch (dr16->data.rc.sw_l) { + case DR16_SW_UP: output->sw[0] = CMD_SW_UP; break; + case DR16_SW_MID: output->sw[0] = CMD_SW_MID; break; + case DR16_SW_DOWN: output->sw[0] = CMD_SW_DOWN; break; + default: output->sw[0] = CMD_SW_ERR; break; + } + switch (dr16->data.rc.sw_r) { + case DR16_SW_UP: output->sw[1] = CMD_SW_UP; break; + case DR16_SW_MID: output->sw[1] = CMD_SW_MID; break; + case DR16_SW_DOWN: output->sw[1] = CMD_SW_DOWN; break; + default: output->sw[1] = CMD_SW_ERR; break; + } + + /* 拨轮映射 */ + output->dial = dr16->data.rc.ch_res; + + return CMD_OK; +} + +int8_t CMD_DR16_PC_GetInput(void *data, CMD_RawInput_t *output) { + DR16_t *dr16 = (DR16_t *)data; + + memset(output, 0, sizeof(CMD_RawInput_t)); + + output->online = dr16->header.online; + + /* PC端鼠标映射 */ + output->mouse.x = dr16->data.pc.mouse.x; + output->mouse.y = dr16->data.pc.mouse.y; + output->mouse.l_click = dr16->data.pc.mouse.l_click; + output->mouse.r_click = dr16->data.pc.mouse.r_click; + + /* 键盘映射 */ + output->keyboard.bitmap = dr16->data.pc.keyboard.value; + + return CMD_OK; +} + +bool CMD_DR16_IsOnline(void *data) { + DR16_t *dr16 = (DR16_t *)data; + return dr16->header.online; +} + +/* 定义适配器实例 */ +CMD_DEFINE_ADAPTER(DR16_RC, dr16, CMD_SRC_RC, CMD_DR16_Init, CMD_DR16_RC_GetInput, CMD_DR16_IsOnline) +CMD_DEFINE_ADAPTER(DR16_PC, dr16, CMD_SRC_PC, CMD_DR16_Init, CMD_DR16_PC_GetInput, CMD_DR16_IsOnline) + +#endif /* CMD_RC_DEVICE_TYPE == 0 */ + +/* ========================================================================== */ +/* AT9S 适配器实现 (示例框架) */ +/* ========================================================================== */ +#if CMD_RC_DEVICE_TYPE == 1 + +int8_t CMD_AT9S_Init(void *data) { + AT9S_t *at9s = (AT9S_t *)data; + return AT9S_Init(at9s); +} + +int8_t CMD_AT9S_GetInput(void *data, CMD_RawInput_t *output) { + AT9S_t *at9s = (AT9S_t *)data; + + memset(output, 0, sizeof(CMD_RawInput_t)); + + output->online = at9s->header.online; + + /* TODO: 按照AT9S的数据格式进行映射 */ + output->joy_left.x = at9s->data.rc.ch_l_x; + output->joy_left.y = at9s->data.rc.ch_l_y; + output->joy_right.x = at9s->data.rc.ch_r_x; + output->joy_right.y = at9s->data.rc.ch_r_y; + + /* 拨杆映射需要根据AT9S的实际定义 */ + + return CMD_OK; +} + +bool CMD_AT9S_IsOnline(void *data) { + AT9S_t *at9s = (AT9S_t *)data; + return at9s->header.online; +} + +CMD_DEFINE_ADAPTER(AT9S, at9s, CMD_SRC_RC, CMD_AT9S_Init, CMD_AT9S_GetInput, CMD_AT9S_IsOnline) + +#endif /* CMD_RC_DEVICE_TYPE == 1 */ + +/* ========================================================================== */ +/* 适配器管理实现 */ +/* ========================================================================== */ + +int8_t CMD_Adapter_Register(CMD_InputAdapter_t *adapter) { + if (adapter == NULL || adapter->source >= CMD_SRC_NUM) { + return CMD_ERR_NULL; + } + g_adapters[adapter->source] = adapter; + return CMD_OK; +} + +int8_t CMD_Adapter_InitAll(void) { + /* 注册编译时选择的RC设备适配器 */ +#if CMD_RC_DEVICE_TYPE == 0 + /* DR16 支持 RC 和 PC 输入 */ + CMD_Adapter_Register(&g_adapter_DR16_RC); + CMD_Adapter_Register(&g_adapter_DR16_PC); +#elif CMD_RC_DEVICE_TYPE == 1 + /* AT9S 目前只支持 RC 输入 */ + CMD_Adapter_Register(&g_adapter_AT9S); +#endif + + /* 初始化所有已注册的适配器 */ + for (int i = 0; i < CMD_SRC_NUM; i++) { + if (g_adapters[i] != NULL && g_adapters[i]->init != NULL) { + g_adapters[i]->init(g_adapters[i]->device_data); + } + } + + return CMD_OK; +} + +int8_t CMD_Adapter_GetInput(CMD_InputSource_t source, CMD_RawInput_t *output) { + if (source >= CMD_SRC_NUM || output == NULL) { + return CMD_ERR_NULL; + } + + CMD_InputAdapter_t *adapter = g_adapters[source]; + if (adapter == NULL || adapter->get_input == NULL) { + memset(output, 0, sizeof(CMD_RawInput_t)); + output->online = false; + return CMD_ERR_NO_INPUT; + } + + return adapter->get_input(adapter->device_data, output); +} + +bool CMD_Adapter_IsOnline(CMD_InputSource_t source) { + if (source >= CMD_SRC_NUM) { + return false; + } + + CMD_InputAdapter_t *adapter = g_adapters[source]; + if (adapter == NULL || adapter->is_online == NULL) { + return false; + } + + return adapter->is_online(adapter->device_data); +} diff --git a/User/module/cmd_v2/cmd_adapter.h b/User/module/cmd_v2/cmd_adapter.h new file mode 100644 index 0000000..4fbbc47 --- /dev/null +++ b/User/module/cmd_v2/cmd_adapter.h @@ -0,0 +1,111 @@ +/* + * CMD 模块 V2 - 输入适配器接口 + * 定义设备到统一输入结构的转换接口 + */ +#pragma once + +#include "cmd_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================== */ +/* 适配器接口定义 */ +/* ========================================================================== */ + +/* 适配器操作函数指针类型 */ +typedef int8_t (*CMD_AdapterInitFunc)(void *device_data); +typedef int8_t (*CMD_AdapterGetInputFunc)(void *device_data, CMD_RawInput_t *output); +typedef bool (*CMD_AdapterIsOnlineFunc)(void *device_data); + +/* 适配器描述结构 */ +typedef struct { + const char *name; /* 适配器名称 */ + CMD_InputSource_t source; /* 对应的输入源 */ + void *device_data; /* 设备数据指针 */ + CMD_AdapterInitFunc init; /* 初始化函数 */ + CMD_AdapterGetInputFunc get_input; /* 获取输入函数 */ + CMD_AdapterIsOnlineFunc is_online; /* 在线检测函数 */ +} CMD_InputAdapter_t; + +/* ========================================================================== */ +/* 适配器注册宏 */ +/* ========================================================================== */ + +/* + * 声明适配器 + * 使用示例: + * CMD_DECLARE_ADAPTER(DR16, dr16, DR16_t) + * + * 会生成: + * - extern DR16_t dr16; // 设备实例声明 + * - int8_t CMD_DR16_Init(void *data); + * - int8_t CMD_DR16_GetInput(void *data, CMD_RawInput_t *output); + * - bool CMD_DR16_IsOnline(void *data); + */ +#define CMD_DECLARE_ADAPTER(NAME, var, TYPE) \ + extern TYPE var; \ + int8_t CMD_##NAME##_Init(void *data); \ + int8_t CMD_##NAME##_GetInput(void *data, CMD_RawInput_t *output); \ + bool CMD_##NAME##_IsOnline(void *data); + +/* + * 定义适配器实例 + * 使用示例: + * CMD_DEFINE_ADAPTER(DR16_RC, dr16, CMD_SRC_RC, CMD_DR16_Init, CMD_DR16_RC_GetInput, CMD_DR16_RC_IsOnline) + */ +#define CMD_DEFINE_ADAPTER(NAME, var, source_enum, init_func, get_func, online_func) \ + static CMD_InputAdapter_t g_adapter_##NAME = { \ + .name = #NAME, \ + .source = source_enum, \ + .device_data = (void*)&var, \ + .init = init_func, \ + .get_input = get_func, \ + .is_online = online_func, \ + }; + +/* ========================================================================== */ +/* RC设备适配器配置 */ +/* ========================================================================== */ + +/* 选择使用的RC设备 - 只需修改这里 */ +#define CMD_RC_DEVICE_TYPE 0 /* 0:DR16, 1:AT9S, 2:VT13 */ + +#if CMD_RC_DEVICE_TYPE == 0 + #include "device/dr16.h" + CMD_DECLARE_ADAPTER(DR16_RC, dr16, DR16_t) + CMD_DECLARE_ADAPTER(DR16_PC, dr16, DR16_t) + #define CMD_RC_ADAPTER_NAME DR16 + #define CMD_RC_ADAPTER_VAR dr16 +#elif CMD_RC_DEVICE_TYPE == 1 + #include "device/at9s_pro.h" + CMD_DECLARE_ADAPTER(AT9S, at9s, AT9S_t) + #define CMD_RC_ADAPTER_NAME AT9S + #define CMD_RC_ADAPTER_VAR at9s +#elif CMD_RC_DEVICE_TYPE == 2 + #include "device/vt13.h" + CMD_DECLARE_ADAPTER(VT13, vt13, VT13_t) + #define CMD_RC_ADAPTER_NAME VT13 + #define CMD_RC_ADAPTER_VAR vt13 +#endif + +/* ========================================================================== */ +/* 适配器管理接口 */ +/* ========================================================================== */ + +/* 初始化所有适配器 */ +int8_t CMD_Adapter_InitAll(void); + +/* 获取指定输入源的原始输入 */ +int8_t CMD_Adapter_GetInput(CMD_InputSource_t source, CMD_RawInput_t *output); + +/* 检查输入源是否在线 */ +bool CMD_Adapter_IsOnline(CMD_InputSource_t source); + +/* 注册适配器 (运行时注册,可选) */ +int8_t CMD_Adapter_Register(CMD_InputAdapter_t *adapter); + +#ifdef __cplusplus +} +#endif diff --git a/User/module/cmd_v2/cmd_behavior.c b/User/module/cmd_v2/cmd_behavior.c new file mode 100644 index 0000000..7dd4814 --- /dev/null +++ b/User/module/cmd_v2/cmd_behavior.c @@ -0,0 +1,174 @@ +/* + * CMD 模块 V2 - 行为处理器实现 + */ +#include "cmd_behavior.h" +#include "cmd.h" +#include + +/* ========================================================================== */ +/* 行为配置表 */ +/* ========================================================================== */ + +/* 行为处理函数实现 */ +int8_t CMD_Behavior_Handle_FORE(CMD_Context_t *ctx) { + ctx->chassis.cmd.ctrl_vec.vy += ctx->config->sensitivity.move_sens; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_BACK(CMD_Context_t *ctx) { + ctx->chassis.cmd.ctrl_vec.vy -= ctx->config->sensitivity.move_sens; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_LEFT(CMD_Context_t *ctx) { + ctx->chassis.cmd.ctrl_vec.vx -= ctx->config->sensitivity.move_sens; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_RIGHT(CMD_Context_t *ctx) { + ctx->chassis.cmd.ctrl_vec.vx += ctx->config->sensitivity.move_sens; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_ACCELERATE(CMD_Context_t *ctx) { + ctx->chassis.cmd.ctrl_vec.vx *= ctx->config->sensitivity.move_fast_mult; + ctx->chassis.cmd.ctrl_vec.vy *= ctx->config->sensitivity.move_fast_mult; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_DECELERATE(CMD_Context_t *ctx) { + ctx->chassis.cmd.ctrl_vec.vx *= ctx->config->sensitivity.move_slow_mult; + ctx->chassis.cmd.ctrl_vec.vy *= ctx->config->sensitivity.move_slow_mult; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_FIRE(CMD_Context_t *ctx) { + ctx->shoot.cmd.firecmd = true; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_FIRE_MODE(CMD_Context_t *ctx) { + ctx->shoot.cmd.mode = (ctx->shoot.cmd.mode + 1) % SHOOT_MODE_NUM; + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_ROTOR(CMD_Context_t *ctx) { + if (ctx->chassis.cmd.mode == CHASSIS_MODE_ROTOR) { + ctx->chassis.cmd.mode = CHASSIS_MODE_FOLLOW_GIMBAL; + } else { + ctx->chassis.cmd.mode = CHASSIS_MODE_ROTOR; + ctx->chassis.cmd.mode_rotor = ROTOR_MODE_RAND; + } + return CMD_OK; +} + +int8_t CMD_Behavior_Handle_AUTOAIM(CMD_Context_t *ctx) { + /* TODO: 自瞄模式切换 */ + return CMD_OK; +} + +/* 行为配置表 - 由宏生成 */ +static const CMD_BehaviorConfig_t g_behavior_configs[] = { + CMD_BEHAVIOR_TABLE(BUILD_BEHAVIOR_CONFIG) +}; + +#define BEHAVIOR_CONFIG_COUNT (sizeof(g_behavior_configs) / sizeof(g_behavior_configs[0])) + +/* ========================================================================== */ +/* API实现 */ +/* ========================================================================== */ + +int8_t CMD_Behavior_Init(void) { + /* 当前静态配置,无需初始化 */ + return CMD_OK; +} + +bool CMD_Behavior_IsTriggered(const CMD_RawInput_t *current, + const CMD_RawInput_t *last, + const CMD_BehaviorConfig_t *config) { + if (config == NULL || current == NULL) { + return false; + } + + bool now_pressed = false; + bool last_pressed = false; + + /* 处理特殊按键 */ + switch (config->key) { + case CMD_KEY_NONE: + return false; + + case CMD_KEY_L_CLICK: + now_pressed = current->mouse.l_click; + last_pressed = last ? last->mouse.l_click : false; + break; + + case CMD_KEY_R_CLICK: + now_pressed = current->mouse.r_click; + last_pressed = last ? last->mouse.r_click : false; + break; + + case CMD_KEY_M_CLICK: + now_pressed = current->mouse.m_click; + last_pressed = last ? last->mouse.m_click : false; + break; + + default: + if (config->key < CMD_KEY_NUM) { + now_pressed = CMD_KEY_PRESSED(¤t->keyboard, config->key); + last_pressed = last ? CMD_KEY_PRESSED(&last->keyboard, config->key) : false; + } + break; + } + + /* 根据触发类型判断 */ + switch (config->trigger) { + case CMD_ACTIVE_PRESSED: + return now_pressed; + + case CMD_ACTIVE_RISING_EDGE: + return now_pressed && !last_pressed; + + case CMD_ACTIVE_FALLING_EDGE: + return !now_pressed && last_pressed; + + default: + return false; + } +} + +int8_t CMD_Behavior_ProcessAll(CMD_Context_t *ctx, + const CMD_RawInput_t *current, + const CMD_RawInput_t *last, + CMD_ModuleMask_t active_modules) { + if (ctx == NULL || current == NULL) { + return CMD_ERR_NULL; + } + + for (size_t i = 0; i < BEHAVIOR_CONFIG_COUNT; i++) { + const CMD_BehaviorConfig_t *config = &g_behavior_configs[i]; + + /* 检查模块掩码 */ + if ((config->module_mask & active_modules) == 0) { + continue; + } + + /* 检查是否触发 */ + if (CMD_Behavior_IsTriggered(current, last, config)) { + if (config->handler != NULL) { + config->handler(ctx); + } + } + } + + return CMD_OK; +} + +const CMD_BehaviorConfig_t* CMD_Behavior_GetConfig(CMD_Behavior_t behavior) { + for (size_t i = 0; i < BEHAVIOR_CONFIG_COUNT; i++) { + if (g_behavior_configs[i].behavior == behavior) { + return &g_behavior_configs[i]; + } + } + return NULL; +} diff --git a/User/module/cmd_v2/cmd_behavior.h b/User/module/cmd_v2/cmd_behavior.h new file mode 100644 index 0000000..049cb7e --- /dev/null +++ b/User/module/cmd_v2/cmd_behavior.h @@ -0,0 +1,69 @@ +/* + * CMD 模块 V2 - 行为处理器 + * 实现PC端按键到行为的映射和处理 + */ +#pragma once + +#include "cmd_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================== */ +/* 行为处理器接口 */ +/* ========================================================================== */ + +/* 行为处理函数类型 */ +struct CMD_Context; /* 前向声明 */ +typedef int8_t (*CMD_BehaviorHandler)(struct CMD_Context *ctx); + +/* 行为配置项 */ +typedef struct { + CMD_Behavior_t behavior; /* 行为枚举 */ + uint8_t key; /* 绑定的按键 */ + CMD_TriggerType_t trigger; /* 触发类型 */ + CMD_ModuleMask_t module_mask; /* 影响的模块 */ + CMD_BehaviorHandler handler; /* 处理函数 */ +} CMD_BehaviorConfig_t; + +/* ========================================================================== */ +/* 行为表生成宏 */ +/* ========================================================================== */ + +/* 从宏表生成配置数组 */ +#define BUILD_BEHAVIOR_CONFIG(name, key, trigger, mask) \ + { CMD_BEHAVIOR_##name, key, trigger, mask, CMD_Behavior_Handle_##name }, + +/* 声明所有行为处理函数 */ +#define DECLARE_BEHAVIOR_HANDLER(name, key, trigger, mask) \ + int8_t CMD_Behavior_Handle_##name(struct CMD_Context *ctx); + +/* 展开声明 */ +CMD_BEHAVIOR_TABLE(DECLARE_BEHAVIOR_HANDLER) +#undef DECLARE_BEHAVIOR_HANDLER + +/* ========================================================================== */ +/* 行为处理器API */ +/* ========================================================================== */ + +/* 初始化行为处理器 */ +int8_t CMD_Behavior_Init(void); + +/* 检查行为是否被触发 */ +bool CMD_Behavior_IsTriggered(const CMD_RawInput_t *current, + const CMD_RawInput_t *last, + const CMD_BehaviorConfig_t *config); + +/* 处理所有触发的行为 */ +int8_t CMD_Behavior_ProcessAll(struct CMD_Context *ctx, + const CMD_RawInput_t *current, + const CMD_RawInput_t *last, + CMD_ModuleMask_t active_modules); + +/* 获取行为配置 */ +const CMD_BehaviorConfig_t* CMD_Behavior_GetConfig(CMD_Behavior_t behavior); + +#ifdef __cplusplus +} +#endif diff --git a/User/module/cmd_v2/cmd_example.c b/User/module/cmd_v2/cmd_example.c new file mode 100644 index 0000000..4779c56 --- /dev/null +++ b/User/module/cmd_v2/cmd_example.c @@ -0,0 +1,153 @@ +/* + * CMD 模块 V2 - 使用示例和配置模板 + * + * 本文件展示如何配置和使用新的CMD模块 + */ +#include "cmd.h" + +/* ========================================================================== */ +/* 配置示例 */ +/* ========================================================================== */ + +/* 默认配置 */ +static CMD_Config_t g_cmd_config = { + /* 输入源优先级:RC > PC > NUC > REF */ + .source_priority = { + CMD_SRC_RC, + CMD_SRC_PC, + CMD_SRC_NUC, + CMD_SRC_REF, + }, + + /* 灵敏度设置 */ + .sensitivity = { + .mouse_sens = 0.8f, + .move_sens = 1.0f, + .move_fast_mult = 1.5f, + .move_slow_mult = 0.5f, + }, + + /* RC拨杆模式映射 */ + .rc_mode_map = { + /* 左拨杆控制底盘模式 */ + .sw_left_up = CHASSIS_MODE_BREAK, + .sw_left_mid = CHASSIS_MODE_FOLLOW_GIMBAL, + .sw_left_down = CHASSIS_MODE_ROTOR, + + /* 用于云台模式 */ + .gimbal_sw_up = GIMBAL_MODE_ABSOLUTE, + .gimbal_sw_mid = GIMBAL_MODE_ABSOLUTE, + .gimbal_sw_down = GIMBAL_MODE_RELATIVE, + }, + + .enable_pc_input = true, +}; + +/* CMD上下文 */ +static CMD_Context_t g_cmd_ctx; + +/* ========================================================================== */ +/* 任务示例 */ +/* ========================================================================== */ + +/* + * 初始化示例 + */ +void Example_CMD_Init(void) { + CMD_Init(&g_cmd_ctx, &g_cmd_config); +} + +/* + * 任务循环示例 + */ +void Example_CMD_Task(void) { + /* 一键更新 */ + CMD_Update(&g_cmd_ctx); + + /* 获取命令发送到各模块 */ + Chassis_CMD_t *chassis_cmd = CMD_GetChassisCmd(&g_cmd_ctx); + Gimbal_CMD_t *gimbal_cmd = CMD_GetGimbalCmd(&g_cmd_ctx); + Shoot_CMD_t *shoot_cmd = CMD_GetShootCmd(&g_cmd_ctx); + + /* 使用命令... */ + (void)chassis_cmd; + (void)gimbal_cmd; + (void)shoot_cmd; +} + +/* ========================================================================== */ +/* 架构说明 */ +/* ========================================================================== */ + +/* + * ## 新架构优势 + * + * ### 1. 统一的输入抽象层 (CMD_RawInput_t) + * - 所有设备(DR16/AT9S/VT13等)都转换成相同格式 + * - 上层代码无需关心具体设备类型 + * - 添加新设备只需实现适配器,不改动主逻辑 + * + * ### 2. 适配器模式 + * - 每个设备一个适配器文件 + * - 实现 Init, GetInput, IsOnline 三个函数 + * - 通过宏选择编译哪个适配器 + * + * ### 3. X-Macro配置表 + * - CMD_INPUT_SOURCE_TABLE: 配置输入源 + * - CMD_OUTPUT_MODULE_TABLE: 配置输出模块 + * - CMD_BEHAVIOR_TABLE: 配置按键行为映射 + * - 编译时生成枚举、配置数组、处理函数 + * + * ### 4. 行为驱动设计 + * - 行为与按键解耦 + * - 运行时可修改映射 + * - 支持边沿触发和持续触发 + * + * ### 5. 清晰的分层 + * + * ┌──────────────────────────────────────┐ + * │ 应用层 (cmd.c) │ + * │ - CMD_Update() │ + * │ - 仲裁、命令生成 │ + * └──────────────┬───────────────────────┘ + * │ + * ┌──────────────▼───────────────────────┐ + * │ 行为处理层 (cmd_behavior.c) │ + * │ - 按键触发检测 │ + * │ - 行为函数调用 │ + * └──────────────┬───────────────────────┘ + * │ + * ┌──────────────▼───────────────────────┐ + * │ 抽象输入层 (cmd_types.h) │ + * │ - CMD_RawInput_t │ + * │ - 统一的摇杆、开关、键鼠结构 │ + * └──────────────┬───────────────────────┘ + * │ + * ┌──────────────▼───────────────────────┐ + * │ 适配器层 (cmd_adapter.c) │ + * │ - DR16_Adapter │ + * │ - AT9S_Adapter │ + * │ - 设备数据 → CMD_RawInput_t │ + * └──────────────────────────────────────┘ + * + * ## 扩展指南 + * + * ### 添加新遥控器设备 + * 1. 在 cmd_adapter.h 中添加宏定义选项 + * 2. 在 cmd_adapter.c 中实现三个适配器函数 + * 3. 修改 CMD_RC_DEVICE_TYPE 宏选择新设备 + * + * ### 添加新输入源(如自定义协议) + * 1. 在 CMD_INPUT_SOURCE_TABLE 添加条目 + * 2. 实现对应的适配器 + * 3. 在 CMD_GenerateCommands 添加处理分支 + * + * ### 添加新行为 + * 1. 在 CMD_BEHAVIOR_TABLE 添加条目 + * 2. 实现 CMD_Behavior_Handle_XXX 函数 + * + * ### 添加新输出模块 + * 1. 在 CMD_OUTPUT_MODULE_TABLE 添加条目 + * 2. 在 CMD_Context_t 中添加输出成员 + * 3. 实现对应的 BuildXXXCmd 函数 + */ diff --git a/User/module/cmd_v2/cmd_types.h b/User/module/cmd_v2/cmd_types.h new file mode 100644 index 0000000..ff1646f --- /dev/null +++ b/User/module/cmd_v2/cmd_types.h @@ -0,0 +1,177 @@ +/* + * CMD 模块 V2 - 类型定义 + * 统一的输入/输出抽象层 + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================== */ +/* 错误码定义 */ +/* ========================================================================== */ +#define CMD_OK (0) +#define CMD_ERR_NULL (-1) +#define CMD_ERR_MODE (-2) +#define CMD_ERR_SOURCE (-3) +#define CMD_ERR_NO_INPUT (-4) + +/* ========================================================================== */ +/* 输入源配置宏表 */ +/* ========================================================================== */ +/* + * 使用方法:在config中定义需要启用的输入源 + * 格式: X(枚举名, 优先级, 适配器初始化函数, 获取数据函数) + */ +#define CMD_INPUT_SOURCE_TABLE(X) \ + X(RC, 0, CMD_RC_AdapterInit, CMD_RC_GetInput) \ + X(PC, 1, CMD_PC_AdapterInit, CMD_PC_GetInput) \ + X(NUC, 2, CMD_NUC_AdapterInit, CMD_NUC_GetInput) \ + X(REF, 3, CMD_REF_AdapterInit, CMD_REF_GetInput) + +/* 输出模块配置宏表 */ +#define CMD_OUTPUT_MODULE_TABLE(X) \ + X(CHASSIS, Chassis_CMD_t, chassis) \ + X(GIMBAL, Gimbal_CMD_t, gimbal) \ + X(SHOOT, Shoot_CMD_t, shoot) + +/* ========================================================================== */ +/* 统一输入数据结构 */ +/* ========================================================================== */ + +/* 摇杆数据 - 统一为-1.0 ~ 1.0 */ +typedef struct { + float x; + float y; +} CMD_Joystick_t; + +/* 开关位置 */ +typedef enum { + CMD_SW_ERR = 0, + CMD_SW_UP, + CMD_SW_MID, + CMD_SW_DOWN, +} CMD_SwitchPos_t; + +/* 鼠标数据 */ +typedef struct { + int16_t x; /* 鼠标X轴移动速度 */ + int16_t y; /* 鼠标Y轴移动速度 */ + int16_t z; /* 鼠标滚轮 */ + bool l_click; /* 左键 */ + bool r_click; /* 右键 */ + bool m_click; /* 中键 */ +} CMD_Mouse_t; + +/* 键盘数据 - 最多支持32个按键 */ +typedef struct { + uint32_t bitmap; /* 按键位图 */ +} CMD_Keyboard_t; + +/* 键盘按键索引 */ +typedef enum { + CMD_KEY_W = 0, CMD_KEY_S, CMD_KEY_A, CMD_KEY_D, + CMD_KEY_SHIFT, CMD_KEY_CTRL, CMD_KEY_Q, CMD_KEY_E, + CMD_KEY_R, CMD_KEY_F, CMD_KEY_G, CMD_KEY_Z, + CMD_KEY_X, CMD_KEY_C, CMD_KEY_V, CMD_KEY_B, + CMD_KEY_NUM +} CMD_KeyIndex_t; + +/* 裁判系统数据 */ +typedef struct { + uint8_t game_status; /* 比赛状态 */ +} CMD_Referee_t; + +/* 统一的原始输入结构 - 所有设备适配后都转换成这个格式 */ +typedef struct { + bool online; + + /* 遥控器部分 */ + CMD_Joystick_t joy_left; /* 左摇杆 */ + CMD_Joystick_t joy_right; /* 右摇杆 */ + CMD_SwitchPos_t sw[4]; /* 最多4个拨杆 */ + float dial; /* 拨轮 */ + + /* PC部分 */ + CMD_Mouse_t mouse; + CMD_Keyboard_t keyboard; + + /* NUC部分 */ + /* 暂无定义,预留扩展 */ + + /* REF部分 - 裁判系统数据 */ + CMD_Referee_t referee; +} CMD_RawInput_t; + +/* ========================================================================== */ +/* 输入源枚举 */ +/* ========================================================================== */ +#define ENUM_INPUT_SOURCE(name, ...) CMD_SRC_##name, +typedef enum { + CMD_INPUT_SOURCE_TABLE(ENUM_INPUT_SOURCE) + CMD_SRC_NUM +} CMD_InputSource_t; +#undef ENUM_INPUT_SOURCE + +/* ========================================================================== */ +/* 模块掩码 */ +/* ========================================================================== */ +typedef enum { + CMD_MODULE_NONE = 0, + CMD_MODULE_CHASSIS = (1 << 0), + CMD_MODULE_GIMBAL = (1 << 1), + CMD_MODULE_SHOOT = (1 << 2), + CMD_MODULE_ALL = 0x07 +} CMD_ModuleMask_t; + +/* ========================================================================== */ +/* 行为定义 */ +/* ========================================================================== */ +/* 行为-按键映射宏表 */ +#define CMD_BEHAVIOR_TABLE(X) \ + X(FORE, CMD_KEY_W, CMD_ACTIVE_PRESSED, CMD_MODULE_CHASSIS) \ + X(BACK, CMD_KEY_S, CMD_ACTIVE_PRESSED, CMD_MODULE_CHASSIS) \ + X(LEFT, CMD_KEY_A, CMD_ACTIVE_PRESSED, CMD_MODULE_CHASSIS) \ + X(RIGHT, CMD_KEY_D, CMD_ACTIVE_PRESSED, CMD_MODULE_CHASSIS) \ + X(ACCELERATE, CMD_KEY_SHIFT, CMD_ACTIVE_PRESSED, CMD_MODULE_CHASSIS) \ + X(DECELERATE, CMD_KEY_CTRL, CMD_ACTIVE_PRESSED, CMD_MODULE_CHASSIS) \ + X(FIRE, CMD_KEY_NONE, CMD_ACTIVE_PRESSED, CMD_MODULE_SHOOT) \ + X(FIRE_MODE, CMD_KEY_B, CMD_ACTIVE_RISING_EDGE, CMD_MODULE_SHOOT) \ + X(ROTOR, CMD_KEY_E, CMD_ACTIVE_RISING_EDGE, CMD_MODULE_CHASSIS) \ + X(AUTOAIM, CMD_KEY_R, CMD_ACTIVE_RISING_EDGE, CMD_MODULE_GIMBAL | CMD_MODULE_SHOOT) + +/* 触发类型 */ +typedef enum { + CMD_ACTIVE_PRESSED, /* 按住时触发 */ + CMD_ACTIVE_RISING_EDGE, /* 按下瞬间触发 */ + CMD_ACTIVE_FALLING_EDGE, /* 松开瞬间触发 */ +} CMD_TriggerType_t; + +/* 特殊按键值 */ +#define CMD_KEY_NONE 0xFF +#define CMD_KEY_L_CLICK 0xFE +#define CMD_KEY_R_CLICK 0xFD +#define CMD_KEY_M_CLICK 0xFC + +/* 行为枚举 - 由宏表自动生成 */ +#define ENUM_BEHAVIOR(name, key, trigger, mask) CMD_BEHAVIOR_##name, +typedef enum { + CMD_BEHAVIOR_TABLE(ENUM_BEHAVIOR) + CMD_BEHAVIOR_NUM +} CMD_Behavior_t; +#undef ENUM_BEHAVIOR + +/* ========================================================================== */ +/* 键盘辅助宏 */ +/* ========================================================================== */ +#define CMD_KEY_PRESSED(kb, key) (((kb)->bitmap >> (key)) & 1) +#define CMD_KEY_SET(kb, key) ((kb)->bitmap |= (1 << (key))) +#define CMD_KEY_CLEAR(kb, key) ((kb)->bitmap &= ~(1 << (key))) + +#ifdef __cplusplus +} +#endif