3. nRF 固件量产版本管理
在 nRF 系列产品的量产交付中,固件版本管理不是简单的“打个 tag 存档”,而是决定产线能否稳定复现、售后能否精准追溯的核心工程能力。nRF5 SDK 与 SoftDevice(S 系列协议栈)是两套独立发布、独立版本号的组件,二者通过 sdk_config.h 与协议栈 API 头文件耦合;nRF Connect SDK(NCS)则把协议栈、驱动、应用统一到 Zephyr 与 west 的版本体系中。无论哪条技术路线,量产版本管理都要回答四个问题:锁定了什么、烧了什么、参数从哪来、出了问题怎么追。
一、SDK 与 SoftDevice 版本锁定
版本锁定要落到“可复现”三个字上。对 nRF5 SDK 项目,至少锁定四项:SDK 版本号(如 nRF5_SDK_17.1.0_ddde560)、SoftDevice 版本与规格(如 S140 v7.3.0,对应 hex 文件 s140_nrf52_7.3.0_softdevice.hex)、编译工具链版本(GCC ARM Embedded 或 Keil MDK 具体版本)、以及芯片型号与修订版(如 nRF52840 QIAA / build code)。SoftDevice 与 SDK 存在兼容矩阵:S140 v7.x 要求 SDK 17.x,且不同 SoftDevice 版本对 RAM 起始地址、中断向量表位置、可用外设资源的要求不同,混用会直接导致启动异常或协议栈初始化失败。
对 NCS 项目,锁定对象是 west.yml 中 manifest 的 revision(commit hash 或 tag)、Zephyr 版本、nrfxlib 与 MCUboot 版本,以及 west build 使用的 board 定义。建议在仓库根目录维护一份 version.lock 或 manifest 快照,把上述信息以文本形式固化,并在 CI 中校验:任何一次量产固件构建,都必须从锁定版本检出,禁止使用“最新 master”。
二、生产固件与测试固件分离
产线上常见的错误是“用同一份固件既做功能测试又做最终交付”。正确做法是把固件分成两类:测试固件(Test FW)用于产线功能验证、射频校准、外设自检,允许包含调试串口、测试模式、临时 GPIO 翻转等;生产固件(Production FW)是最终交付给客户的版本,关闭调试接口、固定安全配置、只保留必要功能。二者共享同一套业务代码,但通过编译宏或独立配置区分,例如:
// 编译期区分
#if defined(FW_BUILD_TEST)
#define DEBUG_UART_ENABLED 1
#define TEST_MODE_GPIO 1
#else
#define DEBUG_UART_ENABLED 0
#define TEST_MODE_GPIO 0
#endif
// 版本号注入
#define FW_VERSION_MAJOR 1
#define FW_VERSION_MINOR 4
#define FW_VERSION_PATCH 2
#define FW_BUILD_TYPE "PROD" // 或 "TEST"
分离的关键在于:测试固件可以“脏”,生产固件必须“干净”。测试固件允许通过 SWD 反复烧录、允许打印日志;生产固件应启用读保护(APPROTECT)、关闭不必要的调试外设,并在启动时校验自身完整性。产线流程通常是:先烧测试固件完成校准与功能测试,再烧生产固件并写入配置参数,最后做一次最小化冒烟测试。
三、版本号烧录与追溯
版本号不能只存在于源码里,必须“烧进芯片、可被读出”。nRF 常用做法是在 Flash 固定地址(如 UICR 或自定义 Flash 页)写入一个版本结构体,包含固件版本、构建时间、Git commit 短哈希、构建类型。设备启动或产线工具通过 SWD/串口读取该结构体,即可确认当前芯片上运行的是哪一版固件。
typedef struct __attribute__((packed)) {
uint32_t magic; // 0x4E524656 "NRFV"
uint8_t major;
uint8_t minor;
uint8_t patch;
uint8_t build_type; // 0=PROD, 1=TEST
uint32_t git_hash; // commit 短哈希
uint32_t build_time; // Unix 时间戳
} fw_version_t;
// 放置到固定 Flash 地址,例如 0x0007F000
const fw_version_t g_fw_version
__attribute__((section(".fw_version"))) = {
.magic = 0x4E524656,
.major = 1, .minor = 4, .patch = 2,
.build_type = 0,
.git_hash = 0xA1B2C3D4,
.build_time = 1710000000
};
追溯链要串起来:芯片唯一 ID(FICR DEVICEID)→ 烧录记录 → 固件版本 → 配置参数 → 测试结果。产线每烧录一颗芯片,就把 DEVICEID、固件版本、MAC、密钥版本、校准值、测试结论写入 MES 或本地数据库。售后返回一台设备时,读出 DEVICEID 和版本结构体,就能反查它当时用的是哪批参数、哪版固件、测试是否通过。
四、配置参数的生成与注入
量产参数主要包括三类:MAC 地址(BLE 公有地址或随机静态地址)、密钥(如绑定密钥、加密密钥、LICENSE key)、校准值(射频偏置、晶振负载电容、温度补偿系数等)。这些参数不能硬编码在固件里,必须“一机一参”,由产线工具生成并注入。
典型注入方式有两种:一是通过 SWD 用 nRF Util 或自研工具写入 UICR/Flash;二是通过串口 Bootloader 或自定义协议写入。以 MAC 为例,nRF52 的 FICR 中有出厂预置的 48 位设备地址,但量产中通常使用自定义 MAC 段,由产线从号段池分配,写入 UICR 的 CUSTOMER[0] 和 CUSTOMER[1] 寄存器,固件启动时读取并设置为 BLE 地址。
# 使用 nrfutil 写入 UICR 示例(示意)
nrfutil settings generate --family NRF52840 \
--application app.hex --application-version 1 \
--bootloader bootloader.hex --bootloader-version 1 \
--bl-settings-version 2 settings.hex
nrfjprog --program settings.hex --chiperase
nrfjprog --program s140_nrf52_7.3.0_softdevice.hex --sectorerase
nrfjprog --program app.hex --sectorerase
nrfjprog --reset
密钥注入要特别注意安全:密钥不应以明文形式出现在产线日志或 MES 中,建议使用加密传输、一次性写入、写入后校验回读。校准值则应在测试工位上实测后写入,例如射频输出功率校准,先测量实际功率,再计算偏置值写入 UICR 或 Flash 参数区,固件启动时加载并应用到 RADIO 外设。
五、版本管理闭环
把上述四件事串起来,就形成量产版本管理的闭环:锁定版本 → 构建测试/生产固件 → 产线烧录并注入参数 → 记录追溯信息 → 售后反查。任何一次量产,都应该能回答:这批设备用的是哪版 SDK、哪版 SoftDevice、哪版应用固件、哪批 MAC 号段、哪组校准参数、测试通过率多少。做不到这一点,故障闭环就无从谈起。
最后强调一点:版本管理的价值不在“记录”,而在“可复现”和“可反查”。当产线出现批量异常、售后返回故障机时,能在一分钟内定位到固件版本、参数批次和测试记录,才是这套机制真正发挥作用的时候。