资料介绍
描述
介绍
本文中的“app”是指预编译的二进制文件,无需使用 Arduino IDE,即可直接在 Arduino 板卡上运行。
并且因为它是一个文件,“应用程序”可以通过 SD 卡、以太网、WiFi 或任何合适的方法分发。
标题图显示了执行 Arduino 应用程序RTT-QRCode的 MKR ZERO 板。
你有兴趣吗?
(本文基于Arduino RT-Thread库v0.6.0。)
动态模块
在 RT-Thread 架构中,“app”被称为动态模块,构建为动态共享库,扩展名为“ .mo
”或“ .so
”。(什么是 RT-Thread?=> Arduino 上的多任务处理)
RT-Thread 提供 API 来访问动态模块。更有趣的是,MSH(一个微型外壳)能够.mo
直接执行“”文件(详细信息在以下部分中)。
RT-Thread 的原始动态链接器似乎不适用于 ARM Cortex-M。所以我修改了 Arduino RT-Thread 库的代码。
多发性硬化症
Module SHell (MSH) 是默认启用的一项新功能(从 v0.5.1 开始),它构建在 FinSH 之上。(什么是 FinSH?=> Arduino 上的多任务处理)
由于 Arduino 应用程序是由 MSH 执行的,让我们简单介绍一下。
相比 FinSH,MSH 更符合 Unix shell 的使用习惯:
- 在 FinSH 中发出命令
led(0, 1)
copy("datalog.txt", "copy.txt")
- 在 MSH 中发出命令
led 0 1
cp datalog.txt copy.txt
但是,MSH 不支持像 FinSH 提供的那样的 shell 变量。
另一个限制是用户定义的 MSH 命令的原型是固定的:
int my_msh_cmd(int argc, char **argv)
MSH 执行用户命令时,参数argc
为参数个数加一,参数列表argv
为参数列表(firstentryiscommandname)。您可能已经猜到了,所有参数只能是char
数组类型。
以下是 MSH 命令格式的“led”示例。
int led(int argc, char **argv) {
// argc - the number of arguments
// argv[0] - command name, e.g. "led"
// argv[n] - nth argument in the type of char array
rt_uint32_t id;
rt_uint32_t state;
if (argc != 3) {
rt_kprintf("Usage: led \n");
return 1;
}
rt_kprintf("led%s=%s\n", argv[1], argv[2]);
// convert arguments to their specific types
sscanf(argv[1], "%u", &id);
sscanf(argv[2], "%u", &state);
if (id != 0) {
rt_kprintf("Error: Invalid led ID\n");
return 1;
}
if (state) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
}
return 0;
}
制作 Arduino 应用程序
首先,CONFIG_USING_MODULE
在“rtconfig.h”中启用,因为默认情况下它是禁用的。
构建可执行文件
让我们在 Arduino IDE 中打开“HelloMo”示例,然后按“验证”。(该示例也可以在下面的“代码”部分中找到。)代码现在被构建到一个包含草图和库的单个可执行文件中。我们可以使用 GCC 工具readelf
(Arduino IDE 提供)来验证。
{path_to_gcc_tools}\arm-none-eabi-readelf -h {path_to_output}\HelloMo.ino.elf
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0xf7fd
Start of program headers: 52 (bytes into file)
Start of section headers: 798052 (bytes into file)
Flags: 0x5000002, has entry point, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 2
Size of section headers: 40 (bytes)
Number of section headers: 18
Section header string table index: 15
如果您不确定 GCC 工具和编译输出的位置,请在 File-> Preferences 中启用以下选项。
再次单击“验证”,您将在输出窗口中观察到信息:
...
Compiling sketch...
"C:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1/bin/arm-none-eabi-gcc" -mcpu=cortex-m0plus -mthumb -c -g -Os -Wall -Wextra -std=gnu11 -ffunction-sections -fdata-sections -nostdlib --param max-inline-insns-single=500 -MMD -DF_CPU=48000000L -DARDUINO=10809 -DARDUINO_SAMD_MKRZERO -DARDUINO_ARCH_SAMD -DUSE_ARDUINO_MKR_PIN_LAYOUT -D__SAMD21G18A__ -DUSB_VID=0x2341 -DUSB_PID=0x804f -DUSBCON "-DUSB_MANUFACTURER="Arduino LLC"" "-DUSB_PRODUCT="Arduino MKRZero"" "-IC:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\CMSIS\\4.5.0/CMSIS/Include/" "-IC:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\CMSIS-Atmel\\1.1.0/CMSIS/Device/ATMEL/" "-IC:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\samd\\1.6.21\\cores\\arduino" "-IC:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\samd\\1.6.21\\variants\\mkrzero" "-IC:\\Users\\onelife\\Documents\\Arduino\\libraries\\RT-Thread\\src" "-IC:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\hardware\\samd\\1.6.21\\libraries\\SPI" "C:\\Users\\onelife\\AppData\\Local\\Temp\\arduino_build_508434\\sketch\\hello_mo.c" -o "C:\\Users\\onelife\\AppData\\Local\\Temp\\arduino_build_508434\\sketch\\hello_mo.c.o"
...
就我而言,GCC 工具位于“ C:\\Users\\onelife\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1/bin/
”,编译输出位于“ C:\\Users\\onelife\\AppData\\Local\\Temp\\arduino_build_508434\\
”。
构建应用程序(动态共享库)
但是,我们要构建的目标“应用程序”是一种共享库。它必须与位置无关,因此可以加载到任何 RAM 地址中。为了让它更小(因为我们的 RAM 大小是有限的),最终的二进制文件将不包含其他库的任何功能。(所有外部功能都应由固件端提供。)
坏消息是 Arduino IDE 不提供这些选项。好消息是 Arduino IDE 确实提供了我们需要的所有工具。我们开始做吧。
第一步是编译。
我们必须将选项“ -mlong-calls -fPIC
”添加到原始编译命令中(在输出窗口中查找“正在编译草图...”)。
{path_to_gcc_tools}\arm-none-eabi-gcc -mlong-calls -fPIC ... {path_to_output}\sketch\hello_mo.c -o {path_to_output}\sketch\hello_mo.c.o
{path_to_gcc_tools}\arm-none-eabi-gcc -mlong-calls -fPIC ... {path_to_output}\sketch\load_mo.c -o {path_to_output}\sketch\load_mo.c.o
第二步是链接。
在这一步中,我们选择将目标文件构建为“ app ”(带有入口点的“.mo”文件)或将其构建为库(不带入口点的“.so”文件)。在以下示例中,我们将“ load_mo.c.o
”构建为“app”,并将“ hello_mo.c.o
”构建为库。
我们通过以下方式修改链接命令(寻找“将所有内容链接在一起......”)
-
只保留目标目标文件,例如“
load_mo.c.o
”,并删除其他 -
删除选项“
-Wl,--unresolved-symbols=report-all
” -
删除选项“
-L{path_to_output}
” -
删除选项“
-T.../flash_with_bootloader.ld
” -
删除选项“
-Wl,--start-group ... -Wl,--end-group
” -
添加选项“
-shared -fPIC -nostdlib -Wl,-marmelf -Wl,-z,max-page-size=0x4
” -
添加入口点选项(例如“
-Wl,-eload_hello
”或“-Wl,-e0
”表示无)
{path_to_gcc_tools}\arm-none-eabi-g++ -shared -fPIC -nostdlib -Wl,-e0 -Wl,-marmelf -Wl,-z,max-page-size=0x4 ... -o {path_to_output}\hello_mo.elf {path_to_output}\hello_mo.c.o
{path_to_gcc_tools}\arm-none-eabi-g++ -shared -fPIC -nostdlib -Wl,-eload_hello -Wl,-marmelf -Wl,-z,max-page-size=0x4 ... -o {path_to_output}\load_mo.elf {path_to_output}\load_mo.c.o
第三步是分条。
为了进一步减小文件大小,我们必须去掉 ELF 文件中不必要的部分。
{path_to_gcc_tools}\arm-none-eabi-strip -R .hash -R .comment -R .ARM.attributes {path_to_output}\hello_mo.elf -o {path_to_output}\hello.so
{path_to_gcc_tools}\arm-none-eabi-strip -R .hash -R .comment -R .ARM.attributes {path_to_output}\load_mo.elf -o {path_to_output}\load.mo
第四步是检查大小(可选)。
{path_to_gcc_tools}\arm-none-eabi-size {path_to_output}\hello.so
{path_to_gcc_tools}\arm-none-eabi-size {path_to_output}\load.mo
恭喜!您刚刚构建了一个 Arduino 应用程序。让我们检查一下输出。
{path_to_gcc_tools}\arm-none-eabi-readelf -h {path_to_output}\hello.so
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: ARM
Version: 0x1
Entry point address: 0x0
Start of program headers: 52 (bytes into file)
Start of section headers: 896 (bytes into file)
Flags: 0x5000000, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 3
Size of section headers: 40 (bytes)
Number of section headers: 9
Section header string table index: 8
{path_to_gcc_tools}\arm-none-eabi-readelf -h {path_to_output}\load.mo
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: ARM
Version: 0x1
Entry point address: 0x285
Start of program headers: 52 (bytes into file)
Start of section headers: 1060 (bytes into file)
Flags: 0x5000002, has entry point, Version5 EABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 3
Size of section headers: 40 (bytes)
Number of section headers: 9
Section header string table index: 8
它表明“ .so
”和“ .mo
”文件都是“ DYN
”(动态)类型。不同之处在于“ .so
”文件没有入口点,而“ .mo
”文件有。
我们还没有完成。
最后一步是公开应用程序所需的功能。
在文件“ ”中,如果启用mo_sym.h
,所有内核 API 都已经公开。CONFIG_USING_MODULE
如有必要,您可以添加自己的。
发出 MSH 命令“ lsym
”将列出所有暴露的符号:
运行 Arduino 应用程序
hello.so
让我们将“ ”和“ ”复制load.mo
到具有以下文件结构的SD卡。
SD_ROOT/
├── lib/
│ └── hello.so
└── mo/
└── load.mo
规则是,如果我们将相对路径传递给dlopen()
或 MSH,它将分别在和中查找“ .so
”和“ ” 。.mo
/lib/
/mo/
现在我们将卡插入 Arduino 板,在本例中为 MRKZERO ,上传“HelloMo”草图(草图什么都不做),然后发出命令“ load
”。
为了显示有关应用程序执行过程的更多详细信息,我们可以在“ dlmodule.c
”中启用调试消息:
#define LOG_LVL LOG_LVL_DBG
结果揭示了以下过程:
-
MSH 线程 ("tshell") 将 "
load.mo
" 加载到 RAM 并创建一个新线程 ("load") 以执行入口点函数 "load_hello()
" -
“
load_hello()
”然后加载“hello.so
”,调用它的“module_init()
”函数,调用它的“say_hello()
”函数(不是入口点) -
在 "
say_hello()
" 返回后, "load_hello()
" 关闭 "hello.so
" (调用它的 "module_cleanup()
" 函数然后销毁它的 RAM 副本) -
“加载”线程标记以销毁“”的RAM副本,
load.mo
然后退出 -
" " 的 RAM 副本
load.mo
最终被空闲线程 ("tidle0") 销毁
" module_init()
" 和 " module_cleanup()
" 是特殊函数。如果定义,前者.mo
在将应用程序加载到 RAM 后由 MSH 线程(在“”文件的情况下)调用,后者.mo
在销毁 RAM 副本之前由空闲线程(在“”文件的情况下)调用。
让我们将“hello_mo.c”重建为一个应用程序(入口点是“ say_hello()
”,例如-Wl,-esay_hello
)并执行。
结果清楚地表明“ module_init()
”被MSH线程(“tshell”)module_cleanup()
调用,“ ”被空闲线程(“tidle0”)调用。顺便说一句,传递给这两个函数的参数是指向模块描述符的指针。
优点缺点
优点
(我们感兴趣的是)Arduino 应用程序可以构建一次并在许多板上运行。根据 Wiki ,“可用于 Cortex-M0 / Cortex-M0+ / Cortex-M1 的二进制指令无需修改即可在 Cortex-M3 / Cortex-M4 / Cortex-M7 上执行。可用于 Cortex-M3 的二进制指令无需修改即可执行在 Cortex-M4 / Cortex-M7 / Cortex-M33 / Cortex-M35P 上进行修改。”
因此,为 MKR Zero 板(SAMD 架构)构建的应用程序应该在 Arduino Due(SAM 架构)上运行而不会出现问题。
此功能可以实现远程添加或更新功能而无需重新启动(与 OTA 固件更新相比)等。
缺点
与 MSH 命令相比,Arduino 应用程序需要更多 RAM。另一个主要缺点是在固件方面,应用程序所需的所有外部功能都必须在那里待机(尽管固件可能不会使用它们)并暴露(在“ mo_sym.h
”中)。
动态模块状态
Arduino RT-Thread 库 v0.6.0 中的功能仍处于 beta 阶段。
在原始代码(RT-Thread 项目)中,除了DYN
ELF 文件的类型,动态链接器还支持REL
类型。但是,经过一些测试,我发现至少对于 ARM Cortex-M 架构,只有“ .o
”(对象)文件类型为REL
. 所以目前REL
Arduino RT-Thread 库不支持 ELF 文件类型。
此外,仅测试了两种重新分配类型:
-
R_ARM_JUMP_SLOT
-
R_ARM_RELATIVE
我需要一些代码来测试其他类型。因此,如果您遇到其他类型的错误,请帮助提出问题。
最后,并非所有“libgcc”函数都默认公开。例如,开关助手功能没有公开。您可以将它们添加到“ mo_sym.h
”或在您的应用程序中将“ switch...case...
”替换为“ ” 。if...else...
RTT-QRCode 应用程序
有一个更复杂的示例RTT-QRCode ,可以构建为 MSH 命令或 Arduino 应用程序。请查看代码并玩得开心!
下一步
- Arduino上的多任务处理
- 带有 RT-Thread 的更好的 SD 库
- RT-Thread Primer(即将推出)
- 带有RT-Thread的更好的SD库
- RT-Thread文档_workqueue
- RT-Thread文档_ringbuffer
- RT-Thread文档_completion
- RT-Thread文档_RT-Thread SMP 介绍与移植
- RT-Thread文档_内核基础
- RT-Thread文档_RT-Thread 潘多拉 STM32L475 上手指南
- RT-Thread文档_RT-Thread 简介
- RT-Thread Smart 上手指南
- 【RT-Thread开源作品秀】基于RT-Thread的星务平台研究
- RT-Thread AI kit开源:轻松实现一键部署AI模型至 RT-Thread
- 嵌入式RT-Thread应用与开发 71次下载
- 嵌入式实时操作系统RT-Thread的特点与体系结构及移植方法详细说明 27次下载
- RT-Thread用户手册 0次下载
- RT-Thread编程指南 0次下载
- 基于 RT-Thread专业版的EtherCAT主站方案 429次阅读
- RT-Thread qemu mps2-an385 bsp移植制作 :系统运行篇 675次阅读
- i.MX RT1170:VGLite移植RT-Thread Nano过程讲解(下) 738次阅读
- i.MX RT1170:VGLite移植RT-Thread Nano过程讲解(上) 2094次阅读
- rt-studio潘多拉开发板最新rt-thread不能运行解决办法 1064次阅读
- RT-Thread自动初始化机制 2362次阅读
- RT-Thread 4.1.0的CMake构建教程 3129次阅读
- 如何使用xmake工具来编译rt-thread工程 2090次阅读
- RT-Thread v4.1.0中FAL介绍 3523次阅读
- 如何创建RT-Thread Nano工程 3572次阅读
- 如何在rt-smart简化应用程序开发 1832次阅读
- 简要分析Thread的通用GPIO设备驱动 1413次阅读
- RT-Thread NetUtils的使用方法 7848次阅读
- 如何创建标准的RT-Thread项目工程?详细过程分析概述 9102次阅读
- RT-Thread软件包定义和使用 1w次阅读
下载排行
本周
- 1山景DSP芯片AP8248A2数据手册
- 1.06 MB | 532次下载 | 免费
- 2RK3399完整板原理图(支持平板,盒子VR)
- 3.28 MB | 339次下载 | 免费
- 3TC358743XBG评估板参考手册
- 1.36 MB | 330次下载 | 免费
- 4DFM软件使用教程
- 0.84 MB | 295次下载 | 免费
- 5元宇宙深度解析—未来的未来-风口还是泡沫
- 6.40 MB | 227次下载 | 免费
- 6迪文DGUS开发指南
- 31.67 MB | 194次下载 | 免费
- 7元宇宙底层硬件系列报告
- 13.42 MB | 182次下载 | 免费
- 8FP5207XR-G1中文应用手册
- 1.09 MB | 178次下载 | 免费
本月
- 1OrCAD10.5下载OrCAD10.5中文版软件
- 0.00 MB | 234315次下载 | 免费
- 2555集成电路应用800例(新编版)
- 0.00 MB | 33566次下载 | 免费
- 3接口电路图大全
- 未知 | 30323次下载 | 免费
- 4开关电源设计实例指南
- 未知 | 21549次下载 | 免费
- 5电气工程师手册免费下载(新编第二版pdf电子书)
- 0.00 MB | 15349次下载 | 免费
- 6数字电路基础pdf(下载)
- 未知 | 13750次下载 | 免费
- 7电子制作实例集锦 下载
- 未知 | 8113次下载 | 免费
- 8《LED驱动电路设计》 温德尔著
- 0.00 MB | 6656次下载 | 免费
总榜
- 1matlab软件下载入口
- 未知 | 935054次下载 | 免费
- 2protel99se软件下载(可英文版转中文版)
- 78.1 MB | 537798次下载 | 免费
- 3MATLAB 7.1 下载 (含软件介绍)
- 未知 | 420027次下载 | 免费
- 4OrCAD10.5下载OrCAD10.5中文版软件
- 0.00 MB | 234315次下载 | 免费
- 5Altium DXP2002下载入口
- 未知 | 233046次下载 | 免费
- 6电路仿真软件multisim 10.0免费下载
- 340992 | 191187次下载 | 免费
- 7十天学会AVR单片机与C语言视频教程 下载
- 158M | 183279次下载 | 免费
- 8proe5.0野火版下载(中文版免费下载)
- 未知 | 138040次下载 | 免费
评论
查看更多