
三分钟说句实在话我第一次搭nRF52840的开发环境光搞清楚协议栈和App工程的地址关系就耗了一下午。但反过来讲如果把这套流程里的坑提前填平从硬件接上线到手机App里看到广播数据确实用不了几分钟。这篇文章我就按Keil MDK这条老路线把nRF52840的BLE开发环境从工具安装、SoftDevice协议栈烧录、应用固件编译下载到手机端APP调试完整走一遍。适合刚拿到开发板、想快速跑起第一个蓝牙Demo的读者也适合从STM32生态转过来的工程师。目标只有一个让你知道每次复位后芯片里到底发生了什么以及怎么把问题定位到协议栈、App还是手机端。接下来把整体设计和环境准备讲清楚。1. 环境准备与版本匹配1.1 为什么选择Keil nRF5 SDK这套老组合nRF52840是Nordic主打的BLE 5.0单芯片方案很多开发者在考虑怎么给它写代码时会发现官方现在主推的是nRF Connect SDK底层是Zephyr RTOS编译构建走CMake默认IDE是SEGGER Embedded Studio。这套新工具链功能强大但对只想快速验证一个外设原型、或者习惯了Keil的工程师来说学习成本太高了。nRF5 SDK是Nordic上一代经典开发包直接基于C裸机例程一个工程就是一堆源文件加一个Keil工程文件改完宏定义就能编译下载几乎不需要额外构建系统。更重要的是网上大量例程、CSDN笔记、老工程师的实战经验都是围绕nRF5 SDK写的遇到问题更容易搜到答案。所以标题里的“Keil版”本质上就意味着走nRF5 SDK这条老路线。它不是过时技术反而因为资料全、上手快非常适合新手把“BLE协议栈应用”这套内存模型弄明白。等你理解清楚了再切换到nRF Connect SDK也不会觉得陌生。1.2 需要准备的工具清单我实际测试下来完整环境需要下面这些东西。硬件就是最常见的nRF52840 DK开发板PCA10056如果没有官方DK用第三方的nRF52840模块加一个J-Link也能跑只是接线稍微麻烦一点。软件上有一个坑要特别注意Keil版本和SDK版本是绑定的不是越新越好。需要的硬软件如下nRF52840 DK开发板板载J-Link-OB调试器不用额外买仿真器。Keil MDK 5版本建议5.23以上。社区版或评估版都能编译Nordic官方例程安装时记得勾选对芯片支持包。ARM Compiler 5编译器。MDK 5.37以后默认是AC6(armclang)老SDK例程是AC5写法建议另外安装AC5并设为项目编译器。nRF5 SDK 17.0.2Nordic官网注册后下载这是传统SDK最后一个大版本。SoftDevice S140 7.2.0它就是标题说的“协议栈”本体实际上是一段Nordic编译好的BLE协议栈固件。nRF Command Line Tools里面包含nrfjprog和mergehex两个主力工具烧录和合并固件都靠它。SEGGER J-Link驱动板载调试器的USB驱动也由它提供。手机端装nRF Connect App用于后续扫描、连接、读写特征值。1.3 版本匹配是基础中的基础SoftDevice和SDK版本如果不匹配编译出来运行必崩表现就是程序跑飞、无法广播、调试日志停在初始化之前。我在工程里见过有人拿S140 6.x去配SDK 17编译能过但一上电就在协议栈初始化那一步出错排查了很久。下面这张表是我验证过比较稳的组合新手直接照抄就行nRF5 SDK版本SoftDevice版本Keil MDK建议备注15.3.0S140 6.1.15.23及以上例程多很多旧笔记都基于它16.0.0S140 6.1.15.23及以上API有一定更新路径和15.x不同17.0.2S140 7.2.05.26及以上传统SDK最后版本我推荐用它安装顺序上先装Keil再装J-Link驱动然后装nRF Command Line Tools最后解压SDK。SDK解压路径里不要有中文和空格不然一些老的make脚本会有莫名其妙的问题。安装过程本身没多少技术含量真正容易出错的是路径和环境变量装完nRF命令行工具后可以在CMD里输入nrfjprog --version确认一下能正常打印版本就算装好了。2. 协议栈烧录先让芯片拥有蓝牙能力2.1 SoftDevice到底是什么很多人第一次接触nRF52840会被“协议栈”这个概念绕晕。说白一点SoftDevice是Nordic提供的独立BLE协议栈二进制度件它编译好后放在芯片Flash的低地址区APP不细管射频、链路层、连接调度这些事只需要调用Nordic提供的SD API就能实现广播、扫描、连接等操作。我常用的一个类比App应用程序是一辆车的驾驶舱SoftDevice是发动机和变速箱你只需要踩油门转方向不需要自己点燃气缸。更准确地说SoftDevice会占用Flash低地址区的一部分比如S140 7.2.0大约占Flash前152KB从0x00000000到0x00025FFF。它还会在RAM的高地址区保留一些空间做协议栈上下文。所以你的App不能从Flash 0x0开始放而是要从0x26000这个地址开始链接。这就是整个流程里最容易出问题的地方烧录和链接地址都必须意识到“Flash的前半段被协议栈占了”。2.2 用命令行把SoftDevice烧进芯片烧录协议栈推荐直接用nrfjprog命令行一是可脚本化二是步骤透明出了问题好排查。先把开发板通过USB连到电脑确认设备管理器里能看到J-Link的COM口和USB设备。然后在CMD里执行nrfjprog --eraseall nrfjprog --program s140_nrf52_7.2.0_softdevice.hex nrfjprog --reset第一条命令全片擦除会把芯片里包括SoftDevice、App、UICR、保护位全部清干净。第二条是把S140的hex文件写入Flash低地址区。执行完第三条复位芯片里就有基础蓝牙协议栈了。需要注意的是有些教程喜欢用一行命令简化nrfjprog --program s140_nrf52_7.2.0_softdevice.hex --chiperase--chiperase的意思是烧录前先擦除整片Flash。新板子第一次刷协议栈这个命令确实方便。但如果你的Flash里已经有App程序或者调试信息执行这条会把它们全部擦掉。所以我的习惯是协议栈只烧一次烧完之后后续全用Keil单独下载App不再重复擦全片。2.3 验证协议栈是否烧录成功协议栈烧完后不想直接进App调试的话可以先验证一下。nrfjprog可以读回Flash内容比如读一下开头几个字节看是不是S140的向量表nrfjprog --memrd 0x00000000 --n 16如果读到的数据和S140 hex文件开头一致说明烧录没问题。更直观的做法是直接烧一个官方广播例程上去手机用nRF Connect App扫描能看到开发板在广播就说明协议栈和App配合起来了。2.4 “永久锁定”的真相与恢复方法网络上经常看到“nRF52840永久锁定”的说法刚接触时确实很吓人我也遇到过一把。实际情况是绝大多数“锁死”都是访问端口保护APPROTECT被意外使能了典型操作是有人勾选了J-Link的Security Protection或者烧了含APPROTECT配置的UICR hex。这之后nrfjprog会报错J-Link也能识别到芯片但读取Flash会被拒。解决办法其实很简单用recover命令nrfjprog --recover这个命令会对芯片做一次全片擦除并解除访问保护芯片会恢复成出厂可烧录状态。注意它同样会清掉所有代码数据所以千万别在产品板上随手执行。如果执行recover时报找不到设备大多数情况是芯片运行在低功耗模式把调试口关了物理上按住板子上的RESET键不放同时执行命令通常能解决。3. Keil工程配置与应用编译下载3.1 从SDK里找到合适的官方例程SDK解压后的目录结构初看很乱但例程路径是有规律的。以我推荐的SDK 17.0.2为例典型的BLE外设例程在examples/ble_peripheral/ble_app_uart/pca10056/s140/arm5/这句话拆开看ble_peripheral表示是广播端外设角色ble_app_uart是串口透传例程pca10056是对应nRF52840 DK开发板s140说明基于S140协议栈arm5则是Keil MDK工程目录。里面双击打开.uvprojx工程文件Keil会加载所有相关源码。官方例程默认已经把SoftDevice相关的源文件和API都配置好了新手不要自己新建空工程再东拼西凑直接拿例程改能省掉大量时间。3.2 三个关键配置项决定能不能跑起来打开例程后不用急着改代码先看三个配置这三处决定了编译出来的固件能不能和协议栈正确共存。第一处是编译宏。在Options for Target - C/C - Preprocessor Symbols里能看到BOARD_PCA10056、CONFIG_GPIO_AS_PINRESET、S140等宏。BOARD_PCA10056告诉SDK当前用的是哪块开发板S140宏决定编译时是否开启SoftDevice相关代码路径。如果换了第三方模块BOARD要改成对应板级文件否则LED、按键这些外设定义全是错的。第二处是目标地址。在Target选项卡里IROM1的起始地址应该是0x26000大小0xDA000对应我们前面说的“Flash低地址区留给SoftDeviceApp从高分区开始”。如果这里的Start还是默认的0x0编译能过但下载后程序直接覆盖协议栈必然异常。第三处是Flash下载算法。在Utilities - Settings - Flash Download里添加的编程算法要是nRF52840的Flash算法且Start地址同样要改成0x26000Size改成0xDA000。很多“烧录失败”就是这里没有改或者算法选成了STM32的。3.3 单次烧录与合并烧录两种方式App工程编译好以后烧录方式有两种。如果你的芯片之前已经烧过SoftDevice这时候直接在Keil里点Download就行因为Flash Download配置已经指向0x26000不会覆盖协议栈区域。下载完成后按一下复位例程基本就能跑起来。另一种是合并烧录适合新板子或者要发给产线批量刷机。用nRF命令行工具自带的mergehex把协议栈、App和启动设置合成一个hex再一次性写入mergehex --merge s140_nrf52_7.2.0_softdevice.hex ble_app_uart_pca10056_s140.hex --output full.hex nrfjprog --program full.hex --chiperase nrfjprog --reset实测下来合并烧录最省心因为不需要关心烧录顺序一次全片擦除、一次写入、一次复位芯片里就是完整的可用系统。我后来刷一批测试板都是这么干的。3.4 编译常见错误与AC5编译器切换老SDK例程在新版Keil里最容易踩的坑是编译报一堆armclang错误。原因很简单MDK 5.37之后的默认编译器是AC6而nRF5 SDK时代的例程主要是按AC5写的两者的语法检查规则、内联汇编、部分宏定义有差异。解决方法是把编译器切回AC5。在Keil里Options for Target - Target - ARM Compiler选“Use default compiler version 5”。如果选项里没有AC5说明你的MDK版本太新AC5需要单独从ARM官网下载安装包装完重启Keil就能看到。切换后重新编译错误数量会骤降。还有一种常见错误是“Error: Flash Download failed - Cortex-M4”多半是Flash Download算法地址没改或者芯片被APPROTECT保护。前者去3.2节第三处检查后者按2.4节recover处理。4. APP调试从手机端把数据跑通4.1 用nRF Connect完成扫描、连接、读写协议栈烧好、例程编译下载复位后打开手机上的nRF Connect App点击SCAN。正常情况下几秒钟内就能看到开发板广播出来的设备名称比如ble_app_uart例程默认设备名是“Nordic_UART”。点击CONNECT连接后APP会列出当前设备的服务列表。ble_app_uart例程里最常用的服务是Nordic UART ServiceNUSUUID是6E400001-B5A3-F393-E0A9-E50E24DCCA9E里面有两个主要特征值一个是写通道RX一个是通知通道TX。手机向RX特征值写入数据数据会通过开发板的串口/UART转发出去开发板收到串口数据后通过TX特征值通知手机手机端就能看到消息。实际操作时点开NUS服务下对应的特征值点向上箭头发送数据点列表上方的“...”保存/打开通知开关就能双向收发。很多朋友说“连上了但收不到数据”绝大多数是忘记打开CCCD通知也就是点那个启用通知的图标特征值根本没进入通知模式。4.2 板子端日志与Keil调试模式配合手机端看到数据只是第一步真正调试代码逻辑还要看板子端的日志。传统做法是接UART串口线但nRF52840 DK板载J-Link同时提供SEGGER RTT接口用RTT Viewer看日志最方便不占串口、不占引脚。打开SEGGER RTT Viewer连接开发板的J-Link复位芯片后就能看到例程的日志输出。如果日志空白先检查sdk_config.h里NRF_LOG_BACKEND_RTT是不是ENABLEDRTT通道名是否正确。也可以在Keil里进入Debug模式设断点、单步执行、看变量比如在协议栈初始化的返回值上打断点观察返回值是不是NRF_SUCCESS。这一步能快速确认是卡在协议栈初始化、广播初始化还是主循环逻辑。4.3 用第二块nRF52840做BLE抓包当广播扫不到、连接秒断、配对异常这些问题出现时手机App很难看到底层原因这时候需要真正意义上的BLE抓包。最省钱也最顺手的方案是再搞一个nRF52840 Dongle或者另一块nRF52840板子刷上Nordic官方的Sniffer固件配合Wireshark和nRF Sniffer插件就能抓取空中的所有BLE包。抓包能看到广播包里的完整广播数据、连接请求的参数、每一条ATT读写的请求和响应、断连时的原因码这些信息在排查“为什么手机连上后马上断开”这种问题时比猜靠谱得多。刷Sniffer固件的方式其实和烧协议栈类似用nrfjprog往Dongle里写一个专门的hex就行整个过程五分钟左右。5. 高频问题与排查经验速查5.1 常见问题排查表我不太喜欢写那种按部就班的长篇教程把最常遇到的问题整理成表格方便直接对照处理现象最常见原因解决办法nrfjprog找不到设备J-Link驱动未装/USB线只是供电线重装SEGGER驱动换数据线按RESET重试nrfjprog报错无法访问Flash芯片被APPROTECT保护执行nrfjprog --recoverKeil烧录报Flash Download failedFlash下载算法地址还是0x0把Flash Algorithm的Start改成0x26000Size选0xDA000烧录成功但没有广播App起始地址不对或SDK版本不匹配核对Target页IROM10x26000检查SoftDevice版本手机能连上但收不到数据没开启特征值通知CCCD在nRF Connect里打开Notification开关RTT Viewer没日志RTT后端未开启或选错目标检查sdk_config.h确认RTT连接的是目标板的J-Link编译报大量armclang错误Keil默认AC6编译老工程切换ARM Compiler到Version 55.2 真实时间账为什么说三分钟能搞定文章标题写了三分钟我是认真的但要说明是哪三分钟。假设你已经有一块nRF52840开发板且之前烧过协议栈那么Keil打开例程、编译、下载、复位这一串动作熟练的话确实两分钟出头。如果是一块全新板子多花几分钟烧一次协议栈也是正常的。真正耗时的是环境搭建和第一次排错。我刚开始完全摸不着门道卡在版本匹配上整整一个下午。后来我把常用命令做成了两个脚本一个用于新板初始化刷协议栈一个用于日常编译加下载。每次拿到新板子USB插上、双击脚本、三分钟做完剩下的时间全在调业务逻辑。这个习惯让我后来刷几十块测试板时一点都不慌。5.3 最后分享一个让烧录更稳的小技巧最后再分享一个小技巧算是我踩过很多次坑之后总结的。日常开发时我很少直接从Keil里点Download而是先把App编译生成hex然后写一行命令烧进去nrfjprog --program _build/ble_app_uart_pca10056_s140.hex --reset这样做的原因是Keil里的Flash Download偶尔会因为算法配置、全片擦除选项、调试会话残留产生奇怪问题而命令行nrfjprog行为更可控也方便接CI。如果连协议栈都不确定在不在就用mergehex合并后一次写入。这套流程用顺了你也会觉得标题里的“三分钟”完全不是夸张。