# shell **Repository Path**: chenx_ark/shell ## Basic Information - **Project Name**: shell - **Description**: 一个适用于单片机的,通过串口,可以调用任意方法和读写变量的shell库。 - **Primary Language**: C/C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2024-12-02 - **Last Updated**: 2024-12-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SHELL 单片机调试控制台 ## 简介 在嵌入式软件的调试阶段,经常需要测试调用某些函数,修改某些变量的值。其中修改变量的值在使用SWD调试的时候,还可以修改,但是想在调试的时候有目的的调用某些函数方法,比如调试IIC、SPI、等设备的时候,需要调用特定函数,输入不同的参数,传统方法就显得有些捉襟见肘了,所以这也就有了本shell库的初衷。 通过使用此库,您可以方便的查看、修改变量、调用任意方法、传入多种参数,并读取返回值,同时结合上位机,可以使相关调试变得更加丝滑。 本库参考了正点原子的usmart串口库,并使用armink大佬的elog库作为标准输出库,正则库则是使用Rob Pike的开源小型正则库,感谢以上各位作者的开源。 本库采用标准ANSI C编写,方便进行移植,但作者仅在ARM 32平台上进行了测试,其它平台在移植前需先行评估。 ## 快速感受 以下是相关功能的演示,您可以针对自己的需求,进行快速的评估。 ``` // INT 等内存操作指令,以指定的类型及长度对内存进行读写 // 本方法会将值-1以4字节的长度写入内存 void test_shell_mm(void) { uint8_t rsp; uint32_t val=0; char datas[64]; sprintf( datas, "INT 0x%08X 4 -1\r\n",(uint32_t)(&val) ); // 构造字符串 rsp = shell_put_buf(datas, (uint8_t)strlen((const char*)datas)); // 放入缓冲区 shell_parse(); // 解析 } // 待调用的方法,该方法需传入 浮点型、整型、字符串型共3个参数 int test(float a, int b, const char *p) { elog_i("shell_test","a:%f b:%d p:%s",a,b,p); return 1; } // CALL指令,根据需求的返回值不同,有CALLV CALLD CALLH,其中CALLx后面的第1个参数是待调用方法的地址,再后面是待传入的参数 // 本方法会调用上面的test函数,并传入3.14、1、"hello world" 共3个参数 void test_shell_call(void) { uint8_t rsp; char datas[64]; sprintf( datas, "CALLD 0x%08X 3.14 0x00000001 \"hello world\"\r\n",(uint32_t)test ); rsp = shell_put_buf(datas, (uint8_t)strlen((const char*)datas)); shell_parse(); } // MD 指令,以逗号间隔的方式输出目标变量的值,方便上位机以图形方式进行展示,或输出到csv文件进行分析。只发送MD指令不带参数,则可重复上一次监测。 // 本方法会分别取 i8v、u8v、i16v、u16v、i32v、u32v、flt这几个变量的值,并以逗号分隔传回 void test_shell_md(void) { uint8_t rsp,adr[6]="hello"; uint8_t u8v = 129; int8_t i8v = -1; uint16_t u16v = 65535; int16_t i16v = -2; uint32_t u32v = 65537; int32_t i32v = -3; float flt = 3.14; char datas[128]; snprintf( datas, 128, "MD 0x%08XI1 0x%08XU1 0x%08XI2 0x%08XU2 0x%08XI4 0x%08XU4 0x%08XG4\r\n", (uint32_t)(&i8v),(uint32_t)(&u8v),(uint32_t)(&i16v),(uint32_t)(&u16v),(uint32_t)(&i32v),(uint32_t)(&u32v),(uint32_t)(&flt) ); rsp = shell_put_buf(datas, (uint8_t)strlen((const char*)datas)); shell_parse(); } // 更多使用方法请参考 shell_test.c文件中的测试样例 ``` ## 内置指令 针对函数调用和内存访问,系统内置了3类方法,包括: 1. 变量访问方法 INT \ UINT \ HEX \ FLT \ STR 1. 内存访问方法 MD \ MM 1. 函数访问方法 CALLV \ CALLD \ CALLH ``` LOGIN [key:str] // SHELL_NEED_LOGIN=1时会要求先验证,默认密码"ADMIN@666" HELP // 展示所有可用的指令 INT [dat:dec] // 以int型输出/入8/16/32位整数 UINT [dat:dec] // 以uint型输出/入8/16/32位整数 HEX [dat:0xXX[XX]] // 以hex型输出/入8/16/32位整数 FLT [dat:flt] // 以float型输出/入4位浮点数 STR [dat:str] // 以字符串型输出/入内存数据 MD [adr|typ|len:0xXXXXXXXXG4] ... // 以逗号间隔输出多变量,直接发送MD指令可以重复上一次监测 MM [data:0xXX[XXXXXX]] // 以十六进制对内存进行访问 CALLV [params...] // 调用void型方法时使用 CALLD [params...] // 调用方法,并以十进制显示返回值 CALLH [params...] // 调用方法,并以十六进制显示返回值 ``` ## 上位机 您可以看出,以上的这些指令,有一个很重要的参数就是地址,可以我又怎么才能知道他们的地址呢?这里就不多不要说一下,Keil等编译器在编译程序的时候,会生成一种elf类型的文件,这个文件中,存储了程序中所有方法和变量的地址、长度等信息。而在keil中,这个文件就就是 **.axf** 文件。它一般在工程目录下,与生成的 **.hex** 等文件放在一起。 而通过解析这个文件就可以获取程序中的所有的方法和变量的信息,便可方便后续对相关对象的操作。 如下图所示为在上位机种调用程序中的 **strlen()** 方法,其中 **@strlen** 即为待调用的方法,它在最终从串口发送前,会被替换为实际的方法所在的地址。 上位机软件放在仓库的 "**doc/XDebugger.rar**" 文件中,软件通过 **"Project"** -> **"Load Symbol File"** 加载 **.axf** 文件。也可以通过 **"File"** -> **"Open Project"** 加载存储的调试信息。软件还提供了 **MM、MD、CALLV 、CALLD、 CALLH、 INT、 UINT、 HEX、 FLT、 STR** 等指令的快捷按钮方便快速调用。 已发送的指令则会存储在右下脚的 **History** 栏中方便再次调用,其中鼠标 **左键双击为发送** , **右键单击为删除** 。 ![调用strlen方法](doc/img/img1.png) 上位机同时依靠MD指令,支持同时对多个变量进行监测及绘制函数图像。优化之后的MD指令,只需要第一次的时候在MD指令后面跟随完整的变量信息,后续只需发送单独的MD指令,即可依据上次的指令对变量进行读写,降低了下位机的解析时间,提高了性能。 图像支持主副坐标轴,图像可以在主副坐标轴上任意上下移动和缩放,方便更好的分析和观察图像。同时还支持对图像的导出等操作。 软件支持通过 ***.ark** 类型的文件实现对包括 **历史记录、监测变量、axf文件** 的该信息进行管理,方便用户快速进入上一次状态。 ![实时监测变量并绘制图像](doc/img/img2.png) ## 移植 本库目前基于Win10_x64+VSCode+MinGW64提供了验证评估配置,在您的本人电脑上进行评估时,需将 **tasks.json** 和 **launch.json** 文件中 **MinGW64** 修改为您自己安装的路径。在移植到包括stm32在内的其它环境时,仅需修改以下2个方法。 ``` // 输出函数,elog_port.c文件中,当在单片机中调试时,需要将printf换成对应的串口输出函数 void elog_port_output(const char *output, size_t size) { /* output to terminal */ printf(output); } // 输入函数,可选下面2个方法中任意1个,当在串口中断中每次接收1个字符,则使用第1个方法, // 当使用缓冲区接收时,则使用第2个方法 uint8_t shell_put_u8( uint8_t c ); uint8_t shell_put_buf( uint8_t *ptr, uint8_t sz ); // 以上方法实现好之后,便可在死循环中调用shell_parse()方法进行轮询解析了。 for(;;){ shell_parse(); delay_ms(50); } ``` ## 配置宏 ``` #define SHELL_NEED_FLOAT 0 // 是否需要支持浮点型 #define SHELL_NEED_LOGIN 1 // 是否需要认证,为0则无需认证 #define SHELL_LOGIN_KEY "ADMIN@666" // 认证的key #define SHELL_CMD_MAX 0x08 // 支持的最大指令字符长度 #define SHELL_PARM_MAX 0x08 // 支持的最大参数的数量 #define SHELL_COMMAND_MAX 128 // 指令缓冲区大小 #define SHELL_CUSTOM_DESP_MAX 64 // 自定义错误描述信息缓冲区大小 ``` ## 资源占用 如下表所示为常规优化等级下的资源占用情况,其中shell模块代码部分占用6.2K,字符串部分占用2.5K,elog模块占用1.6K,re模块占用1.5K,整体占用10K左右。 对于标准库,因为使用到了库中的printf和scanf方法对浮点数进行输入输出,从而使得这部分也明显的增加了体积。 所以如果您的程序中如果不涉及对浮点数的处理的话,则可以屏蔽掉shell库中浮点数处理部分的代码可以有效的减小所占用的体积。同时,如果您有自己的输出函数,通过替换elog模块,也可以减小系统资源的占用。 ``` Code (inc. data) RO Data RW Data ZI Data Debug Object Name 3576 538 17 3 208 26890 shell.o 2752 550 2524 0 128 18933 shell_port.o 1580 552 108 40 274 6352 elog.o 196 44 0 0 12 9861 elog_port.o 254 6 20 0 0 2881 elog_utils.o 1544 26 0 0 280 12468 re.o ... ... ... ... ... ... ... 1054 0 0 0 0 216 _printf_fp_dec.o 764 8 38 0 0 100 _printf_fp_hex.o 128 16 0 0 0 84 _printf_fp_infnan.o 148 4 40 0 0 160 _printf_hex_int_ll_ptr.o 178 0 0 0 0 88 _printf_intcommon.o 124 16 0 0 0 92 _printf_longlong_dec.o 112 10 0 0 0 124 _printf_oct_int_ll.o 884 4 0 0 0 100 _scanf.o 1272 16 0 0 0 168 scanf_fp.o 800 14 0 0 0 100 scanf_hexfp.o 308 16 0 0 0 100 scanf_infnan.o ```