本文以 apps/hsd/resources 作为示例讲解
AI 示例经常需要附带多个资源。以往需要开发者自行烧录多个资源,非常不方便。在大模型开发套件中,这些资源通过一些构建脚本被最终整合到 zephyr.bin
中,方便一次性烧录,达到开箱即用的目的。同时也预留了 Kconfig 开关,可以在日常开发等需要频繁更新程序部分的场景中关闭此功能,以减少每次烧录的大小。
在大模型开发套件 SDK 中,资源操作通常放在示例的 resources
目录下。该目录包含了自己的 Kconfig
和 CMakeLists.txt
文件,可直接由示例程序的顶级 Kconfig
和 CMakeLists.txt
引入:
# Kconfig
rsource "resources/Kconfig"
# CMakeLists.txt
add_subdirectory(resources)
资源打包可以概括为下列几个步骤,本文将分别详解:
资源文件的地址需要在 board overlay 中定义为 Fixed partition。以本文开头的 HSD 示例为例,csk6_duomotai_devkit.overlay
节选如下:
&flash0 {
partitions {
/* ... */
res_hsd_head_shoulder_partition: partition@300000 {
reg = <0x300000 1466832>;
};
res_hsd_gesture_recognition_partition: partition@480000 {
reg = <0x480000 1272048>;
};
res_hsd_head_track_partition: partition@600000 {
reg = <0x600000 442432>;
};
};
};
此处利用 DeviceTree 定义了三个分区,偏移值分别是 0x300000
0x480000
和 0x600000
。其中的 reg
字段分别指示了改分区的偏移和长度。
定义之后的分区需要在 /chosen
节点声明:
/ {
chosen {
resource,hsd_head_shoulder = &res_hsd_head_shoulder_partition;
resource,hsd_gesture_recognition = &res_hsd_gesture_recognition_partition;
resource,hsd_head_track = &res_hsd_head_track_partition;
};
};
然后,它们会在 resources/Kconfig.images
中被转换为相应的 Kconfig 选项,以便在其它不方便直接读取 DeviceTree 的场景(如 Linker script)中使用:
DT_CHOSEN_RES_HSD_HEAD_SHOULDER := resource,hsd_head_shoulder
config FLASH_RES_HSD_HEAD_SHOULDER_ADDR
def_hex $(dt_chosen_reg_addr_hex,$(DT_CHOSEN_RES_HSD_HEAD_SHOULDER))
config FLASH_RES_HSD_HEAD_SHOULDER_SIZE
def_int $(dt_chosen_reg_size_int,$(DT_CHOSEN_RES_HSD_HEAD_SHOULDER))
DT_CHOSEN_RES_HSD_GESTURE_RECOGNITION := resource,hsd_gesture_recognition
config FLASH_RES_HSD_GESTURE_RECOGNITION_ADDR
def_hex $(dt_chosen_reg_addr_hex,$(DT_CHOSEN_RES_HSD_GESTURE_RECOGNITION))
config FLASH_RES_HSD_GESTURE_RECOGNITION_SIZE
def_int $(dt_chosen_reg_size_int,$(DT_CHOSEN_RES_HSD_GESTURE_RECOGNITION))
DT_CHOSEN_RES_HSD_HEAD_TRACK := resource,hsd_head_track
config FLASH_RES_HSD_HEAD_TRACK_ADDR
def_hex $(dt_chosen_reg_addr_hex,$(DT_CHOSEN_RES_HSD_HEAD_TRACK))
config FLASH_RES_HSD_HEAD_TRACK_SIZE
def_int $(dt_chosen_reg_size_int,$(DT_CHOSEN_RES_HSD_HEAD_TRACK))
当启用了 CONFIG_APLICATION_PACK_IMAGES
选项后,资源文件会被嵌入到程序中,被链接到 ELF,最终合并到 zephyr.bin
内。该选项会追加链接脚本 resources/images.ld
中的 SECTION 定义。该脚本节选如下:
#include <listenai/linker/app-res.h>
APP_RES_SECTION(hsd_head_shoulder, CONFIG_FLASH_RES_HSD_HEAD_SHOULDER_ADDR)
APP_RES_SECTION(hsd_gesture_recognition, CONFIG_FLASH_RES_HSD_GESTURE_RECOGNITION_ADDR)
APP_RES_SECTION(hsd_head_track, CONFIG_FLASH_RES_HSD_HEAD_TRACK_ADDR)
该链接脚本使用了一个自定义宏 APP_RES_SECTION
定义了三个 SECTION 及由上一节 resources/Kconfig.images
定义的它们对应的偏移值。以 hsd_head_shoulder
分区为例,这个宏随后会被展开为类似 .hsd_head_shoulder (0x18000000 + 0x300000) : { KEEP(*(.hsd_head_shoulder)); } > FLASH
的形式,追加到最终的链接脚本中。
而与此同时,在 resources/CMakeLists.txt
中还有:
set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated)
generate_inc_file_for_target(
app
${CMAKE_CURRENT_SOURCE_DIR}/images/hsd/head_thinker.bin
${gen_dir}/hsd_head_shoulder.inc
)
generate_inc_file_for_target(
app
${CMAKE_CURRENT_SOURCE_DIR}/images/hsd/gesture_thinker.bin
${gen_dir}/hsd_gesture_recognition.inc
)
generate_inc_file_for_target(
app
${CMAKE_CURRENT_SOURCE_DIR}/images/hsd/track_thinker.bin
${gen_dir}/hsd_head_track.inc
)
这里利用了 Zephyr build system 扩展的 generate_inc_file_for_target
函数,位于示例的 resources/images/hsd
目录下的这三个资源文件被脚本转换为 C 字节数组,并在 include/generated
目录下生成了对应的 .inc
文件。
上述所有的准备,最终都汇集在 resources/images.c
文件中:
#include <listenai/app-res.h>
APP_RES_DECLARE(hsd_head_shoulder) = {
#include "hsd_head_shoulder.inc"
};
APP_RES_DECLARE(hsd_gesture_recognition) = {
#include "hsd_gesture_recognition.inc"
};
APP_RES_DECLARE(hsd_head_track) = {
#include "hsd_head_track.inc"
};
在 images.c
中,分别对应前述的三个分区定义了三个字节数组,它们通过 APP_RES_DECLARE
宏(最终会被展开为类似 __attribute__((section(".hsd_head_shoulder")))
的形式)被指定链接到的由前面定义的 SECTION。它们的内容则分别 include 了前面生成的 .inc
文件。
经过以上一整套过程,最终 head_thinker.bin
gesture_thinker.bin
track_thinker.bin
三个文件的内容被嵌入到了 zephyr.bin
中,它们在 Flash 中的偏移也将遵守 csk6_duomotai_devkit.overlay
中的定义。
在通常情况下,嵌入的资源文件之间的空间会被填充为 0xFF
,即 NOR Flash 被擦除后的初始状态。
在第一节的「地址定义」中,我们已经定义了各个资源文件在 Flash 中所处的偏移值。无论是否启用资源嵌入(CONFIG_APLICATION_PACK_IMAGES=y
),这些定义都是有效的。我们可以在业务逻辑中任何必要场景使用这些定义。
在业务逻辑中,这些定义可以使用 APP_RES_DT_*
宏引用:
#include <listenai/app-res.h>
static const void *head_shoulder_res = (const void *)(CONFIG_FLASH_BASE_ADDRESS + APP_RES_DT_ADDR(hsd_head_shoulder));
static const uint32_t head_shoulder_res_len = APP_RES_DT_SIZE(hsd_head_shoulder);
此处 CONFIG_FLASH_BASE_ADDRESS
是 Flash 的内存映射基地址;传入的 hsd_head_shoulder
是该资源在 /chosen
节点中的引用(省略 resource,
前缀,中划线 -
替换为下划线 _
);Flash 基地址加上由 APP_RES_DT_ADDR
取得的资源偏移值即可通过内存映射访问到资源本身。如直接使用 APP_RES_DT_ADDR(hsd_head_shoulder)
得到的则是该资源的偏移值。