RT-Thread MSH_CMD_EXPORT分析

RT-Thread MSH_CMD_EXPORT分析

1. 源码分析

在rt-thread中,使用FinSH,可以支持命令行。在源码中,使用MSH_CMD_EXPORT导出函数到对应命令。

extern void rt_show_version(void);
long version(void)
{
    rt_show_version();

    return 0;
}
MSH_CMD_EXPORT(version, show RT-Thread version information);

MSH_CMD_EXPORT是一个宏:

#define MSH_CMD_EXPORT(command, desc)   \
    MSH_FUNCTION_EXPORT_CMD(command, command, desc)

#define MSH_FUNCTION_EXPORT_CMD(name, cmd, desc)                      \
                const char __fsym_##cmd##_name[] rt_section(".rodata.name") = #cmd;    \
                const char __fsym_##cmd##_desc[] rt_section(".rodata.name") = #desc;   \
                rt_used const struct finsh_syscall __fsym_##cmd rt_section("FSymTab")= \
                {                           \
                    __fsym_##cmd##_name,    \
                    __fsym_##cmd##_desc,    \
                    (syscall_func)&name     \
                };

嵌套定义为MSH_FUNCTION_EXPORT_CMD

这里的rt_section也是一个宏:

#define rt_section(x)               __attribute__((section(x)))

在ARM中,这是编译器识别的一个符号。用来指定编译后数据存放的位置。

这里相当于是定义__fsym_version_name__fsym_version_desc,将其放到.rodata.name段中。这两个字符串分别是命令对应的名称和描述。又定义了一个结构体__fsym_version,用来存放命令的名称,描述和函数指针。

struct finsh_syscall
{
    const char     *name;       /* the name of system call */
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
    const char     *desc;       /* description of system call */
#endif
    syscall_func func;      /* the function address of system call */
};

函数指针指向的函数和命令同名。将定义的finsh_syscall放到FSymTab段中。

没导出一个命令,就会在.rodata.name段中多两个字符串,FSymTab段中多一个struct finsh_syscall结构体。

导出所有需要的命令后,这里FSymTab可以看做是一个数组,元素类型是struct finsh_syscall,长度是所有命令的总和。

2. map文件

编译时,可以指定生成.map文件。KEIL默认会输出map文件到编译目录。

在map文件中搜索__fsym_version,可以找到version命令的名称和描述字符串变量的链接地址和段位置。链接地址是:0x0800ff690x0800ff71,链接段是.rodata.name,与前面分析一致。可以看到上面和下面确实也是其它命令的名称和描述。

在这里插入图片描述

还能搜索到__fsym_version 结构体的链接地址和段。地址是0x080100c4,段是FSymTab。这里可以看到,所有命令的结构体都存到这个段的,间隔也是正好是12个字节,和struct finsh_syscall结构体长度一致。看这个情况,应该是照编译时的按顺序摆放所有结构体到这个段中。

在这里插入图片描述

这里通过编译时,将这个段的起始地址给到msh,然后通过查这个表来对比命令的名称,匹配上了,就执行相应的函数指针,从而就能够执行对应的命令的函数。

查表:

static cmd_function_t msh_get_cmd(char *cmd, int size)
{
    struct finsh_syscall *index;
    cmd_function_t cmd_func = RT_NULL;
    for (index = _syscall_table_begin;
            index < _syscall_table_end;
            FINSH_NEXT_SYSCALL(index))
    {
        if (strncmp(index->name, cmd, size) == 0 &&
                index->name[size] == '\0')
        {
            cmd_func = (cmd_function_t)index->func;
            break;
        }
    }
    return cmd_func;
}

_syscall_table_begin_syscall_table_end 变量对应就是FSymTab 段的起始地址。

void finsh_system_function_init(const void *begin, const void *end)
{
    _syscall_table_begin = (struct finsh_syscall *) begin;
    _syscall_table_end = (struct finsh_syscall *) end;
}
int finsh_system_init(void)
{
	extern const int FSymTab$$Base;
    extern const int FSymTab$$Limit;
    finsh_system_function_init(&FSymTab$$Base, &FSymTab$$Limit);
}

