Code and Data Relocation 是 Zephyr 中的一个强大的功能。此功能允许把某些源文件中代码的 .text、.rodata、.data 和 .bss 字段放置在指定的内存区域中。这里的源文件可以是一个单独的 xxx.c 文件,也可以是一个库文件。指定的内存可以是片内的 RAM 也可以是片外的 QSPI flash。nRF connect SDK 通过脚本 zephyr/scripts/build/gen_relocate_app.py 来实现这一功能。将代码放置到片外 flash 直接通过 XIP 运行,目前只适用于 nRF52840 和 nRF5340 两款芯片。XIP是外设 QSPI 的一部分,通过普通 SPI 扩展的 flash 不能通过 XIP 直接运行代码。由于在 nRF52840 上, Errata [215] 和 Errata [216] 会对 XIP 的使用造成很大的局限。所以我们更推荐在 nRF5340 上使用这个功能。
准备工作
- 本文使用 NCS 2.9.1 中的例程代码 zephyr\samples\application_development\code_relocation_nocopy。
- 硬件使用 nRF5340DK
例程代码 code_relocation_nocopy 分析
- 此例程中主要的文件如下图。其中 main.c 在片内 flash 上运行。ext-code.c 在片外的 QSPI flash 运行。sram_code.c 在片上的 ram 中运行。
- 仔细看 main.c 里的内容。里面只有两个函数 int main(void) 和 void disable_mpu_rasr_xn(void)。函数 disable_mpu_rasr_xn 的作用是配置 MPU 使得代码可以从 SRAM 中运行。main 函数里调用了 function_in_ext_flash() 和 function_in_sram()。
/** This function will allow execute from sram region. This is needed only for* this sample because by default all soc will disable the execute from SRAM.* An application that requires that the code be executed from SRAM will have* to configure the region appropriately in arm_mpu_regions.c.*/ #ifdef CONFIG_ARM_MPU #include <cmsis_core.h> void disable_mpu_rasr_xn(void) {uint32_t index;/** Kept the max index as 8(irrespective of soc) because the sram would* most likely be set at index 2.*/for (index = 0U; index < 8; index++) {MPU->RNR = index; #if defined(CONFIG_ARMV8_M_BASELINE) || defined(CONFIG_ARMV8_M_MAINLINE)if (MPU->RBAR & MPU_RBAR_XN_Msk) {MPU->RBAR ^= MPU_RBAR_XN_Msk;} #elseif (MPU->RASR & MPU_RASR_XN_Msk) {MPU->RASR ^= MPU_RASR_XN_Msk;} #endif /* CONFIG_ARMV8_M_BASELINE || CONFIG_ARMV8_M_MAINLINE */} } #endif /* CONFIG_ARM_MPU */extern void function_in_ext_flash(void); extern void function_in_sram(void);int main(void) { #ifdef CONFIG_ARM_MPUdisable_mpu_rasr_xn(); #endif /* CONFIG_ARM_MPU */printk("Address of %s function %p\n", __func__, &main);function_in_ext_flash();function_in_sram();printk("Hello World! %s\n", CONFIG_BOARD);return 0; }
- 再来看 ext_code.c,里面只有一个函数 function_in_ext_flash。
#include <zephyr/kernel.h> #include <zephyr/sys/printk.h>uint32_t var_ext_sram_data = 10U;void function_in_ext_flash(void) {printk("Address of %s %p\n", __func__, &function_in_ext_flash);printk("Address of var_ext_sram_data %p (%d)\n", &var_ext_sram_data, var_ext_sram_data); }
- 我sram_code.c里面同样只有一个很简单的函数 function_in_sram。
#include <zephyr/kernel.h> #include <zephyr/sys/printk.h>uint32_t var_sram_data = 10U;void function_in_sram(void) {printk("Address of %s %p\n", __func__, &function_in_sram);printk("Address of var_sram_data %p (%d)\n", &var_sram_data, var_sram_data); }
- 从刚才的代码里,看上去和普通的代码没有什么不同。为什么函数 function_in_sram 和 function_in_ext_flash 可以分别从 SRAM 和外部 FLASH 里运行呢?带着这个疑问我们再来看 CMakeLists.txt。在CMakeLists.txt 里有这样的调用。
zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY) 。
上面这个函数的作用是把文件 ext_code.c 中的 TXT 字段重定位到 EXTFLASH 所在的区域,执行前不需要先把代码拷贝到 SRAM。
# SPDX-License-Identifier: Apache-2.0cmake_minimum_required(VERSION 3.20.0)find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(code_relocation_nocopy)FILE(GLOB app_sources src/*.c) target_sources(app PRIVATE ${app_sources})# Run ext_code from the external flash (XIP). No need to copy. zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY)# But still relocate (copy) the data to RAM zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM_DATA)# sram_code instead runs entirely from SRAM after being copied there. zephyr_code_relocate(FILES src/sram_code.c LOCATION RAM)
我们再来详细解释一下这个函数 zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY) 。
- FILES 表明的是目标类型。可选的类型除了 FILES 还可以是 LIBRARY
- src/ext_code.c 是目标的名字或者路径。如果上一个参数是 LIBRARY 这里就是库的名字。如果上一个参数选 FILES 这里可以是一个或多个源文件的路径。
- EXTFLASH_TEXT 是 目标区域_字段 目标区域 EXTFLASH 是在 linker.ld 中已经定义的区域。字段 TEXT 表示把 .text 字段的内容重新定位。除了 TEXT 还有 DATA, RODATA, BSS 可以选择。
- NOCOPY 是附加选项。除了 NOCOPY 还有 NOKEEP。
- 接下来我们来看一下 prj.conf 文件里的内容。其中 CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y 和 CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_nocopy.ld" 为此代码的链接器指定了一个脚本文件。
CONFIG_CODE_DATA_RELOCATION=y CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_nocopy.ld" CONFIG_BUILD_NO_GAP_FILL=y CONFIG_COVERAGE=n CONFIG_XIP=y CONFIG_LOG=y CONFIG_FLASH=y CONFIG_NORDIC_QSPI_NOR=y CONFIG_NORDIC_QSPI_NOR_XIP=y
我们再来看一下下面的链接脚本 linker_arm_nocopy.ld 里面主要的内容就是定义了 EXTFLASH 的起始地址和长度。 在刚才CMakeLists.txt 里调用 zephyr_code_relocate 时 EXTFLASH 作为输入参数规定了重定位的目标地址。
#include <zephyr/linker/sections.h> #include <zephyr/devicetree.h>#include <zephyr/linker/linker-defs.h> #include <zephyr/linker/linker-tool.h>/* On nRF5340, external flash is mapped in XIP region at 0x1000_0000. */#define EXTFLASH_NODE DT_INST(0, nordic_qspi_nor) #define EXTFLASH_ADDR 0x10000000 #define EXTFLASH_SIZE DT_PROP_OR(EXTFLASH_NODE, size_in_bytes, \DT_PROP(EXTFLASH_NODE, size) / 8)MEMORY {EXTFLASH (rx) : ORIGIN = EXTFLASH_ADDR, LENGTH = EXTFLASH_SIZE }#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
- 我们用编译指令 west build -p -b nrf5340dk/nrf5340/cpuapp 编译代码。我们可以看一个名为 EXTFLASH 的外部 flash 分区,大小是 8M bytes。
-- west build: building application [5/10] Performing build step for 'sdk_291_code_relocation' [4/166] Generating include/generated/zephyr/version.h -- Zephyr version: 3.7.99 (C:/ncs/v2.9.1/zephyr), build: v3.7.99-ncs2-1 [166/166] Linking C executable zephyr\zephyr.elf Memory region Used Size Region Size %age UsedEXTFLASH: 56 B 8 MB 0.00%FLASH: 41580 B 1 MB 3.97%RAM: 9032 B 448 KB 1.97%IDT_LIST: 0 GB 32 KB 0.00% Generating files from D:/ncs/sdk_291_code_relocation/build/sdk_291_code_relocation/zephyr/zephyr.elf for board: nrf5340dk [10/10] Generating ../merged.hex
- 编译完成后使用指令 west flash 烧写代码。
(v2.9.1) D:\ncs\sdk_291_code_relocation> west flash -- west flash: rebuilding [0/5] Performing build step for 'sdk_291_code_relocation' ninja: no work to do. [4/5] cmd.exe /C "cd /D D:\ncs\sdk_291_code_relocation\build\_sysbuild && C:\ncs\toolchains\b620d30767\opt\bin\cmake.exe -E true" -- west flash: using runner nrfjprog -- runners.nrfjprog: reset after flashing requested Using board 1050042793 -- runners.nrfjprog: Flashing file: D:\ncs\sdk_291_code_relocation\build\merged.hex [ #################### ] 0.000s | Erasing non-volatile memory - Erase successful [ #################### ] 2.435s | Erase file - Done erasing [ #################### ] 0.663s | Program file - Done programming [ #################### ] 0.539s | Verify file - Done verifying Applying pin reset. -- runners.nrfjprog: Board with serial number 1050042793 flashed successfully.
- 连接串口查看 log。 log 里面显示了函数和变量所在的地址。其中 function_in_ext_flash 的地址是以 0x10000000 开头的,这个地址是外部 flash 在 XIP 里映射的起始地址。
*** Booting nRF Connect SDK v2.9.1-60d0d6c8d42d *** *** Using Zephyr OS v3.7.99-ca954a6216c9 *** Address of main function 0x219 Address of function_in_ext_flash 0x10000001 Address of var_ext_sram_data 0x200000a0 (10) Address of function_in_sram 0x20000001 Address of var_sram_data 0x200000a4 (10) Hello World! nrf5340dk
在 BLE 例程中使用 Code And Data Relocation
分析完刚才的例程,我们总结一下把一个文件里的代码放到外部 flash 运行需要三步:
- 在 prj.conf 里添加例程中的配置选项。
- 在 CMakeList.txt 里添加需要外部运行的 xxx.c 文件,并用 zephyr_code_relocate 把它放到外部的 flash 里。
- 添加 linker script。
- 下面我们就修改例程 nrf\samples\bluetooth\peripheral_lbs ,把它的部分代码放到外部 flash 里运行。
- 在 prj.conf 里添加例程中的配置选项。
CONFIG_CODE_DATA_RELOCATION=y CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_nocopy.ld" CONFIG_BUILD_NO_GAP_FILL=y CONFIG_COVERAGE=n CONFIG_XIP=y CONFIG_FLASH=y CONFIG_NORDIC_QSPI_NOR=y CONFIG_NORDIC_QSPI_NOR_XIP=y
- 修改 CMakeList.txt,添加代码 ext_code.c。并且使用 zephyr_code_relocate 把 ext_code.c 中的 TEXT 部分放到了分区 EXTFLASH 中。把 ext_code.c 中的 DATA 部分放到了分区 RAM 中。
# # Copyright (c) 2018 Nordic Semiconductor # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(NONE) # NORDIC SDK APP START target_sources(app PRIVATEsrc/main.csrc/ext_code.c ) # Run ext_code from the external flash (XIP). No need to copy. zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY)# But still relocate (copy) the data to RAM zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM_DATA) # NORDIC SDK APP END
- 添加文件 linker_arm_nocopy.ld, 里面的内容如下。
#include <zephyr/linker/sections.h> #include <zephyr/devicetree.h>#include <zephyr/linker/linker-defs.h> #include <zephyr/linker/linker-tool.h>#if defined(CONFIG_NORDIC_QSPI_NOR) && defined(CONFIG_SOC_NRF5340_CPUAPP)/* On nRF5340, external flash is mapped in XIP region at 0x1000_0000. */#define EXTFLASH_NODE DT_INST(0, nordic_qspi_nor) #define EXTFLASH_ADDR 0x10000000 #define EXTFLASH_SIZE DT_PROP_OR(EXTFLASH_NODE, size_in_bytes, \DT_PROP(EXTFLASH_NODE, size) / 8)#endifMEMORY {EXTFLASH (rx) : ORIGIN = EXTFLASH_ADDR, LENGTH = EXTFLASH_SIZERAM (wx) : ORIGIN = 0x20000000, LENGTH = 0x60000RAM2 (wx) : ORIGIN = 0x20060000, LENGTH = 0x10000 }#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
这些修改完全按照 zephyr\samples\application_development\code_relocation_nocopy 里的内容。经过指令 west build -p -b nrf5340dk/nrf5340/cpuapp 编译后,我们可以看到下面的编译信息。里面多了一个分区 EXTFLASH 这个就是外部 flash 的区域。EXTFLASH 中已经使用的56个字节,存放了 ext_code.c 中的 TEXT 部分。
-- west build: building application [9/20] Performing build step for 'sdk_291_peripheral_lbs' [6/259] Generating include/generated/zephyr/version.h -- Zephyr version: 3.7.99 (C:/ncs/v2.9.1/zephyr), build: v3.7.99-ncs2-1 [259/259] Linking C executable zephyr\zephyr.elf Memory region Used Size Region Size %age UsedEXTFLASH: 56 B 8 MB 0.00%FLASH: 129916 B 1008 KB 12.59%RAM: 26892 B 448 KB 5.86%IDT_LIST: 0 GB 32 KB 0.00% Generating files from D:/ncs/sdk_291_peripheral_lbs/build/sdk_291_peripheral_lbs/zephyr/zephyr.elf for board: nrf5340dk [11/20] Performing build step for 'ipc_radio' [4/202] Generating include/generated/zephyr/version.h -- Zephyr version: 3.7.99 (C:/ncs/v2.9.1/zephyr), build: v3.7.99-ncs2-1 [202/202] Linking C executable zephyr\zephyr.elf Memory region Used Size Region Size %age UsedFLASH: 168848 B 256 KB 64.41%RAM: 46560 B 64 KB 71.04%SRAM1: 0 GB 64 KB 0.00%IDT_LIST: 0 GB 32 KB 0.00% Generating files from D:/ncs/sdk_291_peripheral_lbs/build/ipc_radio/zephyr/zephyr.elf for board: nrf5340dk [20/20] Generating ../merged_CPUNET.hex
运行编译好的代码,我们可以看到下面的 log 。从 log 中我们可以看到 main 函数的运行地址是 0x755,这是片内 flash 的地址。 函数 function_in_ext_flash 的运行地址是 0x10000001,这个地址是我们 XIP 映射的片外 flash 的地址。
*** Booting My Application v2.9.1-e115fed8f669 *** *** Using nRF Connect SDK v2.9.1-60d0d6c8d42d *** *** Using Zephyr OS v3.7.99-ca954a6216c9 *** Starting Bluetooth Peripheral LBS example Address of main 0x755 Address of function_in_ext_flash 0x10000001 Address of var_ext_sram_data 0x200001a8 (10) I: 2 Sectors of 4096 bytes I: alloc wra: 0, fd0 I: data wra: 0, 1c I: HW Platform: Nordic Semiconductor (0x0002) I: HW Variant: nRF53x (0x0003) I: Firmware: Standard Bluetooth controller (0x00) Version 121.4259 Build 3078678206 I: No ID address. App must call settings_load() Bluetooth initialized I: Identity: F2:23:7E:40:9D:67 (random) I: HCI: version 6.0 (0x0e) revision 0x209a, manufacturer 0x0059 I: LMP: version 6.0 (0x0e) subver 0x209a Advertising successfully started
- 刚才我们只是把 ext_code.c 这个文件中的 TEXT 放到了外部 flash 中运行。下面我们通过修改 CMakeList.txt 把整个 app 库中的代码放到外部 flash 上。app 库中包含了 main.c 和 ext_code.c 两个文件。我们仍然使用 zephyr_code_relocate 来重定位代码。和之前的区别是,第一个参数使用 LIBRARY 来取代之前的 FILES ,表示现在是要对库进行重定位。
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(NONE) # NORDIC SDK APP START target_sources(app PRIVATE src/main.c src/ext_code.c ) # Run ext_code from the external flash (XIP). No need to copy. #zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY) # But still relocate (copy) the data to RAM #zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM_DATA) zephyr_code_relocate(LIBRARY app LOCATION EXTFLASH_TEXT NOCOPY) zephyr_code_relocate(LIBRARY app LOCATION RAM_DATA) # NORDIC SDK APP END
编译之后的编译信息显示,和之前相比代码占用外部 flash 空间明显变大。从之前的56个字节,增大到现在的896个字节。
-- west build: building application [9/20] Performing build step for 'sdk_291_peripheral_lbs' [6/259] Generating include/generated/zephyr/version.h -- Zephyr version: 3.7.99 (C:/ncs/v2.9.1/zephyr), build: v3.7.99-ncs2-1 [259/259] Linking C executable zephyr\zephyr.elf Memory region Used Size Region Size %age UsedEXTFLASH: 896 B 8 MB 0.01%FLASH: 129212 B 1008 KB 12.52%RAM: 26892 B 448 KB 5.86%IDT_LIST: 0 GB 32 KB 0.00% Generating files from D:/ncs/sdk_291_peripheral_lbs/build/sdk_291_peripheral_lbs/zephyr/zephyr.elf for board: nrf5340dk [11/20] Performing build step for 'ipc_radio' [4/202] Generating include/generated/zephyr/version.h -- Zephyr version: 3.7.99 (C:/ncs/v2.9.1/zephyr), build: v3.7.99-ncs2-1 [202/202] Linking C executable zephyr\zephyr.elf Memory region Used Size Region Size %age UsedFLASH: 168848 B 256 KB 64.41%RAM: 46560 B 64 KB 71.04%SRAM1: 0 GB 64 KB 0.00%IDT_LIST: 0 GB 32 KB 0.00% Generating files from D:/ncs/sdk_291_peripheral_lbs/build/ipc_radio/zephyr/zephyr.elf for board: nrf5340dk [20/20] Generating ../merged_CPUNET.hex
从运行输出的 log ,main 函数的地址大于 0x10000000 ,它运行在 XIP 映射的外部 flash 的地址范围。
*** Booting My Application v2.9.1-e115fed8f669 *** *** Using nRF Connect SDK v2.9.1-60d0d6c8d42d *** *** Using Zephyr OS v3.7.99-ca954a6216c9 *** Starting Bluetooth Peripheral LBS example Address of main 0x10000165 Address of function_in_ext_flash 0x10000001 Address of var_ext_sram_data 0x200001a8 (10) I: 2 Sectors of 4096 bytes I: alloc wra: 0, fd0 I: data wra: 0, 1c I: HW Platform: Nordic Semiconductor (0x0002) I: HW Variant: nRF53x (0x0003) I: Firmware: Standard Bluetooth controller (0x00) Version 121.4259 Build 3078678206 I: No ID address. App must call settings_load() Bluetooth initialized I: Identity: F2:23:7E:40:9D:67 (random) I: HCI: version 6.0 (0x0e) revision 0x209a, manufacturer 0x0059 I: LMP: version 6.0 (0x0e) subver 0x209a Advertising successfully started
- Code And Data Relocation 可以对 .text, .rodata, .data, .bss 等多个字段重定位。之前的演示中我们展示了对 .text 和 .bss 的重定位。下面我们来演示如何对 .rodata 和 .bss 字段重定位。我们修改了 ext_code.c ,和之前的代码相比下面的代码添加了两个变量。一个是 var_data 另一个是 ext_flash_string。var_data 在编译后会被放到 .bss 字段。ext_flash_string 会被放到 .rodata 字段。
#include <zephyr/kernel.h> #include <zephyr/sys/printk.h> uint32_t var_ext_sram_data = 10U; uint32_t var_data; const char * const ext_flash_string = "Hello from external flash!"; void function_in_ext_flash(void) { var_data = 0; printk("Address of %s %p\n", __func__, &function_in_ext_flash); printk("Address of var_ext_sram_data %p (%d)\n", &var_ext_sram_data, var_ext_sram_data); printk("Address of var_data %p (%d)\n", &var_data, var_data); printk("Address of ext_flash_string %p (%s)\n", &ext_flash_string, ext_flash_string); }
这里我们来说一下这4个字段都是怎么划分的。
字段名 | 存储内容 | 读写权限 | 典型示例 |
.text | 可执行代码 | 只读 | 指令函数 |
.rodata | 只读常量 | 只读 | const 数据、字符串常量 |
.data | 已初始化全局/静态变量 | 可读写 | int x = 5 |
.bss | 未初始化全局/静态变量 | 可读写 | int y |
修改 CMakeList.txt 把 ext_code.c 里的这4个字段都重新定位,其中 text 和 rodata 里的内容被放到了外部的 flash 里。
# NORDIC SDK APP START target_sources(app PRIVATEsrc/main.csrc/ext_code.c ) # Run ext_code from the external flash (XIP). No need to copy. zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY) # But still relocate (copy) the data to RAM zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM_DATA) zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM_BSS) zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_RODATA NOCOPY) #zephyr_code_relocate(LIBRARY app LOCATION EXTFLASH_TEXT NOCOPY) #zephyr_code_relocate(LIBRARY app LOCATION RAM_DATA) # NORDIC SDK APP END
从运行输出的 log,我们可以看到 rodata 字段的常量 ext_flash_string 的地址为 0x10000080, 在外部 flash 上。
*** Booting My Application v2.9.1-e115fed8f669 *** *** Using nRF Connect SDK v2.9.1-60d0d6c8d42d *** *** Using Zephyr OS v3.7.99-ca954a6216c9 *** Starting Bluetooth Peripheral LBS example Address of main 0x755 Address of function_in_ext_flash 0x10000001 Address of var_ext_sram_data 0x200001a8 (10) Address of var_data 0x20001400 (0) Address of ext_flash_string 0x10000080 (Hello from external flash!) I: 2 Sectors of 4096 bytes I: alloc wra: 0, fd0 I: data wra: 0, 1c I: HW Platform: Nordic Semiconductor (0x0002) I: HW Variant: nRF53x (0x0003) I: Firmware: Standard Bluetooth controller (0x00) Version 121.4259 Build 3078678206 I: No ID address. App must call settings_load() Bluetooth initialized I: Identity: F2:23:7E:40:9D:67 (random) I: HCI: version 6.0 (0x0e) revision 0x209a, manufacturer 0x0059 I: LMP: version 6.0 (0x0e) subver 0x209a Advertising successfully started
-
- 我们把 ext_code.c 里的 text 和 rodata 的内容都放在了外部 flash 里,但是 data 和 bss 字段仍然和其它文件中的变量放在相同的 RAM 分区里。有时候我们希望把 ext_code.c 里的变量单独放在一个 RAM 的分区上,下面我们就来演示如何单独划分一个 RAM 分区 RAM2,然后把 ext_code.c 里的变量重新定位到 RAM2 里。
首先我们先修改 linker_arm_nocopy.ld 来定义分区 RAM2。 RAM2 的起始地址为 0x20060000。
#include <zephyr/linker/sections.h> #include <zephyr/devicetree.h>#include <zephyr/linker/linker-defs.h> #include <zephyr/linker/linker-tool.h>#if defined(CONFIG_NORDIC_QSPI_NOR) && defined(CONFIG_SOC_NRF5340_CPUAPP)/* On nRF5340, external flash is mapped in XIP region at 0x1000_0000. */#define EXTFLASH_NODE DT_INST(0, nordic_qspi_nor) #define EXTFLASH_ADDR 0x10000000 #define EXTFLASH_SIZE DT_PROP_OR(EXTFLASH_NODE, size_in_bytes, \DT_PROP(EXTFLASH_NODE, size) / 8)#endifMEMORY {EXTFLASH (rx) : ORIGIN = EXTFLASH_ADDR, LENGTH = EXTFLASH_SIZERAM (wx) : ORIGIN = 0x20000000, LENGTH = 0x60000RAM2 (wx) : ORIGIN = 0x20060000, LENGTH = 0x10000 }#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
接下来我们修改 CMakeList.txt 把 ext_code.c 中的 data 和 bss 字段重新定位到 RAM2 里。
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(NONE) # NORDIC SDK APP START target_sources(app PRIVATE src/main.c src/ext_code.c ) # Run ext_code from the external flash (XIP). No need to copy. zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_TEXT NOCOPY) # But still relocate (copy) the data to RAM zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM2_DATA) zephyr_code_relocate(FILES src/ext_code.c LOCATION RAM2_BSS) zephyr_code_relocate(FILES src/ext_code.c LOCATION EXTFLASH_RODATA NOCOPY)
修改完成后编译,烧写,查看 log。我们可以看到 var_ext_sram_data 的地址是 0x20060000, var_data 的地址是 0x200060004。正好在我们之前定义的 RAM2 上。
*** Booting My Application v2.9.1-ce414a3d8014 *** *** Using nRF Connect SDK v2.9.1-60d0d6c8d42d *** *** Using Zephyr OS v3.7.99-ca954a6216c9 *** Starting Bluetooth Peripheral LBS example Address of main 0x755 Address of function_in_ext_flash 0x10000001 Address of var_ext_sram_data 0x20060000 (10) Address of var_data 0x20060004 (0) Address of ext_flash_string 0x10000080 (Hello from external flash!) I: 2 Sectors of 4096 bytes I: alloc wra: 0, fd0 I: data wra: 0, 1c I: HW Platform: Nordic Semiconductor (0x0002) I: HW Variant: nRF53x (0x0003) I: Firmware: Standard Bluetooth controller (0x00) Version 121.4259 Build 3078678206 I: No ID address. App must call settings_load() Bluetooth initialized I: Identity: F2:23:7E:40:9D:67 (random) I: HCI: version 6.0 (0x0e) revision 0x209a, manufacturer 0x0059 I: LMP: version 6.0 (0x0e) subver 0x209a Advertising successfully started
外部 flash 的烧写
之前我们烧写程序都是通过 west flash 来实现的。无论程序是在片内 flash 还是在片外 flash,都是通过这条指令来烧写。下面我们就分析一下这是怎么实现的。
- 当我们使用 west flash 时 python 脚本会调用指令烧写当前工程目录下的 build/merged.hex 文件。我们打开这个文件看一下,可以找到下面的内容。
:020000041000EA :1000000008B5064A0649074800F012F8BDE808405E :10001000054906480A6800F00BB800BF010000104F :1000200083B201004BB20100A80100205DB20100C3 :080030005FF800F02953010004 :00000001FF
这个 hex 文件采用的 Intel hex 格式。由多行 ASCII 文本组成,每行称为一个 record。每一行由格式为 :LLAAAATT[DD...]CC 的文本组成。
- :记录起始符
- LL 数据长度(1 字节),表示
DD
的字节数(十六进制,范围00
–FF
) - AAAA 地址域(2 字节),表示数据加载的起始偏移地址
- TT 记录类型(1 字节),定义记录功能(见下表)
- DD 数据域(长度由
LL
指定),存储实际二进制数据的十六进制 ASCII 表示 - CC 校验和(1 字节),用于验证记录完整性
类型(TT) | 名称 | 功能说明 | 数据域(DD)内容 |
00 |
数据记录(Data) | 存储实际程序代码或常量数据,需加载到 AAAA 指定地址 |
二进制数据 |
01 | 文件结束记录(EOF) | 标记文件终止,必须出现在末尾 | 无(LL=00) |
02 | 扩展段地址记录(ESA) | 分段内存模型。在纯32位系统中,ESA几乎被ELA完全取代。 | |
04 | 扩展线性地址记录(ELA) | 定义后续数据记录的高 16 位线性基址(用于 32 位地址) | 高 16 位地址(如 0100 ) |
03 /05 |
起始地址记录 | 指定程序起始执行地址(03 为段地址模式,05 为线性地址模式) |
CS 或 EIP 值 |
依据上面的说明中我们可以解读之前 merged.hex 文件中的内容。可以看出这段数据是在以 0x10000000 起始的外部 flash 空间。:020000041000EA 表示下面的数据的起始地址是 0x10000000。:1000000008B5064A0649074800F012F8BDE808405E 表示在地址 0x10000000 由16个字节的数据。
merged.hex 里面我们包含了地址位于外部 flash 我们再来看 zephyr\scripts\west_commands\runners\nrf_common.py 这个文件。 当我们执行 west flash 命令行的时候会调用 nrf_common.py 里的 program_hex。我们看一下 program_hex 里的内容。program_hex 会检测 merged.hex 里包含的内容,如果包含片外 flash 的内容, qspi_erase_opt 会被赋值 ERASE_ALL。接下来调用的参数会根据 qspi_erase_opt 的值来操作外部的 flash。west flash 的底层使用的是 nrfjprog。
xip_ranges = {'NRF52_FAMILY': (0x12000000, 0x19FFFFFF),'NRF53_FAMILY': (0x10000000, 0x1FFFFFFF),}qspi_erase_opt = Noneif self.family in xip_ranges:xip_start, xip_end = xip_ranges[self.family]if self.hex_refers_region(xip_start, xip_end):qspi_erase_opt = 'ERASE_ALL'# What tool commands do we need to flash this target?if self.family == 'NRF53_FAMILY':# nRF53 requires special treatment due to the extra coprocessor.self.program_hex_nrf53(erase_arg, qspi_erase_opt)else:self.op_program(self.hex_, erase_arg, qspi_erase_opt, defer=True, core=core)self.flush(force=False)def program_hex_nrf53(self, erase_arg, qspi_erase_opt):
看到这里大家会有疑问,外部的 flash 通过 qspi 接口来驱动,不同的 flash 参数不同怎么办?这里有两种方法来修改 flash 驱动的参数。
- 用户可以在 CMakeList.txt 中来设定初始化 flash 所需要的参数的文件。下面的 CMakeList.txt 中指定了 Qspi_thingy53.ini 这个文件来描述 flash 驱动的参数。
cmake_minimum_required(VERSION 3.20.0) macro(app_set_runner_args) if(CONFIG_BOARD_THINGY53_NRF5340_CPUAPP) # Use alternative QSPI configuration file when flashing Thingy53 board_runner_args(nrfjprog "--qspiini=${CMAKE_CURRENT_SOURCE_DIR}/Qspi_thingy53.ini") endif() endmacro() find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
- 如果直接使用 nrfjprog 来烧写外部 flash, 用户可以使用 --qspiini 来设定初始化 flash 所需要的参数文件。下面是 nrfjprog 的帮助文档中关于 --qspiini 这个参数的说明。
--qspiini <file> Deprecated: Use a TOML file with --config instead.Uses the QSPI settings file specified in the givenfile ini path instead of searching for the defaultconfig.toml file in the installation folder.Must be combined with either --erasepage, --memrd,--memwr, --program, --verify, --readqspi or--qspieraseall commands.
我们来看一下 Qspi_thingy53.ini 这个文件,看一看里面有哪些 flash 相关的参数。
; nrfjprog QSPI configuration file. [DEFAULT_CONFIGURATION] ; Define the capacity of the flash memory device in bytes. Set to 0 if no external memory device is present in your board. MemSize = 0x800000 ; Define the desired ReadMode. Valid options are FASTREAD, READ2O, READ2IO, READ4O and READ4IO ReadMode = READ2IO ; Define the desired WriteMode. Valid options are PP, PP2O, PP4O and PP4IO WriteMode = PP ; Define the desired AddressMode. Valid options are BIT24 and BIT32 AddressMode = BIT24 ; Define the desired Frequency. Valid options are M2, M4, M8, M16 and M32 Frequency = M16 ; Define the desired SPI mode. Valid options are MODE0 and MODE3 SpiMode = MODE0 ; Define the desired SckDelay. Valid options are in the range 0 to 255 SckDelay = 0x80
总结
本文从例程 zephyr\samples\application_development\code_relocation_nocopy 出发,介绍了 Code and Data Relocation 这个 zephyr 中的使用功能。并通过实操展示如何在常用的 BLE 例程 nrf\samples\bluetooth\peripheral_lbs 上,一步一步把指定的编译字段重新定位到指定的存储区域里。最后我们还简单讲述了如何根据自己的 flash 的硬件规格,来修改驱动参数,外部 flash 的烧写。