当前位置: 首页 > news >正文

Letter Shell:自定义命令 - EM

仓库:https://github.com/NevermindZZT/letter-shell

前言

在完成基本交互功能和修复了一些常见问题之后,Letter Shell 已经能够稳定运行于我的嵌入式项目中;然而,项目开发中常常需要更多“定制化”的功能,比如:

  • 注册自己的命令,快速调试;
  • 添加快捷键,提高开发效率;
  • 让 Shell 能调用带多个复杂参数的函数(支持自动解析参数),避免为每个测试场景重复编写包装函数;

好消息是,Letter Shell 本身就提供了完善的接口支持这些扩展;坏消息是,官方文档对这些内容略显简略,实际用起来还需要一点“摸索”;

这些扩展并非仅是“锦上添花”,而是将 Shell 从一个输入工具,变成一个真正实用的交互调试平台的关键;

Letter Shell 中,所有能被执行的“功能”:无论是你写的函数、绑定的快捷键、设置的变量,甚至是用户本身,在底层其实都被统一视为“命令项(Command)”;这些命令共享一套注册、查找与执行机制,使得 Shell 的扩展非常灵活;

不过在实际使用中,我们往往会将“自定义命令”专指 将 C 函数导出为 Shell 命令的行为,这是调试开发中最常用、最直接、最强大的手段之一;通过简单的宏定义,就可以将任意函数变成终端可调用的命令,极大提升调试效率和系统可观测性;

写在前面

Letter Shell 针对常用编译器都提供了宏支持,而为了保证导出的命令不会被优化掉,不同编译器还有一些额外的配置技巧:

  • Keil MDK:建议在编译选项中添加 --keep shellCommand*,防止命令结构被链接器裁剪;
  • GCC:需要在链接脚本(ld 文件)中显式保留命令段:
_shell_command_start = .;
KEEP (*(shellCommand))
_shell_command_end = .;

函数命令导出

函数导出是最常见的命令注册方式,适用于调试、信息打印、功能调用等场景;

宏定义原型:

/*** @brief shell 命令定义** @param _attr 命令属性* @param _name 命令名* @param _func 命令函数* @param _desc 命令描述*/
#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc) \const char shellCmd##_name[] = #_name; \const char shellDesc##_name[] = #_desc; \SHELL_USED const ShellCommand \shellCommand##_name SHELL_SECTION("shellCommand") =  \{ \.attr.value = _attr, \.data.cmd.name = shellCmd##_name, \.data.cmd.function = (int (*)())_func, \.data.cmd.desc = shellDesc##_name \}

这行代码中将 _func 强转为 (int (*)()) 是为了兼容不同的函数签名(不管参数多少),但这样做会掩盖类型安全,有些 IDE 可能会报警告;

这种强制类型转换方式牺牲了类型检查,适合用于调试环境,但如果对类型安全有更严格要求,也可以配合包装函数使用;

使用示例:

void myShellTask(void)
{Shell* shell = shellGetCurrent();if(shell){shellWriteString(shell, "Shell自定义程序测试");}
}SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0) |SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC) |SHELL_CMD_DISABLE_RETURN,mytest, myShellTask, print message);

使用 mytest 命令后,将输出 "Shell自定义程序测试";这个机制可以非常方便地快速验证某个函数逻辑;

变量导出

除了函数,也可以将变量导出到 Shell 中进行读写;支持类型包括:char、short、int、字符串、指针等;

宏定义原型:

/*** @brief shell 变量定义** @param _attr 变量属性* @param _name 变量名* @param _value 变量值(地址)* @param _desc 变量描述*/
#define SHELL_EXPORT_VAR(_attr, _name, _value, _desc) \const char shellCmd##_name[] = #_name; \const char shellDesc##_name[] = #_desc; \SHELL_USED const ShellCommand \shellVar##_name SHELL_SECTION("shellCommand") =  \{ \.attr.value = _attr, \.data.var.name = shellCmd##_name, \.data.var.value = (void *)_value, \.data.var.desc = shellDesc##_name \}

注意:_value 必须是变量的地址,除非是字符串(数组本身即指针);如果变量是只读的,可以加上 SHELL_CMD_READ_ONLY 属性;

使用示例:

int varInt = 0;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_INT), varInt, &varInt, test);char str[] = "test string";
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_STRING), varStr, str, test);Log log;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_POINT), log, &log, test);

上述变量在终端中直接输入变量名(如 varInt),即可显示或修改值(除非是只读变量);

按键定义导出

除了函数和变量,也可以绑定终端中的某些按键(如 Tab、Ctrl+C)来触发特定功能;

宏定义原型:

/*** @brief shell 按键定义** @param _attr 按键属性* @param _value 按键键值(大端序)* @param _func 按键函数* @param _desc 描述信息*/
#define SHELL_EXPORT_KEY(_attr, _value, _func, _desc) \const char shellDesc##_value[] = #_desc; \SHELL_USED const ShellCommand \shellKey##_value SHELL_SECTION("shellCommand") =  \{ \.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_KEY), \.data.key.value = _value, \.data.key.function = (void (*)(Shell *))_func, \.data.key.desc = shellDesc##_value \}

