如何在MCU上从程序内部执行单独编译的二进制文件



我有一个MCU(比如STM32)在运行,我想通过UART/USB"传递"一个单独编译的二进制文件,并像调用函数一样使用它,在那里我可以向它传递数据并收集它的输出?完成后,会发送第二个不同的二进制文件来执行,依此类推

我该怎么做?这需要运行操作系统吗?我想避免那些开销。

谢谢!

具体的调用函数是什么对mcu来说有些特殊,但您只是在进行函数调用。您可以尝试函数指针,但已知thumb(在gcc上)会失败(stm32使用arm中的thumb指令集)。

首先,您需要在整个系统设计中决定是否要为此代码使用特定地址。例如0x20001000。或者你想让其中几个同时驻留,并想将它们加载到多个可能的地址中的任何一个?这将决定如何链接此代码。这段代码是独立的吗?它想知道如何调用其他代码中的函数吗?所有这些都决定了如何构建此代码。最简单的,至少是第一次尝试,是一个固定的地址。像构建普通应用程序一样构建,但基于0x20001000这样的ram地址。然后加载发送到该地址的程序。

在任何情况下;呼叫";拇指中的一个函数(比如stm32)。是bl或blx指令。但通常在这种情况下,您会使用bx,但要进行呼叫,需要一个返回地址。arm/thumb的工作方式是,对于bx和其他相关指令,lsbit确定分支时切换/停留的模式。Lsbit集是拇指,Lsbit集是手臂。这一切都记录在arm文档中,该文档完全涵盖了你的问题BTW,不确定你为什么要问。。。

Gcc和我认为llvm很难做到这一点,然后一些用户知道这很危险,会做最糟糕的事情,添加一个(而不是ORRing),甚至试图把它放在那里。有时,把一个放在那里有助于编译器(如果你尝试使用函数指针方法,并希望编译器为你做所有的工作*myfun=0x10000之类的事情)。但这个网站上已经表明,你可以对代码进行细微的更改,或者根据具体情况,编译器会判断它是对是错,不看代码,你就必须帮助解决orr问题。就像大多数事情一样,当你需要一个确切的指导时,只需自己在asm中做这件事(请不要内联,使用real),让你的生活轻松一万倍。。。并且您的代码更加可靠。

因此,这里是我的琐碎解决方案,非常可靠,将asm移植到您的汇编语言。

.thumb
.thumb_func
.globl HOP
HOP:
bx r0

I C它看起来像这个

void HOP ( unsigned int );

现在,如果你加载到地址0x20001000,那么在加载之后

HOP(0x20001000|1);  

或者你可以

.thumb
.thumb_func
.globl HOP
HOP:
orr r0,#1
bx r0

然后

HOP(0x20001000);

编译器生成一个bl-to-hop,这意味着返回路径被覆盖。

如果你想发送一个参数。。。

.thumb
.thumb_func
.globl HOP
HOP:
orr r1,#1
bx r1
void HOP ( unsigned int, unsigned int );
HOP(myparameter,0x20001000);

简单且非常可靠,编译器不会把它搞砸。

如果你需要在主应用程序和下载的应用程序之间有函数和全局变量,那么有一些解决方案,它们涉及解析地址,如果加载的应用程序和主应用程序没有同时链接(进行复制和跳转以及单个链接通常很痛苦,应该避免,但是…),那么就像任何共享库一样,你需要有一个解析地址的机制。如果这个下载的代码有几个函数和全局变量,和/或你的主应用程序有下载库需要的几个函数和全球变量,那么你必须解决这个问题。从本质上讲,一方必须有一个地址表,双方在格式上达成一致,可以是一个简单的地址数组,双方只需从位置就知道哪个地址是哪个地址。或者,你创建了一个带有标签的地址列表,然后你必须在列表中搜索所有需要解决的问题,将名称与地址相匹配。例如,您可以使用上面的设置函数将数组/结构传递给它(跨编译域的结构当然是一件非常糟糕的事情)。然后,该函数设置指向主应用程序的所有本地函数指针和变量指针,以便该下载库中的后续函数可以调用主应用程序中的函数。反之亦然,第一个函数可以传回库中所有事物的数组结构。

