letter shell是一个轻量级的命令行交互工具,特别适合在资源受限的嵌入式系统中使用。它提供了类似Linux shell的交互体验,允许开发者通过串口终端与MCU进行交互。在STM32裸机环境下移植letter shell,可以为开发调试提供极大便利。
我最近在一个物联网终端设备项目中使用了letter shell,发现它相比传统的串口调试方式有几个显著优势:首先,它支持命令自动补全和命令历史记录;其次,可以方便地扩展自定义命令;最重要的是,它占用的资源非常少,在我的STM32F103C8T6(仅有64KB Flash和20KB RAM)上运行良好。
对于STM32F1系列MCU,你需要确保:
letter shell的最新源码可以从GitHub获取:
code复制git clone https://github.com/NevermindZZT/letter-shell
或者直接下载zip包。源码目录结构如下:
code复制letter-shell/
├── docs/ # 文档
├── examples/ # 示例代码
└── src/ # 核心源码
├── shell.c # shell核心实现
├── shell.h # 头文件
├── shell_cfg.h # 配置文件
└── ...
提示:建议使用最新发布的稳定版本,避免使用开发中的分支版本,以减少潜在的兼容性问题。
在你的STM32工程中创建一个新目录(如Middlewares/letter_shell),然后将src/目录下的所有文件复制到该目录中。在Keil MDK中添加这些源文件时,需要注意:
.c文件添加到工程的Source Group中Include Paths中添加头文件路径由于是裸机移植,需要在shell_cfg.h中关闭任务循环模式:
c复制#define SHELL_TASK_WHILE 0
在shell_ext.h中添加标准库头文件:
c复制#include <stddef.h>
根据你的硬件连接,修改串口发送函数。例如使用USART1:
c复制/**
* @brief 用户自定义shell写函数
*/
void userShellWrite(char *data, unsigned short len) {
HAL_UART_Transmit(&huart1, (uint8_t *)data, len, HAL_MAX_DELAY);
}
然后在shell_cfg.h中配置写函数:
c复制#define SHELL_WRITE_FUNC userShellWrite
letter shell需要定期处理输入,在裸机环境下可以通过串口中断实现:
c复制HAL_UART_Receive_IT(&huart1, &rx_data, 1);
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
shellHandler(&shell, rx_data);
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
使用CH340等USB转串口芯片时,可能会遇到终端无响应的问题。这是因为Windows默认启用了Modem流控制。解决方法:
letter shell支持两种命令定义方式:
c复制int myCommand(int argc, char *argv[]) {
// 命令实现
return 0;
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN),
myCommand, cmd_name, command description);
c复制void keyHandler(void) {
// 按键处理
}
SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 'a', keyHandler, key description);
letter shell可以与日志系统协同工作,互不干扰。集成步骤:
log.c和log.h添加到工程c复制void userLogWrite(char *data, unsigned short len) {
HAL_UART_Transmit(&huart1, (uint8_t *)data, len, HAL_MAX_DELAY);
}
log_cfg.h中配置:c复制#define LOG_OUTPUT_FUNC userLogWrite
#define LOG_ASYNC_OUTPUT 1
在资源受限的MCU上,可以调整以下参数优化内存使用:
c复制#define SHELL_HISTORY_MAX_NUM 5 // 历史命令记录数
#define SHELL_DOUBLE_CLICK_TIME 200 // 双击间隔(ms)
#define SHELL_CMD_MAX_LENGTH 32 // 命令最大长度
#define SHELL_ARG_NUM_MAX 8 // 最大参数数量
letter shell支持简单的权限管理:
c复制#define SHELL_GET_USER_PERMISSION() getUserPermission()
然后在命令导出时指定权限:
c复制SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(1)|..., ...);
可以修改提示符样式:
c复制#define SHELL_DEFAULT_PROMPT "STM32> "
或者动态设置:
c复制shellSetPrompt(&shell, "CustomPrompt> ");
在我的一个环境监测项目中,letter shell被用于:
c复制int readTemp(int argc, char *argv[]) {
float temp = readTemperature();
printf("Current temperature: %.1fC\n", temp);
return 0;
}
SHELL_EXPORT_CMD(..., readTemp, temp, read temperature);
c复制int setInterval(int argc, char *argv[]) {
if(argc != 2) {
printf("Usage: interval <seconds>\n");
return -1;
}
g_interval = atoi(argv[1]);
return 0;
}
SHELL_EXPORT_CMD(..., setInterval, interval, set sampling interval);
c复制int reboot(int argc, char *argv[]) {
NVIC_SystemReset();
return 0;
}
SHELL_EXPORT_CMD(..., reboot, reboot, reboot system);
shell_cfg.h中减小缓冲区大小c复制#define SHELL_DEBUG 1
c复制shellInit(); // 必须在所有外设初始化完成后调用
命令无法识别:
输入字符丢失:
系统响应缓慢:
实现自定义参数补全函数:
c复制void complete(char *prefix, char *match[], unsigned short *num) {
// 实现补全逻辑
}
SHELL_EXPORT_CMD(..., complete, ..., ...);
结合FatFS等文件系统实现文件操作命令:
c复制int ls(int argc, char *argv[]) {
DIR dir;
FILINFO fno;
// 实现ls命令
}
SHELL_EXPORT_CMD(..., ls, ls, list files);
通过TCP/IP协议栈实现远程shell访问:
c复制void tcpShellHandler(void) {
char data;
while(tcpReceive(&data, 1)) {
shellHandler(&shell, data);
}
}
虽然本文以STM32为例,但letter shell可以轻松移植到其他平台:
SHELL_TASK_WHILE并创建专用任务移植关键点: