杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目概述与能力地图
    • 构建系统与编译流程
    • 芯片系列与规格
  • 应用示例

    • SPP 与 BLE 双模透传
    • AT 指令串口协议
    • HID 设备应用
    • 蓝牙 Mesh 应用
    • 公共组件与第三方协议
  • 芯片平台支持

    • 外设驱动
    • 电源与充电管理
    • 启动与链接脚本
    • 配置工具与 OTA 资源
  • 协议栈与系统库

    • 蓝牙控制器
    • BTStack 协议栈接口
    • 系统内核与服务
    • OTA 升级机制
  • 文档与参考

    • 蓝牙 AT 协议参考
    • 开发文档与认证信息

启动与链接脚本

本文档介绍 fw-AC630N_BT_SDK(BD29 平台)的启动与链接脚本体系,包括主链接脚本 sdk.ld 的内存布局、Mask ROM 服务符号绑定机制、启动入口 _start 与代码段组织方式,以及 boot 镜像与预编译库的链接脚本。

Purpose and Scope

本页覆盖以下内容:

  • 链接脚本体系:cpu/bd29/sdk.ld(主链接脚本)、maskrom_stubs.ld / maskrom_stubs_os.ld(Mask ROM 桩)、include_lib/ 下各预编译库的放置脚本(system_lib_*.ld、btctler_lib_*.ld、btstack_lib.ld、driver_lib.ld、update.ld)。
  • 内存布局设计:RAM 分区(ISR 表、ROM RAM、UPDATA 标志区)、Flash 代码区(code0)起始地址与容量。
  • Mask ROM 符号重定向机制:应用直接链接到 ROM 内固化的服务函数地址(中断开关、C 库、NVRAM、SFC、OS/FreeRTOS API 等)。
  • 启动入口与段布局:ENTRY(_start)、startup.o 首放、BT 控制器 / BLE LL / LMP 加密代码段的分组。

以下主题属于其他页面,不在本页展开:OTA 升级流程(见 include_lib/update/update.ld 相关页面)、BT 协议栈与控制器实现、OS 调度器(FreeRTOS)内部机制。

Overview

AC630N 是杰理科技(Jieli)面向蓝牙音频/物联网应用的 SoC,其 SDK 采用 Mask ROM + 应用固件 的双层架构:芯片出厂时在 Mask ROM 中固化了一批基础服务函数(C 库、NVRAM、SPI Flash 控制器、RTOS 内核等),应用固件(编译产物)通过链接脚本将这些符号直接绑定到 ROM 中的固定绝对地址,从而大幅减小应用固件体积、并保证 ROM 与 Flash 代码可以无缝互相调用。

cpu/bd29/sdk.ld 是该架构的核心纽带,它同时承担了三项职责:

  1. 定义存储区域:code0(Flash 代码区)、ram0(数据区)、irq_table(中断向量表)。
  2. 导出 ROM 服务符号:把 memcpy、os_task_create、nvram_set_boot_state、chip_reset 等符号固定到 Mask ROM 绝对地址。
  3. 编排代码段:规定 _start 入口、startup.o 首位、BT 控制器/BLE/LMP 各代码段的存放顺序,并输出段边界符号供运行时(如 OTA、低功耗)使用。

Architecture

