C语言 如何在单片机上执行独立编译的二进制文件?

vsmadaxz  于 2022-12-11  发布在  其他
关注(0)|答案(2)|浏览(188)

我有一个正在运行的MCU(比如STM32),我想通过UART/USB向它“传递”一个单独编译的二进制文件,并像调用函数一样使用它,在这里我可以向它传递数据并收集其输出。在它完成后,将发送另一个不同的二进制文件以供执行,依此类推。
我该怎么做?这需要运行操作系统吗?我希望避免这种开销。
谢谢你!

gr8qqesn

gr8qqesn1#

具体的调用函数是什么是MCU特有的,但你只是在进行函数调用。你可以尝试函数指针的事情,但已知失败的thumb(在gcc上)(stm32使用来自arm的thumb指令集)。
首先,您需要在整个系统设计中决定是否要为该代码使用特定的地址。例如0x 20001000。或者您是否要同时驻留多个这些代码,并将它们加载到多个可能地址中的任何一个?这将决定如何链接该代码。该代码是独立的吗?或者它想知道如何调用其他代码中的函数?所有这些都决定了你如何构建这段代码。最简单的方法,至少首先要尝试一下,是一个固定的地址。像你构建你的普通应用程序一样构建,但是基于一个像0x 20001000这样的ram地址。然后你在那个地址加载发送给你的程序。
在任何情况下,在thumb中“调用”函数的正常方式(比如stm32)。是bl或blx指令。但通常在这种情况下,你会使用bx,但要使它成为一个调用,需要一个返回地址。arm/thumb的工作方式是,对于bx和其他相关指令,lsbit决定你在分支时切换/停留的模式。Lsbit set是thumb lsbit clear是arm。这一切都记录在手臂文档中,其中完全涵盖了您的问题顺便说一句,不知道您为什么要问...
Gcc和我认为llvm很难做到这一点,然后一些用户知道这是危险的,并做了最糟糕的事情添加一个(而不是对一执行“或”运算),甚至尝试将一放在那里。有时候将一放在那里有助于编译器(如果您尝试使用函数指针方法,并希望编译器为您完成所有工作 *myfun = 0x 10000之类的事情,则会出现这种情况)。但是在这个网站上已经表明,你可以对代码做一些细微的修改,或者根据具体的情况,编译器会得到正确或错误的结果,而不需要看代码,你必须帮助做一件事。(请不要内联,请使用真实的),让您的生活轻松一万倍......而且您的代码明显更可靠。
这是我的简单解决方案,非常可靠,将asm移植到汇编语言。

.thumb
.thumb_func
.globl HOP
HOP:
   bx r0

I C它看起来像这样

void HOP ( unsigned int );

现在,如果您加载到地址0x 20001000,则在加载之后

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中是如何工作的,你必须自己复制。

或者你同时走链接的路,那么下载的代码必须和正在运行的代码沿着构建,这可能是不可取的,但有些人这样做,或者出于各种原因,他们这样做是为了将代码从闪存加载到RAM。但这是一种在构建时解析所有地址的方法。然后从最终的二进制文件中单独提取构建中的二进制文件的一部分,并在以后传递它。
如果不需要固定地址,则需要构建与位置无关的下载二进制文件,并且应该在同一地址将其与.text、.bss和.data链接。

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

你显然应该这样做无论如何,但与位置无关,然后你有它的所有 Package 沿着GOT(可能需要一个.got条目,但我认为它知道使用.data).注意,如果你把.data放在.bss之后,至少要有gnu,并确保,即使它是一个你不使用的假变量,也要确保你有一个.data,那么.bss是为你填充零并分配的,不需要在 Bootstrap 中设置它。
如果你为位置独立而构建,那么你几乎可以在任何地方加载它,显然在手臂/拇指上,至少在单词边界上。
一般来说,对于其他指令集来说,函数指针的作用就很好了。在所有情况下,你只要看看处理器的文档,看看指令(s)用于调用和返回或分支并简单地使用该指令,可以让编译器执行,也可以强制执行正确的指令,这样就不会在重新编译时出现错误(并且有一个非常痛苦的调试)。arm和mips有16位模式,需要特定的指令或解决方案来切换模式。x86有不同的模式32位和64位以及切换模式的方法,但通常你不需要为了这样的事情而乱来。msp430,pic,avr,这些应该只是一个函数指针的东西在C中应该工作得很好。一般来说,做函数指针的东西,然后看看编译器生成什么,并与处理器文档进行比较。(将它与非函数指针调用进行比较)。
如果你不知道函数指针、在MCU/处理器上链接裸机应用程序、 Bootstrap 、.text、.data等这些基本的C概念,你需要去学习所有这些。
你决定切换到操作系统的时候是....如果你需要一个文件系统,网络,或者一些你不想自己做的事情。现在肯定有网络的lwip和一些嵌入式文件系统库。还有多线程,然后是操作系统。但是如果你想做的只是生成一个分支/跳转/调用指令,那么你不需要操作系统来完成这件事。2只需要生成调用/分支/任何指令。

tgabmvqs

tgabmvqs2#

加载和执行一个完全链接的二进制文件和加载和调用一个函数(并返回到调用者)实际上并不是一回事。后者有些复杂,涉及到“动态链接”,其中代码在与调用者相同的执行环境中有效和安全。
另一方面,加载一个完整的独立可执行文件则更为直接,这是引导加载程序的功能。引导加载程序加载并跳转到加载的可执行文件,然后建立自己的执行环境。返回引导加载程序需要处理器复位。
在这种情况下,如果您要频繁加载不同的代码,则让引导加载程序在RAM中加载和执行代码是有意义的。但是,请注意,在STM32等哈佛Architecture设备上,RAM执行可能会减慢执行速度,因为数据和指令获取共享同一总线。
引导加载程序的实际实现将取决于目标架构,但对于Cortex-M设备来说,这是相当简单的,在其他地方处理。
STM32实际上包含一个片上引导加载程序(需要配置引导源引脚才能调用它),我相信它可以加载和执行RAM中的代码,它通常用于加载二级引导加载程序来加载和编程闪存,但它可以用于加载任何代码。
您确实需要构建和链接代码,以便从加载程序定位的地址的RAM运行,或者如果支持构建可以从任何位置运行的位置无关的代码。

相关问题