From 20afc1a6567d019b987735af5fbba486c37c4975 Mon Sep 17 00:00:00 2001 From: Robofish <1683502971@qq.com> Date: Wed, 4 Feb 2026 13:03:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96mr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- User/device/mrobot.c | 1134 +++++++++++++++++++++++++----------------- User/device/mrobot.h | 263 +++++++--- 2 files changed, 861 insertions(+), 536 deletions(-) diff --git a/User/device/mrobot.c b/User/device/mrobot.c index 35f3f1e..8e924d5 100644 --- a/User/device/mrobot.c +++ b/User/device/mrobot.c @@ -1,3 +1,8 @@ +/** + * @file mrobot.c + * @brief MRobot CLI 实现 + */ + /* Includes ----------------------------------------------------------------- */ #include "device/mrobot.h" #include "device/device.h" @@ -7,42 +12,82 @@ #include #include #include +#include #include #include +#include #include /* Private constants -------------------------------------------------------- */ static const char *const CLI_WELCOME_MESSAGE = "\r\n" - " ______ __ _______ __ \r\n" - " | __ \\.-----.| |--.-----.| | |.---.-.-----.| |-.-----.-----.\r\n" - " | <| _ || _ | _ || || _ |__ --|| _| -__| _|\r\n" - " |___|__||_____||_____|_____||__|_|__||___._|_____||____|_____|__| \r\n" + " ______ __ _______ __ \r\n" + " | __ \\.-----.| |--.-----.| | |.---.-.-----.| |-.-----.-----.\r\n" + " | <| _ || _ | _ || || _ |__ --|| _| -__| _|\r\n" + " |___|__||_____||_____|_____||__|_|__||___._|_____||____|_____|__| \r\n" " -------------------------------------------------------------------\r\n" " FreeRTOS CLI. Type 'help' to view a list of registered commands. \r\n" "\r\n"; +/* ANSI 转义序列 */ +#define ANSI_CLEAR_SCREEN "\033[2J\033[H" +#define ANSI_CURSOR_HOME "\033[H" +#define ANSI_BACKSPACE "\b \b" + +/* 弧度转角度常量 */ +#define RAD_TO_DEG 57.2957795131f + +/* Private types ------------------------------------------------------------ */ +/* CLI 上下文结构体 - 封装所有状态 */ +typedef struct { + /* 设备管理 */ + MRobot_Device_t devices[MROBOT_MAX_DEVICES]; + uint8_t device_count; + + /* 自定义命令 */ + CLI_Command_Definition_t *custom_cmds[MROBOT_MAX_CUSTOM_COMMANDS]; + uint8_t custom_cmd_count; + + /* CLI 状态 */ + MRobot_State_t state; + char current_path[MROBOT_PATH_MAX_LEN]; + + /* 命令缓冲区 */ + uint8_t cmd_buffer[MROBOT_CMD_BUFFER_SIZE]; + volatile uint8_t cmd_index; + volatile bool cmd_ready; + + /* UART 相关 */ + uint8_t uart_rx_char; + volatile bool tx_complete; + volatile bool htop_exit; + + /* 输出缓冲区 */ + char output_buffer[MROBOT_OUTPUT_BUFFER_SIZE]; + + /* 初始化标志 */ + bool initialized; + + /* 互斥锁 */ + SemaphoreHandle_t mutex; +} MRobot_Context_t; + /* Private variables -------------------------------------------------------- */ -static MRobot_Device_t devices[MROBOT_MAX_DEVICES]; -static uint8_t device_count = 0; -static char current_path[64] = "/"; -static char output_buffer[MROBOT_MAX_OUTPUT_LEN]; - -/* 自定义命令存储(最多16个) */ -#define MROBOT_MAX_CUSTOM_COMMANDS 16 -static CLI_Command_Definition_t *custom_commands[MROBOT_MAX_CUSTOM_COMMANDS]; -static uint8_t custom_command_count = 0; - -/* UART 相关变量 */ -static uint8_t uart_rx_char; -static uint8_t cmd_buffer[128]; -static volatile uint8_t cmd_index = 0; -static volatile bool cmd_ready = false; -static volatile bool htop_mode = false; -static volatile bool htop_exit = false; -static volatile bool tx_complete = true; +static MRobot_Context_t ctx = { + .device_count = 0, + .custom_cmd_count = 0, + .state = MROBOT_STATE_IDLE, + .current_path = "/", + .cmd_index = 0, + .cmd_ready = false, + .tx_complete = true, + .htop_exit = false, + .initialized = false, + .mutex = NULL +}; /* Private function prototypes ---------------------------------------------- */ +/* 命令处理函数 */ static BaseType_t cmd_help(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); static BaseType_t cmd_htop(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); static BaseType_t cmd_cd(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); @@ -50,456 +95,677 @@ static BaseType_t cmd_ls(char *pcWriteBuffer, size_t xWriteBufferLen, const char static BaseType_t cmd_show(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); static BaseType_t cmd_pwd(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); -/* CLI 命令定义 */ -static const CLI_Command_Definition_t cmd_def_help = { - "help", - "help: 显示所有可用命令\r\n", - cmd_help, - 0 +/* 内部辅助函数 */ +static void uart_tx_callback(void); +static void uart_rx_callback(void); +static void send_string(const char *str); +static void send_prompt(void); +static int format_float(char *buf, size_t size, float val, int precision); +static int print_imu_device(const void *device_data, char *buffer, size_t buffer_size); +static int print_motor_device(const void *device_data, char *buffer, size_t buffer_size); + +/* CLI 命令定义表 */ +static const CLI_Command_Definition_t builtin_commands[] = { + { "help", "help: 显示所有可用命令\r\n", cmd_help, 0 }, + { "htop", "htop: 动态显示 FreeRTOS 任务状态 (按 'q' 退出)\r\n", cmd_htop, 0 }, + { "cd", "cd : 切换目录\r\n", cmd_cd, 1 }, + { "ls", "ls: 列出当前目录内容\r\n", cmd_ls, 0 }, + { "show", "show [device] [count]: 显示设备信息\r\n", cmd_show, -1 }, + { "pwd", "pwd: 显示当前目录\r\n", cmd_pwd, 0 }, }; +#define BUILTIN_CMD_COUNT (sizeof(builtin_commands) / sizeof(builtin_commands[0])) -static const CLI_Command_Definition_t cmd_def_htop = { - "htop", - "htop: 动态显示 FreeRTOS 任务状态 (按 'q' 退出)\r\n", - cmd_htop, - 0 -}; +/* ========================================================================== */ +/* 辅助函数实现 */ +/* ========================================================================== */ -static const CLI_Command_Definition_t cmd_def_cd = { - "cd", - "cd : 切换目录\r\n", - cmd_cd, - 1 -}; - -static const CLI_Command_Definition_t cmd_def_ls = { - "ls", - "ls: 列出当前目录内容\r\n", - cmd_ls, - 0 -}; - -static const CLI_Command_Definition_t cmd_def_show = { - "show", - "show [device] [count]: 显示设备信息,count 默认为 1\r\n", - cmd_show, - -1 /* 可变参数 */ -}; - -static const CLI_Command_Definition_t cmd_def_pwd = { - "pwd", - "pwd: 显示当前目录\r\n", - cmd_pwd, - 0 -}; - -/* Private functions -------------------------------------------------------- */ - -/* 通用 IMU 设备打印函数 */ -static void mrobot_print_imu(void *device_data, char *buffer, uint16_t buffer_size) { - DEVICE_IMU_t *imu = (DEVICE_IMU_t *)device_data; +/** + * @brief 格式化浮点数为字符串(避免嵌入式 printf 浮点支持问题) + */ +static int format_float(char *buf, size_t size, float val, int precision) { + if (buf == NULL || size == 0) return 0; - /* 将浮点数转换为整数和小数部分,避免 printf 浮点数支持问题 */ - int accl_x_int = (int)(imu->accl.x); - int accl_x_frac = (int)((imu->accl.x - accl_x_int) * 1000); - int accl_y_int = (int)(imu->accl.y); - int accl_y_frac = (int)((imu->accl.y - accl_y_int) * 1000); - int accl_z_int = (int)(imu->accl.z); - int accl_z_frac = (int)((imu->accl.z - accl_z_int) * 1000); + int offset = 0; - int gyro_x_int = (int)(imu->gyro.x); - int gyro_x_frac = (int)((imu->gyro.x - gyro_x_int) * 1000); - int gyro_y_int = (int)(imu->gyro.y); - int gyro_y_frac = (int)((imu->gyro.y - gyro_y_int) * 1000); - int gyro_z_int = (int)(imu->gyro.z); - int gyro_z_frac = (int)((imu->gyro.z - gyro_z_int) * 1000); + /* 处理负数 */ + if (val < 0) { + if (offset < (int)size - 1) buf[offset++] = '-'; + val = -val; + } - int temp_int = (int)(imu->temp); - int temp_frac = (int)((imu->temp - temp_int) * 100); + /* 计算乘数 */ + int multiplier = 1; + for (int i = 0; i < precision; i++) multiplier *= 10; - float roll_deg = imu->euler.rol * 57.2958f; - float pitch_deg = imu->euler.pit * 57.2958f; - float yaw_deg = imu->euler.yaw * 57.2958f; + int int_part = (int)val; + int frac_part = (int)((val - int_part) * multiplier + 0.5f); - int roll_int = (int)roll_deg; - int roll_frac = (int)((roll_deg - roll_int) * 100); - int pitch_int = (int)pitch_deg; - int pitch_frac = (int)((pitch_deg - pitch_int) * 100); - int yaw_int = (int)yaw_deg; - int yaw_frac = (int)((yaw_deg - yaw_int) * 100); + /* 处理进位 */ + if (frac_part >= multiplier) { + int_part++; + frac_part -= multiplier; + } - snprintf(buffer, buffer_size, - "状态: %s\r\n" - "加速度计: X=%d.%03d Y=%d.%03d Z=%d.%03d m/s²\r\n" - "陀螺仪: X=%d.%03d Y=%d.%03d Z=%d.%03d rad/s\r\n" - "温度: %d.%02d °C\r\n" - "欧拉角: Roll=%d.%02d Pitch=%d.%02d Yaw=%d.%02d °\r\n", - imu->header.online ? "在线" : "离线", - accl_x_int, abs(accl_x_frac), accl_y_int, abs(accl_y_frac), accl_z_int, abs(accl_z_frac), - gyro_x_int, abs(gyro_x_frac), gyro_y_int, abs(gyro_y_frac), gyro_z_int, abs(gyro_z_frac), - temp_int, abs(temp_frac), - roll_int, abs(roll_frac), pitch_int, abs(pitch_frac), yaw_int, abs(yaw_frac)); + /* 格式化输出 */ + int written = snprintf(buf + offset, size - offset, "%d.%0*d", int_part, precision, frac_part); + return (written > 0) ? (offset + written) : offset; } -/* 通用电机设备打印函数 */ -static void mrobot_print_motor(void *device_data, char *buffer, uint16_t buffer_size) { - MOTOR_t *motor = (MOTOR_t *)device_data; +/** + * @brief 发送字符串到 UART(阻塞等待完成) + */ +static void send_string(const char *str) { + if (str == NULL || *str == '\0') return; - int angle_int = (int)(motor->feedback.rotor_abs_angle); - int angle_frac = (int)((motor->feedback.rotor_abs_angle - angle_int) * 100); - - int speed_int = (int)(motor->feedback.rotor_speed); - int speed_frac = (int)((motor->feedback.rotor_speed - speed_int) * 100); - - int current_int = (int)(motor->feedback.torque_current); - int current_frac = (int)((motor->feedback.torque_current - current_int) * 100); - - int temp_int = (int)(motor->feedback.temp); - int temp_frac = (int)((motor->feedback.temp - temp_int) * 10); - - snprintf(buffer, buffer_size, - "状态: %s\r\n" - "反装: %s\r\n" - "角度: %d.%02d °\r\n" - "转速: %d.%02d RPM\r\n" - "电流: %d.%02d A\r\n" - "温度: %d.%01d °C\r\n", - motor->header.online ? "在线" : "离线", - motor->reverse ? "是" : "否", - angle_int, abs(angle_frac), - speed_int, abs(speed_frac), - current_int, abs(current_frac), - temp_int, abs(temp_frac)); + ctx.tx_complete = false; + BSP_UART_Transmit(MROBOT_UART_PORT, (uint8_t *)str, strlen(str), true); + while (!ctx.tx_complete) { osDelay(1); } } -/* help 命令实现 */ +/** + * @brief 发送命令提示符 + */ +static void send_prompt(void) { + char prompt[MROBOT_PATH_MAX_LEN + 20]; + snprintf(prompt, sizeof(prompt), "root@mrobot:%s$ ", ctx.current_path); + send_string(prompt); +} + +/** + * @brief UART 发送完成回调 + */ +static void uart_tx_callback(void) { + ctx.tx_complete = true; +} + +/** + * @brief UART 接收回调 + */ +static void uart_rx_callback(void) { + uint8_t ch = ctx.uart_rx_char; + + /* htop 模式下检查退出键 */ + if (ctx.state == MROBOT_STATE_HTOP) { + if (ch == 'q' || ch == 'Q' || ch == 27) { + ctx.htop_exit = true; + } + BSP_UART_Receive(MROBOT_UART_PORT, &ctx.uart_rx_char, 1, false); + return; + } + + /* 正常命令输入处理 */ + if (ch == '\r' || ch == '\n') { + if (ctx.cmd_index > 0) { + ctx.cmd_buffer[ctx.cmd_index] = '\0'; + ctx.cmd_ready = true; + BSP_UART_Transmit(MROBOT_UART_PORT, (uint8_t *)"\r\n", 2, false); + } + } else if (ch == 127 || ch == 8) { /* 退格键 */ + if (ctx.cmd_index > 0) { + ctx.cmd_index--; + BSP_UART_Transmit(MROBOT_UART_PORT, (uint8_t *)ANSI_BACKSPACE, 3, false); + } + } else if (ch >= 32 && ch < 127 && ctx.cmd_index < sizeof(ctx.cmd_buffer) - 1) { + ctx.cmd_buffer[ctx.cmd_index++] = ch; + BSP_UART_Transmit(MROBOT_UART_PORT, &ch, 1, false); + } + + BSP_UART_Receive(MROBOT_UART_PORT, &ctx.uart_rx_char, 1, false); +} + +/* ========================================================================== */ +/* 设备打印函数实现 */ +/* ========================================================================== */ + +/** + * @brief IMU 设备打印函数 + */ +static int print_imu_device(const void *device_data, char *buffer, size_t buffer_size) { + if (device_data == NULL || buffer == NULL || buffer_size == 0) return 0; + + const DEVICE_IMU_t *imu = (const DEVICE_IMU_t *)device_data; + + char ax[16], ay[16], az[16]; + char gx[16], gy[16], gz[16]; + char temp[16]; + char roll[16], pitch[16], yaw[16]; + + format_float(ax, sizeof(ax), imu->accl.x, 3); + format_float(ay, sizeof(ay), imu->accl.y, 3); + format_float(az, sizeof(az), imu->accl.z, 3); + format_float(gx, sizeof(gx), imu->gyro.x, 3); + format_float(gy, sizeof(gy), imu->gyro.y, 3); + format_float(gz, sizeof(gz), imu->gyro.z, 3); + format_float(temp, sizeof(temp), imu->temp, 2); + format_float(roll, sizeof(roll), imu->euler.rol * RAD_TO_DEG, 2); + format_float(pitch, sizeof(pitch), imu->euler.pit * RAD_TO_DEG, 2); + format_float(yaw, sizeof(yaw), imu->euler.yaw * RAD_TO_DEG, 2); + + return snprintf(buffer, buffer_size, + "状态: %s\r\n" + "加速度计: X=%s Y=%s Z=%s m/s²\r\n" + "陀螺仪: X=%s Y=%s Z=%s rad/s\r\n" + "温度: %s °C\r\n" + "欧拉角: Roll=%s Pitch=%s Yaw=%s °\r\n", + imu->header.online ? "在线" : "离线", + ax, ay, az, + gx, gy, gz, + temp, + roll, pitch, yaw); +} + +/** + * @brief 电机设备打印函数 + */ +static int print_motor_device(const void *device_data, char *buffer, size_t buffer_size) { + if (device_data == NULL || buffer == NULL || buffer_size == 0) return 0; + + const MOTOR_t *motor = (const MOTOR_t *)device_data; + + char angle[16], speed[16], current[16], temp[16]; + + format_float(angle, sizeof(angle), motor->feedback.rotor_abs_angle, 2); + format_float(speed, sizeof(speed), motor->feedback.rotor_speed, 2); + format_float(current, sizeof(current), motor->feedback.torque_current, 2); + format_float(temp, sizeof(temp), motor->feedback.temp, 1); + + return snprintf(buffer, buffer_size, + "状态: %s\r\n" + "反装: %s\r\n" + "角度: %s °\r\n" + "转速: %s RPM\r\n" + "电流: %s A\r\n" + "温度: %s °C\r\n", + motor->header.online ? "在线" : "离线", + motor->reverse ? "是" : "否", + angle, speed, current, temp); +} + +/* ========================================================================== */ +/* CLI 命令实现 */ +/* ========================================================================== */ + +/** + * @brief help 命令 - 显示帮助信息 + */ static BaseType_t cmd_help(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { (void)pcCommandString; - snprintf(pcWriteBuffer, xWriteBufferLen, - "MRobot CLI v1.0\r\n" - "可用命令:\r\n" - " help - 显示帮助信息\r\n" - " htop - 动态显示任务状态 (按 'q' 退出)\r\n" - " cd - 切换目录\r\n" - " ls - 列出目录内容\r\n" - " pwd - 显示当前路径\r\n" - " show - 显示设备信息\r\n" - "\r\n"); + + int offset = snprintf(pcWriteBuffer, xWriteBufferLen, + "MRobot CLI v2.0\r\n" + "================\r\n" + "内置命令:\r\n"); + + for (size_t i = 0; i < BUILTIN_CMD_COUNT && offset < (int)xWriteBufferLen - 50; i++) { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + " %s", builtin_commands[i].pcHelpString); + } + + if (ctx.custom_cmd_count > 0) { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + "\r\n自定义命令:\r\n"); + for (uint8_t i = 0; i < ctx.custom_cmd_count && offset < (int)xWriteBufferLen - 50; i++) { + if (ctx.custom_cmds[i] != NULL) { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + " %s", ctx.custom_cmds[i]->pcHelpString); + } + } + } + return pdFALSE; } -/* htop 命令实现 - 由 cli.c 处理动态显示 */ +/** + * @brief htop 命令 - 设置 htop 模式标志 + */ static BaseType_t cmd_htop(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { (void)pcCommandString; (void)pcWriteBuffer; (void)xWriteBufferLen; - /* htop 命令在 cli.c 中被特殊处理,这里返回空 */ + /* htop 模式在 MRobot_Run 中处理 */ return pdFALSE; } -/* pwd 命令实现 */ +/** + * @brief pwd 命令 - 显示当前路径 + */ static BaseType_t cmd_pwd(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { (void)pcCommandString; - snprintf(pcWriteBuffer, xWriteBufferLen, "%s\r\n", current_path); + snprintf(pcWriteBuffer, xWriteBufferLen, "%s\r\n", ctx.current_path); return pdFALSE; } -/* cd 命令实现 */ +/** + * @brief cd 命令 - 切换目录 + */ static BaseType_t cmd_cd(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { const char *param; BaseType_t param_len; param = FreeRTOS_CLIGetParameter(pcCommandString, 1, ¶m_len); - if (param != NULL) { - char path[64]; - strncpy(path, param, param_len); - path[param_len] = '\0'; - - if (strcmp(path, "/") == 0 || strcmp(path, "..") == 0) { - strcpy(current_path, "/"); - } else if (strcmp(path, "dev") == 0 || strcmp(path, "/dev") == 0) { - strcpy(current_path, "/dev"); - } else if (strcmp(path, "modules") == 0 || strcmp(path, "/modules") == 0) { - strcpy(current_path, "/modules"); - } else { - snprintf(pcWriteBuffer, xWriteBufferLen, "错误: 目录不存在\r\n"); - return pdFALSE; - } - snprintf(pcWriteBuffer, xWriteBufferLen, "切换到: %s\r\n", current_path); - } else { - snprintf(pcWriteBuffer, xWriteBufferLen, "错误: 缺少路径参数\r\n"); + if (param == NULL) { + /* 无参数时切换到根目录 */ + strcpy(ctx.current_path, "/"); + snprintf(pcWriteBuffer, xWriteBufferLen, "切换到: %s\r\n", ctx.current_path); + return pdFALSE; } + /* 安全复制路径参数 */ + char path[MROBOT_PATH_MAX_LEN]; + size_t copy_len = (size_t)param_len < sizeof(path) - 1 ? (size_t)param_len : sizeof(path) - 1; + strncpy(path, param, copy_len); + path[copy_len] = '\0'; + + /* 路径解析 */ + if (strcmp(path, "/") == 0 || strcmp(path, "..") == 0 || strcmp(path, "~") == 0) { + strcpy(ctx.current_path, "/"); + } else if (strcmp(path, "dev") == 0 || strcmp(path, "/dev") == 0) { + strcpy(ctx.current_path, "/dev"); + } else if (strcmp(path, "modules") == 0 || strcmp(path, "/modules") == 0) { + strcpy(ctx.current_path, "/modules"); + } else { + snprintf(pcWriteBuffer, xWriteBufferLen, "错误: 目录 '%s' 不存在\r\n", path); + return pdFALSE; + } + + snprintf(pcWriteBuffer, xWriteBufferLen, "切换到: %s\r\n", ctx.current_path); return pdFALSE; } -/* ls 命令实现 */ +/** + * @brief ls 命令 - 列出目录内容 + */ static BaseType_t cmd_ls(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { (void)pcCommandString; int offset = 0; - if (strcmp(current_path, "/") == 0) { - offset = snprintf(pcWriteBuffer, xWriteBufferLen, "dev/\r\nmodules/\r\n"); - } else if (strcmp(current_path, "/dev") == 0) { - offset = snprintf(pcWriteBuffer, xWriteBufferLen, "设备列表:\r\n"); - for (uint8_t i = 0; i < device_count && offset < (int)xWriteBufferLen; i++) { - offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, - " %s\r\n", devices[i].name); + if (strcmp(ctx.current_path, "/") == 0) { + snprintf(pcWriteBuffer, xWriteBufferLen, + "dev/\r\n" + "modules/\r\n"); + } else if (strcmp(ctx.current_path, "/dev") == 0) { + offset = snprintf(pcWriteBuffer, xWriteBufferLen, "设备列表 (%d 个):\r\n", ctx.device_count); + + if (ctx.device_count == 0) { + snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, " (无设备)\r\n"); + } else { + /* 按类型分组显示 */ + static const char *type_names[] = { "IMU", "电机", "传感器", "自定义" }; + for (uint8_t t = 0; t < MROBOT_DEVICE_TYPE_NUM && offset < (int)xWriteBufferLen - 50; t++) { + bool has_type = false; + for (uint8_t i = 0; i < ctx.device_count; i++) { + if (ctx.devices[i].type == t) { + if (!has_type) { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + " [%s]\r\n", type_names[t]); + has_type = true; + } + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + " %s\r\n", ctx.devices[i].name); + } + } + } } - if (device_count == 0) { - offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, - " (无设备)\r\n"); - } - } else if (strcmp(current_path, "/modules") == 0) { - offset = snprintf(pcWriteBuffer, xWriteBufferLen, "(模块功能暂未实现)\r\n"); + } else if (strcmp(ctx.current_path, "/modules") == 0) { + snprintf(pcWriteBuffer, xWriteBufferLen, "(模块功能暂未实现)\r\n"); } return pdFALSE; } -/* show 命令实现 */ +/** + * @brief show 命令 - 显示设备信息 + */ static BaseType_t cmd_show(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { const char *param; const char *count_param; BaseType_t param_len, count_param_len; + + /* 使用局部静态变量跟踪多次打印状态 */ static uint32_t print_count = 0; - static uint32_t current_iteration = 0; - static char saved_device_name[32] = {0}; + static uint32_t current_iter = 0; + static char target_device[MROBOT_DEVICE_NAME_LEN] = {0}; /* 首次调用时解析参数 */ - if (current_iteration == 0) { + if (current_iter == 0) { + /* 检查是否在 /dev 目录 */ + if (strcmp(ctx.current_path, "/dev") != 0) { + snprintf(pcWriteBuffer, xWriteBufferLen, + "错误: show 命令仅在 /dev 目录下可用\r\n" + "提示: 使用 'cd /dev' 切换到设备目录\r\n"); + return pdFALSE; + } + param = FreeRTOS_CLIGetParameter(pcCommandString, 1, ¶m_len); count_param = FreeRTOS_CLIGetParameter(pcCommandString, 2, &count_param_len); - if (strcmp(current_path, "/dev") != 0) { - snprintf(pcWriteBuffer, xWriteBufferLen, "错误: show 命令仅在 /dev 目录下可用\r\n"); - return pdFALSE; - } - - /* 解析打印次数,默认为 1 */ + /* 解析打印次数 */ print_count = 1; if (count_param != NULL) { char count_str[16]; - strncpy(count_str, count_param, count_param_len < 15 ? count_param_len : 15); - count_str[count_param_len < 15 ? count_param_len : 15] = '\0'; - int parsed_count = atoi(count_str); - if (parsed_count > 0 && parsed_count <= 100) { - print_count = parsed_count; + size_t copy_len = (size_t)count_param_len < sizeof(count_str) - 1 ? + (size_t)count_param_len : sizeof(count_str) - 1; + strncpy(count_str, count_param, copy_len); + count_str[copy_len] = '\0'; + int parsed = atoi(count_str); + if (parsed > 0 && parsed <= 1000) { + print_count = (uint32_t)parsed; } } - /* 保存设备名称 */ + /* 保存目标设备名称 */ if (param != NULL) { - strncpy(saved_device_name, param, param_len); - saved_device_name[param_len] = '\0'; + size_t copy_len = (size_t)param_len < sizeof(target_device) - 1 ? + (size_t)param_len : sizeof(target_device) - 1; + strncpy(target_device, param, copy_len); + target_device[copy_len] = '\0'; } else { - saved_device_name[0] = '\0'; + target_device[0] = '\0'; } } - /* 执行打印 */ int offset = 0; - /* 多次打印时清屏(像htop一样) */ + /* 连续打印模式:清屏 */ if (print_count > 1) { - const char *clear = "\033[2J\033[H"; /* ANSI清屏 + 光标归位 */ - offset = snprintf(pcWriteBuffer, xWriteBufferLen, "%s", clear); + offset = snprintf(pcWriteBuffer, xWriteBufferLen, "%s[%lu/%lu]\r\n", + ANSI_CLEAR_SCREEN, + (unsigned long)(current_iter + 1), + (unsigned long)print_count); } - if (saved_device_name[0] == '\0') { + if (target_device[0] == '\0') { /* 显示所有设备 */ - if (print_count > 1) { - offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, - "[第 %lu/%lu 次]\r\n", (unsigned long)(current_iteration + 1), (unsigned long)print_count); - } - offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, "所有设备信息:\r\n"); - for (uint8_t i = 0; i < device_count && offset < (int)xWriteBufferLen; i++) { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + "=== 所有设备 ===\r\n\r\n"); + + for (uint8_t i = 0; i < ctx.device_count && offset < (int)xWriteBufferLen - 100; i++) { offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, - "\r\n=== %s ===\r\n", devices[i].name); - if (devices[i].print_callback != NULL) { - devices[i].print_callback(devices[i].data, - pcWriteBuffer + offset, - xWriteBufferLen - offset); - offset = strlen(pcWriteBuffer); - } - } - } else { - /* 显示特定设备 */ - bool found = false; - for (uint8_t i = 0; i < device_count; i++) { - if (strcmp(devices[i].name, saved_device_name) == 0) { - found = true; - int offset = 0; - if (print_count > 1) { - offset = snprintf(pcWriteBuffer, xWriteBufferLen, - "[第 %lu/%lu 次] === %s ===\r\n", - (unsigned long)(current_iteration + 1), (unsigned long)print_count, devices[i].name); - } else { - offset = snprintf(pcWriteBuffer, xWriteBufferLen, - "=== %s ===\r\n", devices[i].name); - } - if (devices[i].print_callback != NULL) { - devices[i].print_callback(devices[i].data, - pcWriteBuffer + offset, - xWriteBufferLen - offset); - } else { - snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, - "无打印函数\r\n"); - } - break; + "--- %s ---\r\n", ctx.devices[i].name); + + if (ctx.devices[i].print_cb != NULL) { + int written = ctx.devices[i].print_cb(ctx.devices[i].data, + pcWriteBuffer + offset, + xWriteBufferLen - offset); + offset += (written > 0) ? written : 0; + } else { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + "(无打印函数)\r\n"); } + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, "\r\n"); } - if (!found && current_iteration == 0) { - snprintf(pcWriteBuffer, xWriteBufferLen, "错误: 设备 '%s' 未找到\r\n", saved_device_name); - current_iteration = 0; + if (ctx.device_count == 0) { + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + "(无已注册设备)\r\n"); + } + } else { + /* 显示指定设备 */ + const MRobot_Device_t *dev = MRobot_FindDevice(target_device); + + if (dev == NULL) { + snprintf(pcWriteBuffer, xWriteBufferLen, + "错误: 设备 '%s' 未找到\r\n", target_device); + current_iter = 0; return pdFALSE; } + + offset += snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + "=== %s ===\r\n", dev->name); + + if (dev->print_cb != NULL) { + dev->print_cb(dev->data, pcWriteBuffer + offset, xWriteBufferLen - offset); + } else { + snprintf(pcWriteBuffer + offset, xWriteBufferLen - offset, + "(无打印函数)\r\n"); + } } - /* 判断是否需要继续打印 */ - current_iteration++; - if (current_iteration < print_count) { - osDelay(200); /* 延时 200ms 再打印下一次 */ - return pdTRUE; /* 返回 pdTRUE 表示还有更多输出 */ + /* 判断是否继续打印 */ + current_iter++; + if (current_iter < print_count) { + osDelay(MROBOT_HTOP_REFRESH_MS); + return pdTRUE; } else { - current_iteration = 0; /* 重置计数器 */ + current_iter = 0; return pdFALSE; } } -/* UART 发送完成回调 */ -static void mrobot_uart_tx_callback(void) { - tx_complete = true; -} +/* ========================================================================== */ +/* htop 模式实现 */ +/* ========================================================================== */ -/* UART 接收中断回调 */ -static void mrobot_uart_rx_callback(void) { - uint8_t ch = uart_rx_char; +/** + * @brief 处理 htop 模式显示 + */ +static void handle_htop_mode(void) { + /* 清屏 */ + send_string(ANSI_CLEAR_SCREEN); - /* 如果在 htop 模式,检查退出键 */ - if (htop_mode) { - if (ch == 'q' || ch == 'Q' || ch == 27) { /* q 或 ESC */ - htop_exit = true; + /* 显示头部 */ + send_string( + "MRobot Task Monitor (按 'q' 退出)\r\n" + "================================================================================\r\n" + "Task Name State Prio Stack Num\r\n" + "--------------------------------------------------------------------------------\r\n"); + + /* 获取任务列表 */ + char task_buffer[1024]; + char display_line[128]; + + vTaskList(task_buffer); + + /* 解析并格式化任务列表 */ + char *line = strtok(task_buffer, "\r\n"); + while (line != NULL) { + char name[17] = {0}; + char state_char = '?'; + int prio = 0, stack = 0, num = 0; + + if (sscanf(line, "%16s %c %d %d %d", name, &state_char, &prio, &stack, &num) == 5) { + const char *state_str; + switch (state_char) { + case 'R': state_str = "Running"; break; + case 'B': state_str = "Blocked"; break; + case 'S': state_str = "Suspend"; break; + case 'D': state_str = "Deleted"; break; + case 'X': state_str = "Ready"; break; + default: state_str = "Unknown"; break; + } + + snprintf(display_line, sizeof(display_line), + "%-16s %-8s %-4d %-8d %-4d\r\n", + name, state_str, prio, stack, num); + send_string(display_line); } - /* 重新启动接收 */ - BSP_UART_Receive(BSP_UART_VOFA, &uart_rx_char, 1, false); - return; + line = strtok(NULL, "\r\n"); } - if (ch == '\r' || ch == '\n') { - if (cmd_index > 0) { - cmd_buffer[cmd_index] = '\0'; - cmd_ready = true; - /* 换行使用中断方式发送(数据量小) */ - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)"\r\n", 2, false); - } - } else if (ch == 127 || ch == 8) { /* 退格键 */ - if (cmd_index > 0) { - cmd_index--; - /* 回显退格(中断方式) */ - const char *backspace = "\b \b"; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)backspace, 3, false); - } - } else if (ch >= 32 && ch < 127 && cmd_index < sizeof(cmd_buffer) - 1) { - /* 只接受可打印字符 */ - cmd_buffer[cmd_index++] = ch; - /* 回显字符(中断方式) */ - BSP_UART_Transmit(BSP_UART_VOFA, &ch, 1, false); + /* 显示系统信息 */ + snprintf(display_line, sizeof(display_line), + "--------------------------------------------------------------------------------\r\n" + "System Tick: %lu | Free Heap: %lu bytes\r\n", + (unsigned long)xTaskGetTickCount(), + (unsigned long)xPortGetFreeHeapSize()); + send_string(display_line); + + /* 检查退出 */ + if (ctx.htop_exit) { + ctx.state = MROBOT_STATE_IDLE; + ctx.htop_exit = false; + send_string(ANSI_CLEAR_SCREEN); + send_prompt(); } - /* 重新启动接收 */ - BSP_UART_Receive(BSP_UART_VOFA, &uart_rx_char, 1, false); + osDelay(MROBOT_HTOP_REFRESH_MS); } -/* Exported functions ------------------------------------------------------- */ +/* ========================================================================== */ +/* 公共 API 实现 */ +/* ========================================================================== */ void MRobot_Init(void) { - /* 初始化设备数组 */ - memset(devices, 0, sizeof(devices)); - device_count = 0; + if (ctx.initialized) return; - /* 注册 CLI 命令 */ - FreeRTOS_CLIRegisterCommand(&cmd_def_help); - FreeRTOS_CLIRegisterCommand(&cmd_def_htop); - FreeRTOS_CLIRegisterCommand(&cmd_def_cd); - FreeRTOS_CLIRegisterCommand(&cmd_def_ls); - FreeRTOS_CLIRegisterCommand(&cmd_def_show); - FreeRTOS_CLIRegisterCommand(&cmd_def_pwd); + /* 创建互斥锁 */ + ctx.mutex = xSemaphoreCreateMutex(); + + /* 初始化状态 */ + memset(ctx.devices, 0, sizeof(ctx.devices)); + ctx.device_count = 0; + ctx.custom_cmd_count = 0; + ctx.state = MROBOT_STATE_IDLE; + strcpy(ctx.current_path, "/"); + ctx.cmd_index = 0; + ctx.cmd_ready = false; + ctx.tx_complete = true; + ctx.htop_exit = false; + + /* 注册内置命令 */ + for (size_t i = 0; i < BUILTIN_CMD_COUNT; i++) { + FreeRTOS_CLIRegisterCommand(&builtin_commands[i]); + } /* 注册 UART 回调 */ - BSP_UART_RegisterCallback(BSP_UART_VOFA, BSP_UART_RX_CPLT_CB, mrobot_uart_rx_callback); - BSP_UART_RegisterCallback(BSP_UART_VOFA, BSP_UART_TX_CPLT_CB, mrobot_uart_tx_callback); + BSP_UART_RegisterCallback(MROBOT_UART_PORT, BSP_UART_RX_CPLT_CB, uart_rx_callback); + BSP_UART_RegisterCallback(MROBOT_UART_PORT, BSP_UART_TX_CPLT_CB, uart_tx_callback); /* 启动 UART 接收 */ - BSP_UART_Receive(BSP_UART_VOFA, &uart_rx_char, 1, false); + BSP_UART_Receive(MROBOT_UART_PORT, &ctx.uart_rx_char, 1, false); - /* 等待用户按下回车键再显示欢迎消息 */ - volatile bool enter_pressed = false; - while (!enter_pressed) { - if (uart_rx_char == '\r' || uart_rx_char == '\n') { - enter_pressed = true; - } + /* 等待用户按下回车 */ + while (ctx.uart_rx_char != '\r' && ctx.uart_rx_char != '\n') { osDelay(10); } - /* 发送欢迎消息 */ - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)CLI_WELCOME_MESSAGE, strlen(CLI_WELCOME_MESSAGE), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ + /* 发送欢迎消息和提示符 */ + send_string(CLI_WELCOME_MESSAGE); + send_prompt(); - /* 发送提示符 */ - const char *prompt = "root@mrobot:~$ "; - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)prompt, strlen(prompt), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ + ctx.initialized = true; } -int8_t MRobot_RegisterDevice(const char *name, MRobot_DeviceType_t type, - void *data, MRobot_PrintCallback_t print_callback) { - if (device_count >= MROBOT_MAX_DEVICES) { - return -1; +void MRobot_DeInit(void) { + if (!ctx.initialized) return; + + /* 释放自定义命令内存 */ + for (uint8_t i = 0; i < ctx.custom_cmd_count; i++) { + if (ctx.custom_cmds[i] != NULL) { + vPortFree(ctx.custom_cmds[i]); + ctx.custom_cmds[i] = NULL; + } } + /* 删除互斥锁 */ + if (ctx.mutex != NULL) { + vSemaphoreDelete(ctx.mutex); + ctx.mutex = NULL; + } + + ctx.initialized = false; +} + +MRobot_State_t MRobot_GetState(void) { + return ctx.state; +} + +MRobot_Error_t MRobot_RegisterDevice(const char *name, MRobot_DeviceType_t type, + void *data, MRobot_PrintCallback_t print_cb) { if (name == NULL || data == NULL) { - return -1; + return MROBOT_ERR_NULL_PTR; } - strncpy(devices[device_count].name, name, sizeof(devices[device_count].name) - 1); - devices[device_count].type = type; - devices[device_count].data = data; - devices[device_count].print_callback = print_callback; - device_count++; - - return 0; -} - -int8_t MRobot_RegisterIMU(const char *name, void *imu_device) { - return MRobot_RegisterDevice(name, MROBOT_DEVICE_TYPE_IMU, imu_device, mrobot_print_imu); -} - -int8_t MRobot_RegisterMotor(const char *name, void *motor) { - return MRobot_RegisterDevice(name, MROBOT_DEVICE_TYPE_MOTOR, motor, mrobot_print_motor); -} - -int8_t MRobot_RegisterCommand(const char *command, const char *help_text, - MRobot_CommandCallback_t callback, int8_t param_count) { - if (custom_command_count >= MROBOT_MAX_CUSTOM_COMMANDS) { - return -1; /* 命令数量已达上限 */ + if (ctx.device_count >= MROBOT_MAX_DEVICES) { + return MROBOT_ERR_FULL; } + if (type >= MROBOT_DEVICE_TYPE_NUM) { + return MROBOT_ERR_INVALID_ARG; + } + + /* 检查重名 */ + for (uint8_t i = 0; i < ctx.device_count; i++) { + if (strcmp(ctx.devices[i].name, name) == 0) { + return MROBOT_ERR_INVALID_ARG; /* 设备名已存在 */ + } + } + + /* 线程安全写入 */ + if (ctx.mutex != NULL) { + xSemaphoreTake(ctx.mutex, portMAX_DELAY); + } + + strncpy(ctx.devices[ctx.device_count].name, name, MROBOT_DEVICE_NAME_LEN - 1); + ctx.devices[ctx.device_count].name[MROBOT_DEVICE_NAME_LEN - 1] = '\0'; + ctx.devices[ctx.device_count].type = type; + ctx.devices[ctx.device_count].data = data; + ctx.devices[ctx.device_count].print_cb = print_cb; + ctx.device_count++; + + if (ctx.mutex != NULL) { + xSemaphoreGive(ctx.mutex); + } + + return MROBOT_OK; +} + +MRobot_Error_t MRobot_UnregisterDevice(const char *name) { + if (name == NULL) { + return MROBOT_ERR_NULL_PTR; + } + + if (ctx.mutex != NULL) { + xSemaphoreTake(ctx.mutex, portMAX_DELAY); + } + + for (uint8_t i = 0; i < ctx.device_count; i++) { + if (strcmp(ctx.devices[i].name, name) == 0) { + /* 移动后续设备 */ + for (uint8_t j = i; j < ctx.device_count - 1; j++) { + ctx.devices[j] = ctx.devices[j + 1]; + } + ctx.device_count--; + + if (ctx.mutex != NULL) { + xSemaphoreGive(ctx.mutex); + } + return MROBOT_OK; + } + } + + if (ctx.mutex != NULL) { + xSemaphoreGive(ctx.mutex); + } + return MROBOT_ERR_NOT_FOUND; +} + +MRobot_Error_t MRobot_RegisterIMU(const char *name, DEVICE_IMU_t *imu_device) { + return MRobot_RegisterDevice(name, MROBOT_DEVICE_TYPE_IMU, imu_device, print_imu_device); +} + +MRobot_Error_t MRobot_RegisterMotor(const char *name, void *motor) { + return MRobot_RegisterDevice(name, MROBOT_DEVICE_TYPE_MOTOR, motor, print_motor_device); +} + +MRobot_Error_t MRobot_RegisterCommand(const char *command, const char *help_text, + MRobot_CommandCallback_t callback, int8_t param_count) { if (command == NULL || help_text == NULL || callback == NULL) { - return -1; /* 参数无效 */ + return MROBOT_ERR_NULL_PTR; } - /* 动态分配命令定义结构体 */ - CLI_Command_Definition_t *cmd_def = (CLI_Command_Definition_t *)pvPortMalloc(sizeof(CLI_Command_Definition_t)); + if (ctx.custom_cmd_count >= MROBOT_MAX_CUSTOM_COMMANDS) { + return MROBOT_ERR_FULL; + } + + /* 动态分配命令结构体 */ + CLI_Command_Definition_t *cmd_def = pvPortMalloc(sizeof(CLI_Command_Definition_t)); if (cmd_def == NULL) { - return -1; /* 内存分配失败 */ + return MROBOT_ERR_ALLOC; } - /* 强制转换以移除 const 限制(仅用于初始化) */ + /* 初始化命令定义 */ *(const char **)&cmd_def->pcCommand = command; *(const char **)&cmd_def->pcHelpString = help_text; *(pdCOMMAND_LINE_CALLBACK *)&cmd_def->pxCommandInterpreter = (pdCOMMAND_LINE_CALLBACK)callback; @@ -508,135 +774,87 @@ int8_t MRobot_RegisterCommand(const char *command, const char *help_text, /* 注册到 FreeRTOS CLI */ FreeRTOS_CLIRegisterCommand(cmd_def); - /* 保存指针以便后续可能的清理(虽然一般不需要) */ - custom_commands[custom_command_count] = cmd_def; - custom_command_count++; - return 0; + ctx.custom_cmds[ctx.custom_cmd_count] = cmd_def; + ctx.custom_cmd_count++; + + return MROBOT_OK; +} + +uint8_t MRobot_GetDeviceCount(void) { + return ctx.device_count; +} + +const MRobot_Device_t *MRobot_FindDevice(const char *name) { + if (name == NULL) return NULL; + + for (uint8_t i = 0; i < ctx.device_count; i++) { + if (strcmp(ctx.devices[i].name, name) == 0) { + return &ctx.devices[i]; + } + } + return NULL; +} + +MRobot_Error_t MRobot_Print(const char *str) { + if (str == NULL) return MROBOT_ERR_NULL_PTR; + if (!ctx.initialized) return MROBOT_ERR_NOT_INIT; + + send_string(str); + return MROBOT_OK; +} + +int MRobot_Printf(const char *fmt, ...) { + if (fmt == NULL || !ctx.initialized) return -1; + + char buffer[MROBOT_OUTPUT_BUFFER_SIZE]; + va_list args; + va_start(args, fmt); + int len = vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + if (len > 0) { + send_string(buffer); + } + return len; } void MRobot_Run(void) { - /* 动态 htop 模式 */ - if (htop_mode) { - /* 清屏并重置光标 */ - const char *clear = "\033[2J\033[H"; - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)clear, strlen(clear), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ - - /* 显示 htop 头部 */ - const char *header = - "MRobot Task Monitor (按 'q' 退出)\r\n" - "================================================================================\r\n"; - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)header, strlen(header), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ - - /* 获取任务列表 */ - char task_buffer[1024]; - vTaskList(task_buffer); - - /* 格式化输出 */ - char display_buffer[1536]; - int offset = snprintf(display_buffer, sizeof(display_buffer), - "%-16s %-8s %-4s %-8s %-4s\r\n" - "--------------------------------------------------------------------------------\r\n", - "Task Name", "State", "Prio", "Stack", "Num"); - - /* 解析并美化任务列表 */ - char *line = strtok(task_buffer, "\r\n"); - while (line != NULL && offset < (int)sizeof(display_buffer) - 100) { - char name[17] = {0}; - char state[9] = {0}; - int prio = 0; - int stack = 0; - int num = 0; - - if (sscanf(line, "%16s %c %d %d %d", name, &state[0], &prio, &stack, &num) == 5) { - /* 状态字符转换 */ - const char *state_str = "?"; - switch(state[0]) { - case 'R': state_str = "Running"; break; - case 'B': state_str = "Blocked"; break; - case 'S': state_str = "Suspend"; break; - case 'D': state_str = "Deleted"; break; - default: state_str = "Unknown"; break; - } - - offset += snprintf(display_buffer + offset, sizeof(display_buffer) - offset, - "%-16s %-8s %-4d %-8d %-4d\r\n", - name, state_str, prio, stack, num); - } - line = strtok(NULL, "\r\n"); - } - - /* 添加运行时统计 */ - offset += snprintf(display_buffer + offset, sizeof(display_buffer) - offset, - "--------------------------------------------------------------------------------\r\n" - "System Tick: %lu\r\n", - (unsigned long)xTaskGetTickCount()); - - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)display_buffer, strlen(display_buffer), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ - - /* 检查退出标志 */ - if (htop_exit) { - htop_mode = false; - htop_exit = false; - - /* 清屏 */ - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)clear, strlen(clear), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ - - /* 显示提示符 */ - char prompt[128]; - snprintf(prompt, sizeof(prompt), "root@mrobot:%s$ ", current_path); - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)prompt, strlen(prompt), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ - } - - osDelay(200); /* 每 200ms 刷新一次,5fps */ + if (!ctx.initialized) return; + + /* htop 模式 */ + if (ctx.state == MROBOT_STATE_HTOP) { + handle_htop_mode(); return; } - /* 检查是否有命令需要处理 */ - if (cmd_ready) { + /* 处理命令 */ + if (ctx.cmd_ready) { + ctx.state = MROBOT_STATE_PROCESSING; + /* 检查是否是 htop 命令 */ - if (strcmp((char *)cmd_buffer, "htop") == 0) { - htop_mode = true; - htop_exit = false; + if (strcmp((char *)ctx.cmd_buffer, "htop") == 0) { + ctx.state = MROBOT_STATE_HTOP; + ctx.htop_exit = false; } else { /* 处理其他命令 */ - BaseType_t result; - output_buffer[0] = '\0'; /* 清空输出缓冲区 */ - + BaseType_t more; do { - result = FreeRTOS_CLIProcessCommand((char *)cmd_buffer, - output_buffer, - sizeof(output_buffer)); + ctx.output_buffer[0] = '\0'; + more = FreeRTOS_CLIProcessCommand((char *)ctx.cmd_buffer, + ctx.output_buffer, + sizeof(ctx.output_buffer)); - /* 立即发送输出 */ - if (strlen(output_buffer) > 0) { - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)output_buffer, strlen(output_buffer), true); - while (!tx_complete) { osDelay(1); } - output_buffer[0] = '\0'; /* 清空缓冲区准备下一次 */ + if (ctx.output_buffer[0] != '\0') { + send_string(ctx.output_buffer); } - } while (result != pdFALSE); + } while (more != pdFALSE); - /* 发送提示符 */ - char prompt[128]; - snprintf(prompt, sizeof(prompt), "root@mrobot:%s$ ", current_path); - tx_complete = false; - BSP_UART_Transmit(BSP_UART_VOFA, (uint8_t *)prompt, strlen(prompt), true); - while (!tx_complete) { osDelay(1); } /* 等待发送完成 */ + send_prompt(); + ctx.state = MROBOT_STATE_IDLE; } - /* 重置状态 */ - cmd_index = 0; - cmd_ready = false; + ctx.cmd_index = 0; + ctx.cmd_ready = false; } osDelay(10); diff --git a/User/device/mrobot.h b/User/device/mrobot.h index f8c251e..fba4adb 100644 --- a/User/device/mrobot.h +++ b/User/device/mrobot.h @@ -1,26 +1,14 @@ -/*基于freertos_cli实现的虚拟命令行 -自身作为一个设备存在,通过串口和上位机进行交互,获得类似命令行的体验, -例如输入help可以获得帮助 -输出htop 可以获得freertos任务状态等信息 - -通过cd可以切换目录 -目录结构是 -/ (root) - |-- /dev - |-- bmi088 - |-- chassis_motor1 - |-- /modules - |-- balance_chassis - |-- gimbal - |-- shoot -在dev里可以用show命令查看设备信息 -modules暂无实现 - -至于dev里的设备有哪些,需要通过一个函数MRobot_RegisterDevices(设备名(字符串),结构体指针)来注册设备,我可以在任何一个任务里调用这个函数。 -然后我在我的一个单独的线程里打印和发送他们。 -例如我在atti_esit任务里注册了bmi088和他的结构体,我在cli线程里需要能够打印bmi088的数据。 -然后我在show的时候就可以看到imu的数据了, -*/ +/** + * @file mrobot.h + * @brief MRobot CLI - 基于 FreeRTOS CLI 的嵌入式调试命令行系统 + * + * 功能特性: + * - 设备注册与监控(IMU、电机、传感器等) + * - 类 Unix 文件系统命令(cd, ls, pwd) + * - htop 风格的任务监控 + * - 自定义命令扩展 + * - 线程安全设计 + */ #pragma once @@ -31,110 +19,229 @@ extern "C" { /* Includes ----------------------------------------------------------------- */ #include #include +#include #include "component/ahrs.h" #include "device/device.h" +#include "bsp/uart.h" -/* Exported constants ------------------------------------------------------- */ -#define MROBOT_MAX_DEVICES 32 -#define MROBOT_MAX_CMD_LEN 128 -#define MROBOT_MAX_OUTPUT_LEN 512 -#define MROBOT_RX_BUFFER_SIZE 256 +/* Configuration ------------------------------------------------------------ */ +/* 可在编译时通过 -D 选项覆盖这些默认值 */ -/* Exported types ----------------------------------------------------------- */ +#ifndef MROBOT_MAX_DEVICES +#define MROBOT_MAX_DEVICES 32 /* 最大设备数 */ +#endif + +#ifndef MROBOT_MAX_CUSTOM_COMMANDS +#define MROBOT_MAX_CUSTOM_COMMANDS 16 /* 最大自定义命令数 */ +#endif + +#ifndef MROBOT_CMD_BUFFER_SIZE +#define MROBOT_CMD_BUFFER_SIZE 128 /* 命令缓冲区大小 */ +#endif + +#ifndef MROBOT_OUTPUT_BUFFER_SIZE +#define MROBOT_OUTPUT_BUFFER_SIZE 512 /* 输出缓冲区大小 */ +#endif + +#ifndef MROBOT_DEVICE_NAME_LEN +#define MROBOT_DEVICE_NAME_LEN 32 /* 设备名最大长度 */ +#endif + +#ifndef MROBOT_PATH_MAX_LEN +#define MROBOT_PATH_MAX_LEN 64 /* 路径最大长度 */ +#endif + +#ifndef MROBOT_HTOP_REFRESH_MS +#define MROBOT_HTOP_REFRESH_MS 200 /* htop 刷新间隔 (ms) */ +#endif + +#ifndef MROBOT_UART_PORT +#define MROBOT_UART_PORT BSP_UART_VOFA /* 默认 UART 端口 */ +#endif + +/* Error codes -------------------------------------------------------------- */ typedef enum { - MROBOT_DEVICE_TYPE_IMU, - MROBOT_DEVICE_TYPE_MOTOR, - MROBOT_DEVICE_TYPE_SENSOR, - MROBOT_DEVICE_TYPE_CUSTOM, + MROBOT_OK = 0, /* 成功 */ + MROBOT_ERR_FULL = -1, /* 容量已满 */ + MROBOT_ERR_NULL_PTR = -2, /* 空指针 */ + MROBOT_ERR_INVALID_ARG = -3, /* 无效参数 */ + MROBOT_ERR_NOT_FOUND = -4, /* 未找到 */ + MROBOT_ERR_ALLOC = -5, /* 内存分配失败 */ + MROBOT_ERR_BUSY = -6, /* 设备忙 */ + MROBOT_ERR_NOT_INIT = -7, /* 未初始化 */ +} MRobot_Error_t; + +/* Device types ------------------------------------------------------------- */ +typedef enum { + MROBOT_DEVICE_TYPE_IMU, /* IMU 设备 */ + MROBOT_DEVICE_TYPE_MOTOR, /* 电机设备 */ + MROBOT_DEVICE_TYPE_SENSOR, /* 传感器设备 */ + MROBOT_DEVICE_TYPE_CUSTOM, /* 自定义设备 */ + MROBOT_DEVICE_TYPE_NUM /* 设备类型总数 */ } MRobot_DeviceType_t; -/* 设备打印回调函数类型 */ -typedef void (*MRobot_PrintCallback_t)(void *device_data, char *buffer, uint16_t buffer_size); +/* CLI 运行状态 */ +typedef enum { + MROBOT_STATE_IDLE, /* 空闲状态,等待输入 */ + MROBOT_STATE_HTOP, /* htop 模式 */ + MROBOT_STATE_PROCESSING, /* 正在处理命令 */ +} MRobot_State_t; -/* 命令处理回调函数类型(与 pdCOMMAND_LINE_CALLBACK 相同) */ +/* Callback types ----------------------------------------------------------- */ + +/** + * @brief 设备打印回调函数类型 + * @param device_data 设备数据指针 + * @param buffer 输出缓冲区 + * @param buffer_size 缓冲区大小 + * @return 实际写入的字节数 + */ +typedef int (*MRobot_PrintCallback_t)(const void *device_data, char *buffer, size_t buffer_size); + +/** + * @brief 命令处理回调函数类型(与 FreeRTOS CLI 兼容) + */ typedef long (*MRobot_CommandCallback_t)(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); -/* 注册的设备结构 */ +/* Device structure --------------------------------------------------------- */ typedef struct { - char name[32]; - MRobot_DeviceType_t type; - void *data; /* 指向实际设备数据的指针 */ - MRobot_PrintCallback_t print_callback; /* 打印设备信息的回调函数 */ + char name[MROBOT_DEVICE_NAME_LEN]; /* 设备名称 */ + MRobot_DeviceType_t type; /* 设备类型 */ + void *data; /* 设备数据指针 */ + MRobot_PrintCallback_t print_cb; /* 打印回调函数 */ } MRobot_Device_t; -/* 设备模板结构体 - 使用 AHRS 标准结构 */ +/* 标准设备数据结构 ---------------------------------------------------------- */ + +/** + * @brief IMU 设备数据结构(标准模板) + */ typedef struct { DEVICE_Header_t header; - AHRS_Accl_t accl; /* 加速度 m/s² */ - AHRS_Gyro_t gyro; /* 角速度 rad/s */ - AHRS_Eulr_t euler; /* 欧拉角 rad */ - AHRS_Quaternion_t quat; /* 四元数 */ - float temp; /* 温度 °C */ + AHRS_Accl_t accl; /* 加速度 m/s² */ + AHRS_Gyro_t gyro; /* 角速度 rad/s */ + AHRS_Eulr_t euler; /* 欧拉角 rad */ + AHRS_Quaternion_t quat; /* 四元数 */ + float temp; /* 温度 °C */ } DEVICE_IMU_t; +/** + * @brief 电机反馈数据结构 + */ typedef struct { - float rotor_abs_angle; /* 转子绝对角度 */ - float rotor_speed; /* 实际转子转速 */ - float torque_current; /* 转矩电流 */ - float temp; /* 温度 */ + float rotor_abs_angle; /* 转子绝对角度 */ + float rotor_speed; /* 实际转子转速 */ + float torque_current; /* 转矩电流 */ + float temp; /* 温度 */ } DEVICE_MOTOR_Feedback_t; +/** + * @brief 电机设备数据结构(标准模板) + */ typedef struct { DEVICE_Header_t header; - bool reverse; /* 是否反装 true表示反装 */ + bool reverse; /* 是否反装 */ DEVICE_MOTOR_Feedback_t feedback; } DEVICE_MOTOR_t; -/* Exported functions ------------------------------------------------------- */ +/* Public API --------------------------------------------------------------- */ /** - * @brief 初始化 MRobot 命令行系统(包含 UART 初始化) + * @brief 初始化 MRobot CLI 系统 + * @note 必须在 FreeRTOS 调度器启动后调用 */ void MRobot_Init(void); /** - * @brief 注册设备到 MRobot 系统 - * @param name 设备名称 - * @param type 设备类型 - * @param data 指向设备数据结构的指针 - * @param print_callback 打印设备信息的回调函数 - * @retval 0 成功, -1 失败 + * @brief 反初始化 MRobot CLI 系统,释放资源 */ -int8_t MRobot_RegisterDevice(const char *name, MRobot_DeviceType_t type, - void *data, MRobot_PrintCallback_t print_callback); +void MRobot_DeInit(void); /** - * @brief 注册 IMU 设备到 MRobot(使用通用打印函数) + * @brief 获取当前 CLI 状态 + * @return MRobot_State_t 当前状态 + */ +MRobot_State_t MRobot_GetState(void); + +/** + * @brief 注册设备到 MRobot 系统 + * @param name 设备名称(会被截断到 MROBOT_DEVICE_NAME_LEN-1) + * @param type 设备类型 + * @param data 设备数据指针(不能为 NULL) + * @param print_cb 打印回调函数(可为 NULL,但无法用 show 命令查看) + * @return MRobot_Error_t 错误码 + */ +MRobot_Error_t MRobot_RegisterDevice(const char *name, MRobot_DeviceType_t type, + void *data, MRobot_PrintCallback_t print_cb); + +/** + * @brief 注销设备 + * @param name 设备名称 + * @return MRobot_Error_t 错误码 + */ +MRobot_Error_t MRobot_UnregisterDevice(const char *name); + +/** + * @brief 注册 IMU 设备(使用内置打印函数) * @param name 设备名称 * @param imu_device 指向 DEVICE_IMU_t 结构的指针 - * @retval 0 成功, -1 失败 + * @return MRobot_Error_t 错误码 */ -int8_t MRobot_RegisterIMU(const char *name, void *imu_device); +MRobot_Error_t MRobot_RegisterIMU(const char *name, DEVICE_IMU_t *imu_device); /** - * @brief 注册电机设备到 MRobot(使用通用打印函数) + * @brief 注册电机设备(使用内置打印函数) * @param name 设备名称 - * @param motor 指向 MOTOR_t 结构的指针 - * @retval 0 成功, -1 失败 + * @param motor 指向 MOTOR_t 或兼容结构的指针 + * @return MRobot_Error_t 错误码 */ -int8_t MRobot_RegisterMotor(const char *name, void *motor); +MRobot_Error_t MRobot_RegisterMotor(const char *name, void *motor); /** - * @brief 注册自定义命令到 MRobot CLI - * @param command 命令名称(例如 "test") - * @param help_text 帮助文本(例如 "test: 测试命令\r\n") - * @param callback 命令处理回调函数 - * @param param_count 参数个数(0=无参数,-1=可变参数) - * @retval 0 成功, -1 失败 + * @brief 注册自定义命令 + * @param command 命令名称 + * @param help_text 帮助文本 + * @param callback 命令回调函数 + * @param param_count 参数个数(-1 表示可变参数) + * @return MRobot_Error_t 错误码 */ -int8_t MRobot_RegisterCommand(const char *command, const char *help_text, - MRobot_CommandCallback_t callback, int8_t param_count); +MRobot_Error_t MRobot_RegisterCommand(const char *command, const char *help_text, + MRobot_CommandCallback_t callback, int8_t param_count); /** - * @brief MRobot 主循环,在 CLI 任务中调用 + * @brief 获取已注册设备数量 + * @return 设备数量 + */ +uint8_t MRobot_GetDeviceCount(void); + +/** + * @brief 根据名称查找设备 + * @param name 设备名称 + * @return 设备指针,未找到返回 NULL + */ +const MRobot_Device_t *MRobot_FindDevice(const char *name); + +/** + * @brief MRobot 主循环,在 CLI 任务中周期性调用 + * @note 建议调用周期 10ms */ void MRobot_Run(void); +/** + * @brief 发送字符串到 CLI 终端(线程安全) + * @param str 要发送的字符串 + * @return MRobot_Error_t 错误码 + */ +MRobot_Error_t MRobot_Print(const char *str); + +/** + * @brief 格式化输出到 CLI 终端(线程安全) + * @param fmt 格式字符串 + * @param ... 可变参数 + * @return 实际输出的字符数,失败返回负数 + */ +int MRobot_Printf(const char *fmt, ...); + #ifdef __cplusplus } #endif \ No newline at end of file