flowchart TD
    subgraph sg_ROM["Mask ROM (0x100000 区段)"]
        ROM_C["C 库 memcpy/strcmp/...<br/>0x103fa8~0x103fcc"]
        ROM_IRQ["local_irq_enable/disable<br/>0x103e88/0x103e8c"]
        ROM_NV["NVRAM/SFC/CRC<br/>0x103fd4~0x103ff4"]
        ROM_OS["OS/FreeRTOS API<br/>0x103eac~0x103fa4"]
        ROM_BOOT["boot_arg_list @ 0xbee4<br/>the_debug_isr @ 0x100016"]
    end

    subgraph sg_LD["链接脚本 (构建期)"]
        LD_MAIN["cpu/bd29/sdk.ld"]
        LD_STUB["maskrom_stubs.ld<br/>maskrom_stubs_os.ld"]
        LD_LIB["include_lib/*.ld<br/>system/btctler/btstack/driver/update"]
    end

    subgraph sg_APP["应用固件 (Flash code0 @ 0x1E000C0)"]
        START["_start (startup.o)"]
        TEXT[".text* / .rodata*"]
        BT["BTCTLER 控制器段<br/>bt_rf/vendor/hci"]
        BLE["BLE LL/HCI 段"]
        LMP["LMP + 加密段<br/>auth/bigint/ecdh/hmac"]
        DEV["设备表段<br/>gsensor/storage_device"]
    end

    subgraph sg_BOOT["boot 镜像 (cpu/bd29/tools)"]
        UB["uboot.boot / uboot_no_ota.boot<br/>(含 _debug 变体)"]
    end

    LD_MAIN -->|"ENTRY(_start) + ABSOLUTE 符号"| START
    LD_MAIN -->|"段放置"| TEXT
    LD_MAIN -->|"段放置"| BT
    LD_MAIN -->|"段放置"| BLE
    LD_MAIN -->|"段放置"| LMP
    LD_MAIN -->|"段放置"| DEV
    LD_STUB --> LD_MAIN
    LD_LIB --> LD_MAIN
    START -->|"调用 (链接至绝对地址)"| ROM_IRQ
    TEXT -->|"调用 (链接至绝对地址)"| ROM_C
    TEXT -->|"调用 (链接至绝对地址)"| ROM_OS
    BT -->|"调用"| ROM_NV
    UB -->|"上电加载并跳转"| START

架构说明:sdk.ld 在构建期把应用固件与 Mask ROM 服务函数"缝合"在一起——应用的每次函数调用都编译成对固定 ROM 地址的直接跳转(ABSOLUTE 符号),因此固件中无需携带这些基础代码;运行时由 Mask ROM 中的 bootloader(uboot.boot 镜像,存放于 cpu/bd29/tools/)完成初始化后跳转到应用入口 _start。预编译库(系统库、BT 控制器库、协议栈库、驱动库、升级库)则通过 include_lib/ 下各自的 .ld 片段被精确安放到对应段,保证库内节区与 sdk.ld 中定义的符号/段边界一致。

链接脚本体系

仓库中链接脚本按职责分为四类,共同构成完整的构建期布局方案:

类别文件职责
主链接脚本cpu/bd29/sdk.ld定义 MEMORY 区域、Mask ROM 符号、ENTRY(_start)、全部段布局与边界符号
Mask ROM 桩cpu/bd29/maskrom_stubs.ld、cpu/bd29/maskrom_stubs_os.ld为 Mask ROM 服务函数提供桩/别名定义(os 变体面向带 OS 的场景)
库放置脚本include_lib/system/system_lib_text/data/bss.ld、include_lib/btctrler/btctler_lib_text/data/bss.ld、include_lib/btstack/btstack_lib.ld、include_lib/driver/cpu/bd29/driver_lib.ld、include_lib/update/update.ld将预编译库的 text/data/bss 节区放入 sdk.ld 定义的段内
boot 镜像cpu/bd29/tools/uboot.boot、uboot_no_ota.boot 及 _debug 变体Mask ROM 加载的引导程序二进制,负责初始化并跳转应用

这种"一主多从"的设计意图:预编译库与主工程解耦。库以二进制形式发布(如 btctler、btstack),它们各自的节区名由库作者预先约定,sdk.ld 与库放置脚本配合,把这些节区按功能(控制器、协议栈、加密)分组摆放,既便于裁剪(某些段可整段丢弃),也便于运行时按段边界做搬运/校验(如 OTA 时按 BTCTLER_*_CODE_SIZE 计算)。

内存布局

sdk.ld 顶部用一组符号常量定义了整个 RAM 的划分(单位字节):

RAM_LIMIT_L = 0x00000;
RAM_LIMIT_H = 0x0C000;

ISR_SIZE = 0x100;
ISR_BASE = RAM_LIMIT_H - ISR_SIZE;

ROM_RAM_SIZE = 0x400;
ROM_RAM_BEGIN = RAM_LIMIT_H - ROM_RAM_SIZE - ISR_SIZE;

UPDATA_SIZE = 0x80;
UPDATA_BEG = RAM_LIMIT_H - UPDATA_SIZE - ROM_RAM_SIZE - ISR_SIZE;

UPDATA_COPY_BEG = 0x1d000 - UPDATA_SIZE;

RAM_BEGIN = RAM_LIMIT_L;
RAM_END = RAM_LIMIT_H - UPDATA_SIZE - ROM_RAM_SIZE - ISR_SIZE;
RAM_SIZE = RAM_END - RAM_BEGIN;