按键值说明,按键值为按键发送的字节序列,按 大端模式 表示;例如:

  • Tab 键发送 0x0B:键值为 0x0B000000
  • 方向上箭头发送序列 0x1B 0x5B 0x41:键值为 0x1B5B4100

使用示例(绑定 Ctrl+C):

uint8_t is_CtrlC_Pressed = 0;static void CtrlC_Interrupt(Shell* shell)
{// 标记 Ctrl+C 被按下,用于其他程序中判断is_CtrlC_Pressed = 1;// 下面两行是标准行为:换行 + 触发下一轮命令解析extern void myshellWritePrompt(Shell *shell, unsigned char newline);myshellWritePrompt(shell, 1);extern void myshellExec(Shell *shell);myshellExec(shell);
}SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x03000000, CtrlC_Interrupt, Ctrl_C);

这样在终端中按下 Ctrl+C 时,即可自动执行 CtrlC_Interrupt() 中定义的行为;

提示:可以使用串口调试工具(如 SecureCRT)开启“十六进制查看模式”,按下目标键并记录其字节序列,以获取精确键值;

总结

Letter Shell 的命令导出机制设计得非常灵活统一,使用 .shellCommand 段 + 宏展开的方式,让用户可以零成本地将已有的函数、变量或按键操作暴露到终端;这一机制不仅极大提高了调试效率,也为后期维护和动态控制提供了便利;

只需根据不同平台配置保留 .shellCommand 段(如通过 --keep shellCommand* 或 KEEP(*(.shellCommand))),即可确保这些命令在最终镜像中生效;

阅读原文:Letter Shell:自定义命令

http://www.jsqmd.com/news/752325/

相关文章:

  • Diablo Edit2:免费开源暗黑破坏神2存档修改器终极指南
  • 为Hermes Agent配置自定义Provider并指向Taotoken服务端点
  • 别再只用Mosaic了!YOLOv8数据增强实战:从CutMix到MixUp的完整对比与代码实现
  • Detect It Easy完整指南:从快速安装到高级文件分析技巧
  • 别再只用LSTM了!用Keras/TensorFlow手把手搭建TCN时序预测模型(附完整代码)
  • 【YaShanDB】给YaShanDB开发R2DBC驱动
  • 别再只用ref了!Vue3 script setup中,用defineExpose优雅控制子组件权限
  • 抖音视频怎么保存到相册?抖音视频保存到相册的方法全攻略(2026最新实测) - 爱上科技热点
  • Letter Shell:问题修复与功能扩展 - EM
  • amlogic-s9xxx-armbian项目深度解析:全志H6机顶盒网络适配终极指南
  • 终极指南:3分钟掌握网易云音乐NCM文件解密转换
  • Windows系统优化神器:WinUtil如何用5分钟重塑你的电脑体验?
  • 利用taotoken实现aigc内容创作平台的模型降本与调度
  • 抖音不能下载的视频怎么保存到相册?最新方法攻略 - 爱上科技热点
  • 如何3步突破AI编辑器限制:跨平台智能标识重置完整指南
  • 2026 年 5 月兰州宝宝照 / 百天照评测,四大靠谱门店排行推荐 - 生活测评君
  • 从游戏道具到建筑外墙:3ds Max多维子材质(Multi/Sub-object)实战应用拆解,附避坑指南
  • 做针头检测仪的朋友,这款屏适配性拉满✨ - 浴缸里的巡洋舰
  • 【含五月份最新安装包】OpenClaw 2.6.6 飞书接入|机器人配置全流程
  • 2026届学术党必备的十大降AI率助手推荐
  • KMS智能激活工具:彻底告别Windows和Office激活烦恼的终极解决方案
  • 在 Taotoken 模型广场中根据任务与预算进行模型选型的直观体验
  • 【限时首发】.NET 9容器配置安全白皮书:3类高危配置泄露路径+OWASP Top 10容器适配方案
  • Atombot:500行代码构建个人AI助手,模块化设计实现本地化智能
  • 告别拥堵!用MINCO算法为无人机集群规划“空中立交桥”(附避障实战代码)
  • 一年做座舱,我才搞懂这10件事(全是血泪)
  • fre:ac音频转换器终极指南:免费开源的多功能音频处理解决方案
  • 如何用Battery Toolkit彻底解决MacBook电池焦虑:Apple Silicon用户的终极指南
  • 2026年3月QJ型水泵厂商推荐,热水泵/高压潜水泵/天津水泵/300QJ型水泵/矿用高压泵,QJ型水泵公司哪家强 - 品牌推荐师
  • 魔兽争霸3终极兼容解决方案:WarcraftHelper完整配置指南