背景

通常,用户应用程序可以独立开发,即无需引导加载程序,并且可以加载到微控制器中并直接进行调试。但是,出于生产目的,值得将用户应用程序和引导加载程序合并在一起,因此可以将其作为一个文件一次性全部下载到微控制器中,从而减少了制造时间和成本。
本文档介绍了几种合并应用程序的方法
1、通过链接器命令进行应用程序的合并
2、使用第三方工具进行应用程序的合并
文档中描述的步骤使用S32K MCU进行介绍,其他系列的MCU如果使用arm-gcc可以使用类似的方法,由于编译器版本的差异使用的步骤有可能会存在差异请根据相应的编译器版本支持的相关命令进行修改。

基本概念

链接器文件(.ld)

通常,用户应用程序可以独立开发,即无需引导加载程序,并且可以加载到微控制器中并直接进行调试。但是,出于生产目的,值得将用户应用程序和引导加载程序合并在一起,因此可以将其作为一个文件一次性全部下载到微控制器中,从而减少了制造时间和成本。

内存段

存储器段用于将微控制器存储器分成多个段。每个段可以具有读取,写入和执行属性。还定义了每个段的地址和长度。清单1显示了一个示例。

  1. MEMORY
  2. {
  3. /* Flash */
  4. m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
  5. m_flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
  6. m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x0007FBF0
  7. /* SRAM_L */
  8. m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
  9. /* SRAM_U */
  10. m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
  11. }

节段

在节段中定义目标内存节的内容。换句话说,一个部分指示将在每个内存段中分配应用程序的哪些部分。主要部分是” .text”和” .bss”,其中” .text”包含应用程序的所有代码和常量,”.data”包含所有初始化的数据,”.bss”包含所有未初始化的数据。
您可以在下面看到使用K64的应用程序的” .text”部分。如您所见,它包含在”m_text”段中。

  1. /* The program code and other data goes into internal flash */
  2. .text :
  3. {
  4. . = ALIGN(4);
  5. *(.text) /* .text sections (code) */
  6. *(.text*) /* .text* sections (code) */
  7. *(.rodata) /* .rodata sections (constants, strings, etc.) */
  8. *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
  9. *(.glue_7) /* glue arm to thumb code */
  10. *(.glue_7t) /* glue thumb to arm code */
  11. *(.eh_frame)
  12. KEEP (*(.init))
  13. KEEP (*(.fini))
  14. . = ALIGN(4);
  15. } > m_text

合并两个应用程序

创建应用程序

在开始合并项目之前,我们需要使用S32DS创建2个新的裸机或SDK项目,选择S32K144作为设备。我们称它们为Application1和Application2。

Application1可能是Bootloader,例如,它将仅将蓝色LED切换5次。它将链接到默认内存地址0x0000,并以0xA000结尾(为引导加载程序保留的闪存空间取决于引导加载程序的大小)。

Application2应该是用户Application,例如,它将永久切换红色LED。它将链接到地址0xA000,并以0x0010_0000结尾。

这是如何对引导程序和应用程序进行编程的Flash布局:
image.png
在默认项目中,S32K144的内存段在链接器文件中定义如下:

  1. MEMORY
  2. {
  3. /* Flash */
  4. m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
  5. m_flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
  6. m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x0007FBF0
  7. /* SRAM_L */
  8. m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
  9. /* SRAM_U */
  10. m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
  11. }

首先,我们需要减小Application1中的内存大小以保留一些空间,并为Application2创建一个新的内存段,该内存段将从地址0xA000开始。打开位于”$ {ProjDirPath} / Project_Settings / Linker_Files”中的S32K144_64_flash.ld,MEMORY段在编辑后必须如下所示。

  1. MEMORY
  2. {
  3. /* Flash */
  4. m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
  5. m_flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
  6. m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x00009BF0
  7. app2_text (RX) : ORIGIN = 0x0000A000, LENGTH = 0x000F6000
  8. /* SRAM_L */
  9. m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
  10. /* SRAM_U */
  11. m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
  12. }
  13. [m_text] Bootloader space
  14. [app2_text] Application space


如前所述,Application2必须链接到地址0xA000,因此我们必须向所有闪存段添加偏移量0xA000。打开位于” $ {ProjDirPath} / Project_Settings / Linker_Files”中的S32K144_64_flash.ld,必须按以下方式编辑MEMORY段:

  1. MEMORY
  2. {
  3. /* Flash */
  4. m_interrupts (RX) : ORIGIN = 0x0000A000, LENGTH = 0x00000400
  5. m_flash_config (RX) : ORIGIN = 0x0000A400, LENGTH = 0x00000010
  6. m_text (RX) : ORIGIN = 0x0000A410, LENGTH = 0x0007FBF0
  7. /* SRAM_L */
  8. m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
  9. /* SRAM_U */
  10. m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
  11. }

