在物联网、智能硬件等嵌入式应用中,BLE(蓝牙低功耗)通信因低功耗、低成本的优势被广泛应用。STM32WB 系列双核 MCU(支持 BLE5.x)是开发 BLE 应用的优选方案,但多数开发者面临 “标准例程无法满足非标需求” 的痛点 —— 传统 BLE 例程仅支持固定 GAP 服务,难以自定义 ATT 属性和服务,而实际开发中使用 BLE 私有协议的非标产品占比极高。
ST 官方 LAT1197 应用笔记提供了高效解决方案:借助 STM32CubeMX 的 Custom Template(自定义模板),无需深入理解 BLE 底层协议,即可快速搭建支持 “读 / 写、只写、只读、通知” 四种核心数据访问的通用通信框架,适配绝大多数私有协议场景。本文基于该笔记,以 STM32WB55-Nucleo 开发板为例,从工程搭建、协议配置、代码开发到测试验证,全程拆解实操流程,让开发者快速上手并直接应用于产品。
资料获取:【应用笔记】LAT1197 STM32WB基于Custom Template实现客户定制BLE私有协议
1. 核心开发框架与前期准备
1.1 通信框架设计
本方案构建 “手机 APP-STM32WB” 的 BLE 私有协议通信链路,核心包含 4 种数据访问类型,覆盖绝大多数场景:
- 读 / 写:手机可读写 STM32WB 的目标数据缓冲区(User_Write_Read_Data [64]);
- 只写:手机仅能向缓冲区写入数据(User_Write_Data [64]),STM32WB 不返回数据;
- 只读:手机仅能读取缓冲区数据(User_Read_Data [64]),无法写入;
- 通知:STM32WB 主动向手机推送数据(User_Notify_Data [64]),无需手机请求。
1.2 前期准备
- 硬件:STM32WB55-Nucleo 开发板、ST-LINK 调试器、安卓手机(安装 “BLE 调试助手”);
- 软件:最新版 STM32CubeMX、STM32CubeWB 固件包(建议 V1.13.3 及以上)、Keil MDK(或 IAR)、STM32CubeProgrammer(用于 BLE 协议栈升级)。
2. STM32CubeMX 工程搭建与外设配置
工程搭建核心分 “基础外设配置” 和 “BLE 协议栈配置” 两步,所有操作均为图形化配置,无需手动修改寄存器,步骤可复现。
步骤 1:新建工程与基础外设配置
- 打开 STM32CubeMX,点击 “File→New Project”,输入 “STM32WB55RG” 创建工程,配置工程名称、保存路径,选择编译工具(如 Keil MDK),调整堆栈大小(建议栈 1024、堆 2048);
- 系统配置:启用调试口(默认 SWD 模式),确保调试正常;
- 晶振配置:添加外部高速晶振(HSE)和低速晶振(LSE),HSE 用于系统时钟,LSE 为 RTC 提供时钟源;
- 串口配置:将 USART1 引脚重映射到 PB6(TX)/PB7(RX),使能 USART1 的 TX DMA 功能(DMA1 Channel)和全局中断,用于调试信息打印;
- BLE 相关外设使能(均由 BLE 栈自动管理,无需额外配置):
- 时钟配置:配置系统时钟为 64MHz(或 100MHz,根据实际需求),确保 BLE 通信稳定性;
- NVIC 配置:合理分配各中断的抢占优先级,IPCC、USART1 等核心中断优先级建议设为 0-2 级,避免中断冲突。
步骤 2:BLE 协议栈核心配置
这是私有协议开发的关键,通过 CubeMX 图形化界面完成 GATT 服务、特征属性、广播参数等配置:
- 使能 BLE 协议栈:在 “Connectivity→BLE” 中勾选 “BLE”,启用协议栈;
- 选择 Custom Template:禁用默认的 “Custom P2P Server”,勾选 “Custom Template”(自定义 GATT 通用模板);
- 新建 GATT 服务:点击 “Add” 创建 GATT 服务,名称设为 “My_Data_Server”,UUID 类型选择 128bits(私有协议常用);
- 配置 4 种特征属性(核心步骤):
- 读 / 写特征:名称设为 “User_Write_Read_Data”,UUID 自定义(如 0000fe41-...),属性勾选 “READ、WRITE NO RESPONSE”,数据长度设为 64 字节;
- 只写特征:名称设为 “User_Write_Data”,属性仅勾选 “WRITE NO RESPONSE”,数据长度 64 字节;
- 只读特征:名称设为 “User_Read_Data”,属性仅勾选 “READ”,数据长度 64 字节;
- 通知特征:名称设为 “User_Notify_Data”,属性勾选 “NOTIFY”,数据长度 64 字节;
- 广播参数配置:
- Advertising Type:选择 “Undirected scannable and connectable”(可扫描、可连接);
- 设备名称:CFG_GAP_DEVICE_NAME 设为 “MY_STM32WB”,便于手机搜索识别;
- 广播元素:勾选 “AD_TYPE_COMPLETE_LOCAL_NAME”(广播完整设备名)、“AD_TYPE_TX_POWER_LEVEL”(广播发射功率);
- 配对参数配置:默认配置即可(如加密密钥长度 16 字节),若需安全通信可调整配对模式和密钥;
- 调试打印配置:启用 BLE 协议栈调试,关联 USART1 作为打印端口,便于查看协议栈运行状态。
3. 工程代码生成与关键修改
点击 CubeMX 的 “GENERATE CODE” 生成工程代码后,需进行 3 处关键修改,确保调试和 BLE 协议栈正常运行:
- 取消 USART1 初始化的 static 属性:打开 “main.c”,找到 “MX_USART1_UART_Init (void)”,删除前面的 “static” 关键字,使其可被外部文件调用;
- 添加 Debug 初始化代码:打开 “app_entry.c”,在 “MX_APPE_Init (void)” 函数中添加 “APPD_Init ();”,初始化调试打印模块;
- 测试基础配置:将代码下载到开发板,打开串口助手(波特率 115200、8N1),若打印 “Hello STM32WB55”“Wireless Firmware version x.x.x”,说明 BLE 协议栈和调试功能正常;此时手机蓝牙可搜索到 “MY_STM32WB” 设备,基础配置验证通过。
4. BLE 应用代码开发:事件驱动 + 数据交互
核心代码开发集中在 “custom_stm.c” 和 “custom_app.c”,分别负责 “BLE 栈层事件驱动” 和 “应用层数据处理”,逻辑清晰,无需修改其他自动生成文件。
4.1 定义用户数据缓存区
打开 “custom_app.c”,在 “/* USER CODE BEGIN PV */” 区域定义 4 个 64 字节数据缓冲区,对应 4 种访问类型:
#define USER_MAX_BUF_SIZE (64)
uint8_t User_Write_Read_Data[USER_MAX_BUF_SIZE]; // 读/写缓冲区
uint8_t User_Write_Data[USER_MAX_BUF_SIZE]; // 只写缓冲区
uint8_t User_Read_Data[USER_MAX_BUF_SIZE]; // 只读缓冲区
uint8_t User_Notify_Data[USER_MAX_BUF_SIZE]; // 通知缓冲区
4.2 栈层事件驱动代码(custom_stm.c)
在 “Custom_STM_Event_Handler (void *Event)” 函数中添加事件驱动代码,实现 “栈层数据→应用层” 的传递,4 种特征仅需配置前 3 种(通知特征由 CubeMX 自动生成驱动):
(1)读 / 写特征(User_Write_Read_Data)事件驱动
// 写请求事件
else if(attribute_modified->Attr_Handle == (CustomContext.CustomWrite_Read_Data_BufHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET)) {
return_value = SVCCTL_EvtAckFlowEnable;
// 通知应用层:收到写请求
Notification.Custom_Evt_Opcode = CUSTOM_STM_WRITE_READ_DATA_BUF_WRITE_NO_RESP_EVT;
Notification.DataTransfered.Length = attribute_modified->Attr_Data_Length;
Notification.DataTransfered.pPayload = attribute_modified->Attr_Data;
Custom_STM_App_Notification(&Notification);
}
// 读请求事件
case ACI_GATT_READ_PERMIT_REQ_VSEVT_CODE:
read_req = (aci_gatt_read_permit_req_event_rp0*)blecore_evt->data;
if(read_req->Attribute_Handle == (CustomContext.CustomWrite_Read_Data_BufHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET)) {
return_value = SVCCTL_EvtAckFlowEnable;
// 通知应用层:收到读请求
Notification.Custom_Evt_Opcode = CUSTOM_STM_WRITE_READ_DATA_BUF_READ_EVT;
Custom_STM_App_Notification(&Notification);
aci_gatt_allow_read(read_req->Connection_Handle); // 允许读操作
}
(2)只写特征(User_Write_Data)事件驱动
else if(attribute_modified->Attr_Handle == (CustomContext.CustomWrite_Data_BufHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET)) {
return_value = SVCCTL_EvtAckFlowEnable;
// 通知应用层:收到只写请求
Notification.Custom_Evt_Opcode = CUSTOM_STM_WRITE_DATA_BUF_WRITE_NO_RESP_EVT;
Notification.DataTransfered.Length = attribute_modified->Attr_Data_Length;
Notification.DataTransfered.pPayload = attribute_modified->Attr_Data;
Custom_STM_App_Notification(&Notification);
}
(3)只读特征(User_Read_Data)事件驱动
else if(read_req->Attribute_Handle == (CustomContext.CustomRead_Data_BufHdle + CHARACTERISTIC_VALUE_ATTRIBUTE_OFFSET)) {
return_value = SVCCTL_EvtAckFlowEnable;
// 通知应用层:收到只读请求
Notification.Custom_Evt_Opcode = CUSTOM_STM_READ_DATA_BUF_READ_EVT;
Custom_STM_App_Notification(&Notification);
aci_gatt_allow_read(read_req->Connection_Handle); // 允许读操作
}
4.3 应用层数据处理代码(custom_app.c)
在 “Custom_STM_App_Notification (Custom_STM_App_Notification_evt_t *pNotification)” 函数中,处理应用层数据交互逻辑,实现 “手机 - STM32WB” 的数据读写与通知:
switch(pNotification->Custom_Evt_Opcode) {
// 读/写特征-读请求:返回指定数据
case CUSTOM_STM_WRITE_READ_DATA_BUF_READ_EVT:
strcpy((char*)User_Write_Read_Data, "User_Write_Read_Data[]--STM32WB");
Custom_STM_App_Update_Char(CUSTOM_STM_WRITE_READ_DATA_BUF, (uint8_t*)&User_Write_Read_Data[0]);
APP_DBG_MSG("** 手机读取读/写缓冲区数据...\n");
break;
// 读/写特征-写请求:接收手机数据
case CUSTOM_STM_WRITE_READ_DATA_BUF_WRITE_NO_RESP_EVT:
memset(User_Write_Read_Data, 0, sizeof(User_Write_Read_Data));
memcpy(User_Write_Read_Data, pNotification->DataTransfered.pPayload, pNotification->DataTransfered.Length);
APP_DBG_MSG("** 手机写入数据到读/写缓冲区,长度=%d:%s\n", pNotification->DataTransfered.Length, User_Write_Read_Data);
break;
// 只写特征-写请求:接收手机数据
case CUSTOM_STM_WRITE_DATA_BUF_WRITE_NO_RESP_EVT:
memcpy(User_Write_Data, pNotification->DataTransfered.pPayload, pNotification->DataTransfered.Length);
APP_DBG_MSG("** 手机写入数据到只写缓冲区,长度=%d:%s\n", pNotification->DataTransfered.Length, User_Write_Data);
break;
// 只读特征-读请求:返回指定数据
case CUSTOM_STM_READ_DATA_BUF_READ_EVT:
strcpy((char*)User_Read_Data, "User_Read_Data[]--hello STM32WB");
Custom_STM_App_Update_Char(CUSTOM_STM_READ_DATA_BUF, (uint8_t*)&User_Read_Data[0]);
APP_DBG_MSG("** 手机读取只读缓冲区数据,长度=%d\n", strlen((char*)User_Read_Data));
break;
// 通知特征-使能:推送初始数据
case CUSTOM_STM_NOTIFY_DATA_BUF_NOTIFY_ENABLED_EVT:
strcpy((char*)User_Notify_Data, "User_Notify_Data[]--hello STM32WB");
Custom_STM_App_Update_Char(CUSTOM_STM_NOTIFY_DATA_BUF, (uint8_t*)&User_Notify_Data[0]);
APP_DBG_MSG("** 通知功能使能,推送初始数据,长度=%d\n", strlen((char*)User_Notify_Data));
break;
// 通知特征-禁用
case CUSTOM_STM_NOTIFY_DATA_BUF_NOTIFY_DISABLED_EVT:
APP_DBG_MSG("** 通知功能禁用\n");
Custom_App_Context.Notify_data_buf_Notification_Status = 0;
break;
default:
break;
}
4.4 扩展:修改 BLE 最大数据包长度
默认情况下,BLE 单包传输长度仅 23 字节,需修改配置支持最大 512 字节,适配大数据传输:
- 打开 “app_conf.h”,修改
CFG_BLE_MAX_ATT_MTU为 512:#define CFG_BLE_MAX_ATT_MTU (512) - 打开 “app_ble.c”,在
SVCCTL_App_Notification(void *pckt)函数的HCI_LE_CONNECTION_COMPLETE_SUBEVT_CODEcase 中,添加 MTU 和数据长度配置:aci_gatt_exchange_config(BleApplicationContext.BleApplicationContext_legacy.connectionHandle); hci_le_set_data_length(handleNotification.ConnectionHandle, 251, 265*8);
5. BLE 协议栈升级:关键前置步骤
STM32WB 的 BLE 协议栈需单独安装 / 升级,否则无法正常运行,需通过 STM32CubeProgrammer 操作:
- 升级 ST-LINK 固件:确保 ST-LINK 与 STM32CubeProgrammer 驱动匹配;
- 升级 FUS(固件升级服务):
- 连接开发板,打开 STM32CubeProgrammer,选择 ST-LINK 连接;
- 读取当前 FUS 版本:若为 0.5.3,选择固件 “stm32wb5x_FUS_fw_for_fus_0_5_3.bin”;否则选择 “stm32wb5x_FUS_fw.bin”;
- 选择对应 MCU 型号的下载地址(如 STM32WB55RG 为 0x080EC000),点击 “Start Programming” 完成升级;
- 安装 BLE 协议栈:
- 选择 BLE 协议栈固件(如完整功能栈 “stm32wb5x_BLE_Stack_full_extended_fw.bin”);
- 选择对应 MCU 的下载地址(STM32WB55RG 为 0x080D0000),点击 “Start Programming”,完成后勾选 “Start stack after upgrade”。
6. 测试验证:手机 APP 与 STM32WB 通信
使用安卓手机的 “BLE 调试助手” 验证 4 种数据访问功能,步骤如下:
- 打开 “BLE 调试助手”,下滑扫描 BLE 设备,找到 “MY_STM32WB”,点击 “CONNECT” 连接;
- 修改 MTU:在 APP 中找到 “修改 MTU” 选项,设置为 256 字节(避免分包传输);
- 功能测试:
- 读 / 写测试:找到 “READ WRITE NO RESPONSE” 特征,点击 “写入” 输入数据(如 “test123”),串口助手打印接收信息;再点击 “读取”,APP 接收返回数据 “User_Write_Read_Data []--STM32WB”;
- 只写测试:找到 “WRITE NO RESPONSE” 特征,写入数据,串口助手打印接收信息,APP 无返回;
- 只读测试:找到 “READ” 特征,点击 “读取”,APP 接收 “User_Read_Data []--hello STM32WB”;
- 通知测试:找到 “NOTIFY” 特征,点击 “启用通知”,APP 接收初始推送数据 “User_Notify_Data []--hello STM32WB”,串口助手打印通知使能信息。
7. 开发总结与灵活拓展
7.1 核心优势
本方案基于 STM32CubeMX 的 Custom Template,无需深入理解 BLE 底层 ATT/GATT 协议,即可快速搭建通用私有协议框架,具有三大核心优势:
- 高效开发:图形化配置 + 极简代码,新手也能快速上手;
- 通用性强:4 种数据访问类型覆盖绝大多数场景,可直接迁移到不同产品;
- 稳定可靠:基于 ST 官方协议栈和模板,兼容性和稳定性有保障。
7.2 灵活拓展场景
- 增加更多特征:在 CubeMX 中新增 GATT 特征,重复 “事件驱动 + 应用处理” 代码,即可实现多特征多数据通道;
- 自定义数据格式:修改缓冲区数据的解析逻辑,适配 JSON、二进制等私有协议格式;
- 加密通信:在 CubeMX 中配置 BLE 配对加密参数(如 AES-128),保障数据传输安全;
- 低功耗优化:结合 RTC 唤醒和 BLE 休眠配置,降低设备功耗,适配电池供电场景。
7.3 注意事项
- 协议栈版本:确保 STM32CubeWB 固件包与 BLE 协议栈版本匹配,避免兼容性问题;
- 中断优先级:IPCC、BLE 相关中断优先级需高于普通外设,避免中断阻塞导致数据丢失;
- 缓冲区大小:根据实际需求调整
USER_MAX_BUF_SIZE,避免内存浪费或数据溢出。
STM32WB 的 Custom Template 为 BLE 私有协议开发提供了 “降门槛、提效率” 的最优解,借助 STM32Cube 生态的图形化配置和完善固件包,开发者可聚焦应用逻辑,无需纠结底层协议细节,快速实现产品落地。无论是智能穿戴、智能家居还是工业传感器,该框架都能提供稳定可靠的 BLE 通信支撑。
465