UPDATA_BREDR_BASE_BEG = 0x1C000;

Source: sdk.ld

RAM 总容量 0xC000(48KB),从高地址向低地址依次保留:ISR 表(0x100)→ ROM RAM(0x400)→ UPDATA 标志区(0x80)→ 应用可用 RAM。ISR 与 ROM RAM 从顶部固定下沉,使得 irq_table 的绝对地址(0xBF00)稳定不变——中断向量表地址固定是嵌入式系统的硬性要求,任何重定位都会导致中断失效。UPDATA 区位于 RAM 最高端,用于记录升级状态/签名,UPDATA_COPY_BEG = 0x1d000 - 0x80 表明其备份位于另一地址,供升级流程在写坏主标志时回退。

MEMORY 区域声明如下:

MEMORY
{
    code0(rx)    : ORIGIN = 0x1E000C0, LENGTH = (1024 * 1024)
    ram0(rwx)    : ORIGIN = RAM_BEGIN, LENGTH = RAM_SIZE
    irq_table(rw): ORIGIN = 0xbf00,    LENGTH = 0x100
}

Source: sdk.ld

code0 是应用代码区:起始地址 0x1E000C0、长度 1MB,位于 SPI Flash 地址空间中(0x1E00000 附近为 Flash 映射区,C0 偏移用于容纳引导头/文件头)。ram0 从 0x00000 开始,长度由上面推导的 RAM_SIZE 决定。irq_table 单独成区且地址固定 0xBF00,与前面 ISR_BASE 的计算完全一致。

flowchart LR
    subgraph sg_RAM["RAM 0x00000 ~ 0x0C000 (48KB)"]
        R_APP["ram0 应用数据<br/>0x00000 ~ 0xBAFF"]
        R_MASK["Mask 参数区<br/>0xBB00 ~ 0xBEDF (_MASK_MEM_BEGIN/_SIZE)"]
        R_ARG["boot_arg_list @ 0xBEE4"]
        R_IRQ["irq_table 中断向量表<br/>0xBF00 ~ 0xBFFF"]
        R_ROM["ROM_RAM 保留<br/>0xC000-0x500 ~ 0xC000-0x100"]
        R_UP["UPDATA 升级标志<br/>0xC000-0x80 ~ 0xC000"]
        R_APP --- R_MASK --- R_ARG --- R_IRQ --- R_ROM --- R_UP
    end

    subgraph sg_FLASH["Flash 代码区 code0 (1MB)"]
        F_BOOT["引导头/文件头 @ 0x1E00000"]
        F_TEXT["_start / startup.o / .text* @ 0x1E000C0"]
        F_BT["BT 控制器段 (bt_rf/hci/device_manager)"]
        F_BLE["BLE LL/HCI 段"]
        F_LMP["LMP 加密段 (ecdh/hmac)"]
    end
  • _MASK_MEM_BEGIN = ABSOLUTE(0xbb00)、_MASK_MEM_SIZE = ABSOLUTE(0x3f0)、boot_arg_list = ABSOLUTE(0xbee4) 在 sdk.ld 中定义:0xBB00 起的一段 RAM 是 Mask ROM 与应用共享的参数区,boot_arg_list 即引导参数链表首址,位于该区末尾、紧邻中断向量表之前——上电时 ROM 写入引导参数,应用在 _start 阶段即可读取(如本次启动原因、升级请求等)。

Mask ROM 符号绑定机制

sdk.ld 中段数量最多的部分是绝对地址符号表,其作用是把应用调用的基础函数全部"钉"到 Mask ROM 的固定地址。这样固件里不包含这些函数的实现,链接器直接解析为对 ROM 的跳转。

中断与基础服务(0x103e88 ~ 0x103ff8)

local_irq_enable = ABSOLUTE(0x103e88);
local_irq_disable = ABSOLUTE(0x103e8c);

