电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
MCU调试大法:使用串口实现简单shell功能
分 享
扫描二维码分享
MCU调试大法:使用串口实现简单shell功能
MCU调试
串口
shell
freeze_chen
关注
发布时间: 2019-11-05
丨
阅读: 2277
# MCU调试大法:使用串口实现简单shell功能 ## 常用的调试方法 MCU程序调试方法有很多,比如软/硬件仿真、添加数据打印等。 像Keil MDK就支持不少单片机的软件仿真,在没有拿到单片机的情况下,就可以先仿真调试部分功能,查看代码逻辑是否正确。硬件仿真则需要借助仿真器,如调试Cortex内核MCU常用的J-Link/ST-Link等。通过watch窗口可以查看变量的值: ![watch窗口查看变量](https://cf05.ickimg.com/bbsimages/201911/cba2228932fbffb660f3a989348f42e1.png "watch窗口查看变量") 在代码中添加数据的打印,则需要借助MCU的串口功能,将运行时的关键数据通过串口打印至PC,便于观察。这是我调试时非常喜欢使用的一个功能,因为需要打印哪些数据完全自主可控,而且可以做到基本不影响程序正常运行。 这里顺便把如何使用printf的方法讲一下,比较简单,会的同学可以直接略过: ``` /@@* 头文件不能少 */ #include
/@@* 平台的选择 */ #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif /@@* __GNUC__ */ PUTCHAR_PROTOTYPE { /@@* 这里只需要实现一个字符ch的发送即可,以下以ST为例 */ HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); return ch; } ``` ## 可交互的调试方法--shell 有了串口数据打印,寻找BUG方便了不少;但是随着使用场景的增多:比如我需要在某个时刻打印某些数据、需要控制程序进入某个分支、调试算法时需要经常修改某些变量的值。此时光有打印就不行了,我需要一个可以实时和MCU进行交互的系统,那就是shell。 这里介绍一个体积极小的嵌入式shell,功能如下: * 命令自动补全,使用tab键补全命令 * 命令长帮助,使用help [command]显示命令长帮助 * 长帮助补全,输入命令后双击tab键补全命令长帮助指令 * 快捷键,支持使用Ctrl + A~Z组合按键直接调用函数 * shell变量,支持在shell中查看和修改变量值,支持变量作为命令参数 ![shell界面](https://cf05.ickimg.com/bbsimages/201911/d0ee3ff4d3a908576eb8d8997be64ee7.png "shell界面") ## 开始移植 #### 1. 下载源码并添加至工程中:[戳这里下载](https://github.com/NevermindZZT/letter-shell "戳这里") ![shell文件](https://cf05.ickimg.com/bbsimages/201911/419dbb39e4390b0971023622ae6e496b.png "shell文件") 算上h文件,也就5个。 #### 2. 初始化shell 定义shell全局实体: ``` SHELL_TypeDef shell; ``` 在main中初始化,这里需要提供write函数,即字符发送函数: ``` /@@* 初始化shell */ shell.read = NULL; //采用中断方式,所以不需要提供read方法 shell.write = user_shellWrite; shellInit(&shell); /@@* shell write定义 */ void user_shellWrite(const char ch) { /@@* 实现一个字符ch的发送功能,使用阻塞方式发送 */ HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000); } ``` #### 3. shell调用 在串口接收中断中,调用shellHandler处理函数: ``` void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&huart1); //将收到的字符实时送入shell处理 shellHandler(&shell, uart1_it_buf); HAL_UART_Receive_IT(&huart1, (uint8_t *)&uart1_it_buf, 1); } ``` #### 4. 配置 将SHELL_DISPLAY_RETURN关闭,不然会打印shell函数返回值;SHELL_USING_CMD_EXPORT则根据个人喜好来,我这里将其关闭,所以以下将使用命令表来添加命令: ``` /@@** * @brief 是否显示命令调用函数返回值 * 使能此宏,则每次调用shell命令之后会以整形和十六进制的方式打印函数的返回值 */ #define SHELL_DISPLAY_RETURN 0 /@@** * @brief 是否使用命令导出方式 * 使能此宏后,可以使用`SHELL_EXPORT_CMD()`或者`SHELL_EXPORT_CMD_EX()` * 定义shell命令,关闭此宏的情况下,需要使用命令表的方式 */ #define SHELL_USING_CMD_EXPORT 0 ``` #### 5. 添加命令 因为定义了SHELL_USING_CMD_EXPORT为0,所以我们使用命令表来添加命令。在shell.c中,这里我们可以看到默认实现了两个命令help和cls: ``` const SHELL_CommandTypeDef shellDefaultCommandList[] = { SHELL_CMD_ITEM_EX(help, shellHelp, command help, help [command] --show help info of command), SHELL_CMD_ITEM(cls, shellClear, clear command line), /@@* 在这里按照格式添加自己的命令,如显示版本 */ SHELL_CMD_ITEM(version, shell_showVersion, show current version), }; ``` shell_showVersion的实现,可以在其他C文件中实现 ``` /@@** * @brief shell显示当前软件版本 * */ void shell_showVersion(void) { SHELL_TypeDef *shell = shellGetCurrent(); if (!shell) { return; } shellDisplay(shell, "\r\n V1.0.0\r\n"); shellDisplay(shell, "\r\n Build: "__DATE__" "__TIME__"\r\n"); return; } ``` #### 6. run 实际效果如下: ![run_shell](https://cf05.ickimg.com/bbsimages/201911/8fe2bde8bcbe1482de555735f4a13b4c.png "run_shell") 按TAB可以显示所有命令,在输入命令时按TAB还可以自动补全。 ## 其他 这里只是完成了最基础的移植工作,还有一些高级的功能就等着大家自行摸索啦。 有了shell调试起来肯定如虎添翼,呼呼哈哈! 还有一点,给客户演示demo的时候,逼格也高了很多,哈哈哈!
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
2
)
freeze_chen
关注
评论
(1)
登录后可评论,请
登录
或
注册
shuimogezi
30
天前...
好东西,哈哈
0
回复
发布
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交