技术标签: # STM32
letter shell 3.0是一个C语言编写的,可以嵌入在程序中的嵌入式shell,主要面向嵌入式设备,以C语言函数为运行单位,可以通过命令行调用,运行程序中的函数。
GitHub:https://github.com/NevermindZZT/letter-shell
硬件:STM32F103CBT6最小系统板
软件:Keil 5.29 + STM32CubeMX6.01
定义shell对象
Shell shell;
定义shell读,写函数,函数原型如下
/**
* @brief shell读取数据函数原型
*
* @param char shell读取的字符
*
* @return char 0 读取数据成功
* @return char -1 读取数据失败
*/
typedef signed char (*shellRead)(char *);
/**
* @brief shell写数据函数原型
*
* @param const char 需写的字符
*/
typedef void (*shellWrite)(const char);
申请一片缓冲区
char shellBuffer[512];
调用shellInit进行初始化
shell.read = shellRead;
shell.write = shellWrite;
shellInit(&shell, shellBuffer, 512);
调用(建立)shell任务
对于运行在操作系统的情况,建立shellTask
任务(确保sell_cfg.h中的配置无误),任务参数为shell对象
OsTaskCreate(shellTask, &shell, ...);
对于裸机环境,在主循环中调用shellTask
,或者在接收到数据时,调用shellHandler
说明
shell->read
,但需要在中断中调用shellHandler
SHEHLL_TASK_WHILE
宏,然后创建shellTask任务其他配置
SHELL_GET_TICK()
为获取系统tick函数,使能tab双击操作,用户长帮助补全配置宏
shell_cfg.h文件中包含了所有用于配置shell的宏,在使用前,需要根据需要进行配置
宏 | 意义 |
---|---|
SHELL_TASK_WHILE | 是否使用默认shell任务while循环 |
SHELL_USING_CMD_EXPORT | 是否使用命令导出方式 |
SHELL_USING_COMPANION | 是否使用shell伴生对象功能 |
SHELL_SUPPORT_END_LINE | 是否支持shell尾行模式 |
SHELL_HELP_LIST_USER | 是否在输入命令列表中列出用户 |
SHELL_HELP_LIST_VAR | 是否在输入命令列表中列出变量 |
SHELL_HELP_LIST_KEY | 是否在输入命令列表中列出按键 |
SHELL_ENTER_LF | 使用LF作为命令行回车触发 |
SHELL_ENTER_CR | 使用CR作为命令行回车触发 |
SHELL_ENTER_CRLF | 使用CRLF作为命令行回车触发 |
SHELL_EXEC_UNDEF_FUNC | 使用执行未导出函数的功能 |
SHELL_COMMAND_MAX_LENGTH | shell命令最大长度 |
SHELL_PARAMETER_MAX_NUMBER | shell命令参数最大数量 |
SHELL_HISTORY_MAX_NUMBER | 历史命令记录数量 |
SHELL_DOUBLE_CLICK_TIME | 双击间隔(ms) |
SHELL_MAX_NUMBER | 管理的最大shell数量 |
SHELL_GET_TICK() | 获取系统时间(ms) |
SHELL_MALLOC(size) | 内存分配函数(shell本身不需要) |
SHELL_FREE(obj) | 内存释放函数(shell本身不需要) |
SHELL_SHOW_INFO | 是否显示shell信息 |
SHELL_CLS_WHEN_LOGIN | 是否在登录后清除命令行 |
SHELL_DEFAULT_USER | shell默认用户 |
SHELL_DEFAULT_USER_PASSWORD | 默认用户密码 |
SHELL_LOCK_TIMEOUT | shell自动锁定超时 |
letter shell 3.0同时支持两种形式的函数定义方式,形如main函数定义的func(int argc, char *agrv[])
以及形如普通C函数的定义func(int i, char *str, ...)
,两种函数定义方式适用于不同的场景
main函数形式
使用此方式,一个函数定义的例子如下:
int func(int argc, char *agrv[])
{
printf("%dparameter(s)\r\n", argc);
for (char i = 1; i < argc; i++)
{
printf("%s\r\n", argv[i]);
}
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), func, func, test);
终端调用
letter:/$ func "hello world"
2 parameter(s)
hello world
普通C函数形式
使用此方式,shell会自动对参数进行转化处理,目前支持二进制,八进制,十进制,十六进制整形,字符,字符串的自动处理,如果需要其他类型的参数,请使用代理参数解析的方式(参考代理函数和代理参数解析),或者使用字符串的方式作为参数,自行进行处理,例子如下:
int func(int i, char ch, char *str)
{
printf("input int: %d, char: %c, string: %s\r\n", i, ch, str);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), func, func, test);
终端调用
letter:/$ func 666 'A' "hello world"
input int: 666, char: A, string: hello world
letter shell 3.0支持导出变量,通过命令行查看,设置以及使用变量的值
导出变量
变量导出使用SHELL_EXPORT_VAR
宏,支持整形(char, short, int),字符串,指针以及节点变量,变量导出需要使用引用的方式,如果不允许对变量进行修改,在属性中添加SHELL_CMD_READ_ONLY
int varInt = 0;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_INT), varInt, &varInt, test);
char str[] = "test string";
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_STRING), varStr, str, test);
Log log;
SHELL_EXPORT_VAR(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_VAR_POINT), log, &log, test);
查看变量
在命令行直接输入导出的变量名即可查看变量当前的值
letter:/$ varInt
varInt = 0, 0x00000000
letter:/$ varStr
varStr = "test string"
修改变量
使用setVar
命令修改变量的值,对于字符串型变量,请确认字符串有分配足够的空间,指针类型的变量不可修改
letter:/$ setVar varInt 45678
varInt = 45678, 0x0000b26e
letter:/$ setVar varStr "hello"
varStr = "hello"
使用变量
letter shell 3.0的变量可以在命令中作为参数传递,对于需要传递结构体引用到命令中的场景特别适用,使用$
+变量名的方式传递
letter:/$ shellPrint $shell "hello world\r\n"
hello world
letter shell采取一个静态数组对定义的多个shell进行管理,shell数量可以修改宏SHELL_MAX_NUMBER
定义(为了不使用动态内存分配,此处通过数据进行管理),从而,在shell执行的函数中,可以调用shellGetCurrent()
获得当前活动的shell对象,从而可以实现某一个函数在不同的shell对象中发生不同的行为,也可以通过这种方式获得shell对象后,调用shellWriteString(shell, string)
进行shell的输出
letter shell支持通过函数地址直接执行函数,可以方便执行那些没有导出,但是有临时需要使用的函数,使用命令exec [addr] [args]
执行,使用此功能需要开启SHELL_EXEC_UNDEF_FUNC
宏,注意,由于直接操作函数地址执行,如果给进的地址有误,可能引起程序崩溃
函数的地址可以通过编译生成的文件查找,比如说对于keil,可以在.map
文件中查找到每个函数的地址,对于keil,.map
文件中的地址需要偏移一个字节,才可以成功执行,比如说shellClear
函数地址为0x08028620
,则通过exec
执行应为exec 0x08028621
其他编译器查找函数地址的方式和地址偏移的处理,请参考各编译器手册
letter shell 3.0将可执行的函数命令定义,用户定义,按键定义以及变量定义统一归为命令定义,使用相同的结构储存,查找和执行
letter shell 支持使用命令导出方式和命令表方式进行命令的添加,定义,通过宏SHELL_USING_CMD_EXPORT
控制
命令导出方式支持keil,IAR(未测试)以及GCC
命令导出方式
letter shell 支持在函数体外部,采用定义常量的方式定义命令,例如SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE (SHELL_TYPE_CMD_MAIN)|SHELL_CMD_DISABLE_RETURN,help, shellHelp, show command info\r\nhelp [cmd]);
对于使用keil进行编译,需要在keil的target option中增加--keep shellCommand*,防止定义的命令被优化掉
使用GCC编译时,需要在ld文件中的只读数据区(建议)添加:
_shell_command_start = .;
KEEP (*(shellCommand))
_shell_command_end = .;
命令表方式
当使用其他暂时不支持使用命令导出方式的编译器时,需要在shell_cmd_list.c
文件的命令表中添加
const SHELL_CommandTypeDef shellDefaultCommandList[] =
{
SHELL_CMD_ITEM(
SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN)|SHELL_CMD_DISABLE_RETURN,
help, shellHelp, show command info\r\nhelp [cmd]),
};
letter shell 3.0对可执行命令,按键,用户以及变量分别提供了一个宏,用于进行命令定义
可执行命令定义
使用宏SHELL_EXPORT_CMD
定义可执行命令,定义如下
/**
* @brief shell 命令定义
*
* @param _attr 命令属性
* @param _name 命令名
* @param _func 命令函数
* @param _desc 命令描述
*/
#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
const ShellCommand \
shellCommand##_name SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.cmd.name = shellCmd##_name, \
.data.cmd.function = (int (*)())_func, \
.data.cmd.desc = shellDesc##_name \
}
变量定义
使用宏SHELL_EXPORT_VAR
定义变量,定义如下
/**
* @brief shell 变量定义
*
* @param _attr 变量属性
* @param _name 变量名
* @param _value 变量值
* @param _desc 变量描述
*/
#define SHELL_EXPORT_VAR(_attr, _name, _value, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellDesc##_name[] = #_desc; \
const ShellCommand \
shellVar##_name SECTION("shellCommand") = \
{ \
.attr.value = _attr, \
.data.var.name = shellCmd##_name, \
.data.var.value = (void *)_value, \
.data.var.desc = shellDesc##_name \
}
变量定义时,_value
应该是变量的引用,如果变量不允许修改,则需要在增加SHELL_CMD_READ_ONLY
属性
用户定义
使用宏SHELL_EXPORT_USER
定义用户,定义如下
/**
* @brief shell 用户定义
*
* @param _attr 用户属性
* @param _name 用户名
* @param _password 用户密码
* @param _desc 用户描述
*/
#define SHELL_EXPORT_USER(_attr, _name, _password, _desc) \
const char shellCmd##_name[] = #_name; \
const char shellPassword##_name[] = #_password; \
const char shellDesc##_name[] = #_desc; \
const ShellCommand \
shellUser##_name SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_USER), \
.data.user.name = shellCmd##_name, \
.data.user.password = shellPassword##_name, \
.data.user.desc = shellDesc##_name \
}
按键定义
使用宏SHELL_EXPORT_KEY
定义按键,定义如下
/**
* @brief shell 按键定义
*
* @param _attr 按键属性
* @param _value 按键键值
* @param _func 按键函数
* @param _desc 按键描述
*/
#define SHELL_EXPORT_KEY(_attr, _value, _func, _desc) \
const char shellDesc##_value[] = #_desc; \
const ShellCommand \
shellKey##_value SECTION("shellCommand") = \
{ \
.attr.value = _attr|SHELL_CMD_TYPE(SHELL_TYPE_KEY), \
.data.key.value = _value, \
.data.key.function = (void (*)(Shell *))_func, \
.data.key.desc = shellDesc##_value \
}
按键键值为在终端输入按键会发送的字符串序列,以大端模式表示,比如在SecureCRT中断,按下Tab键,会发送0x0B,则这个按键的键值为0x0B000000,如果按下方向上,会依次发送0x1B, 0x5B, 0x41, 则这个键的键值为0x1B5B4100
在命令定义中,有一个attr
字段,表示该命令的属性,具体定义为
union
{
struct
{
unsigned char permission : 8; /**< command权限 */
ShellCommandType type : 4; /**< command类型 */
unsigned char enableUnchecked : 1; /**< 在未校验密码的情况下可用 */
unsigned char readOnly : 1; /**< 只读 */
unsigned char reserve : 1; /**< 保留 */
unsigned char paramNum : 4; /**< 参数数量 */
} attrs;
int value;
} attr;
在定义命令时,需要给定这些值,可以通过宏SHELL_CMD_PERMISSION(permission)
, SHELL_CMD_TYPE(type)
, SHELL_CMD_ENABLE_UNCHECKED
, SHELL_CMD_DISABLE_RETURN
, SHELL_CMD_READ_ONLY
, SHELL_CMD_PARAM_NUM(num)
快速声明
letter shell 3.0原生支持将整数,字符,字符串参数,以及在某些情况下的浮点参数直接传递给执行命令的函数,一般情况下,这几种参数类型完全可以满足调试需要,然而在某些情况下,用户确实需要传递其他类型的参数,此时,可以选择将命令定义成main函数形式,使用字符串传递参数,然后自行对参数进行解析,除此之外,letter shell还提供了代理函数的机制,可以对任意类型的参数进行自定义解析
关于代理函数的实现原理和具体使用示例,可以参考letter-shell代理函数解析
使用代理函数,用户需要自定义代理参数解析器,即一个将基本参数(整数,字符,字符串参数)转换成目标类型参数的函数或者宏,letter shell默认实现了浮点类型的参数解析器SHELL_PARAM_FLOAT(x)
然后,使用代理函数命令导出宏定义命令,比如需要需要传递多个浮点参数的函数,如下
void test(int a, float b, int c, float d)
{
printf("%d, %f, %d, %f \r\n", a, b, c, d);
}
SHELL_EXPORT_CMD_AGENCY(SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
test, test, test,
p1, SHELL_PARAM_FLOAT(p2), p3, SHELL_PARAM_FLOAT(p4));
相比常规的命令导出,代理函数命令导出前4个参数和常规形式的命令导出一致,之后的参数即传递至目标函数的参数,letter shell默认实现的代理函数定义支持最多7个参数,p1~p7,对于不需要代理参数解析的参数,只需要对应写入px(x为1~7)
即可,比如上方示例的p1
和p3
,而需要代理参数解析的参数,则需要使用对应的参数解析器,比如上方示例的p2
和p4
letter shell 3.0的权限管理同用户定义紧密相关,letter shell 3.0使用8个bit位表示命令权限,当用户和命令的权限按位与为真,或者命令权限为0时,表示该用户拥有此命令的权限,可以调用改命令
letter shell 3.0.3版本引入了伴生对象的概念,通过宏SHELL_USING_COMPANION
开启或者关闭,若使用伴生对象的功能,需要同时将shell_companion.c文件加入到工程中,伴生对象可以用于需要将某个对象同shell关联的场景,比如说,通过快捷键控制shell终端对应的日志打印对象
一般情况下,使用shellCompanionAdd
将伴生对象同shell对象进行关联,之后,可以在shell操作中,通过shellCompanionGet
获取相应的伴生对象,以达到在不同的shell中,操作不同对象的目的
letter shell 3.0.4版本新增了尾行模式,适用于需要在shell所使用的交互终端同时输入其他信息(比如说日志)时,防止其他信息的输出,导致shell交互体验极差的情况,使用时,使能宏SHELL_SUPPORT_END_LINE
,然后对于其他需要使用终端输入信息的地方,调用shellWriteEndLine
接口将信息输入,此时,调用shellWriteEndLine
进行输入的内容将会插入到命令行上方,终端会一直保持shell命令行位于最后一行
使用letter shell尾行模式结合log日志输出的效果如下:
letter shell 3.0提供了一个用于遍历工程中命令导出的工具,位于tools/shellTools.py,需要python3环境运行,可以列出工程中,所有使用SHELL_EXPORT_XXX
导出的命令名,以及位置,结合VS Code可以直接进行跳转
python shellTools.py project
注意:shellTools会遍历指定目录中所有文件,所以当工程中文件较多时,速度会比较慢,建议只用于遍历用户模块的目录
传送门->代码
参考文章:https://blog.csdn.net/Mculover666/article/details/105141286
未完待续。。。
好了,就介绍到此,有了这个神器,非常适合开发串口终端调试场景产品。
1.String 是固定长度的字符串。这是因为String出来的字符串存储在字符串常量池中,常量池中的字符串的长度是不会改变的。StringBulider和StringBuffer 的长度是可变的。StringBuilder和StringBuffer通过构造函数来创建字符串对象,创造出来的对象值是存储在堆内存中,StringBulider 和StringBuffer可以利用append()方法拼接字符串,也就是new出来的对象可以继续拼接在原来的存储空间上。2.StringBulider的执行效
毕业了,整理下本科期间自己记的笔记。这个是之前上Oracle数据库的课的时候老师交给我们的任务,每个人都必须按照步骤手动创建数据库手动创建Oracle数据库的步骤如下:1、设置数据库名称和实例名称2、创建相关目录3、创建参数文件(修改)4、创建实例oradim -new -sid test -intpwd test -srvcstart system/demandoradim -delete -sid test5、连接Oracle实例6、启动实例7、使用create database创建
转载:http://www.cnblogs.com/mliudong/p/4094519.htmlubuntu自带了openssh-client,可以远程连接别的机器。 要想通过ssh被连接,ubuntu系统需要openssh-server。通过apt-get安装sshserver时,如果安装源获取到的server版本和自带的client依赖冲突时,则会安装失败。安装对应版本的openssh-cl
图像配准的定义:图像配准是将一幅图像上的点映射到另一幅图像上同源点的空间转换过程。eg:平移、旋转、缩放等变换。下图为镜像变换。配准框架:配准框架的基本成员:两个输入图像、一个变换、一个路径选择、一个校对机和一个优化器。过程:待配准图像通过 变换函数(T(x)) 到参考图像的映射过程。传递函数T(X)表示从参考图像上的点到待配准图像上的点的空间映射关系。
学习网站https://www.3dgep.com/learning-directx-12-1/#cite-13
Log4j作为日志组件被大多数的系统所使用,Jboss也不例外的采用了Log4j作为它的日志输出组件。但在使用JBoss时,很多人经常碰到一些冲突,这些冲如自己配置的log4j文件无效,系统抛org.jboss.logging.util.OnlyOnceErrorHandlerobject is not assignable to a org.apache.log4j.spi.Erro
Tower 个人写作物料发布文章https://mp.csdn.net/图片物料代码物料推广物料微信公众号
尺度不变特征变换一、特征检测1、DoG图像金字塔2、寻找局部极值3、SIFT关键点展示3、为关键点指定方向二、局部图像描述三、索引和匹配1、 图像匹配2、特征匹配3、结果评估一、特征检测1、DoG图像金字塔大概就是,用不同 σ\sigmaσ 的高斯滤波器对图像滤波,然后做差2、寻找局部极值1、对于DoG金字塔的每一个点,比较它邻近的(包括临近层)26个点2、寻找极值,作为潜在的关键点,这基本上意味着关键点最好在这个尺度上表示3、SIFT关键点展示1、去除低对比度特征:如
这题后台数据一定很坑,估计是那种从节点1可以扩展到节点2~19,但是就是无法与节点20连通的那种,才导致了刚开始没有逆向搜索的时候TLE到3000MS。逆向DFS找到所有与终点相连的节点保存下来,速度很快的。这样正向搜索时就要把与终点不相连的节点剪枝。判断连通性的方法有很多,其实本题还可以用BFS或者并查集防止超时。#include <iostream>#incl...
一、配置好环境:需要安装配置的软件环境:jdk、tomcat服务器、eclipse、mysql(可以使用附加的图形界面软件工具:navicat for mysql)、webstorm。二、编写web项目文件先用webstorm编辑好web项目文件。三、根据web项目的数据需求设计和建造数据库表。用例: 新建数据库名为:users;
今天在写脚本时,本来昨天可以运行的代码突然报错:UnicodeDecodeError:'utf-8' codec can't decode byte 0xe5 in position 1797: invalid continuation然后查了很多资料,都说是编码的问题,但我查看了所有文件以及源码的编码,都没有问题,后来找到了一个文章解决了问题打开你报错的utils文件修改函数参数errors='strict'为errors='ignore'1. with open(file, "r".
默认iBus非常难用1.安装fcitx终端输入fcitx提示程序尚未安装。使用命令安装sudo apt-get install fcitx-bin相关的依赖库和框架都会自动安装上。2.安装输入法sudo apt-get install fcitx-table3.配置fcitx安装完fcixt后需要把输入法系统更改成fcitx; 系统->区域和语言->管理已安装的语言->...