1. 环境准备与工程创建

在开始移植之前,我们需要准备好开发环境。我使用的是STM32CubeMX和Keil MDK-ARM,这两个工具是STM32开发的黄金搭档。STM32CubeMX可以图形化配置芯片引脚和中间件,大大简化了初始化工作。

首先打开STM32CubeMX,选择STM32F103C8T6芯片(这是最常用的F1系列芯片)。在Pinout界面中,我们需要配置几个关键部分:

  • RCC:选择外部高速晶振,这样系统时钟更稳定
  • SYS:调试接口选择SW,因为我们要用ST-LINK下载调试
  • USART1:选择异步模式,波特率保持115200,开启全局中断

配置FreeRTOS时有个重要细节:系统滴答时钟默认使用SysTick,但HAL库也需要用SysTick,这会产生冲突。我的做法是切换到TIM1作为时基,这样就不会有警告了。

时钟树配置也很关键。STM32F103的最高主频是72MHz,我们需要通过PLL倍频达到这个频率。具体设置是:外部8MHz晶振,经过PLL倍频9倍,得到72MHz系统时钟。

最后设置工程目录时,我习惯单独创建一个"Middlewares"文件夹存放第三方组件,这样项目结构更清晰。生成代码时选择MDK-ARM V5,虽然我用VSCode写代码,但编译还是用Keil更稳定。

提示:记得在工程设置中勾选"Use MicroLIB",这样串口打印才能正常工作。

2. letter-shell移植详解

letter-shell是一个轻量级嵌入式Shell工具,特别适合资源有限的STM32芯片。我选择3.12版本是因为它稳定且功能完善。

首先从GitHub下载源码,将src目录下的所有文件复制到工程中。重点要修改的是shell_port.c文件,这里需要实现Shell的读写接口。

// shell_port.c
short userShellWrite(char *data, unsigned short len) {
    return HAL_UART_Transmit(&huart1, (uint8_t *)data, len, 100);
}

写函数比较简单,直接调用HAL库的串口发送函数。读函数就比较讲究了,我建议使用中断方式接收数据,而不是阻塞读取。阻塞读取在RTOS中容易导致任务卡死。

配置shell_cfg.h时需要根据实际需求调整参数。我通常启用这些功能:

#define SHELL_TASK_WHILE 1           // 使用任务循环
#define SHELL_USING_CMD_EXPORT 1     // 使用命令导出
#define SHELL_ENTER_CR 1             // 支持回车键
#define SHELL_ENTER_LF 1             // 支持换行键
#define SHELL_PRINT_BUFFER 128       // 输出缓冲区大小

在FreeRTOS中创建Shell任务时,我给了256字节的栈空间,优先级设为普通。关键是要在串口中断回调函数中处理数据接收:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if(huart->Instance == USART1) {
        shellHandler(&shell, received_data);
        HAL_UART_Receive_IT(&huart1, &received_data, 1);
    }
}

这样每收到一个字节就会触发中断,交给letter-shell处理。实测下来很稳定,不会丢失数据。

3. FreeRTOS集成与任务调度

在FreeRTOS环境中集成letter-shell需要注意任务调度问题。我创建了两个任务:一个Shell任务,一个应用任务。

Shell任务负责处理用户输入,应用任务用来测试其他功能。两个任务之间通过消息队列通信,这样不会互相阻塞。

// FreeRTOS配置
osThreadDef(myshell, shellTask, osPriorityNormal, 0, 256);
myshellHandle = osThreadCreate(osThread(myshell), NULL);

这里有个坑要注意:CubeMX生成的代码中,任务函数默认有const修饰符,需要去掉才能正常编译。

Shell任务函数需要适当加入延时,否则会占用太多CPU资源:

void shellTask(void const * argument) {
    for(;;) {
        osDelay(10);  // 每10ms检查一次输入
    }
}

在实际项目中,我还会给Shell任务设置看门狗,防止卡死。如果Shell任务5秒内没有运行,就会触发复位,这个机制在生产环境中很实用。

