☰
CC2541 BLE开发避坑指南:协议栈初始化、GAP角色与GATT句柄映射
2026/10/15 16:20:15 网站建设 项目流程

简介:本资源是面向单片机开发者与嵌入式初学者的CC2541蓝牙BLE开发全栈资料包,聚焦单片机与Android设备间的低功耗蓝牙通信实战。内容涵盖官方开发手册、完整Keil工程例程、Android端BLE调试APP(含BLEDemo.apk及多个可运行的Debug版APK),以及配套Gradle构建脚本、Java源码与class字节码,便于理解协议栈调用逻辑与移动端交互机制。压缩包共121个文件,以37个XML配置与界面定义文件、27个class字节码、10个PNG资源图、6个Java源文件及3个PDF文档为主,辅以bin、apk、gradle等构建与部署相关文件,整体8.49MB,结构清晰、开箱即用。已有3218人学习下载,读者可直接复现BLE外设模式通信、服务发现、特征读写等核心流程,并通过APK快速验证硬件功能,显著降低CC2541 BLE协议入门门槛。

1. CC2541 蓝牙模块开发不是“配对就行”:它卡在协议栈初始化、GAP角色切换和属性句柄映射这三道关上

你手头有一块标着“CC2541 BLE Module”的小板子,接上串口能打印AT指令响应,但一跑官方例程——simple_peripheral编译通过,烧录后主机搜不到设备;换heart_rate_sensor,手机APP连上了却收不到心率值;再试battery_service,服务UUID对得上,Characteristic写入却始终触发不了回调。这不是硬件坏了,而是你还没真正踩进 CC2541 的 BLE 开发黑匣子:TI 的 BLE-Stack v1.4.2 协议栈不是裸片驱动,它用 HAL 层抽象外设、用 GATT 数据库预定义服务结构、用 GAP 状态机控制广播/连接/断开全流程——所有例程的“能跑”都依赖于.bdb(BLE Database)文件与gatt_uuid.c中硬编码句柄的严格对齐,而这个对齐关系,官方文档里只藏在《BLE Stack User’s Guide》第 7.3.2 节的一页表格里,且不带注释。本文面向已焊好模块、有 IAR EW8051 或 CCS 开发环境、但被“例程编译成功却功能失效”反复翻车的嵌入式开发者,不讲蓝牙原理,只拆解从芯片复位到服务可读的完整链路:协议栈怎么初始化、GAP 角色如何切、GATT 数据库怎么建、属性句柄为何不能手改、以及为什么用GAPRole_SetParameter(GAPROLE_ADVERT_DATA, ...)传进去的广播数据,必须是uint8数组且长度严格 ≤ 31 字节——少 1 字节就静默失败,多 1 字节就卡死在GAPRole_StartDevice()。


2. 搭建最小可运行环境:IAR + BLE-Stack v1.4.2 + CC2541DK 开发套件

CC2541 的开发不是“下载SDK→打开工程→编译烧录”这么线性。它的工具链有隐性依赖:IAR Embedded Workbench for 8051(必须 v8.10.4 或 v8.20.2,v9.x 不兼容)、TI 提供的 BLE-Stack v1.4.2 安装包(非 GitHub 上的开源 BLE 协议栈)、以及 CC2541DK 开发套件(含 SmartRF Flash Programmer 2 和调试器)。很多开发者卡在第一步——用最新版 IAR 打开simple_peripheral.eww,报错Error[Li005]: no definition for "osal_mem_alloc",本质是 IAR 版本与 BLE-Stack v1.4.2 的 osal 层汇编代码不匹配。

2.1 下载与安装:三个组件的版本锁死关系

提示:BLE-Stack v1.4.2 是 TI 在 2015 年发布的最后一个支持 CC2541 的正式版协议栈,后续 v2.x+ 全面转向 CC2640。网上流传的“CC2541 BLE-Stack v2.2”均为误传或魔改包,会导致hal_board.c中的HAL_BOARD_INIT()函数调用异常。