生成Application2,然后查找Application2.map,您将找到” $ {ProjDirPath} / Debug”
打开Application2.map文件,并搜索Reset_Handler,这是应用程序入口点地址。如您所见,它是0x0000a4d8:
.text 0x0000a4d8 0x30 ./Project_Settings/Startup_Code/startup_S32K144.o
0x0000a4d8 Reset_Handler
现在回到Application1。我们将使该应用程序切换板载蓝色LED 5次,然后跳转到Application2的输入代码(Reset_Handler)。您可以使用以下代码:

  1. #define GPIO_PIN_MASK 0x1Fu
  2. #define GPIO_PIN(x) (((1)<<(x & GPIO_PIN_MASK)))
  3. void delay();
  4. int main(void)
  5. {
  6. int i;
  7. /* Turn on all port clocks */
  8. SIM_SCGC5 = SIM_SCGC5_PORTA_MASK | SIM_SCGC5_PORTB_MASK | SIM_SCGC5_PORTC_MASK | SIM_SCGC5_PORTD_MASK | SIM_SCGC5_PORTE_MASK;
  9. /*Set PTB21 (connected to BLUE LED) for GPIO functionality*/
  10. PORTB_PCR21=(0|PORT_PCR_MUX(1));
  11. /*Change PTB21 to output*/
  12. GPIOB_PDDR=GPIO_PDDR_PDD(GPIO_PIN(21));
  13. for(i = 0; i < 10; i++)
  14. {
  15. /*Toggle the blue LED on PTB21*/
  16. GPIOB_PTOR|=GPIO_PDOR_PDO(GPIO_PIN(21));
  17. delay();
  18. }
  19. __asm("bl 0x0000a4d8"); //Jump to Application2 entry point
  20. return 0;
  21. }
  22. void delay()
  23. {
  24. unsigned int i, n;
  25. for(i=0;i<10000;i++)
  26. {
  27. for(n=0;n<200;n++)
  28. {
  29. __asm("nop");
  30. }
  31. }
  32. }


下一步是使Application2永久切换红色LED。您可以使用以下代码:

  1. #define GPIO_PIN_MASK 0x1Fu
  2. #define GPIO_PIN(x) (((1)<<(x & GPIO_PIN_MASK)))
  3. void delay();
  4. int main(void)
  5. {
  6. /* Turn on all port clocks */
  7. SIM_SCGC5 = SIM_SCGC5_PORTA_MASK | SIM_SCGC5_PORTB_MASK | SIM_SCGC5_PORTC_MASK | SIM_SCGC5_PORTD_MASK | SIM_SCGC5_PORTE_MASK;
  8. /*Set PTB22 (connected to RED LED) for GPIO functionality*/
  9. PORTB_PCR22=(0|PORT_PCR_MUX(1));
  10. /*Change PTB22 to output*/
  11. GPIOB_PDDR=GPIO_PDDR_PDD(GPIO_PIN(22));
  12. while(1)
  13. {
  14. /*Toggle the RED LED on PTB22*/
  15. GPIOB_PTOR|=GPIO_PDOR_PDO(GPIO_PIN(22));
  16. delay();
  17. }
  18. return 0;
  19. }
  20. void delay()
  21. {
  22. unsigned int i, n;
  23. for(i=0;i<10000;i++)
  24. {
  25. for(n=0;n<200;n++)
  26. {
  27. __asm("nop");
  28. }
  29. }
  30. }

通过链接器命令合并应用程序

现在已经创建了两个应用程序,我们将使用链接器命令合并它们,首先我们需要生成Application2的二进制文件以将其插入到Application1中,然后按照附录A中所述的步骤进行操作。在此之后,复制将二进制文件放入Application1中的” Sources”文件夹中。
下一步是告诉Application1链接器包括Application2二进制文件。首先,您需要使用TARGET,INPUT和OUTPUT_FORMAT命令。您可以在MEMORY段之后和SECTIONS段之前执行此操作:

  1. MEMORY
  2. {
  3. /* Flash */
  4. m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
  5. m_flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
  6. m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x00009BF0
  7. app2_text (RX) : ORIGIN = 0x0000A000, LENGTH = 0x000F6000
  8. /* SRAM_L */
  9. m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
  10. /* SRAM_U */
  11. m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
  12. }
  13. TARGET(binary) /* specify the file format of binary file */
  14. INPUT (Application2.bin) /* provide the file name */
  15. OUTPUT_FORMAT(default) /* restore the out file format */


然后在SECTIONS段中添加一个新段,以告诉链接器在何处分配此二进制文件。您可以将其称为” .app2”部分,并将其放在” .data”部分之前。请注意,此部分包含在” my_text”段中。

  1. .app2 :
  2. {
  3. Application2.bin (.data)
  4. . = ALIGN (0x4);
  5. } > app2_text
  6. .data : AT(__DATA_ROM)
  7. {

最后,转到菜单>项目>属性> C / C ++构建>设置> ARM Ltd Windows GCC C链接器>库,然后在”库搜索路径(-L)”下添加Sources文件夹路径” $ {workspace_loc:/ $ {ProjName} / Sources},这样链接程序就可以找到Application2二进制文件。
现在,您可以使用”文件中的Flash …”选项来构建和编程应用程序。只需单击”文件中的Flash …”图标即可获得”Flash配置”菜单,选择您的连接以及Application1生成的.elf文件,然后单击” Flash”按钮。

重置您的电路板,您应该看到蓝色LED切换5次,这表示正在执行Application1,之后红色LED将开始切换,表示程序已跳至Application2。

使用工具合并应用程序


在按照第3.1节中描述的步骤创建了两个应用程序之后,生成了固件可以通过一些工具将两个文件进行合并,推荐一种开源的工具Srecord,这个工具有很多功能合并固件只是功能之一,更多的功能可以参考软件的使用手册,简单介绍合并应用程序的大概流程
image.png
image.png
image.png