memmem = ABSOLUTE(0x103fa8);
memcpy = ABSOLUTE(0x103fac);
memmove = ABSOLUTE(0x103fb0);
memcmp = ABSOLUTE(0x103fb4);
memset = ABSOLUTE(0x103fb8);
strcmp = ABSOLUTE(0x103fbc);
strcpy = ABSOLUTE(0x103fc0);
strlen = ABSOLUTE(0x103fc4);
strncmp = ABSOLUTE(0x103fc8);
strstr = ABSOLUTE(0x103fcc);
mask_init = ABSOLUTE(0x103fd0);
wdt_clr = ABSOLUTE(0x103fd4);
nvram_set_boot_state = ABSOLUTE(0x103fd8);
nvram_jumpaddr_set = ABSOLUTE(0x103fdc);
nvram_signature_set = ABSOLUTE(0x103fe0);
sfc_suspend = ABSOLUTE(0x103fe4);
sfc_resume = ABSOLUTE(0x103fe8);
sfc_drop_cache = ABSOLUTE(0x103fec);
chip_crc16 = ABSOLUTE(0x103ff0);
CrcDecode = ABSOLUTE(0x103ff4);
chip_reset = ABSOLUTE(0x103ff8);

Source: sdk.ld

设计意图:这些函数属于"ROM 特权区"——chip_reset、wdt_clr 等涉及芯片级操作,nvram_*、sfc_* 访问外设寄存器,若应用可自由重实现会破坏系统一致性。把它们固定在 ROM 中,还能保证同一 SDK 的所有固件与 ROM 版本严格匹配(地址即 ABI 契约)。

OS / FreeRTOS API(0x103eac ~ 0x103fa4)

os_init = ABSOLUTE(0x103eac);
os_start = ABSOLUTE(0x103eb0);
os_get_curr_tcb_var = ABSOLUTE(0x103eb4);
os_task_create = ABSOLUTE(0x103eb8);
os_time_dly = ABSOLUTE(0x103ec0);
os_time_get = ABSOLUTE(0x103ec4);
os_taskq_pend = ABSOLUTE(0x103ed8);
os_sem_create = ABSOLUTE(0x103f08);
os_mutex_create = ABSOLUTE(0x103f24);
task_queue_post_event = ABSOLUTE(0x103ef0);

Source: sdk.ld

除杰理自研的 os_* 封装外,链接脚本还导出了完整的 FreeRTOS 符号:vTaskSetApplicationTaskTag、xQueueGenericCreateStatic/Send/Receive、uxQueueMessagesWaiting、pcTaskGetName、xPortStartScheduler、vPortYield、vTickISR、OS_ClrPending 等(sdk.ld)。这说明 RTOS 内核整体驻留于 Mask ROM,应用侧(含 BT 协议栈)只是内核的使用者——这与 maskrom_stubs_os.ld 的命名呼应:该桩文件为 OS 相关调用提供类型/包装定义,保证应用链接时不与 ROM 符号冲突。

中断与调试辅助符号

_IRQ_MEM_ADDR = ABSOLUTE(0xbf00);
_MASK_MEM_BEGIN = ABSOLUTE(0xbb00);
_MASK_MEM_SIZE = ABSOLUTE(0x3f0);
boot_arg_list = ABSOLUTE(0xbee4);
the_debug_isr = ABSOLUTE(0x100016);

Source: sdk.ld

_IRQ_MEM_ADDR 与 irq_table 区起始一致;the_debug_isr 指向 ROM 内调试中断服务程序(0x100016 位于 ROM 代码区),用于异常时进入 ROM 的调试/打印路径。

启动入口与段布局

链接脚本明确指定程序入口与代码段的摆放顺序:

ENTRY(_start)

SECTIONS
{
    . = ORIGIN(code0);
    .text ALIGN(4):
    {
        PROVIDE(text_rodata_begin = .);

        *startup.o(.text)

        *(.text*)
        *(.rodata*)

        *(.LOG_TAG_CONST*)

        . = ALIGN(4);
        gsensor_dev_begin = .;
        KEEP(*(.gsensor_dev))
        gsensor_dev_end = .;

        . = ALIGN(4);
        OMSensor_dev_begin = .;
        KEEP(*(.omsensor_dev))
        OMSensor_dev_end = .;

        . = ALIGN(4);
        storage_device_begin = .;
        KEEP(*(.storage_device))
        storage_device_end = .;
    }
}

Source: sdk.ld

关键点:

  • ENTRY(_start) + *startup.o(.text):startup.o 是汇编启动文件(仓库中未提供其源码,属于编译期生成/库内对象),其 _start 符号即复位入口;把它放在 .text 首位,保证复位向量/第一条指令必然落在 code0 起始地址 0x1E000C0。上电后 Mask ROM 跳转到该地址,即进入 _start。
  • KEEP() 设备表段:gsensor_dev、omsensor_dev、storage_device 是由编译期构造的"设备描述符表"(链接期用 __attribute__((section)) 收集各驱动实例)。KEEP 防止链接器垃圾回收丢弃,gsensor_dev_begin/end 等边界符号供运行时遍历注册表。这种"链接期聚合"模式避免了动态注册的初始化顺序问题。
  • PROVIDE(text_rodata_begin = .):导出代码区起始边界,供 CRC 校验、OTA 搬运等模块计算固件范围。

