本帖最后由 yang377156216 于 2022-5-24 19:10 编辑
#申请原创# @21小跑堂
整体概览如今,对于做 Linux 开发的研发人员来说,大家都喜欢通过输入指令符来执行一些命令操作,特别享受在黑乎乎的 cmd 窗口中敲击命令然后得到 echo 的感觉;而对于一些极客,他们乐忠于使用树莓派或者其它开源硬件加上 MicroPython 作为脚本开发语言去完成自己的 DIY 创作,很大程度也是因为有交互式解释器(又称 REPL)的存在,该环境能够让调试代码变得很轻松;而对于 MCU 的程序开发来说,除非会使用一些高级的手段(在 RAM 中运行代码),否则每次修改程序调试就需要重新下载,有时候改动点仅仅是几个全局变量参数,这样反复修改程序、重新编译下载就显得很浪费时间,并且在调试电机、电源等高压电源类应用时,如果出现操作错误,还有可能会造成炸机、毁坏电脑等危险。另外,在处理多个 AT 指令的时候,是不是也想有个优雅的框架能够完成此任务?此时是不是会想,如果有一个类似 Linux 的命令行操作环境,可以让开发者通过串口调试助手输入命令然后运行一些调试函数,这样将会极大方便开发进程?那是否需要自己去开发一套这样的环境呢?由于 MCU 存储资源和运算速度等限制,需要做很多处理优化去实现这么个 shell 功能,好在目前其实已经有很多类似的嵌入式 shell 代码框架可以直接拿来用了,只需要掌握如何移植以及如何添加自定义命令即可。为了响应本版一些工程师的呼吁,本次将介绍如何在 MM32 MCU 上使用 shell 框架去做开发,主要分为以下几个部分内容: 有哪些常用的串口/终端工具 有哪些常见嵌入式 shell 移植和使用嵌入式 shell 附件内容 参考资源
一、有哪些常用的串口/终端工具在嵌入式开发中,串口工具应该是平时工作中打交道最多的了。一般常见的串口工具根据功能划分以下几大类: 纯串口的 带 Modbus RTU 协议功能的 串口网络互转功能的 串口波形显示器 带多种调试助手的集成工具 带 SSH 等远程传输协议的终端工具
一般在选择工具的时候,会考虑功能是否齐全并且能够满足自己本身开发需求?比如有些特殊波特率的设置,数据位长度的设置等等;会考虑是否免费开源,自己能否获取到源码去做修改?会考虑交互的便捷性,以及界面逼格是否高大上,等等。本人将这两天搜集到的一些常用的串口/终端工具全部整理罗列在下面,并且全部放到网盘中,如果大家能够用得上可以尽情下载,有其它好用的也请跟帖上传哟。 - Tabby
- SecureCRT
- MobaXterm
- Putty
- XShell
- SSCOM
- XCOM
- Hypertrm
- 野火/山外多功能调试助手
- 友善串口调试助手
- comNG
- Commix
- UartX
- Termius
- NxShell
- FinalShell
- Fish Shell
- electerm
- SEGGER Jlink-RTT Viewer
- vscode EIDE 插件
- ……
二、有哪些常见嵌入式 shell
在 PC 机上面 shell 的概念是:用户与操作系统间接口的程序,它允许用户向操作系统输入需要执行的命令,并将操作系统的运行结果返回给用户。所有 PC 机都是运行于操作系统之上的,系统中的内核管理着整台计算机的硬件,但是由于内核处于系统的底层,普通用户不能随意操作,不然一个不小心系统就崩溃啦!但我们总还是要让用户操作系统的,怎么办呢?这就需要一个专门的程序,它接受用户输入的命令,然后帮我们与内核沟通,最后让内核完成我们的任务。这个提供用户界面的程序被叫做 shell (壳层)。它提供了一个用户操作系统的入口,一般是通过 shell 去调用其他各种各样的应用程序,最后来达成我们的目的。 在 PC 机上 shell 通常可以分为命令行 shell 与 图形 shell 。顾名思义,前者提供一个 CLI 命令行界面,后者提供一个图形用户界面 GUI。 历史上知名的命令行 shell 有: 至于为什么叫做 shell ,与其在系统中的地位很有关联,下面这幅图是不是很像一层壳呢? 而对于嵌入式系统特别是 MCU 软件工程而言,一般用户开发不复杂的应用时会习惯直接跑裸机运行程序,针对这种没有操作系统的程序,如何高效便捷的进行系统调试往往是一个比较令人头疼的问题,而一些常见的嵌入式 shell 程序就是作为一个用户与设备端的连接桥梁的存在,极大的方便了系统的调试。往深了讲,shell 的运行原理是通过在命令行输入命令,shell 通过串口接收数据并且对命令进行解析,然后执行相应的操作。更通俗地来说,就是使用输入的字符串,匹配到对应的函数,然后执行下去。那么,就需要建立一个命令与函数的一一对应的关系,将其定义为一张执行表。 在使用嵌入式 shell 过程中,我整理了一些常用且好用的 shell 程序可以方便大家拿去使用,大致可以分为裸机适用的和 RTOS 自带的组件,它们分别是: - nr micro shell
- Letter shell
- RTT shell
- USMART
- cmd parser
- FinSH msh
- FreeRTOS CLI
上面每个 shell 原理都一样,移植的核心也就是分为接口适配以及增加自定义命令,下面逐一介绍。
三、移植和使用嵌入式 shellnr micro shell(https://gitee.com/nrush/nr_micro_shell)
它是一个开源的 MCU 级命令行交互组件,前面几篇内容中也大都是用到了这个组件,它具有以下优点:占用资源少,编译后占用 ROM 不到 4K,RAM 占用 1K 左右,使用简单,灵活方便。使用过程只涉及两个 shell_init() 和 shell() 两个函数,无论是使用 RTOS 还是裸机都可以方便的应用该工具,不需要额外的编码工作。 交互体验好。完全类似于 linux shell 命令行,当串口终端支持 ANSI(如 Hypertrm 终端)时,其不仅支持基本的命令行交互,还提供Tab键命令补全,查询历史命令,方向键移动光标修改功能。 扩展性好。nr_micro_shell 为用户提供自定义命令的标准函数原型,只需要按照命令编写命令函数,并注册命令函数,即可使用命令。 nr_micro_shell 不支持 ESC 键等控制键(控制符)。
接着介绍移植步骤。先将源码下载好并且添加到 template 工程中且包含到头文件路径,源码目录结构如下: 在串口初始化以及接收中断中添加以下初始化 nr_micro_shell 和接收命令接口函数,接收函数也可以通过串口轮询方式处理: if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)
{
shell(UART_ReceiveData(UART1));//接收串口命令
UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
}
保证 shell_printf() 可以正常打印字符数据,这里可以直接将其定义为 printf(),接着使用下面两种方式添加自定义命令,一种如下: const static_cmd_st static_cmd[] =
{
{"ls", shell_ls_cmd},
{"test", shell_test_cmd},
{"blink", blink_led_cmd},
{"\0", NULL}
};
其中最后一行 {"\0", NULL} 是不允许被删除的,另一种如下: #define NR_SHELL_USING_EXPORT_CMD
NR_SHELL_CMD_EXPORT(reset, system_reset_cmd);
NR_SHELL_CMD_EXPORT(hwfault, hwfault_test_cmd);
上面两种方式根据自己的喜好去做定义,2 个参数分别表示交互时候的命令以及要被执行的函数,命令允许带其它参数,比如: blink 0 。执行函数可以解析附加的参数,也可以不管它们而做其它的事情。上面我自定义了 3 个函数,分别执行闪灯、软复位以及触发硬件错误动作,对于一些没有复位按键的板子该复位指令显得尤为重要了。下面是函数的具体实现: /**
* [url=home.php?mod=space&uid=247401]@brief[/url] blink_led_cmd
* blink 0 turn off the led
* blink 1 turn on the led
*/
void blink_led_cmd(char argc, char *argv)
{
if (!strcmp("1", &argv[argv[1]]))
{
LED1_ON();
LED2_ON();
LED3_ON();
LED4_ON();
shell_printf("led is ON\r\n");
}
else if (!strcmp("0", &argv[argv[1]]))
{
LED1_OFF();
LED2_OFF();
LED3_OFF();
LED4_OFF();
shell_printf("led is OFF\r\n");
}
}
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] system_reset_cmd
*/
void system_reset_cmd(char argc, char *argv)
{
NVIC_SystemReset();
}
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] hwfault_test_cmd
*/
void hwfault_test_cmd(char argc, char *argv)
{
#if 0
volatile int * SCB_CCR = (volatile int *) 0xE000ED14; // SCB->CCR
int x, y, z;
*SCB_CCR |= (1 << 4); /* bit4: DIV_0_TRP. */
x = 10;
y = 0;
z = x / y;
printf("z:%d\n", z);
#endif
*(uint32_t *)0x32 = 888 ;
}
移植完后可以进行测试,使用前面介绍的串口终端工具:
Letter shell(https://gitee.com/zhang-ge/letter-shell)
Letter shell 是一个 C 语言编写的,可以嵌入在程序中的嵌入式 shell,主要面向嵌入式设备,以 C 语言函数为运行单位,可以通过命令行调用,运行程序中的函数。相对2.x版本,Letter shell 3.x 增加了用户管理,权限管理,以及对文件系统的初步支持,此外 3.x 版本修改了命令格式和定义,2.x 版本的工程需要经过简单的修改才能完成迁移,若只需要使用基础功能,可以使用 Letter shell 2.x 版本。 Letter shell 有以下功能特点: 命令自动补全 快捷键功能定义 命令权限管理 用户管理 变量支持 代理函数和参数代理解析
这里仅需要实现基础的交互功能,所以选择使用 2.x 版本作为移植对象,移植的接口与前面尤为相似,主要有以下几个点:
#include "shell_port.h"
SHELL_TypeDef shell;
/*******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]
* @param
* @retval
* [url=home.php?mod=space&uid=93590]@Attention[/url]
*******************************************************************************/
void shellPortWrite(const char ch)
{
UART_SendData(UART1, (uint8_t)ch);
while(UART_GetFlagStatus(UART1, UART_IT_TXIEN) == RESET);
}
shell.write = shellPortWrite;
shellInit(&shell);//初始化阶段
/*******************************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url]
* @param
* @retval
* [url=home.php?mod=space&uid=93590]@Attention[/url]
*******************************************************************************/
void UART1_IRQHandler(void)
{
if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)
{
shellHandler(&shell, UART_ReceiveData(UART1));
UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
}
}
然后可以根据自己的需求,在 shell_cfg.h 中更改各项宏定义配置,比如是否开启登录 shell 密码、命令头符号等等,具体宏定义内容如下:
可以看出,letter shell 不仅能够用于裸机程序,也能够适配上各种操作系统的,只是 shell_Task 与 shell_Handler 的处理方式不同。另外,自定义命令的方式与前面也很相似,这里只列举一条指令:
/******************************************************************************
* @brief Jump to the specified address
* @param wUserFlashAddr: 0x08000000 ~ 0x08xxxxxx
* @retval None
* [url=home.php?mod=space&uid=93590]@Attention[/url] None
******************************************************************************/
void BOOT_Jump_To_APP(void)
{
/* 复位所有外设以及去除所有中断标志关闭中断*/
/* 将中断向量表拷贝到SRAM区 */
Iap_Jump_To_Address(APPLICATION_ADDRESS);
}
SHELL_EXPORT_CMD(jump, BOOT_Jump_To_APP, Jump to APP);
其中第三个参数为解释说明性语言,前两个参数与 nr_micro_shell 一样。Letter shell 功能挺强大的,如果还用到其它扩展功能,可以根据说明文档迁移到 3.X 版本去。
Segger RTT shell(https://www.segger.com/products/debug-probes/j-link/tools/rtt-viewer/#rtt-viewer-startup)
SEGGER 的实时传输(Real Time Transfer, RTT)是嵌入式应用中用户 I/O 交互的一种新技术。J-Link RTT Viewer 是在调试主机上使用 RTT 功能的 Windows GUI 应用程序。它结合了 SWO 和半主机 semihosting 的优点,具有很高的性能,使用 RTT,可以从目标微控制器输出信息,并以非常高的速度向应用程序发送输入,而不会影响目标的实时性。它有以下特性:- 与目标应用程序进行双向通信
- 非常高的传输速度,不影响实时行为
- 使用调试通道进行通信
- 目标上不需要额外的硬件或引脚
- 支持任何J-Link模型
- 支持ARM Cortex-M0/M0+/M1/M3/M4/M7/M23/M33和Renesas RX100/200/600
- 提供功能和自由的完整实现代码
- 通道0上的终端输出
- 将文本输入发送到通道0
- 最多16个虚拟终端,只有一个目标通道
- 控制文本输出:彩色文本,擦除控制台
- 在通道1上记录数据
RTT 支持两个方向上的多个通道,向上到主机,向下到目标板,可以用于不同的目标,并为用户提供尽可能多的自由。默认实现每个方向使用一个通道,这意味着多个可打印的终端输入和输出。有了 J-Link RTT 查看器,这个通道可以用于多个“虚拟”终端,只需要一个目标缓冲区就可以打印到多个窗口(例如,一个用于标准输出,一个用于错误输出,一个用于调试输出)。例如,可以使用另一个up (to host)通道发送分析或事件跟踪数据。具体移植说明参见附件中的文档,在此不做过多赘述。
USMART
相信熟悉正点原子的朋友都应该熟悉该组件,它是原子哥开发的一款小型 shell 工具,几乎在所有例程中都能见到。由于它是专为 STM32 而设计的,移植到 MM32 上是需要对底层实现方式做些修改的,好在 MM32 的 TIM 和 UART 外设与 STM32 还是很像的,只是需要注意一点,串口接收中断缓存完数据后需要手动清除标志位,而 ST 的是读取数据寄存器会自动清除接收有效标志位的。
下面是移植的关键代码:#if USMART_ENTIMX_SCAN==1
//复位runtime
//需要根据所移植到的MCU的定时器参数进行修改
void usmart_reset_runtime(void)
{
TIM_ClearFlag(TIM14, TIM_FLAG_Update); //清除中断标志位
TIM_SetAutoreload(TIM14, 0XFFFF); //将重装载值设置到最大
TIM_SetCounter(TIM14, 0); //清空定时器的CNT
usmart_dev.runtime = 0;
}
//获得runtime时间
//返回值:执行时间,单位:0.1ms,最大延时时间为定时器CNT值的2倍*0.1ms
//需要根据所移植到的MCU的定时器参数进行修改
u32 usmart_get_runtime(void)
{
if (TIM_GetFlagStatus(TIM14, TIM_FLAG_Update) == SET) //在运行期间,产生了定时器溢出
{
usmart_dev.runtime += 0XFFFF;
}
usmart_dev.runtime += TIM_GetCounter(TIM14);
return usmart_dev.runtime; //返回计数值
}
//下面这两个函数,非USMART函数,放到这里,仅仅方便移植.
//定时器14中断服务程序
void TIM14_IRQHandler(void)
{
if (TIM_GetITStatus(TIM14, TIM_IT_Update) == SET) //溢出中断
{
usmart_dev.scan(); //执行usmart扫描
TIM_SetCounter(TIM14, 0); //清空定时器的CNT
TIM_SetAutoreload(TIM14, 100); //恢复原来的设置
}
TIM_ClearITPendingBit(TIM14, TIM_IT_Update); //清除中断标志位
}
//使能定时器14,使能中断.
void Timer14_Init(u16 arr, u16 psc)
{
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM14, ENABLE); ///使能TIM14时钟
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM14, &TIM_TimeBaseInitStructure); //初始化定时器4
TIM_ITConfig(TIM14, TIM_IT_Update, ENABLE); //允许定时器4更新中断
TIM_Cmd(TIM14, ENABLE); //使能定时器4
NVIC_InitStructure.NVIC_IRQChannel = TIM14_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置NVIC
}
#endif
////////////////////////////////////////////////////////////////////////////////////////
//初始化串口控制器
//sysclk:系统时钟(Mhz)
void usmart_init(u8 sysclk)
{
#if USMART_ENTIMX_SCAN==1
Timer14_Init(1000, (u32)sysclk * 100 - 1); //分频,时钟为10K ,100ms中断一次,注意,计数频率必须为10Khz,以和runtime单位(0.1ms)同步.
#endif
usmart_dev.sptype = 1; //十六进制显示参数
}
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA = 0; //接收状态标记
void UART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if (UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res = UART_ReceiveData(UART1);
UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
if ((USART_RX_STA & 0x8000) == 0) //接收未完成
{
if (USART_RX_STA & 0x4000) //接收到了0x0d
{
if (Res != 0x0a)USART_RX_STA = 0; //接收错误,重新开始
else USART_RX_STA |= 0x8000; //接收完成了
}
else //还没收到0X0D
{
if (Res == 0x0d)USART_RX_STA |= 0x4000;
else
{
USART_RX_BUF[USART_RX_STA & 0X3FFF] = Res ;
USART_RX_STA++;
if (USART_RX_STA > (USART_REC_LEN - 1))USART_RX_STA = 0; //接收数据错误,重新开始接收
}
}
}
}
}
实现完底层接口后,在 usmart_config.c 中完成自定义命令的注册以及相关函数的声明及定义,当然内部自带了用于打印所有 usmart 可调用函数 list 命令、用于获取各个函数的入口地址 id 命令、打印 usmart 使用的帮助信息 help/? 命令、以及查看十六进制和十进制数据的 hex 和 dec 命令等等。这里简单的增加了几条自定义命令:
//翻转LED
void led_toggle(void)
{
LED1_TOGGLE();
}
/**
* @brief reset
*/
void reset(void)
{
NVIC_SystemReset();
}
/**
* @brief prvHardFaultCommand
*/
void hardfault(void)
{
*(uint32_t *)0x32 = 888 ;
}
//函数参数调用测试函数
void test_fun(void(*led_toggle)(void))
{
led_toggle();
}
//函数名列表初始化(用户自己添加)
//用户直接在这里输入要执行的函数名及其查找串
struct _m_usmart_nametab usmart_nametab[] =
{
#if USMART_USE_WRFUNS==1 //如果使能了读写操作
(void *)read_addr, "u32 read_addr(u32 addr)",
(void *)write_addr, "void write_addr(u32 addr,u32 val)",
#endif
(void *)led_toggle,"void led_toggle(void)",
(void *)reset,"void reset(void)",
(void *)hardfault,"void hardfault(void)",
(void *)test_fun,"void test_fun(void(*led_toggle)(void))",
};
在串口助手中输入的字符串需要为 “led_toggle()”、“reset()” 等等,这点与前面几个 shell 不一样,需要注意,且 usmart 还能支持输入函数 ID 去执行该函数,这点设计蛮实用的,只要在使用前查询一下所有函数的 ID 号。实际测试时当然结合原子自己的 XCOM 串口助手来得方便,需要注意勾选换新行:
FinSH msh
FinSH 是 RT-Thread 的命令行组件,提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。用户在控制终端输入命令,控制终端通过串口、USB、网络等方式将命令传给设备里的 FinSH,FinSH 会读取设备输入命令,解析并自动扫描内部函数表,寻找对应函数名,执行函数后输出回应,回应通过原路返回,将结果显示在控制终端上。 需要注意的是,该组件需要配合 RT-Thread 任意一个版本来运行,为了简化代码量,可以在 Keil 中以 CMSIS PACK 的方式在 RTE 中进行添加 rt-thread nano 与 msh/finsh。具体添加过程在此略过,主要还是描述移植的接口主要在 board.c 文件中,这里使用了串口接收中断和非中断的 2 种方式实现,用到了一个开关宏定义来做切换: /*******************************************************************************
* @brief
* @param
* @retval
* [url=home.php?mod=space&uid=93590]@Attention[/url]
*******************************************************************************/
void rt_hw_console_output(const char *str)
{
rt_size_t i = 0, size = 0;
char a = '\r';
size = rt_strlen(str);
for (i = 0; i < size; i++)
{
if (*(str + i) == '\n')
{
while((UART1->CSR & UART_IT_TXIEN) == 0);
UART1->TDR = (a & (u16)0x00FF);
}
while((UART1->CSR & UART_IT_TXIEN) == 0);
UART1->TDR = (*(str + i) & (u16)0x00FF);
}
}
/*******************************************************************************
* @brief
* @param
* @retval
* [url=home.php?mod=space&uid=93590]@Attention[/url]
*******************************************************************************/
char rt_hw_console_getchar(void)
{
int ch = -1;
#ifdef UART_USE_IT
rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER);//接收信号量
ch = UART_ReceiveData(UART1);
#else
if (UART_GetFlagStatus(UART1, UART_FLAG_RXAVL) != RESET)
{
ch = UART_ReceiveData(UART1);
UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
}
else
{
rt_thread_mdelay(10);
}
#endif
return ch;
}
#ifdef UART_USE_IT
/*******************************************************************************
* @brief
* @param
* @retval
* @attention
*******************************************************************************/
void UART1_IRQHandler(void)
{
rt_interrupt_enter();
if(UART_GetITStatus(UART1, UART_ISR_RX) != RESET)
{
UART_ReceiveData(UART1);
UART_ClearITPendingBit(UART1,UART_IT_RXIEN);
rt_sem_release(&shell_rx_sem); //释放信号量
}
rt_interrupt_leave();
}
#endif
FinSH msh 自定义命令也类似于前面的 shell ,语句样式为: MSH_CMD_EXPORT(cycle_fix, change blink period);
实际测试用的 SecureCRT ,中间遇到一个问题,默认情况下该工具未设置“ENTER”键为回车换行,而只是回车,需要做如下设置才能让 shell 正常工作: FreeRTOS CLl(https://www.freertos.org/FreeRTOS-Plus/FreeRTOS_Plus_CLI/FreeRTOS_Plus_Command_Line_Interface.html)
FreeRTOS+CLI(命令行界面)提供了一种简单、小型、可扩展且 RAM 高效的方法,使 FreeRTOS 应用程序能够处理命令行输入。移植添加 CLI 命令组件所需的步骤如下面所示:
限于篇幅,在此针对移植过程不做过多描述,具体的 FreeRTOS + CLI 的移植细节可以参阅:https://blog.csdn.net/sinat_36568888/article/details/124407768
我移植好的工程结构如下:
对于串口发送,我暂时未参照 ST 的,用的不是中断方式而是直接 printf 方式,大家可以在我工程中屏蔽位置进行发送中断方式实现。另外,实际测试时发现输出结果会输出 2 遍,调试发现了问题所在,这个组件与上面其它几个 shell 不一样,它认的终止符号为回车或者换行而不是回车换行:
最后,对于 cmd_parser 这个命令解析组件的移植和使用在此不做过多探讨和研究了,大家可以参照该链接进行研究:
https://mculover666.blog.csdn.net/article/details/106102372?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-1-106102372-blog-80486733.pc_relevant_baidufeatures_v7&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-1-106102372-blog-80486733.pc_relevant_baidufeatures_v7&utm_relevant_index=2
希望能够帮助到社区里面讨论使用 shell 的用户,在这么多 shell 面前千万不要挑花了眼哟,具体占用资源情况可以打开各个工程进行编译,个人推荐 nr_micro_shell !
四、附件内容附件资源包中有以下内容可供参考下载: 基于 MM32F0140 的 FreeRTOS+CLI shell 工程 —— 1. MM32F0140_FreeRTOS_CLI_Shell.zip 基于 MM32F0140 的 USMART shell 工程 —— 2. MM32F0140_USMART_Shell.zip 基于 MM32F0140 的 nr_micro_shell 工程 —— 3. MM32F0140_NR_Micro_Shell.zip 基于 MM32F0140 的 nr_micro_shell 工程 —— 3. MM32F0140_NR_Micro_Shell.zip 基于 MM32F0270 的 Letter_Shell 2.X 工程 —— 4. MM32F0270_Letter_Shell.zip 基于 MM32F0140 的 RT-Thread Nano_MSH_Shell 工程 —— 5. MM32F0140_RT-Thread Nano_MSH_Shell.zip 收纳的串口终端工具下载地址 —— 9. 串口终端工具_百度网盘.zip
五、参考资源 本文创作参阅学习了以下下资源,在此声明感谢! https://www.cnblogs.com/sddai/p/9769086.html https://zhuanlan.zhihu.com/p/141988605 https://zhuanlan.zhihu.com/p/60329896
|