4. SoftDevice初始化与使能流程

在 nRF5 SDK 的 BLE 工程中,SoftDevice 并不是“上电即用”的普通外设,而是一段独立运行的协议栈固件。应用核(Application Core)必须先通过 SVC 调用向协议栈申请使能,协议栈才会接管射频、时钟、中断向量表以及部分 RAM 资源。理解 nrf_sdh_enable_request() 的调用顺序、时钟配置、中断优先级设置和事件回调注册,是排查“协议栈起不来”“广播发不出”“连接后立即断开”等问题的关键。

一、使能请求的调用顺序

SDK 中推荐使用 nrf_sdh_enable_request() 作为统一入口,它内部封装了 sd_softdevice_enable()、时钟配置和中断优先级设置。典型调用顺序如下:

  1. 调用 nrf_sdh_enable_request(),传入低频时钟源配置结构体 nrf_clock_lf_cfg_t。
  2. 协议栈内部执行 sd_softdevice_enable(),校验固件版本、初始化协议栈内核。
  3. 配置低频时钟(LFCLK),选择 RC 振荡器或外部 32.768 kHz 晶振。
  4. 设置协议栈使用的中断优先级,并接管相关外设。
  5. 使能成功后,通过 nrf_sdh_evts_poll() 或中断方式分发协议栈事件。

需要特别注意的是:nrf_sdh_enable_request() 必须在任何 BLE 相关 API(如 ble_stack_init() 中的 nrf_sdh_ble_default_cfg_set())之前调用,否则会返回 NRF_ERROR_INVALID_STATE。同时,它不能在中断上下文中调用,因为内部涉及临界区与时钟启动等待。

二、低频时钟配置

SoftDevice 依赖低频时钟维持协议栈的定时基准,包括连接事件、广播间隔和协议定时器。时钟源通过 nrf_clock_lf_cfg_t 指定:

#define NRF_CLOCK_LF_ACCURACY  NRF_CLOCK_LF_ACCURACY_250_PPM
#define NRF_CLOCK_LF_SRC       NRF_CLOCK_LF_SRC_XTAL
#define NRF_CLOCK_LF_RC_CTIV   16
#define NRF_CLOCK_LF_RC_TEMPIV 2

static const nrf_clock_lf_cfg_t lf_cfg = {
    .source        = NRF_CLOCK_LF_SRC,
    .rc_ctiv       = NRF_CLOCK_LF_RC_CTIV,
    .rc_temp_ctiv  = NRF_CLOCK_LF_RC_TEMPIV,
    .accuracy      = NRF_CLOCK_LF_ACCURACY
};

若板载 32.768 kHz 晶振,应选择 NRF_CLOCK_LF_SRC_XTAL,精度高、功耗稳定;若使用内部 RC,则需根据温度校准周期设置 rc_ctiv 与 rc_temp_ctiv。精度参数 accuracy 直接影响协议栈对连接窗口的容忍度,设置过松会导致连接不稳定,过紧则可能无法建立连接。示例中 250 ppm 适用于多数 RC 场景,实际应以硬件手册为准。

三、中断优先级设置

SoftDevice 使用固定的中断优先级范围,应用只能使用协议栈未占用的优先级。SDK 通过 nrf_sdh_ble_default_cfg_set() 和 sd_nvic_SetPriority() 完成配置。常见约定如下:

中断源优先级说明
SD_EVT_IRQn6协议栈事件中断,由 SoftDevice 内部使用
SWI2_EGU2_IRQn6SDK 用于调度协议栈事件
应用定时器3~5需低于协议栈优先级,避免抢占
UART/SPI2~4根据实时性要求选择

原则是:应用中断优先级数值必须大于协议栈占用的优先级(Cortex-M 中数值越大优先级越低),否则会破坏协议栈的实时性,导致连接事件丢失。若使用 app_timer,其默认优先级为 3,需确认不与协议栈冲突。

四、协议栈事件回调注册

使能成功后,应用需要注册事件观察者,才能接收 BLE 事件。SDK 提供 NRF_SDH_BLE_OBSERVER 宏,将回调函数挂接到协议栈事件分发链:

static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GAP_EVT_CONNECTED:
            break;
        case BLE_GAP_EVT_DISCONNECTED:
            break;
        default:
            break;
    }
}

NRF_SDH_BLE_OBSERVER(m_ble_observer, 3, ble_evt_handler, NULL);

其中第二个参数是观察者优先级,数值越小越先被调用。注册必须在 nrf_sdh_ble_enable() 之后、开始广播之前完成。若忘记注册,协议栈事件将无人处理,表现为连接后无任何回调、状态机停滞。

SoftDevice 使能与事件分发链路 应用层 main() / ble_stack_init() nrf_sdh_enable_request() 传入 nrf_clock_lf_cfg_t sd_softdevice_enable() 协议栈内核初始化 低频时钟配置 XTAL / RC + 精度参数 rc_ctiv / rc_temp_ctiv / accuracy 中断优先级设置 SD_EVT_IRQn = 6 应用中断 > 6(数值更大) 事件回调注册 NRF_SDH_BLE_OBSERVER 优先级 + ble_evt_handler 协议栈事件分发 BLE_GAP_EVT_* 连接 / 断开 / 广播 关键约束: 使能请求必须在 BLE API 之前 · 不可在中断中调用 · 回调注册须在广播前完成

五、典型初始化代码与结果判断

将上述步骤整合到 ble_stack_init() 中,典型实现如下:

static void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    uint32_t ram_start = 0;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(err_code);

    err_code = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);

    NRF_SDH_BLE_OBSERVER(m_ble_observer, 3, ble_evt_handler, NULL);
}

判断使能是否成功,可观察三点:nrf_sdh_enable_request() 返回 NRF_SUCCESS;nrf_sdh_is_enabled() 返回 true;调用 sd_ble_gap_adv_start() 后能收到 BLE_GAP_EVT_ADV_SET_TERMINATED 或连接事件。若返回 NRF_ERROR_SDM_INCORRECT_INTERRUPT_CONFIGURATION,说明中断优先级配置与协议栈要求不符,需检查 sd_nvic_SetPriority() 的调用。

调试建议:在 ble_evt_handler 入口处翻转一个 GPIO,用示波器观察事件到达时间,可快速判断协议栈是否正常分发事件。
常见错误:在 nrf_sdh_enable_request() 之前调用 sd_ble_gap_* 系列 API,会直接触发断言;另外,若低频时钟源选择错误(如无晶振却选 XTAL),协议栈会卡在时钟启动等待,表现为程序停在 sd_softdevice_enable() 内部。

掌握这条链路后,后续的 GAP 参数配置、GATT 服务注册和连接参数协商才有稳定的运行基础。实际工程中建议将时钟配置、中断优先级和观察者注册集中在一个初始化函数内,便于版本维护和问题定位。