这里这两个全局变量找不到定义的位置。查找资料得知,FSymTab$$Base表示FSymTab段的开始地址,FSymTab$$Limit表示FSymTab段的结束地址。

参考:https://www.cnblogs.com/King-Gentleman/p/4573652.html

3. bin文件

前面分析得到了__fsym_version_name__fsym_version_desc的地址,分别是0x0800ff690x0800ff71__fsym_version 的地址是0x080100c40x08开始的地址表示ROM上的地址,即FLASH地址空间。

打开编译生成的rtthread.bin文件,搜索versionversion符号出现的地址正好是ff69,是字符串 “version”,紧接着是描述部分内容 “show RT-Thread version information”。由于是bin文件,是相对地址,因此地址前面没有0x08

在这里插入图片描述

在跳到100c4地址:

在这里插入图片描述

这里开始的12个字节,对应的就是__fsym_version结构体中各个字段的内容。注意大小端转换,命令的名称地址69 ff 00 08,即0x0800ff69,描述对应的地址是71 ff 00 08,即0x0800ff71。函数指针对应的地址是4d ea 00 08,即0x0800ea4d,和map文件中链接的地址一致。

在这里插入图片描述

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RT-Thread作品秀】通用型数据采集设备作者:鱼柯 概述(说明应用产生的背景、实现功能)在一些低频采集设备中,典型的运行策略是,采集数据,上传数据,关闭外设进入睡眠状态;但是,在运行过程中,需要根据实际需求,更改采集频率,连接不同的设备,如果每次通过修改代码解决,通用性就很难保证,这个项目将一些uart型的传感器进行归类,通过文件设置数据交互过程中的命令,解析方式等,可以适配大多数的uart型传感器;同时,对一些网络摄像头也以同样的方式进行处理; 实现数据采集调度配置,数据采集,数据上传,图片采集,图片上传, 配置文件解析,固件远程更新;由于contab配置文件中的event使用的是MSH_CMD_EXPRT宏导出的命令。所以,它也支持系统需要定时执行的相对时间间隔需要变化的任务,比如:12:00. 13:10, 15:35, 18:23分别执行一次任务; 开发环境(所采用的软、硬件方案)硬件:art-pi, INDUSTRY-IO, 微气象仪, 网络摄像头 RT-Thread版本:rt-thread 4.0.3 开发工具及版本:ubuntu 18.04,gcc-arm-none-eabi-6_2-2016q4,scons v3.0.1,python 3.6.9,pkgs RT-Thread使用情况概述(简要总结下应用中RT-Thread使用情况:内核部分、组件部分、软件包部分、内核、其他)内核部分Inter-thread communication Event Semaphore mutex memory management device object 组件部分Finsh DFS (device virtual file system) serial device, mtd nor flash device, gpio device, ntp rtc device, sd/mmc device, spi device, serial flash universal driver (device driver) posix layer and c stand library SAL (socket abstraction layer) ping, ifconfig, netstat, netdev (network interface) LwIP 2.0.2 Ymodem ulog 软件包部分agile_console-v1.0.0 fal-v0.5.0 ota_downloader-v1.0.0 agile_telnet-v2.0.0 littlefs-v2.2.1 SignalLed-latest cJSON-v1.0.2 netutils-v1.2.0 vi-latest EasyFlash-v4.1.0 webclient-v2.1.1 硬件框架(概述应用所采用的硬件方案框图,并对核心部分做介绍)软件框架说明(介绍应用所采用的软件方案框图、流程图等,并加以解说)软件模块说明(介绍应用软件关键部分的逻辑、采用的实现方式等)类似 linux定时任务contab解析相关json配置文件,构建设备运行数据树: "contab": [{"event":"misc_check","time":"0 18"},{"event":"img_cap_start","time":"20 7,9,14"},{"event":"app_image_upload","time":"20 7,9,14"},{"event":"sensor_acq_start","time":"5,10,15,20,25,30,35,40,45,50,55 *"},{"event":"app_data_upload","time":"5,10,15,20,25,30,35,40,45,50,55 *"}] 事件执行分钟: 表示xx:5, xx:25, xx:30, xx:36, xx:45, xx:54 事件执行小时:*通配符,表示1-24小时 上面参数表示:每个小时的5,25,30,45,54分,执行img_upload_invl事件; 上传数据每次采集数据后,将数据存在本地一个缓存文件中,按照采集时间从前到后写入;同时会生成一个读取位置的缓存文件指示,下一次从哪个文件的那一行读取数据进行上传,上传成功后,更新读取位置的缓存;如20201217,227, 表示从文件20201217.txt的227个字符后读取一行数据进行上传,避免文件过大引起设备死机; 上传图片每次拍照时,将拍照成功的照片名及端口追加记录到一个缓存文件中,每次从缓存文件中,读取需要上传的图片构造form-data上传图片;如4,/sd/1608167012_4.jpg;如果上传成功,则删
RT-Thread作品秀】Art-pi机械臂控制作者:李志青 概述5G时代物联走入更多行业与领域,使用广和通的L610在模块,通过MQTT与中国移动onenet互联,也尝试搭建了本地MQTT服务器,适合内部布署,其再加入柿饼派两块,结合体感手套与机械臂,数据进行通讯,实现远程控制。 开发环境硬件:art-pi,广和通4G模块,柿饼派,体感手套,SG90舵机 RT-Thread版本:4.0.3 开发工具及版本:RT-Thread Studio, STM32CubeMX, Arduino IDE1.8.5,PersimmonUIBuilder RT-Thread使用情况概述ulog:日志标红打印,更显眼 pahomqtt:与中国移动OneNet进行通讯 at_device:建立与广和通L610通讯 onnet:加密授权与中国移动OneNet配置信息一致 pwm:控制sg90舵机 uart:运用了三个,分别是uart1(控制台使用),uart4(与广和通L610通讯),uart6(接收柿饼派串口信息) 硬件框架请见附件word文件 体感手套使用arduino板子,串口通讯至第一块柿饼派 第一块柿饼派通过MQTT通讯到第二块柿饼派 第二块柿饼派通过串口与art-pi通讯 Art-pi与舵机,广和通L610通讯相互 软件框架说明请见附件word文件开启串口并指定相应引脚 开启多个组件,并做具体配置 开启线程 开启指定GPIO并指定引脚 软件模块说明具体软件模块: Uart串口通讯主要在main,c: /*数据解析线程---发送串口指令*/ staticvoiddata_parsing(void) { charch; chardata[ONE_DATA_MAXLEN]; staticchari = 0; while(1) { ch = uart_sample_get_char(); rt_device_write(serial, 0, &ch, 1); if(ch == DATA_CMD_END) { data[i++] ='\0'; rt_kprintf("data=%s\r\n",data); i = 0; if(strcmp(data,"0") == 0) { UserPWM_0_5msStart();// 0度 } if(strcmp(data,"45") == 0) { UserPWM_1_0msStart();// 45度 } if(strcmp(data,"90") == 0) { UserPWM_1_5msStart();// 90度 } if(strcmp(data,"135") == 0) { UserPWM_2_0msStart();// 135度 } if(strcmp(data,"180") == 0) { UserPWM_2_5msStart();// 180度 } //UserPWM_0_5msStart(); continue; } i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i; data[i++] = ch; } } Pwm控制主要在drv_pwm.c: /*封装舵机控制函数,即pwm */ voidUserPWM_Init(void) { MX_TIM3_Init(); } MSH_CMD_EXPORT(UserPWM_Init, user pwm init); voidUserPWM_Stop(void) { HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); } MSH_CMD_EXPORT(UserPWM_Stop, user pwm stop); voidUserPWM_0_5msStart(void) { HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,500); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3); } MSH_CMD_EXPORT(UserPWM_0_5msStart, angle zero); voidUserPWM_1_0msStart(void) { HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_3); __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_3,1000); HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3); } MSH_CMD_EXPORT(UserPWM_1_0

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值