BT 控制器与协议栈段分组

sdk.ld 继续按功能把控制器代码分组,并输出每组边界与大小符号:

btctler_code_start = .;
BTCTLER_CONTROLLER_CODE_START = .;
*(.bt_rf_const)
*(.bt_rf_code)
*(.vendor_manager_const)
*(.vendor_manager_code)
*(.device_manager_const)
*(.device_manager_code)
*(.hci_controller_const)
*(.hci_controller_code)
*(.hci_interface_const)
*(.hci_interface_code)
BTCTLER_CONTROLLER_CODE_SIZE = ABSOLUTE(. - BTCTLER_CONTROLLER_CODE_START);

BTCTLER_LE_CONTROLLER_CODE_START = .;
*(.ble_rf_const)
*(.ble_rf_code)
*(.ble_ll_const)
*(.ble_ll_code)
*(.ble_hci_const)
*(.ble_hci_code)
*(.classic_hci_const)
*(.classic_hci_code)
BTCTLER_LE_CONTROLLER_CODE_SIZE = ABSOLUTE(. - BTCTLER_LE_CONTROLLER_CODE_START);

BTCTLER_CL_CODE_START = .;
*(.bredr_irq)
*(.bredr_irq_code)
*(.bredr_irq_const)
*(.classic_lmp_const)
*(.classic_lmp_linkbulk_const)
*(.classic_lmp_code)
*(.classic_lmp_linkbulk_code)

LMP_ENC_CODE_START = .;
*(.classic_lmp_auth_const)
*(.classic_lmp_bigint_const)
*(.classic_lmp_crypt_const)
*(.classic_lmp_ecdh_const)
*(.classic_lmp_hmac_const)
*(.classic_lmp_auth_code)
*(.classic_lmp_bigint_code)
*(.classic_lmp_crypt_code)
*(.classic_lmp_ecdh_code)
*(.classic_lmp_hmac_code)
LMP_ENC_CODE_SIZE = ABSOLUTE(. - LMP_ENC_CODE_START);

Source: sdk.ld

设计意图:蓝牙控制器代码(bt_rf、vendor_manager、device_manager、hci_controller)来自预编译的 btctler 库,节区名即"放置协议"。LE 控制器(ble_ll、ble_hci、classic_hci)紧随其后;经典蓝牙 LMP 与加密相关段(auth/bigint/crypt/ecdh/hmac)被单独围出边界——LMP_ENC_CODE_SIZE、BTCTLER_*_CODE_SIZE 这类尺寸符号是 OTA 差分升级与安全校验(按加密段单独处理)的输入。若某功能被裁剪,对应 *(.xxx) 输入为空,段边界符号自动归零,无需改动脚本。

启动流程

BD29 平台的完整启动链路:Mask ROM 上电引导 → bootloader 镜像(uboot.boot)→ 应用入口 _start → OS 初始化 → 业务任务。链接脚本在其中扮演"地址契约"的角色,每一跳转地址都在构建期被固定。

sequenceDiagram
    participant PWR as 上电复位
    participant ROM as Mask ROM
    participant UB as uboot.boot<br/>(cpu/bd29/tools)
    participant APP as 应用固件 code0<br/>(0x1E000C0)
    participant ROMSVC as ROM 服务函数<br/>(0x103e88~0x103ff8)

    PWR->>ROM: 复位向量,初始化最小硬件
    ROM->>ROM: 读取 Flash 引导头,定位 uboot 镜像
    ROM->>UB: 加载 uboot.boot 到 RAM 并跳转
    UB->>UB: 初始化时钟/SFC/看门狗
    UB->>ROM: 调用 wdt_clr / sfc_resume (0x103fd4/0x103fe8)
    UB->>ROM: nvram_set_boot_state / nvram_jumpaddr_set 记录引导状态
    UB->>ROM: 写 boot_arg_list @ 0xbee4(启动原因/升级请求)
    UB->>APP: 跳转 0x1E000C0 (_start)
    APP->>APP: startup.o 初始化 C 运行环境
    APP->>ROM: 调用 local_irq_disable / memset (0x103e8c/0x103fb8)
    APP->>APP: 遍历设备表段 (gsensor/storage_device)
    APP->>ROM: os_init (0x103eac) / os_start (0x103eb0)
    APP->>ROM: 创建业务任务 (os_task_create 0x103eb8)
    APP->>ROM: xPortStartScheduler (0x103f90),进入调度
    ROMSvc-->>APP: 返回服务结果

