听说国人开发的RT-Thread OS 已经有一段时间了,一直没有尝试。毕竟学习一个新的平台花费的时间和精力非常多。这次来测试RT-Thread 的主要目的是希望使用它的动态库功能。现在Cortex-M 系列的Arm 处理器已经非常强大了。但是由于Cortex-M没有内存管理器。程序缺乏重定位的功能。所以难以实现类似linux OS 的共享库(lib.so )。而共享库对于系统的功能扩展非常重要。比如在控制器中,需要动态地导入功能块。如果没有共享库功能。那么新增加的程序库必须要和内核,OS 一起重新编译下载。实现远程添加,更新功能块就更加麻烦。所以,共享库对于控制设备的嵌入式软件而言是非常重要的。
初识RT-Thread
RT-Thread Studio 开发环境非常方便
RT-Thread 已经提供了一个一体化的开发环境RT-Thread Studio。使用起来十分方便。为了减少一开始设置的麻烦,购买了一个Art PI 开发板。开发一个闪灯程序非常快捷和顺利。
RT-Thread的 依赖项
RT-Thread 使用ST link 作为程序下载工具,需要下载和安装STlink,同时也是有Python 需要安装Python 2.7
RT-Thread 的msh
与其他嵌入式OS 不同,RT-Thread 提供了一个类似Linux shell 的命令工具, 前叫做msh。通过开发板上的USB 与PC 链接。你可以使用一个串口软件使用。以前好像叫做Finsh 。不知道为什么不直接叫做shell ?
摆脱不了 RT-Thread 的ENV 工具
以前RT-Thread 是使用ENV 工具开发的。目前开发动态模块,好像依然脱不了ENV 工具。它是是在windows cmd 下使用的。在相关的目录下 击右键,选择 conEMU Here 进入。
吐槽一下
也许是一些历史的包袱,RT-Thread 的某些概念,命名和使用方式有点古怪,与linux ,或者其他的开发环境不同。初学起来有点摸不着头脑。正如我所指出的。开发一个软件,要十分小心地起名字和术语。尽量符合社会化语义(也就是人们的习惯)。
RT-Thread 的另一个问题是国人开发的软件普遍存在一个问题,不是软件本身的技术不好,而是社区不好。遇到问题在网络上很难找到答案。网络上的除了原厂的文档以外,回答问题,相互讨论和分享经验比较少。大多数是文章是相互转发的,或者是自己的学习笔记,或者是培训老师的文案。说的话都一个样。一个地方出错,许多地方都是错。这是使用国内软件的最大困惑。
使用动态模块
主要参考【STM32H750】玩转ART-Pi(八)——添加动态模块 这篇博文。视乎没有遇到太大的问题。
源码来自于 rtthread-apps 里面包括了两个程序,一个是库lib,一个是主程序hello
lib.c
#include <stdio.h>
int lib_func(void)
{
printf("hello world from RTT::dynamic library!\n");
return 0;
}
int add_func(int a, int b)
{
return (a + b);
}
在hello目录中的main.c
#include <stdio.h>
#include <dlfcn.h>
#include <rtthread.h>
#define APP_PATH "/sdcard/lib.so"
typedef int (*add_func_t)(int, int);
typedef void (*lib_func_t)(void);
int dlmodule_sample(void)
{
void* handle;
lib_func_t lib_function;
add_func_t add_function;
handle = dlopen(APP_PATH,RTLD_LAZY);
if(!handle) {
printf("dlopen %s failed!\n",APP_PATH); return -1;
}
lib_function =
(lib_func_t)dlsym(handle,"lib_func");
if(!lib_function) {
printf("dlsym %p failed!\n",handle); return -1; }
lib_function();
add_function = (add_func_t)dlsym(handle,"add_func");
if(!add_function) { printf("dlsym %p failed!\n",handle); return -1; }
printf("add_function result is:%d\n",add_function(3,4));
dlclose(handle); return 0; }
MSH_CMD_EXPORT(dlmodule_sample, dlmodule sample);
int main(void) {
printf("rt-thread dynamic module Test!\n");
return 0;
}
仔细对比一下的话,我们可做了一点改动。
原来的 #define APP_PATH "/lib.so" 改成
#define APP_PATH "/sdcard/lib.so",也许某个地方设置一下使用/lib.so 也是可以的。
另外,不知道为什么
MSH_CMD_EXPORT(dlmodule_sample, dlmodule sample); 不起作用,按说这个语句可以将dlmount_sample 程序转变成为一条msh 命令。在msh 下运行这个程序。我后来直接在main 中调用dlmount_sample这个程序了。
编译的命令
set RTT_ROOT=C:\RT-ThreadStudio\workspace\art_pi_module\rt-thread
set BSP_ROOT=C:\RT-ThreadStudio\workspace\art_pi_module
scons --target=ua -s
scons --lib=lib
scons --app=hello
进一步的测试
【STM32H750】玩转ART-Pi(八)——添加动态模块 的文章中到此结束了,但是真正要使用动态模块,好像还有一些事情要做
在studio 的项目文件中调用lib.so 库
这个好像比较简单,将dlmount_sample 拷贝到了项目的main中。
/*
* Copyright (c) 2006-2020, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2020-09-02 RT-Thread first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"
#include <stdio.h>
#include <dlfcn.h>
#define LED_PIN GET_PIN(I, 8)
#define APP_PATH "/sdcard/lib.so"
typedef int (*add_func_t)(int, int);
typedef void (*lib_func_t)(void);
int dlmodule_sample(void)
{
void* handle;
lib_func_t lib_function;
add_func_t add_function;
handle = dlopen(APP_PATH,RTLD_LAZY);
if(!handle) {
printf("dlopen %s failed!\n",APP_PATH); return -1;
}
lib_function =
(lib_func_t)dlsym(handle,"lib_func");
if(!lib_function) {
printf("dlsym %p failed!\n",handle); return -1; }
lib_function();
//add function
add_function = (add_func_t)dlsym(handle,"add_func");
if(!add_function) { printf("dlsym %p failed!\n",handle); return -1; }
printf("add_function result is:%d\n",add_function(3,4));
dlclose(handle);
return 0;
}
int main(void)
{
rt_uint32_t count = 1;
printf("rt-thread dynamic module Test!\n");
rt_thread_mdelay(500);
dlmodule_sample();
rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
while(count++)
{
rt_thread_mdelay(500);
rt_pin_write(LED_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED_PIN, PIN_LOW);
}
return RT_EOK;
}
#include "stm32h7xx.h"
static int vtor_config(void)
{
/* Vector Table Relocation in Internal QSPI_FLASH */
SCB->VTOR = QSPI_BASE;
return 0;
}
INIT_BOARD_EXPORT(vtor_config);
在这里唯一的一个问题是运行时,先运行main 线程,在mount sdcard 所以一开始就open lib.so 文件时,找不到 sdcard 。所以我在调用 dlmount_sample 之前添加了一个
rt_thread_mdelay(500);
确保 sdcard 已经mount 成功了。
lib 调用外部函数
如何在lib程序中回调main 的函数。这里我们使用了C语言 callback 的方法。经测试是可行的。
lib.c
#include <stdio.h>
int lib_func(void)
{
printf("hello world from RTT::dynamic library!\n");
return 0;
}
int add_func(int a, int b)
{
return (a + b);
}
int handle(int y, int (*Callback)(int)){
printf("Entering Handle Function. \n");
Callback(y);
printf("Leaving Handle Function. \n");
return 0;
}
dlmodule_sample 子程序
#include <rtdevice.h>
#include "drv_common.h"
#include <stdio.h>
#include <dlfcn.h>
#define LED_PIN GET_PIN(I, 8)
#define APP_PATH "/sdcard/lib.so"
typedef int (*add_func_t)(int, int);
typedef void (*lib_func_t)(void);
typedef void (*handle_func_t)(int,int (*Callback)(int));
int Callback(int x) // Callback Function 1
{
printf("Hello, this is Callback_1: x = %d ", x);
return 0;
}
int dlmodule_sample(void)
{
void* handle;
lib_func_t lib_function;
add_func_t add_function;
handle_func_t handle_function;
handle = dlopen(APP_PATH,RTLD_LAZY);
if(!handle) {
printf("dlopen %s failed!\n",APP_PATH); return -1;
}
lib_function =
(lib_func_t)dlsym(handle,"lib_func");
if(!lib_function) {
printf("dlsym %p failed!\n",handle); return -1; }
lib_function();
//add function
add_function = (add_func_t)dlsym(handle,"add_func");
if(!add_function) { printf("dlsym %p failed!\n",handle); return -1; }
printf("add_function result is:%d\n",add_function(3,4));
// callback
handle_function = (handle_func_t)dlsym(handle,"handle");
if(!handle_function) { printf("dlsym %p failed!\n",handle); return -1; }
handle_function(100,&Callback);
dlclose(handle);
return 0;
}
结束语
完成了RT-Thread OS 外部模块调用的初步测试。外部模块调用对应嵌入式程序而言是很有用的,也许是因为RT-Thread 的外部模块功能使我爱上了RT-Thread。