4. 调试技巧与常见问题解决

移植过程中最容易遇到的问题是串口无法输入。我踩过这个坑,原因是HAL库的返回值判断错误。

letter-shell默认判断读取函数返回值是否为1,但HAL_UART_Receive返回的是HAL_StatusTypeDef枚举值。正确的判断应该是:

if (shell->read && shell->read(&data, 1) == HAL_OK) {
    shellHandler(shell, data);
}

另一个常见问题是输出乱码。这通常是时钟配置错误导致的。确保系统时钟、串口时钟都配置正确,特别是APB2总线时钟要是72MHz。

我用逻辑分析仪抓过波形,发现当系统时钟偏差时,波特率也会不准。建议用示波器测量实际波特率,确保与终端软件设置一致。

内存不足也是常见问题。letter-shell需要512字节缓冲区,FreeRTOS任务也需要栈空间。如果出现HardFault,可以先检查栈空间是否足够。

注意:调试时先确保纯串口收发正常,再集成letter-shell,这样可以排除硬件问题。

5. log模块集成与使用

log模块是letter-shell的扩展功能,可以提供分级日志输出。我将log.c和log.h添加到工程中,然后在shell_port.c中初始化。

Log uartLog = {
    .write = uartLogWrite,
    .active = LOG_ENABLE,
    .level = LOG_DEBUG
};

void userShellInit(void) {
    shell.write = userShellWrite;
    shellInit(&shell, shellBuffer, 512);
    logRegister(&uartLog, &shell);
}

log支持多种级别:DEBUG、INFO、WARN、ERROR等。在实际项目中,我通常在生产环境设置LOG_LEVEL为INFO,开发环境设置为DEBUG。

使用log非常简单:

logDebug("传感器读数: %d", sensor_value);
logError("系统初始化失败");

log还支持十六进制数据导出:

uint8_t data[4] = {0x01, 0x02, 0x03, 0x04};
logHexDumpAll(data, 4);

这个功能在调试通信协议时特别有用,可以直观查看原始数据。

6. 实际应用与性能优化

在实际项目中,letter-shell帮我节省了大量调试时间。我可以用命令行实时查看系统状态、修改变量值、调用测试函数。

比如我经常用这些命令:

task status    # 查看任务运行状态
mem info       # 查看内存使用情况
log level 3    # 设置日志级别

性能方面,我做了这些优化:

  1. 减小打印缓冲区到128字节,足够一般使用
  2. 关闭历史记录功能节省内存
  3. 使用短命令名减少输入时间
  4. 将常用命令编译进固件,不常用命令通过文件系统加载

资源占用情况:整个letter-shell加上log模块,占用约3KB Flash和1KB RAM,在STM32F103上完全可接受。

稳定性方面,连续运行72小时压力测试没有出现丢数据或卡死现象。我做了这些测试:

  • 长时间高速输入命令
  • 同时进行文件传输和命令操作
  • 在低电压环境下测试
  • 高温环境下运行

7. 高级功能与扩展应用

letter-shell还有一些高级功能很实用。比如命令自动补全,按Tab键可以提示可用命令。还有参数解析功能,支持字符串、数字等多种参数类型。

我扩展了一个文件系统支持,可以通过命令行读写SD卡文件:

cat log.txt      # 查看日志文件
rm old_file.bin  # 删除文件
ls /             # 列出根目录

另一个实用功能是执行脚本。我可以把常用命令序列保存成脚本,一次性执行:

# debug.txt
task status
mem info
log level 4

然后通过命令执行:exec debug.txt

对于远程调试,我增加了TCP支持。通过ESP8266模块,可以用Telnet连接设备,实现无线调试。这个功能在现场调试时特别方便,不用插串口线了。

最后,我建议增加权限管理功能。不同用户有不同的操作权限,生产环境只开放基本命令,开发环境开放全部命令。这样既方便又安全。

letter-shell的模块化设计让这些扩展都很容易实现。我在实际项目中根据需求灵活选择功能,既满足需求又不浪费资源。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