流程要点:

  1. Mask ROM 是唯一的"上帝之手":所有跳转目标(uboot 地址、应用入口、服务函数地址)都是 ROM 或链接脚本固定的绝对地址,应用本身不参与引导决策,因此即使应用固件损坏,ROM 仍能进入升级流程。
  2. uboot_no_ota.boot 与 uboot.boot:两者对应不同升级能力(no_ota 变体不带 OTA 引导逻辑),_debug 后缀变体保留调试接口。它们都是二进制镜像,源码未包含在本仓库可见范围内,其行为由 Mask ROM 约定。
  3. _start 即链接契约:ENTRY(_start) 保证 ROM 跳转地址(0x1E000C0)处就是 startup 代码,中间无任何偏移;任何对 startup.o 顺序的改动都会破坏启动。

配置选项

sdk.ld 顶部的符号常量即"配置面",改动即改变布局(需与 ROM 版本匹配,否则地址失效):

符号值(十六进制)说明
RAM_LIMIT_L / RAM_LIMIT_H0x00000 / 0x0C000RAM 地址上下限(48KB)
ISR_SIZE / ISR_BASE0x100 / 0x0BF00中断向量表大小与基址
ROM_RAM_SIZE / ROM_RAM_BEGIN0x400ROM 与 RAM 交互保留区
UPDATA_SIZE / UPDATA_BEG0x80升级标志区(RAM 最高端)
UPDATA_COPY_BEG0x1d000 - 0x80升级标志备份地址(Flash 侧)
UPDATA_BREDR_BASE_BEG0x1C000经典蓝牙升级数据基址
code0 ORIGIN / LENGTH0x1E000C0 / 1MB应用代码区(Flash 映射)
irq_table ORIGIN / LENGTH0x0BF00 / 0x100中断向量表区域
_MASK_MEM_BEGIN / _MASK_MEM_SIZE0x0BB00 / 0x3F0ROM 与应用共享参数区
boot_arg_list0x0BEE4引导参数链表地址

表中符号均出自 sdk.ld 与 sdk.ld。

符号 API 参考(Mask ROM 服务表)

应用与 ROM 的交互面全部由链接脚本导出,按功能分组列举(地址见上文代码块):

中断控制

  • local_irq_enable / local_irq_disable:开/关全局中断,临界区原语的基础。

C 运行时库(memmem、memcpy、memmove、memcmp、memset、strcmp、strcpy、strlen、strncmp、strstr):签名与标准 C 库一致;应用不链接任何 libc,全部走 ROM 实现,保证 Flash 代码极简。

芯片/外设服务

  • mask_init:Mask ROM 侧初始化入口。
  • wdt_clr:喂狗,防止引导阶段看门狗复位。
  • nvram_set_boot_state / nvram_jumpaddr_set / nvram_signature_set:写入启动状态、跳转地址、固件签名,供升级与安全校验。
  • sfc_suspend / sfc_resume / sfc_drop_cache:SPI Flash 控制器挂起/恢复/缓存失效——低功耗与升级时防止 Flash 访问冲突。
  • chip_crc16 / CrcDecode:CRC16 校验与解码(固件完整性)。
  • chip_reset:芯片软复位。

OS / FreeRTOS 内核(os_init、os_start、os_task_create、os_task_del、os_time_dly、os_time_get、os_taskq_pend/post/accept/flush/del、task_queue_post_event、os_sem_*、os_mutex_*、vTaskSetApplicationTaskTag、xQueueGeneric*、xPortStartScheduler、vPortYield、vTickISR 等):RTOS 驻留 ROM;应用通过 os_* 封装或直接调用 FreeRTOS API 使用。若某符号缺失(未定义),链接即失败——这是 ROM 版本不匹配时最早暴露的信号。