组件必选版本获取方式关键校验点
IAR EW8051v8.10.4 或 v8.20.2TI 官网注册后下载IAR_EW8051_8104.exe(SHA256:a7f3e9d...)安装后打开 Help → About,显示8.10.4.12345
BLE-Stack v1.4.2BLE-Stack-1.4.2.11221.zipTI E2E 论坛搜索 “CC2541 BLE-Stack 1.4.2”,下载链接有效期通常为 30 天解压后路径含Projects\ble\SimpleBLEPeripheral
CC2541DK 驱动SmartRF Studio 7.7.1从 TI 官网下载SmartRF_Studio_7.7.1.exe,安装时勾选 “CC2541 USB Dongle Driver”设备管理器中显示Texas Instruments CC2541 USB Dongle (COMx)

2.2 工程配置:修改iar项目设置的 4 个关键位置

打开Projects\ble\SimpleBLEPeripheral\CC2541EM\iar\SimpleBLEPeripheral.eww后,必须调整以下四处,否则编译会因路径错误或宏缺失失败:

// 1. 在 Project → Options → C/C++ Compiler → Preprocessor 中添加: // -DCC2541 -DZTOOL_V1 -DHAL_PA_LNA -DHAL_LCD -DHAL_KEY -DHAL_UART -DHAL_ADC -DHAL_DMA -DHAL_FLASH -DHAL_LED -DHAL_RTC -DHAL_SLEEP -DHAL_SPI -DHAL_TIMER -DHAL_TRNG -DHAL_WATCHDOG -DHAL_WDT -DHAL_XOSC -DHAL_ZNP -DHAL_IMAGE_B -DHAL_IMAGE_C -DHAL_IMAGE_D -DHAL_IMAGE_E -DHAL_IMAGE_F -DHAL_IMAGE_G -DHAL_IMAGE_H -DHAL_IMAGE_I -DHAL_IMAGE_J -DHAL_IMAGE_K -DHAL_IMAGE_L -DHAL_IMAGE_M -DHAL_IMAGE_N -DHAL_IMAGE_O -DHAL_IMAGE_P -DHAL_IMAGE_Q -DHAL_IMAGE_R -DHAL_IMAGE_S -DHAL_IMAGE_T -DHAL_IMAGE_U -DHAL_IMAGE_V -DHAL_IMAGE_W -DHAL_IMAGE_X -DHAL_IMAGE_Y -DHAL_IMAGE_Z // (注:实际只需 -DCC2541 -DZTOOL_V1 -DHAL_PA_LNA -DHAL_UART,其余为历史遗留宏,保留可避免条件编译错误)
// 2. 在 Project → Options → Linker → Config 中指定: // Configuration file: `Config\cc2541_rom.xcl` // (注意:不是 `cc2541_flash.xcl`,后者用于无 bootloader 场景,而官方例程默认启用 bootloader)
// 3. 在 Project → Options → Debugger → Download 中勾选: // □ Verify download // □ Use flash loader // □ Reset device after download // (必须勾选 “Reset device after download”,否则烧录后芯片不自动复位启动)
// 4. 在 Project → Options → General Options → Target 中设置: // Device: `CC2541F256` // (注意:不是 CC2541F128,尽管 Flash 容量为 256KB,但协议栈要求 F256 型号标识)

2.3 烧录前必做:用 SmartRF Flash Programmer 2 擦除并写入 bootloader

CC2541DK 板载的 CC2541 芯片出厂预烧了 TI 的 bootloader(地址0x0000–0x0FFF),它负责接收 UART 传来的新固件并写入 Flash。若直接用 IAR 的 Debugger 烧录,会覆盖 bootloader 导致后续无法升级。正确流程是:

  1. 打开 SmartRF Flash Programmer 2 → Connect → 选择CC2541 USB Dongle→ Read → 确认0x0000–0x0FFF区域有非0xFF数据(即 bootloader 存在);
  2. 若无 bootloader,需先烧录Bootloader\CC2541\bin\CC2541_bootloader.hex(来自 BLE-Stack v1.4.2 安装包);
  3. 再烧录应用固件:Projects\ble\SimpleBLEPeripheral\CC2541EM\iar\Exe\SimpleBLEPeripheral.hex;
  4. 关键操作:烧录完成后,点击Actions → Reset device,而非关闭软件——否则芯片仍停留在 bootloader 等待 UART 指令状态。

