1. SoftDevice协议栈工程导论

在 Nordic 的 nRF5 系列 SoC 上开发低功耗蓝牙应用,几乎无法绕开一个核心概念——SoftDevice。它不是一段普通的库代码,而是一个预先编译、经过蓝牙 SIG 认证、以二进制形式烧录进芯片的完整协议栈固件。理解 SoftDevice 在 nRF SoC 中扮演的角色,以及它与应用程序之间的边界,是后续所有工程实践的基础。

SoftDevice 是什么,为什么需要它

nRF SoC 内部集成了 2.4GHz 射频收发器、ARM Cortex-M 内核、Flash、RAM 以及丰富的外设。射频硬件本身只能收发比特流,要让它真正跑起 BLE,需要实现从物理层到 GATT 的整套协议。如果每个项目都从零实现链路层状态机、跳频算法、加密引擎和属性数据库,开发周期和认证成本都无法接受。

SoftDevice 正是为解决这一问题而设计的。它把 BLE 协议栈(以及部分 ANT、802.15.4 变体)封装成一个二进制固件,运行在 Cortex-M 内核上,占用固定的 Flash 和 RAM 区域,对外通过一套称为 SVC(Supervisor Call)的软中断接口暴露 API。应用程序调用 sd_ble_gap_adv_start() 之类的函数时,实际是通过 SVC 指令陷入 SoftDevice,由它完成协议处理后再返回。这种设计让协议栈与应用程序在同一个内核上分时运行,但逻辑上严格隔离。

SoftDevice 的关键特性包括:已通过蓝牙认证,可直接用于产品;中断优先级由它统一管理,应用程序不能随意抢占;Flash 和 RAM 占用固定,需在链接脚本中预留。常见的版本有 S112(仅从机)、S132(从机+主机,支持多连接)、S140(支持更高并发和长距离),选择时需根据角色和连接数需求决定。

协议栈与应用程序的边界

SoftDevice 与应用程序的边界体现在三个层面:地址空间、中断优先级和 API 调用。

地址空间上,SoftDevice 占据 Flash 起始的一段区域(例如 S132 v7.2.0 约 152KB)和 RAM 起始的一段区域(约 5.5KB 基础占用,加上每个连接约 1.3KB)。应用程序的代码和数据必须放在这些区域之后。链接脚本中通过 FLASH 和 RAM 的起始地址偏移来实现,例如把应用程序起始地址设为 0x00026000。如果偏移设置错误,程序可能覆盖 SoftDevice 导致无法启动。

中断优先级方面,SoftDevice 使用 Cortex-M 的优先级分组,并保留了最高几个优先级供自己使用。应用程序可用的中断优先级被限制在一个范围内,通常不能高于 _PRIO_APP_HIGH。这意味着应用程序的中断响应可能被协议栈中断打断,实时性要求极高的任务需要仔细评估。

API 调用是日常开发接触最多的边界。所有 SoftDevice API 都以 sd_ 开头,返回 uint32_t 错误码,NRF_SUCCESS 为 0。调用时需注意:部分 API 只能在特定状态下调用(如未初始化时不能启动广播);部分 API 是异步的,结果通过事件回调返回。事件回调函数 ble_evt_handler() 是应用程序接收协议栈状态变化的主要入口,例如连接建立、断开、GATT 写请求等。

nRF SoC 内 SoftDevice 与应用程序的边界 nRF SoC 芯片边界 Flash 地址空间 SoftDevice 固件 起始 0x00000000 S132 v7.2.0 约 152KB 应用程序代码 起始 0x00026000 main() / 业务逻辑 / 驱动 Bootloader / 应用数据 可选,位于应用之后 RAM 地址空间 SoftDevice RAM 基础约 5.5KB 每连接 +1.3KB 应用程序 RAM 全局变量 / 堆 / 栈 起始地址由链接脚本决定 协议栈事件队列 ble_evt_handler 消费 SVC 调用 事件回调

BLE 协议栈分层模型

SoftDevice 内部实现了完整的 BLE 协议栈,从下到上依次为:物理层(PHY)、链路层(LL)、主机控制器接口(HCI)、逻辑链路控制与适配协议(L2CAP)、安全管理器(SM)、属性协议(ATT)、通用属性配置文件(GATT)以及通用访问配置文件(GAP)。

物理层负责 2.4GHz 频段的调制解调,BLE 使用 40 个信道,其中 3 个广播信道(37、38、39)和 37 个数据信道。链路层管理广播、扫描、发起连接和连接状态机,处理跳频和自适应跳频。HCI 在传统蓝牙中是主机与控制器之间的物理接口,在 SoftDevice 中它被内部化,但 API 中仍能看到 HCI 命令的影子,例如 sd_ble_gap_adv_set_configure() 底层就对应 HCI 的广播参数设置。

L2CAP 负责数据分片和重组,BLE 中主要使用固定信道。SM 处理配对和加密,包括密钥分发和身份解析。ATT 定义了客户端与服务器之间的属性读写协议,GATT 则在此基础上构建服务、特征和描述符的层次结构。GAP 管理设备的广播、扫描、连接建立和角色切换。

对应用程序开发者而言,直接接触最多的是 GAP 和 GATT。GAP 决定设备如何被看见和如何连接,GATT 决定连接后能交换什么数据。理解这两层的 API 和事件,就能完成大部分 BLE 应用开发。

开发环境与工具链概览

Nordic 官方推荐的开发环境是 nRF Connect SDK(基于 Zephyr)或传统的 nRF5 SDK。对于 SoftDevice 工程,nRF5 SDK 仍是经典选择,它提供了完整的示例、驱动和 SoftDevice 二进制文件。工具链方面,可以使用 Keil MDK、IAR Embedded Workbench 或 GCC + Makefile。nRF5 SDK 自带 GCC 的 Makefile 工程,适合跨平台开发。

烧录和调试通常使用 nRF Connect for Desktop 中的 Programmer 工具,或命令行工具 nrfjprog。例如,烧录 SoftDevice 和应用程序的命令为:

nrfjprog --eraseall
nrfjprog --program s132_nrf52_7.2.0_softdevice.hex --chiperase
nrfjprog --program ble_app_template.hex
nrfjprog --reset

调试时,J-Link 配合 RTT(Real Time Transfer)可以输出日志而不占用 UART。在代码中调用 NRF_LOG_INFO() 即可通过 RTT Viewer 查看。需要注意的是,SoftDevice 运行时不能随意暂停 CPU,否则协议栈时序会出错,调试断点应谨慎使用。

选择 SoftDevice 版本时,先确认所需角色(从机/主机/两者)、最大连接数、是否需长距离(Coded PHY)和是否需 DFU。S132 适合多数从机+主机场景,S140 适合高并发和长距离。

工程结构示例

一个典型的 nRF5 SDK BLE 工程目录包含:main.c(应用入口)、ble_evt_handler()(事件处理)、services/(自定义 GATT 服务)、sdk_config.h(SDK 配置)、链接脚本 ble_app_template.ld(或 Keil 的 scatter 文件)以及 SoftDevice 的 hex 文件。编译时,链接脚本中的 Flash 起始地址必须与所选 SoftDevice 的结束地址对齐,否则会出现 HardFault 或无法启动。

初始化顺序也很关键:先调用 nrf_sdh_enable_request() 使能 SoftDevice,再调用 ble_stack_init() 配置 GAP 参数,最后注册事件回调并启动广播。顺序错误会导致 API 返回 NRF_ERROR_INVALID_STATE。