故障模式、边界情况与并发

  • ROM 版本不匹配(最典型的链接期故障):Mask ROM 服务地址是 ABSOLUTE 常量,若 SDK 与芯片 ROM 版本不一致,链接产物会在运行期跳转到错误地址(表现:复位、跑飞、wdt 超时)。由于是直接绝对地址跳转,链接器无法发现"逻辑"错误,只能靠 ROM 版本号(通常经 boot_arg_list 或 nvram_signature_set 校验)在运行时拦截。升级 SDK 时必须同步确认 ROM 版本。
  • 段溢出:ram0 长度由 RAM_LIMIT_H 减去保留区得出,code0 固定 1MB。若应用/BT 功能膨胀,链接器报"region overflow",此时需裁剪功能(对应输入节区为空时边界符号自动归零,裁剪是安全的)而非修改地址——修改地址会破坏与 ROM 的地址契约。
  • irq_table 被覆盖:中断向量表在 0xBF00~0xBFFF,紧邻 boot_arg_list(0xBEE4)与 Mask 参数区。若 startup 前有代码向 RAM 顶部写数据(例如错误地把 UPDATA 区当普通变量用),会静默破坏中断表,表现为"偶发死机/中断异常"。这也是保留区被链接脚本"隐藏"(不暴露给 ram0)的原因。
  • 并发/重入:sfc_suspend/sfc_resume 对 Flash 的访问在低功耗与 OTA 场景存在竞争——链接脚本不提供互斥,依赖 local_irq_disable/OS 临界区在调用侧串行化;wdt_clr 必须周期性调用,任何任务饿死都会触发看门狗复位。
  • 升级中断恢复:UPDATA 区(RAM 最高端)与 UPDATA_COPY_BEG(Flash 备份)是"双槽"设计;若升级中途断电,ROM 通过 nvram_set_boot_state 记录的状态决定回退到备份,链接脚本保证该区域地址永远稳定可用。

性能与运维注意事项

  • 调用开销:ROM 服务函数经绝对地址直接跳转,无 PLT/重定位开销,与本地调用几乎等价——这是把 C 库/内核放 ROM 的性能红利。
  • Flash 代码密度:由于 libc 与内核不在固件内,code0 的 1MB 几乎全部让给业务与蓝牙栈;LMP_ENC_CODE_SIZE 等尺寸符号可被构建脚本读取用于生成固件体积报告。
  • 调试变体:uboot.boot_debug / uboot_no_ota.boot_debug 保留了调试通道,量产固件应使用非 debug 镜像以减小攻击面与启动时间。
  • 布局稳定性:irq_table、UPDATA、Mask 参数区的绝对地址是跨构建稳定的(由 ROM 契约固定),因此 OTA 升级包可以只携带"段差"数据;任何对这些常量的本地改动都会破坏 OTA 兼容性。

扩展点

  1. 新增驱动/传感器注册:在 .gsensor_dev、.omsensor_dev、.storage_device 节区中追加条目(通过 __attribute__((section(...))) 定义描述符),KEEP 保证其被保留,*_begin/_end 符号提供遍历接口——无需修改 sdk.ld。
  2. BT 功能裁剪:删除 *(.classic_lmp_*) 等输入节区中的库对象即可整段剔除,BTCTLER_*_CODE_SIZE 自动收缩,OTA/校验逻辑无需改动。
  3. 升级策略选择:引导镜像在 cpu/bd29/tools/ 下按 uboot.boot(带 OTA)与 uboot_no_ota.boot(无 OTA)区分,替换引导镜像即可切换升级能力。
  4. 库放置脚本复用:新增预编译库时,仿照 include_lib/btctrler/btctler_lib_text.ld 等文件编写 text/data/bss 放置片段,并在 sdk.ld 对应分组中登记其节区名。

Related Links

  • sdk.ld(主链接脚本)
  • maskrom_stubs.ld / maskrom_stubs_os.ld(Mask ROM 桩)
  • include_lib/update/update.ld(升级库放置脚本)
  • include_lib/system/system_lib_text.ld(系统库放置脚本)
  • include_lib/btctrler/btctler_lib_text.ld(BT 控制器库放置脚本)
  • cpu/bd29/tools/uboot.boot(引导镜像)

说明:boot 镜像(.boot)为二进制文件,引导程序源码未包含在本仓库;启动细节(uboot 内部初始化序列)如需深入,请结合芯片 Mask ROM 手册与 OTA/升级相关页面。

Prev
电源与充电管理
Next
配置工具与 OTA 资源