可替换地,在下载的库中的已知偏移可以是阵列/结构,例如该下载的库的第一个字/字节。提供其中一个或另一个或两者,主应用程序可以找到所有的函数地址和变量,和/或调用者可以获得主应用程序的函数地址或变量,这样当一个调用另一个时,一切都正常。。。当然,这意味着函数指针和变量指针在两个方向上都可以工作。想想.so或.dll在linux或windows中是如何工作的,你必须自己复制它。

或者你同时进行链接,然后下载的代码必须与正在运行的代码一起构建,这可能是不可取的,但有些人这样做,或者出于各种原因,他们这样做是为了将代码从flash加载到ram。但这是在构建时解析所有地址的一种方法。然后从最终二进制文件中单独提取构建中的二进制文件的一部分,然后在稍后传递。

如果你不想要一个固定的地址,那么你需要将下载的二进制文件构建为与位置无关的,并且你应该将其与同一地址的.text、.bss和.data链接。

MEMORY
{
hello : ORIGIN = 0x20001000, LENGTH = 0x1000
}
SECTIONS
{
.text   : { *(.text*)   } > hello
.rodata : { *(.rodata*) } > hello
.bss    : { *(.bss*)    } > hello
.data   : { *(.data*)   } > hello
}

很明显,无论如何你都应该这样做,但在与位置无关的情况下,你可以将其与GOT一起打包(可能需要.get条目,但我认为它知道使用.data)。注意,如果你至少使用gnu将.data放在.bss之后并确保,即使它是一个你不使用的伪变量,也要确保你有一个.data then.bss为零填充并分配给你,无需在引导程序中设置它。

如果你建立了位置独立性,那么你几乎可以在任何地方加载它,显然是在手臂/拇指上,至少在单词边界上。

一般来说,对于其他指令集,函数指针的作用很好。在所有情况下,你只需查看处理器的文档,看到用于调用、返回或分支的指令,然后简单地使用该指令,无论是让编译器执行,还是强制执行正确的指令,这样你就不会让它在重新编译时失败(并进行非常痛苦的调试)。arm和mip具有16位模式,需要特定的指令或切换模式的解决方案。x86有不同的32位和64位模式以及切换模式的方法,但通常情况下,您不需要为类似的事情而处理这些问题。msp430,pic,avr,这些应该只是一个函数指针的东西,在C中应该可以正常工作。一般来说,做函数指针的事情,然后看看编译器生成了什么,并将其与处理器文档进行比较。(将其与非函数指针调用进行比较)。

如果你不知道函数指针、在mcu/处理器上链接裸机应用程序、引导程序、.text、.data等基本C概念,你需要去学习所有这些。

您决定切换到操作系统的时间是。。。。如果您需要一个文件系统、网络或一些类似的东西,而您只是不想自己这样做。现在肯定有lwip用于网络和一些嵌入式文件系统库。多线程也是操作系统,但如果你只想生成一个分支/跳转/调用指令,你就不需要操作系统了。只需生成调用/分支/任何内容。

加载和执行一个完全链接的二进制文件与加载和调用单个函数(并返回给调用者)实际上不是一回事。后者有些复杂;"动态链接";,其中代码在与调用方相同的执行环境中有效且安全。

另一方面,加载一个完整的独立可执行文件更简单,并且是引导加载程序的功能。引导加载程序加载并跳转到加载的可执行文件,然后该可执行文件建立自己的执行环境。返回到引导加载程序需要重置处理器。

在这种情况下,如果要频繁加载不同的代码,那么让引导加载程序在RAM中加载并执行代码是有意义的。但是,请注意,在像STM32这样的哈佛体系结构设备上,RAM执行可能会减慢执行速度,因为数据和指令获取共享同一总线。

引导加载程序的实际实现将取决于目标体系结构,但对于Cortex-M设备来说,这相当简单,并在其他地方处理。

STM32实际上包括一个片上引导加载程序(您需要配置引导源引脚来调用它),我相信它可以在RAM中加载和执行代码。它通常用于加载辅助引导加载程序以加载和编程闪存,但它可以用于加载任何代码。

您确实需要构建并链接您的代码,以便从加载程序定位的RAM中运行,或者如果支持,则构建可以从任何地方运行的独立代码。

最新更新