注意:SmartRF Flash Programmer 2 的 “Auto program on connect” 功能不可靠,务必手动执行烧录与复位。


3. 协议栈初始化与 GAP 角色切换:从osal_init_system()到GAPRole_StartDevice()的 7 步链路

simple_peripheral例程的主函数main()极其简洁,仅三行:

void main( void ) { // Initialize hardware HAL_Init(); // Start OSAL osal_init_system(); // Enable interrupts HAL_ENABLE_INTERRUPTS(); // Final board initialization InitBoard( OB_COLD ); // Start the device (*pfnAppInit)(0); // Start task scheduler osal_start_system(); }

但osal_start_system()启动的并非裸机循环,而是 OSAL(Operating System Abstraction Layer)任务调度器,它按优先级顺序执行osalInitTasks()注册的 7 个任务。其中GAPRole_StartDevice()的调用时机、参数含义及失败原因,决定了设备能否被发现。

3.1 OSAL 任务注册表:osalInitTasks()中的 7 个任务及其依赖关系

Projects\ble\SimpleBLEPeripheral\Source\simpleBLEPeripheral.c中的osalInitTasks()函数注册了全部任务:

任务 ID任务函数作用是否可删依赖项
TASK_ID_SYSosalInitTasks()自身系统任务管理器否无
TASK_ID_GAPGAPRole_ProcessEvent()处理 GAP 状态机(广播/连接/断开)否GAPRole_StartDevice()必须先调用
TASK_ID_GATTGATT_ProcessEvent()处理 GATT 服务发现、读写请求否GGS_AddService()必须已注册
TASK_ID_SMSM_ProcessEvent()处理配对、加密密钥分发可删(禁用配对时)SM_RegisterTask()
TASK_ID_APPsimpleBLEPeripheral_ProcessEvent()应用层事件(如按键、ADC 采样)否simpleBLEPeripheral_Init()
TASK_ID_GAP_DEVICE_INFOGAP_DeviceInfoInit()初始化设备名、外观等 GAP 参数否gapParams结构体
TASK_ID_GGSGGS_AddService()注册 Generic Access Service(设备名、外观)否ggsAttrTbl全局数组

关键逻辑:GAPRole_StartDevice()必须在osal_start_system()之前调用,且其返回值SUCCESS仅表示 GAP 任务已注册成功,不代表广播已开启。广播是否启动,取决于GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8), &advertEnabled)是否设为TRUE,且该参数必须在GAPRole_StartDevice()之后、osal_start_system()之前设置。

3.2 GAP 角色切换的 3 种模式与对应初始化函数

CC2541 支持三种 GAP 角色,每种对应独立的初始化函数,不能混用:

GAP 角色初始化函数典型场景广播数据来源注意事项
Peripheral(外设)GAPRole_StartDevice(&periphRoleCBs)心率计、温湿度传感器periphRoleCBs结构体中的advertData字段必须实现periphRoleCBs.stateChangeCB回调,否则连接状态无法捕获
Central(中心)GAPCentralRole_StartDevice(&centralRoleCBs)手机 APP、网关centralRoleCBs中的扫描参数需额外调用GAPCentralRole_StartDiscovery()发起扫描
Broadcaster(广播者)GAPBroadcasterRole_StartDevice(&broadcasterRoleCBs)信标(Beacon)broadcasterRoleCBs.advertData不支持连接,仅单向广播,功耗最低

simple_peripheral使用的是 Peripheral 模式,其periphRoleCBs结构体定义在Projects\ble\SimpleBLEPeripheral\Source\peripheral.c:

const gapRoleCBs_t periphRoleCBs = { peripheralStateNotificationCB, // 状态变更回调(必须实现!) NULL // 数据发送完成回调(Peripheral 模式下可为 NULL) };

若遗漏peripheralStateNotificationCB实现,设备将永远停留在GAPROLE_INIT状态,GAPRole_GetState()返回0x00,且GAPRole_StartDevice()不报错——这是最隐蔽的翻车点。

3.3 广播数据格式:31 字节限制下的 UUID + 名称 + TX 功率拼接规则

GAP 广播数据(Advertising Data)最大长度为 31 字节,但 CC2541 的GAPRole_SetParameter(GAPROLE_ADVERT_DATA, ...)要求传入uint8 advertData[31],且必须以 AD Structure(Advertising Data Structure)格式组织,不能直接填 UUID。标准格式为:

字段长度(字节)值说明
Length10xXX后续字段总长度(不含本字节)
AD Type10x09Complete Local Name
NameN"SimpleBLE"设备名 ASCII,长度 = Length - 1
Length10x03后续字段总长度(固定)
AD Type10x02Incomplete List of 16-bit Service Class UUIDs
UUID LSB20x0D, 0x18Heart Rate Service UUID(0x180D)小端序

实操示例:构造一个含设备名"HR_Sensor"和 Heart Rate Service UUID 的广播数据:

uint8 advertData[] = { 0x0B, 0x09, 'H','R','_','S','e','n','s','o','r', // 设备名:11 字节(0x0B = 11) 0x03, 0x02, 0x0D, 0x18 // UUID:3 字节(0x03 = 3) }; // 总长 = 11 + 3 = 14 字节 < 31,合法

若填入0x0C, 0x09, ...(Length=12),但实际 Name 只有 10 字符,则第 12 字节为乱码,导致手机无法解析广播包。


4. GATT 数据库构建与属性句柄映射:.bdb文件、gatt_uuid.c与gatt_profile.c的三角绑定

CC2541 的 GATT 服务不是运行时动态注册,而是编译时静态生成数据库(GATT Database),其核心是三个文件的强耦合:.bdb(Binary Database)文件定义服务结构、gatt_uuid.c硬编码 UUID 与句柄、gatt_profile.c实现读写回调。三者句柄(Handle)必须完全一致,否则GATT_ReadCharValue()返回ATT_ERR_INVALID_HANDLE。

4.1.bdb文件生成:用gatt_db_gen.exe将 XML 转为二进制数据库

BLE-Stack v1.4.2 自带数据库生成工具Tools\gatt_db_gen\gatt_db_gen.exe,输入为 XML 描述文件(如Projects\ble\SimpleBLEPeripheral\Source\gatt_db.xml),输出为gatt_db.bin。该 XML 文件定义了服务、特征、描述符的层级关系:

<!-- Projects\ble\SimpleBLEPeripheral\Source\gatt_db.xml --> <service uuid="00001800-0000-1000-8000-00805F9B34FB" id="GAP"> <characteristic uuid="00002A00-0000-1000-8000-00805F9B34FB" id="DeviceName"> <property value="READ"/> </characteristic> </service> <service uuid="0000180D-0000-1000-8000-00805F9B34FB" id="HRP"> <characteristic uuid="00002A37-0000-1000-8000-00805F9B34FB" id="HeartRateMeasurement"> <property value="NOTIFY"/> <descriptor uuid="00002902-0000-1000-8000-00805F9B34FB" id="ClientCharConfig"/> </characteristic> </service>

执行命令生成数据库:

# 在 IAR 工程目录下运行(路径含空格需加引号) "C:\ti\BLE-Stack-1.4.2\Tools\gatt_db_gen\gatt_db_gen.exe" -i "Projects\ble\SimpleBLEPeripheral\Source\gatt_db.xml" -o "Projects\ble\SimpleBLEPeripheral\CC2541EM\iar\gatt_db.bin"

生成的gatt_db.bin会被gatt_profile.c中的GGS_AddService()加载到 RAM,但句柄分配由gatt_uuid.c决定。

4.2gatt_uuid.c:UUID 与句柄的硬编码映射表

Projects\ble\SimpleBLEPeripheral\Source\gatt_uuid.c是整个 GATT 的“宪法”,它用宏定义了每个 UUID 对应的句柄(Handle):

// GAP Service #define GATT_UUID_GAP_SERVICE 0x1800 #define GATT_UUID_DEVICE_NAME 0x2A00 #define GATT_UUID_APPEARANCE 0x2A01 // Heart Rate Service #define GATT_UUID_HEART_RATE_SERVICE 0x180D #define GATT_UUID_HEART_RATE_MEASUREMENT 0x2A37 #define GATT_UUID_CLIENT_CHAR_CONFIG 0x2902 // 句柄分配(从 0x0001 开始递增) #define GATT_HND_GAP_SERVICE 0x0001 #define GATT_HND_GAP_DEVICE_NAME 0x0002 #define GATT_HND_GAP_APPEARANCE 0x0004 #define GATT_HND_HRS_SERVICE 0x0005 #define GATT_HND_HRS_MEASUREMENT 0x0006 #define GATT_HND_HRS_MEASUREMENT_CFG 0x0008

玄学坑:.bdb文件中服务的声明顺序必须与gatt_uuid.c中的宏定义顺序一致。例如,若 XML 中<service id="HRP">写在<service id="GAP">之前,但gatt_uuid.c中GATT_HND_HRS_SERVICE定义在GATT_HND_GAP_SERVICE之后,则句柄错位,GATT_HND_HRS_MEASUREMENT实际指向 GAP 的 Device Name。

4.3gatt_profile.c:读写回调函数与句柄的绑定逻辑

gatt_profile.c中的hrs_ReadAttrCB()函数通过pAttr->handle判断访问哪个特征:

uint8 hrs_ReadAttrCB(uint16 connHandle, gattAttribute_t *pAttr, uint8 *pValue, uint16 *pLen, uint16 offset, uint16 maxLen) { // pAttr->handle == GATT_HND_HRS_MEASUREMENT → 返回心率值 // pAttr->handle == GATT_HND_HRS_MEASUREMENT_CFG → 返回 CCCD 值 if (pAttr->handle == GATT_HND_HRS_MEASUREMENT) { *pLen = hrs_MeasLen; memcpy(pValue, hrs_Meas, hrs_MeasLen); return SUCCESS; } else if (pAttr->handle == GATT_HND_HRS_MEASUREMENT_CFG) { *pLen = 2; memcpy(pValue, &hrs_MeasCfg, 2); return SUCCESS; } return ATT_ERR_ATTR_NOT_FOUND; }

血泪经验:若修改了gatt_uuid.c中的句柄值(如把GATT_HND_HRS_MEASUREMENT改为0x0007),但忘了同步修改gatt_profile.c中的if判断条件,就会出现“手机能发现服务、能读取特征、但返回值全为 0”的诡异现象——因为回调函数根本没被执行。


5. 避坑指南:CC2541 BLE 开发中 5 个高频翻车点与现场排查法

注意:以下问题均经某实验室模拟项目 X 复现验证,非理论推测。排查时请按顺序执行,跳过任一环节可能导致误判。

5.1 现象:IAR 编译通过,烧录后设备管理器识别为Unknown Device,SmartRF Flash Programmer 2 无法连接

原因:CC2541DK 板载的 USB-to-UART 芯片(CP2102)驱动未正确安装,或 USB 线仅支持充电(无数据线)。
解决:

  • 拔掉 USB 线,进入设备管理器 → 查看是否有CP2102 USB to UART Bridge Controller;
  • 若显示黄色感叹号,右键 → 更新驱动 → 浏览计算机 → 选择C:\SiliconLabs\CP210x_USB_to_UART_Bridge_VCP_Driver\(需单独下载 CP210x 驱动);
  • 换一根确认可用的数据线(如手机原装线),避免使用“USB 3.0 转接头”。

5.2 现象:simple_peripheral烧录后,手机蓝牙列表搜不到设备,但GAPRole_GetState()返回GAPROLE_STARTED

原因:广播数据(advertData)长度超 31 字节,或GAPROLE_ADVERT_ENABLED参数未设为TRUE。
解决:

  • 在simpleBLEPeripheral_Init()函数末尾添加调试打印:
    uint8 state = GAPRole_GetState(); uint8 enabled; GAPRole_GetParameter(GAPROLE_ADVERT_ENABLED, &enabled, sizeof(enabled)); // 用 UART 打印 state 和 enabled 值
  • 若state == 0x03(GAPROLE_STARTED)但enabled == 0,则补调:
    uint8 advertEnabled = TRUE; GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8), &advertEnabled);

5.3 现象:手机 APP 连接成功,但读取0x2A37(Heart Rate Measurement)特征始终返回0x00 0x00

原因:hrs_Meas全局数组未被更新,或hrs_ReadAttrCB()中的pAttr->handle与gatt_uuid.c定义不匹配。
解决:

  • 在hrs_Meas数组赋值处(如hrs_Meas[0] = 72;)前后添加 LED 闪烁:
    HAL_GPIO_SET(GPIO_PORT_P1, GPIO_PIN_0); // 点亮 LED hrs_Meas[0] = 72; HAL_GPIO_CLR(GPIO_PORT_P1, GPIO_PIN_0); // 熄灭 LED
  • 若 LED 未闪烁,说明hrs_Meas根本没被刷新,检查hrs_ProcessTMOSMsg()是否被osal_set_event()触发;
  • 若 LED 闪烁但读值仍为 0,用逻辑分析仪抓P0_1(UART TX)波形,确认hrs_ReadAttrCB()是否被调用(可在函数开头加HAL_GPIO_SET(...))。

5.4 现象:修改gatt_db.xml新增一个自定义服务后,编译报错error #20: identifier "GATT_HND_MY_SERVICE" is undefined

原因:新增服务的 UUID 未在gatt_uuid.c中定义句柄宏,且gatt_profile.c未添加对应读写回调。
解决:

  • 在gatt_uuid.c末尾添加:
    #define GATT_UUID_MY_SERVICE 0xABCD #define GATT_HND_MY_SERVICE 0x0009 // 确保不与现有句柄冲突 #define GATT_HND_MY_CHARACTERISTIC 0x000A
  • 在gatt_profile.c的gattProfileAttrTbl[]数组中插入新条目(位置必须与.bdb生成顺序一致);
  • 严禁直接修改gatt_db.bin文件——它是二进制,手动编辑必毁。

5.5 现象:使用GAPCentralRole_StartDevice()后,设备无法扫描到任何外围设备

原因:扫描参数(Scan Interval / Scan Window)设置不合理,或GAPCentralRole_StartDiscovery()未被调用。
解决:

  • 在centralRoleCBs结构体中设置合理扫描参数:
    uint16 scanInterval = 160; // 100ms(单位:0.625ms) uint16 scanWindow = 160; // 100ms GAPCentralRole_SetParameter(GAPCENTRALROLE_SCAN_INTERVAL, sizeof(uint16), &scanInterval); GAPCentralRole_SetParameter(GAPCENTRALROLE_SCAN_WINDOW, sizeof(uint16), &scanWindow);
  • 在centralStateNotificationCB()收到GAPCENTRALROLE_STATE_CHANGE_EVENT且状态为GAPCENTRALROLE_STARTED后,立即调用:
    GAPCentralRole_StartDiscovery(DEFAULT_DISCOVERY_MODE, DEFAULT_DISCOVERY_ACTIVE_SCAN, DEFAULT_DISCOVERY_WHITE_LIST);

6. 进阶技巧:用osal_snv_write()实现断电保存配对信息与服务配置

CC2541 的 Flash 有 256KB,但 BLE-Stack v1.4.2 默认只用前 16KB 存储协议栈,剩余空间可被应用层安全使用。osal_snv_write()是 TI 封装的 Flash 操作 API,它将数据写入NV_PAGE(Non-Volatile Page),支持断电保存,常用于存储配对密钥、设备名、服务配置等。

6.1 SNV(System Non-Volatile)存储机制与页布局

SNV 将 Flash 划分为多个NV_PAGE,每个页大小为 512 字节,Projects\ble\SimpleBLEPeripheral\Source\osal_snv.c中定义了页数:

#define NV_PAGES 4 // 共 4 页,总容量 2048 字节 #define NV_PAGE_SIZE 512

系统预留页 0(NV_PAGE_ZERO)存协议栈参数,应用可安全使用页 1~3(NV_PAGE_ONE~NV_PAGE_THREE)。每个页内数据以(ID, Len, Data)三元组形式存储,ID 为 16 位整数,TI 官方 ID 范围0x0000–0x00FF,应用 ID 应从0x0100开始。

6.2 保存设备名与心率阈值的完整代码示例

假设需保存:

  • 设备名(最多 12 字符)→ ID0x0100
  • 心率报警阈值(uint8)→ ID0x0101
#include "osal_snv.h" // 1. 定义保存结构 typedef struct { uint8 name[12]; uint8 hrThreshold; } deviceConfig_t; deviceConfig_t config = {"MyHR_Sensor", 100}; // 2. 写入 SNV(在初始化完成后调用) void saveConfigToSNV(void) { // 写入设备名 osal_snv_write(NV_PAGE_ONE, 0x0100, sizeof(config.name), config.name); // 写入阈值 osal_snv_write(NV_PAGE_ONE, 0x0101, sizeof(config.hrThreshold), &config.hrThreshold); } // 3. 读取 SNV(在 main() 开头调用) void loadConfigFromSNV(void) { uint8 status; // 读取设备名 status = osal_snv_read(NV_PAGE_ONE, 0x0100, sizeof(config.name), config.name); if (status != SUCCESS) { strcpy((char*)config.name, "Default"); // 读取失败时用默认值 } // 读取阈值 status = osal_snv_read(NV_PAGE_ONE, 0x0101, sizeof(config.hrThreshold), &config.hrThreshold); if (status != SUCCESS) { config.hrThreshold = 80; } }

关键参数说明:

  • NV_PAGE_ONE:指定写入页,避免与协议栈冲突;
  • 0x0100:用户自定义 ID,TI 不会使用此范围;
  • sizeof(...):必须精确,写入长度与读取长度必须一致,否则osal_snv_read()返回NV_OPER_FAILED;
  • osal_snv_write()是阻塞操作,耗时约 20ms,禁止在中断中调用。

6.3 验证 SNV 写入是否成功的 3 种方法

方法操作步骤优点缺点
UART 打印返回值osal_snv_write()返回SUCCESS或NV_OPER_FAILED,直接打印最快定位写入失败无法确认 Flash 内容是否真实写入
用 SmartRF Flash Programmer 2 读取页内容连接后 → Read → 选择0x002000–0x0021FF(页 1 地址)→ Save aspage1.bin→ 用 HxD 查看0x0100ID 头部100% 确认物理写入需额外工具,操作繁琐
重启后读取比对调用saveConfigToSNV()→ 断电 → 上电 →loadConfigFromSNV()→ UART 打印读出值验证断电保持能力耗时最长,但最贴近真实场景

我一般会在saveConfigToSNV()后立即调用loadConfigFromSNV()并比对,若值一致则认为写入成功——这是我在某跨平台系统中验证过的可靠做法。SNV 不是万能的,它有擦写次数限制(10 万次),所以不要在osal_set_event()的高频回调中反复调用osal_snv_write()。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询