BK3432 BLE SoC开发实战:从SDK到水控器应用
简介面向小博通BK3432蓝牙芯片开发者的完整资料包内容涵盖Datasheet硬件手册、SDK软件开发包、Userguide用户指南与Example示例工程适合智能家居、健康监测、穿戴设备等物联网项目的嵌入式开发人员从入门到进阶使用。包内共1643个文件以h头文件、c源码、o目标文件、bin固件、uvproj工程文件为主另有PDF文档、SCH原理图、PCB设计文件可支撑驱动移植、应用编程与硬件调试全流程压缩包约22.61MB已有1525人学习下载。SDK中提供驱动库、API接口及丰富示例覆盖数据收发、设备配对、服务创建等典型蓝牙应用场景Userguide则协助开发者快速搭建开发环境并排查问题附带的原理图与PCB文件更便于参考硬件设计整体目录清晰、资料完整能有效减少重复摸索帮助新人缩短学习曲线并加速产品落地。1. 一包资料里最容易被忽略的 BK3432 起点刚拿到“BK3432资料包V1_bk”时第一感觉是乱datasheet、Userguide、Example 堆在一起工程目录里还躺着一堆 bk3432.uvgui.Administrator、bk3435.uvgui.Administrator 这样的 Keil 界面备份文件。真正开始做一款蓝牙水控器时才发现这正是小博通 BK3432 这种低成本 BLE SoC 的特点——资料看起来老但足够完整SDK 用 Keil 打开就能编译从点灯到 GATT 透传一个晚上能跑通。这篇文章按 datasheet 到 SDK、再到 example 和烧录的顺序拆解适合从嵌入式转到蓝牙开发的工程师也适合要给老产品维护代码的人。后面每一章都有可以直接抄的配置和参数以及看不到的坑。2. BK3432 硬件选型与 SDK 工程结构从 datasheet 到 Keil 工程2.1 为什么选 BK3432 而不是通用蓝牙模块很多新手先玩过 HC-05 这类经典蓝牙模块串口 AT 指令很快但协议栈被封装死了改不了广播名之外的任何行为。BK3432 是完整的 BLE SoC芯片内部同时跑协议栈和应用代码应用层可以访问 GPIO、UART、PWM、Flash 这些资源。相比模块方案它省掉一个 MCU、一个 UART 通道BOM 成本更低响应也更直接。代价是开发方式变了你必须接受 SDK 的工程结构而不是往串口里发 AT 指令。选型时要区分 BLE 和经典蓝牙协议BK3432 是低功耗蓝牙适合周期性上报的小数据量场景比如水控器扣费、穿戴设备传感器同步如果设备要传语音或串口透传大数据量那得走经典蓝牙协议 SPP这类场景 BK3432 并不合适。资料包的 datasheet 里有明确的射频指标和工作模式先看这个再决定能不能用比先跑例程更省时间。2.2 资料包内容表与阅读顺序资料包 V1 里的内容很多但没有索引。按下面的顺序读可以避免在无关文件里浪费时间。资料项主要内容最佳阅读时机datasheet引脚定义、电气参数、射频指标、封装画原理图和 PCB 之前Userguide环境搭建、编译烧录、调试方法第一次打开 Keil 之前SDK驱动源码、API 头文件、协议栈库datasheet 和 Userguide 之后Example外设 demo、BLE demo、协议栈初始化代码理解 API 之后写自己的应用时uvgui/uvproj/uvoptxKeil 工程配置与界面布局只在工程打不开时去动我一般先把 datasheet 里引脚定义拷贝出来对照 SDK 里gpio.h的宏名确认同一个 GPIO 有没有被两个外设占用再开始建工程。这一步能省掉很多后面的跳线。2.3 Keil 工程配置文件拆解资料包根目录有多个.uvgui.Administrator文件。这类文件不是源码是 Keil uVision 保存的窗口布局、字体颜色和断点视图。每个 Windows 用户名会生成一个独立文件所以你会看到 Administrator、user、developer 各自一份。它们不影响编译删掉也能重新生成。BK3432_SDK/ ├── app/ # 应用主函数、外设初始化 ├── bsp/ # 板级支持包按键、LED ├── drivers/ # 芯片寄存器级驱动 ├── profiles/ # BLE GATT 服务定义 ├── startup/ # 启动文件和芯片初始化 ├── bk3432.uvproj # Keil 工程主文件 ├── bk3432.uvoptx # 断点和内存窗口配置 └── bk3432.uvgui.Administrator这里的bk3432.uvproj是真正要双击打开的文件uvoptx是可选的用户配置换电脑后经常发生警告但没有危险。日志里如果出现Missing uvgui直接点 Yes 让 Keil 重新生成即可。2.4 第一次编译要改的三个位置打开工程后不要直接编译先做三件事确认 Target 选择的是 Flash 版本而不是 RAM 版本确认 Device 芯片型号与你手上的样片一致确认烧录器选项里的 Flash 编程算法匹配。常见做法是先切到 SDK 自带的 example 工程编译一次通过建立基线再改自己的代码。# 用 Keil 打开 example/poweron_demo/poweron_demo.uvproj # 在 Options for Target - Target 中选择 Flash 目标 # 按 F7 编译输出 app.hex 到 Objects 目录 # 若报 No Algorithm在 Utilities 里选择对应的 Flash 下载算法这段操作的关键是理解SDK 里不同 Target 对应不同链接脚本。RAM 版本用于在线调试退出调试后程序丢失Flash 版本烧进去才能独立运行。编译后如果报No Algorithm就是第三步没做在 Utilities 设置里选择对应的 Flash 算法而不是去改代码。3. bk3432sdk 外设初始化实战GPIO、UART、PWM 参数陷阱3.1 GPIO 配置与输入拉高的坑BK3432 的 GPIO 一般通过gpio_config配置方向再配合gpio_read/write操作。SDK 里的模式名通常是GPIO_OUTPUT、GPIO_INPUT_PULLUP和GPIO_INPUT_PULLDOWN。按键电路最常用的是内部上拉输入好处是省一颗外部电阻。#include gpio.h #define LED_PIN GPIO_8 /* 板载 LED低电平点亮 */ #define KEY_PIN GPIO_9 /* 按键按下为低 */ void board_gpio_init(void) { gpio_config(LED_PIN, GPIO_OUTPUT); gpio_write(LED_PIN, 1); /* 初始熄灭 */ gpio_config(KEY_PIN, GPIO_INPUT_PULLUP); if (0 gpio_read(KEY_PIN)) { /* 按下检测 */ gpio_write(LED_PIN, 0); /* 点亮 */ } }这里的GPIO_OUTPUT表示推挽输出GPIO_INPUT_PULLUP表示内部上拉输入。BK3432 不是所有引脚都支持上拉查 datasheet 的 IO 复用表时要确认你选的那个引脚的 Pull-up 列不是-。曾经有个工程 PCB 上没放外部上拉代码里也没有开内部上拉按键一直读不到低电平最后查出来是选了一个 reset 复用引脚内部根本没有上拉管。3.2 UART 波特率与外设时钟的关系串口是调试低功耗蓝牙的必经之路。SDK 的uart_printf在 example 工程里默认开启但第一次打印乱码的概率很高原因集中在两处一是 BK3432 的 UART 波特率由系统时钟和分频寄存器共同决定改用外部 32MHz 晶振后如果 SDK 时钟配置没有同步修改115200 实际偏差可能超过 3%二是某些 SDK 版本的uart_printf只重定向了标准输出没有初始化发送 DMA。#include uart.h /* UART0 初始化8 位数据无校验1 停止位 */ uart_init(UART0, 115200, UART_8N1); uart_printf(BK3432 UART OK\r\n);UART0是串口号115200是波特率UART_8N1是数据格式。调试时如果输出乱码先不要改波特率用示波器抓 TX 引脚的波形量一下实际波特率再反推系统时钟设置。更快的办法是先把uart_init里的波特率改成 9600看乱码是否变成有规律的字符如果是说明主要是频率偏差。3.3 PWM 通道与定时器映射PWM 在指示灯、蜂鸣器和电机调速场景很常用。BK3432 的 PWM 通常是芯片内部定时器通道复用出来的不是任意 GPIO 都能输出。不同 SDK 版本对 PWM 通道的映射有差异建议以 example 里的pwm_test为基准。#include pwm.h /* PWM_CH0 映射到 GPIO_6频率 1kHz占空比 50% */ pwm_init(PWM_CH0, 1000, 50); pwm_start(PWM_CH0); /* 动态调占空比控制亮度 */ pwm_update_duty(PWM_CH0, 10);1000是频率单位 Hz50是初始占空比百分比。注意 BK3432 的pwm_update_duty有的版本接受实时占空比有的版本接受的是寄存器计数阈值两者差一个最大计数值。如果调用函数后波形不变去头文件里看参数名是duty还是threshold这是最常被搞错的地方。3.4 在 example 工程里快速验证外设最快的外设验证方法是复制 example 里的代码而不是从零写。把poweron_demo里的main.c整体替换成自己的初始化函数编译下载后再逐步添加逻辑。蓝牙 SDK 的外设初始化顺序一般要求先系统时钟再 GPIO再 UART最后再开 BLE。外设初始化放在协议栈初始化之前否则某些 GPIO 复用配置会被协议栈覆盖。int main(void) { sys_init(); board_gpio_init(); uart_init(UART0, 115200, UART_8N1); pwm_init(PWM_CH0, 1000, 50); ble_init(); /* 协议栈最后启动 */ while (1) { pwm_update_duty(PWM_CH0, 10 (cnt % 81)); } }这段代码体现的是外设层和协议栈层的先后关系。BLE 协议栈可能占用定时器、Flash 和部分 GPIO后初始化可以让它覆盖默认复用表。如果把pwm_init放在ble_init之后部分引脚的 PWM 波形会被协议栈的射频事件打断出现周期性抖动且很难排查。4. 蓝牙协议栈接入从广播到 GATT 服务的完整流程4.1 配置广播数据与广播间隔BLE 设备上电后首先要可被发现广播包里的数据决定了手机 App 能否认出来。BK3432 SDK 的 example 里通常有一段广播配置结构是 data 数组加长度再加广播参数。#include ble_api.h static const uint8_t adv_data[] { 0x02, 0x01, 0x06, /* Flags: LE General Discoverable */ 0x03, 0x02, 0xFF, 0x18, /* 厂商自定义 Service UUID 0x18FF */ 0x05, 0x09, B, K, 3, 4, 3 }; static const uint8_t adv_resp[] { 0x03, 0x03, 0x00, 0x18, /* 完整服务 UUID */ 0x02, 0x0A, 0xEB /* Tx Power Level */ }; ble_gap_adv_start(adv_data, sizeof(adv_data), adv_resp, sizeof(adv_resp), 160, 0);广播数据数组里每一行的第一个字节是长度第二个字节是 AD Type。0x01是 Flags0x02是完整服务 UUID 列表0x09是设备名称0x0A是发射功率。160是广播间隔单位 0.625ms也就是 100ms。广播间隔越小手机扫描发现越快但平均电流越大。如果只是周期性上报数据建议把间隔放到 300ms 以上。4.2 连接参数更新与功耗权衡广播只是开始连接后手机和 BK3432 之间会协商连接间隔、从机延迟和监督超时。常见做法是广播参数里设置一个较大的连接间隔连接建立后再让协议栈请求更新参数。下面这组参数是低功耗传感器常见的组合。参数推荐值影响广播间隔100ms ~ 500ms越小越容易被发现功耗越高连接间隔30ms ~ 50ms数据延迟和功耗的折中从机延迟1 ~ 4允许跳过节拍明显省电监督超时2s ~ 6s超过没收到包就断开连接从机延迟是 BK3432 这类 BLE 设备省电的关键。如果主机每隔 30ms 发一次包而从机延迟设为 4从机可以跳过最多 4 个连接事件只在需要时醒来。水控器这种设备大多数时间不通信用这个参数后平均电流能降不少。4.3 GATT 服务、特征值与透传通道BLE 应用层数据都装在 GATT 服务里。一个服务包含若干特征值每个特征值可以设置为读、写、通知。BK3432 SDK 的 example 里通常已经有一个自定义服务直接改 UUID 和特征值数组就能用。static const uint8_t svc_uuid[] {0xFF, 0x18}; static const uint8_t char_rx_uuid[] {0x01, 0xFF}; static const uint8_t char_tx_uuid[] {0x02, 0xFF}; uint8_t rx_value[20]; uint8_t tx_value[20] {0}; gatt_add_service(svc_uuid, 2); gatt_add_characteristic(char_rx_uuid, GATT_PROP_WRITE, rx_value, 20); gatt_add_characteristic(char_tx_uuid, GATT_PROP_NOTIFY, tx_value, 20);GATT_PROP_WRITE表示主机可以下发数据GATT_PROP_NOTIFY表示设备可以主动通知主机。20是单包最大有效负载这是 BLE 4.2 之前的默认 MTU 限制。BK3432 协议栈是否支持更大的 MTU以 SDK 头文件里的GATT_MTU_SIZE宏为准不要盲目加长发送长度否则数据会被协议栈直接丢弃。4.4 RSSI 测距与经典蓝牙协议对照很多开发者会拿广播 RSSI 做蓝牙测距比如室内防丢器。BK3432 的射频链路本身没有问题但 RSSI 受天线方向、人体遮挡和多径影响很大同一位置 1 米距离的 RSSI 波动通常在 6dB 以上折算成距离偏差会很夸张。测距功能要放在连接态去读并且要过滤掉广播时隙的 RSSI 样本用滑动滤波。这里还要区分 BLE 和经典蓝牙协议HC-05 这类经典蓝牙模块走 SPP连接后就是一条串口管道适合数据量大的透传BK3432 走的 BLE GATT 是面向属性的每次通信要先找到服务句柄、写特征值再等通知回来逻辑上绕一点。选型时如果只做透传且不关心功耗经典蓝牙协议模块反而更省事一旦考虑功耗和移动端原生支持BLE 才是对的方向。5. 蓝牙水控器应用中的低功耗与 Flash 读写5.1 低功耗模式和唤醒源蓝牙水控器这类电池供电设备决定续航的是睡眠时间和唤醒策略。BK3432 SDK 一般提供 sleep 和 deep sleep 两种模式deep sleep 下射频关闭只有 GPIO、定时器和 RTC 可以唤醒。睡前要关闭 UART、PWM 这些外设时钟只保留唤醒源否则唤醒电流比睡眠电流还高。#include power.h #include gpio.h gpio_config(GPIO_2, GPIO_INPUT_PULLUP); power_set_wakeup_src(WAKEUP_GPIO | WAKEUP_TIMER); power_enter_sleep(SLEEP_DEEP); /* 唤醒后从这里继续执行 */WAKEUP_GPIO对应按键唤醒WAKEUP_TIMER对应定时上报。唤醒后第一件事要重新初始化时钟和外设因为 deep sleep 可能把系统时钟源切到低频。调试时如果唤醒后串口打印乱码多半是遗漏了 PLL 重新锁定在power_enter_sleep返回后加一个sys_clock_restore()调用问题就消失了。5.2 Flash 存储用户参数与掉电保护水控器需要保存扣费金额、设备 ID、校准数据掉电不能丢。BK3432 内置 FlashSDK 提供flash_read/write接口但它不像 MCU 的 EEPROM 那样可以直接按字节改写写之前必须擦除整个扇区。常见做法是把参数组织成一条记录写入前先备份旧数据这样擦写中断时还能恢复。#include flash.h #define CFG_SECTOR_ADDR 0x1F000 #define CFG_MAGIC 0x4B42 typedef struct { uint16_t magic; uint16_t len; uint32_t crc; uint8_t data[32]; } cfg_record_t; void save_config(uint8_t *data, uint16_t len) { cfg_record_t cfg; cfg.magic CFG_MAGIC; cfg.len len; cfg.crc calc_crc32(data, len); memcpy(cfg.data, data, len); flash_erase_sector(CFG_SECTOR_ADDR); flash_write(CFG_SECTOR_ADDR, (uint8_t *)cfg, sizeof(cfg)); }这段代码的关键是flash_erase_sector和flash_write不能直接连写中间要留出擦除完成时间。SDK 里如果这两个函数是同步阻塞的问题不大如果是异步的必须等擦除回调完成否则写入地址上全是 0xFF 就没反应了。CRC 字段用于读回时校验写数据前先把整包擦掉再写入掉电中途损坏时还能检测到 magic 不对选择恢复旧值。5.3 泰凌微与 BK3432 的 SDK 差异说明很多团队同时做过泰凌微和 BK3432 的方案两者的资料包风格差别很大。泰凌微蓝牙 SDK 的 flash 操作接口把地址映射封装得更像 EEPROMflash_write内部帮你处理了扇区擦除使用上更友好BK3432 的 SDK 则把擦和写分开要求开发者自己管理扇区地址。这不是谁优谁劣而是设计取向不同前者降低上手门槛后者给上层更大控制力。换平台时最容易出问题的地方恰恰是 flash API 的语义差异而不是蓝牙协议本身。对比项BK3432 SDK泰凌微 SDK开发环境Keil uVision厂商基于 Eclipse 的 IDEflash 写入先擦后写分步调用封装后接近 EEPROMAPI 命名设备名_动作模块_动作 为主例程风格强调单芯片应用强调 Mesh 组网这个表的信息量在于如果你从泰凌微切过来不能拿原来的 flash 读写思路硬套。先看 datasheet 里 Flash 章节的擦除限制再看 example 里有没有保存参数的标准实现通常poweron_demo里就有直接抄是最稳妥的。6. Keil 工程异常恢复与量产烧录从 uvgui 到产测脚本6.1 当 Keil 打开工程卡死时先处理 uvgui资料包里那串重复的bk3432.uvgui.Administrator文件是不同开发者在本机打开工程时留下的窗口布局。多人拷贝工程时这些文件很容易过期损坏典型表现是双击uvproj后 Keil 一直转圈或者提示Cannot load project。这时候不要急着重装 Keil把uvgui和uvoptx都删掉让 Keil 重新生成源码和编译选项都在uvproj里不会丢。cd BK3432_SDK rm -f bk3432.uvgui.* rm -f bk3432.uvoptx删除后重新打开工程Keil 会以默认布局出现断点全部清空。这个操作解决不了编译报错只解决工程打开异常。我建议在收到任何第三方拷贝的 Keil 工程目录时第一步就执行这个清理比一次一次点 Yes 快得多。6.2 烧录参数与产测注意事项量产烧录时BK3432 通常通过串口进入烧录模式。生产脚本一般要先把芯片拉进下载模式再写 app.hex最后校验 flash。常见做法是保留一条verify步骤不要为了省时间取消。烧录器的 GPIO 占用也要看 SDK 的下载协议某些引脚在烧录期间不能接别的负载。# 常见产测烧录指令参数以资料包内工具版本为准 beken_programmer --chip BK3432 --port COM3 --baud 460800 \ --write app.hex --verify --lock--lock用于量产时使能 flash 加密防止固件被直接读出来调试阶段不要加。注意如果烧录失败时报erase failed先检查供电烧录时 BK3432 的电流峰值可能让 USB 转串口掉压。批量验证时建议每片都读一遍 flash 的 CRC和写入时计算出的结果比对这是最省事的产测方法。遇到烧录失败时按这个顺序排查驱动、波特率、供电、flash 加密位。本文还有配套的精品资源点击获取