[MM32生态] 有哪些嵌入式 shell 适合用于单片机上?在这选一个吧

整体概览

如今,对于做 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 有:

  • 适用于 Unix 及类 Unix 系统:


 

    • sh (Bourne shell),最经典的 Unix shell;
    • bash (Bourne-Again shell),目前绝大多数 Linux 发行版的默认 shell;
    • zsh (Z shell),非常友好;
    • fish (Friendly interactive shell),专注于易用性与友好用户体验的 shell;

  • Windows 下的 cmd.exe (命令提示符) 与 PowerShell。

至于为什么叫做 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 和接收命令接口函数,接收函数也可以通过串口轮询方式处理:

shell_init();//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 shellAbout the RTT Viewer

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 这个命令解析组件的移植和使用在此不做过多探讨和研究了

希望能够帮助到社区里面讨论使用 shell 的用户,在这么多 shell 面前千万不要挑花了眼哟,具体占用资源情况可以打开各个工程进行编译,个人推荐 nr_micro_shell !

---------------------
作者:yang377156216
链接:https://bbs.21ic.com/icview-3226456-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值