仓库:https://github.com/NevermindZZT/letter-shell
前言 在之前的文章中,我介绍了 Letter Shell 的基本使用方法;在实际使用过程中,我发现 Letter Shell 存在一些小问题,也结合项目需求对其功能进行了扩展;本文将介绍这些问题的现象与修复方法,并分享新增功能的实现过程和实际用途;
问题修复(部分修复) 现象:在 Letter Shell 中,串口输入时长按退格键或 Delete 键时,终端显示存在字符残留,虽然字符已无法继续删除,但视觉上仍未完全清除;
这背后的常见原因: 终端行为:
串口终端(如 SecureCRT、TeraTerm、MobaXterm 等)长按退格时会快速发出多个 0x08 字节; 这些字符本质是 “光标后退一格”,但 Letter Shell 要负责同步显示以及缓存处理; Letter Shell 内部处理问题:
Letter Shell 会维护一个输入缓存; 如果没有正确处理 “删到头” 的情况,可能会出现:显示没擦干净(字符残留); 缓存指针已到头,仍在处理退格; 没有回显正确的光标移动和空格清除字符; 修复方法要点 判断输入缓存是否为空:在 shellHandler() 中处理退格符(0x08)时,判断光标是否已在开头,防止“负向删除”(即光标越界到输入行左边界之外);
对于连续退格,要保证:
Shell 缓冲区更新正确; 串口输出内容与终端光标同步; 不要超出缓存边界; 修改后的完整代码 此函数整体思路是:先调整缓存,再清空并重绘整行命令,最后将光标还原至正确位置;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 void shellDeleteByte (Shell *shell, signed char direction) { char offset = (direction == -1 ) ? 1 : 0 ; if ((shell->parser.cursor == 0 && direction == 1 ) || (shell->parser.cursor == shell->parser.length && direction == -1 )) { return ; } if (shell->parser.cursor == shell->parser.length && direction == 1 ) { shell->parser.cursor--; shell->parser.length--; shell->parser.buffer[shell->parser.length] = 0 ; shellDeleteCommandLine(shell, 1 ); } else { for (short i = offset; i < shell->parser.length - shell->parser.cursor; i++) { shell->parser.buffer[shell->parser.cursor + i - 1 ] = shell->parser.buffer[shell->parser.cursor + i]; } shell->parser.length--; if (!offset) { shell->parser.cursor--; shellWriteByte(shell, '\b' ); } shell->parser.buffer[shell->parser.length] = 0 ; for (short i = 0 ; i < shell->parser.cursor + 1 ; i++) { shellWriteByte(shell, '\b' ); } for (short i = 0 ; i < shell->parser.length; i++) { shellWriteByte(shell, shell->parser.buffer[i]); } shellWriteByte(shell, ' ' ); for (short i = shell->parser.length; i > shell->parser.cursor; i--) { shellWriteByte(shell, '\b' ); } } }
该函数通过整体刷新命令行缓冲区,并手动控制光标移动,确保删除行为与终端显示保持一致,尤其适用于字符中间删除的情形; 不过在实际使用中,仍会在某些输入场景下出现字符未被正确清除的情况,说明该方案不够完全可靠;如果你对此有更优的实现思路,欢迎联系我交流(联系方式已在主页提供);
功能扩展 在 Linux Shell 中,如果你在终端中键入了一长串命令,但突然不想执行,也不想一个一个退格删除,只需要轻轻按下 Ctrl+C,整行输入就会被取消,重新回到提示符;这种“强制销毁当前输入”的行为,对嵌入式调试来说也非常实用; Letter Shell 默认的 Ctrl+C 实现主要用于中断长时间运行的命令,但并不具备清除当前行的效果;接下来,我们就来实现这一功能;
实现思路 使用 SHELL_EXPORT_KEY 注册一个 Ctrl+C 快捷键; 在回调函数中:将 is_CtrlC_Pressed 标记设为 1(供其他任务判断是否中断); 重新输出提示符(myshellWritePrompt); 清空当前行缓存(myshellExec 直接执行空行); 由于 Letter Shell 的内部函数是 static 的,我们自行实现 myshellWritePrompt 和 myshellExec 两个版本; Ctrl+C 注册代码(port 文件中) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 uint8_t is_CtrlC_Pressed = 0 ;static void CtrlC_Interrupt (Shell* shell) { 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);
添加 myshellWritePrompt 与 myshellExec 函数(shell.c) 由于原函数是 static,我们避免直接修改 Letter Shell 源码,而是新建两个函数:
myshellWritePrompt:打印提示符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void myshellWritePrompt (Shell *shell, unsigned char newline) { if (shell->status.isChecked) { if (newline) { shellWriteString(shell, "\r\n" ); } shellWriteString(shell, shell->info.user->data.user.name); shellWriteString(shell, ":" ); shellWriteString(shell, shell->info.path ? shell->info.path : "/" ); shellWriteString(shell, "$ " ); } else { shellWriteString(shell, shellText[SHELL_TEXT_PASSWORD_HINT]); } }
myshellExec:清空当前命令缓冲区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void myshellExec (Shell *shell) { if (shell->parser.length == 0 ) { return ; } shell->parser.buffer[shell->parser.length] = 0 ; if (shell->status.isChecked) { shellParserParam(shell); shell->parser.length = shell->parser.cursor = 0 ; if (shell->parser.paramCount == 0 ) { return ; } } else { shellCheckPassword(shell); } }
注意 :该功能依赖串口工具正确发送 0x03(Ctrl+C)字符,建议使用支持该行为的终端,如 MobaXterm、TeraTerm 等;
扩展建议:基于 is_CtrlC_Pressed 的中断任务设计 通过 is_CtrlC_Pressed 标志,用户可以在长时间运行的命令中主动检查该变量,实现软中断功能;例如:
1 2 3 4 5 6 while (running) { if (is_CtrlC_Pressed) { break ; } do_some_heavy_task(); }
注意 :此中断机制适用于循环任务或长延时任务的主动中断,不适用于系统层级的任务终止;
总结 本文基于 Letter Shell 在实际嵌入式项目中的使用经验,修复了输入过程中因退格处理不完整导致的字符残留问题,并新增 Ctrl+C 清除当前输入行的功能,提升了交互体验与调试效率;
在功能扩展的同时,尽量保持对原始源码的非侵入性,便于后续升级与维护;Letter Shell 作为一款轻量级命令行工具,其灵活的可扩展性值得深入挖掘;
如果你也在使用它,欢迎交流更多改进思路;