
简介nRF52840 MDK USB Dongle是一套采用USB Dongle规格的小型低成本开发平台资源面向物联网嵌入式开发者、BLE/Thread协议学习者以及FIDO2安全应用爱好者。其核心为Nordic nRF52840 SoC集成ARM Cortex-M4F处理器支持蓝牙5、蓝牙Mesh、Thread、IEEE 802.15.4、ANT及2.4GHz专有协议并带有片上NFC-A和USB接口。板卡提供可编程用户按键、RGB LED、2.4G芯片天线和最多12个GPIO引出既可作为多协议无线开发板也能充当网络协处理器连接PC或USB主机。该资源以zip压缩包形式分享大小约41.92MB具体文件类型与目录结构需下载后查看结合硬件特性可辅助开发者快速掌握nRF52840在USB Dongle形态下的电路设计思路与多协议固件开发要点适用于低功耗蓝牙节点、Thread边界路由或FIDO2安全密钥等原型验证。资源目前已有705人浏览学习适合正在评估或选型nRF52840方案的软硬件工程师作为参考。1. 从“USB 小棒子”到低功耗蓝牙开发板nRF52840 Dongle 到底能干什么如果你习惯了动辄几百元的官方开发板第一次拿到 nRF52840 USB Dongle 这类硬件时大概率会愣一下一个 U 盘大小的板子引出天线和 USB 口连个按键都得用细铁丝去戳复位孔这真能当成开发套件用答案是不仅能而且它在低功耗蓝牙、Thread、Zigbee 甚至 FIDO2 安全密钥场景里都是性价比极高的选择。之前做某跨平台系统的 BLE 网关原型就是用这种小 Dongle 把数据透传到 PC整个物料成本控制在几十元验证完再切正式硬件一点都不心疼。它的核心是一颗 nRF52840 SoCCortex-M4F 内核跑到 64MHz自带 1MB Flash 和 256KB RAM同时支持 BLE 5.0、IEEE 802.15.4Thread/Zigbee以及 NFC-A 标签模式。USB 接口走的是 Nordic 的 USB 控制器搭配板载 Bootloader插上电脑就能被识别成 CDC 串口或 DFU 设备。适合谁适合三类人想把 BLE 开发板成本压到最低的硬件工程师需要便携抓包器调试 BLE 协议栈的固件开发者以及想快速验证 OpenThread 边界路由器或 FIDO2 密钥原型的极客。这块板子不属于“高大全”路线而是把最常用的外设裁剪到极致让你用 USB 供电就能跑完绝大部分协议栈实验。下面从开箱上电、固件烧录、SDK 开发、协议栈实战到常见翻车点按一条可复现的路径整篇讲清楚顺手把我踩过的几个坑也标出来。2. 上电与固件玩法先搞懂 USB CDC、DFU 和 Python 刷写链路2.1 插上电脑先看枚举结果CDC 串口还是 DFU 设备拿到这块板子后第一件事不是急着烧录而是把它插到 USB 口上观察系统枚举状态。出厂时板载的 Bootloader 会优先进入 DFU 模式在 Windows 设备管理器里能看到一个名为“nRF52 Dongle”或类似名称的 USB 设备Linux 下用lsusb能看到 Nordic Semiconductor 的 Vendor ID。如果之前刷过带 USB CDC 串口功能的固件比如 Nordic 官方提供的串口透传示例或者 HID 键盘固件枚举出来的则是 COM 口或 HID 设备。有个容易忽略的细节板上的复位按钮不只是复位它还承担着进入 Bootloader 的物理开关职责。在固件运行状态下按住复位孔里的按钮再松手MCU 会直接跳转到 Bootloader此时 USB 重新枚举为 DFU 设备等待主机下发固件。这个行为由 Nordic 的 Bootloader 工程中APP_TIMER与GPIOTE中断配合实现如果你自己定制了 Bootloader一定要保留这段进入 DFU 的按键逻辑否则后续刷成砖的概率会直线上升。# Linux 下查看枚举结果 lsusb | grep -i nordic dmesg | tail -20逻辑说明lsusb只负责看到 VID/PIDdmesg才是判断 USB 描述符是否正常的关键。如果内核日志里出现device descriptor read/64之类的错误大概率是线缆质量问题或 USB 口供电不足。参数说明nRF52840 USB 控制器要求 D 和 D- 走线阻抗匹配虽然开发板已经帮你布好了线但换用延长线时建议选长度小于 30cm 的质量靠谱线材否则高速枚举阶段容易翻车表现为设备反复断开重连。2.2 用 pyocd 或 nrfutil 刷写命令行才是效率最高的方式Nordic 官方推荐的烧录工具链有两条nrfutil 配合 J-Link 或 OB Dongle以及 pyocd 通过 USB DFU 直接刷写。对于 USB Dongle 这种不带 SWD 接口或者说没引出的板子DFU 几乎是唯一的刷写路径所以 pyocd 的pyocd-flashtool命令就是主力工具。pip install pyocd pyocd list pyocd flash -t nrf52840 -f target.hex逻辑说明pyocd list用来确认板子是否进入了 DFU 模式如果列表为空先按一下复位按钮重进 Bootloader 再试。pyocd flash的-t参数指定目标芯片型号-f参数接受 hex、bin 或 elf 文件。参数说明如果同时插了多块 Dongle需要给pyocd flash加-u unique_id参数指定目标设备否则它会报“multiple targets”错误。另外pyocd 在 Windows 下需要安装对应的 USB 驱动推荐用 Nordic 的nrfjprog配套驱动包统一搞定避免 WinUSB 和 libusb 冲突。2.3 预置固件里有什么从 CDC 透传到 FIDO2 密钥这块板子出厂烧的固件一般是 Nordic 的连接板固件Connectivity Firmware它让你可以通过 PC 上的 nRF Connect 软件直接与手机里的 BLE 设备通信相当于把 Dongle 当成一个免驱的 BLE 抓包/调试网关。但我更建议你直接刷一个带 USB CDC 串口透传的固件这样在 Linux 下能直接minicom打开/dev/ttyACM0和 MCU 内的日志交互调试效率和裸串口看 log 一样。如果你对安全协议感兴趣这块板子也能刷 FIDO2 固件把 USB Dongle 变成 U2F 安全密钥。FIDO2 固件基于 Nordic 的nrf52840-mdk开源 SDK 示例改造把 CTAP 协议栈跑在 USB HID 通道上身份认证时插上电脑按一下触摸按钮即可完成校验。整个刷写过程和刷普通固件没有区别只是 hex 文件体积更大DFU 传输时间会长一点需要保持耐心中间别拔线。import usb.core import usb.util dev usb.core.find(idVendor0x1915, idProduct0x521F) if dev is None: raise ValueError(Dongle not found) print(dev.manufacturer, dev.product)逻辑说明Nordic 的 VID 是0x1915常见的 DFU 产品 ID 是0x521F。这段代码演示了在 Python 里直接枚举 USB 设备并读取厂商信息适合在自动化测试脚本里做前置检查。参数说明如果设备处于 HID 模式idProduct可能变成其他值此时要先用lsusb确认实际 PID再改脚本参数。3. 搭建 nRF5 SDK 开发环境从跑通 blinky 到看透 linker script3.1 SDK 版本与工具链选择GCC 还是 SEGGER选错就是一天nRF52840 的开发绕不开 Nordic 的 nRF5 SDK目前大家用得最顺手的是 SDK 15.3 或 17.1。SDK 15.3 和 Softdevice S140 v7.2 搭配很稳定适合做 BLE 应用但如果想跑 OpenThread 或 ZigbeeSDK 17.1 里集成了更多 802.15.4 的示例能省掉自己移植协议栈的苦工。工具链方面Windows 用户大多用 SEGGER Embedded Studio因为它对 Nordic 工程的兼容性最好双击.emProject就能编译。Linux 或 macOS 用户更适合用 GCC 工具链配合 CMake 或 Makefile。以下是 GCC 工具链最常见的环境配置方式sudo apt install gcc-arm-none-eabi cmake ninja-build export GNU_INSTALL_ROOT/usr/bin逻辑说明export GNU_INSTALL_ROOT必须指向arm-none-eabi-gcc所在目录Nordic 的 Makefile 系统会调用$(GNU_INSTALL_ROOT)/arm-none-eabi-gcc来编译。如果不设置这个环境变量make 会直接报错找不到编译器。参数说明arm-none-eabi-gcc的版本建议用 9-2020-q2 或更新的稳定版太老的编译器可能不支持-mcpucortex-m4的浮点扩展指令导致编译出的二进制在调用float运算时 hardfault。3.2 最小工程模板自己搭一个能编译的目录结构很多初学者喜欢直接把 SDK 里的ble_app_uart示例拿来改但示例工程里塞了太多无关模块出了问题不好定位。我一般会自己搭一个最小工程只包含三个部分main.c、sdk_config.h、Makefile然后逐步往里加外设驱动。这样每加一个模块都能立刻知道它依赖了什么。PROJECT_NAME : minimal_dongle TARGET_CHIP : nrf52840 BOARD : nrf52840_mdk_usb_dongle SDK_ROOT ? /path/to/nrf5_sdk_17.1.0 SOFTDEVICE ? s140 LINKER_SCRIPT : nrf52840_xxaa.ld SRC_FILES main.c SRC_FILES $(SDK_ROOT)/integration/nrfx/legacy/app_timer/app_timer.c SRC_FILES $(SDK_ROOT)/components/libraries/log/src/nrf_log_backend_rtt.c INC_FOLDERS $(SDK_ROOT)/components/libraries/log/src INC_FOLDERS $(SDK_ROOT)/components/softdevice/s140/headers CFLAGS -DBOARD_CUSTOM -DNRF52840_XXAA CFLAGS -mcpucortex-m4 -mthumb -mabiaapcs -mfloat-abihard -mfpufpv4-sp-d16 include $(SDK_ROOT)/components/toolchain/gcc/Makefile.posix include $(SDK_ROOT)/components/toolchain/gcc/Makefile.common逻辑说明这段 Makefile 是 Nordic 工程的标准骨架。LINKER_SCRIPT指向芯片的链接脚本它定义了 Flash 和 RAM 的起始地址与大小。完整的 nRF52840 是 1MB Flash / 256KB RAM但当你启用 Softdevice 时Flash 起点会被推后到0x26000或0x27000RAM 起点变成0x20002200之类的偏移地址。如果不小心把Flash 起点设回0x00000000烧进去能跑但只要调用 Softdevice 相关 API 就会直接死机。参数说明-mfloat-abihard -mfpufpv4-sp-d16是 Cortex-M4F 的硬浮点参数注意 nRF52840 只支持单精度浮点别写-mfpufpv5-d16编译器不认。BOARD : nrf52840_mdk_usb_dongle在旧版 SDK 里可能没有这个定义需要自己往boards.h里加一行#define BOARD_NRF52840_MDK_USB_DONGLE。3.3 sdk_config.h 里那些让你头疼的宏每个 Nordic 工程都有一个sdk_config.h本质是一堆#define开关控制哪些模块参与编译、哪些模块被裁掉。新手最常见的翻车是往工程里加了某个源文件但没开对应的宏导致链接时报一堆 undefined reference。#define NRF_LOG_ENABLED 1 #define NRF_LOG_BACKEND_RTT_ENABLED 1 #define APP_TIMER_ENABLED 1 #define APP_TIMER_VECTOR_RATIO 6 #define NRFX_POWER_ENABLED 1逻辑说明NRF_LOG_ENABLED决定日志框架是否参与编译NRF_LOG_BACKEND_RTT_ENABLED决定日志输出到 SEGGER RTT 还是串口。如果你在代码里调用NRF_LOG_INFO()但没启用后端编译能过但运行时日志像进了黑洞一样什么都看不到排查问题时容易误判程序流程。参数说明APP_TIMER_VECTOR_RATIO影响定时器在软件中断中的优先级分配数值范围 1~10。数值越小定时器响应越及时但留给主循环的时间片越少。SDK 默认给 6BLE 应用里如果出现调度卡顿试着改成 3 或 4 观察一下注意这是一个全局配置改不好会影响 Softdevice 的中断延迟。// 初始化日志和定时器 ret_code_t err_code; err_code app_timer_init(); APP_ERROR_CHECK(err_code); err_code nrf_log_init(NULL); APP_ERROR_CHECK(err_code);逻辑说明nrf_log_init(NULL)传入NULL表示使用默认配置。APP_ERROR_CHECK是个死循环宏出错时程序会停在原地配合 RTT Viewer 能看到出错的具体文件和行号这是排查硬件初始化失败最重要的工具。参数说明如果自己实现了log_backend输出到 USB CDCnrf_log_init的入参要改成指向后端配置结构体的指针否则日志只会往 RTT 里写USB 串口什么都收不到。4. BLE 与 802.15.4 双协议实战把 Dongle 变成低功耗网关4.1 BLE 透传例程改造从 UART 到 USB CDC 的骡子换马SDK 自带的ble_app_uart例程默认用 UART 与外部 MCU 通信但 Dongle 上根本没有引出 UART 引脚它把 nRF52840 的 USB 口当成了通信链路。所以改造思路很直接把例程里的app_uart驱动换成app_usbd驱动其余 BLE 协议栈部分完全不动。#include app_usbd_cdc_acm.h #include app_usbd.h static void usbd_cdc_event_handler(app_usbd_cdc_acm_t *p_cdc, app_usbd_cdc_acm_user_event_t event, void *p_ctx) { ret_code_t ret; switch (event) { case APP_USBD_CDC_ACM_USER_EVT_RX: // 读取 USB 收到的数据并转发到 BLE uint8_t buf[64]; size_t len sizeof(buf); ret app_usbd_cdc_acm_read(p_cdc, buf, len); if (ret NRF_SUCCESS len 0) { ble_nus_data_send(m_nus, buf, len, NULL); } break; default: break; } }逻辑说明USB CDC 的回调机制和 UART 中断类似APP_USBD_CDC_ACM_USER_EVT_RX事件表示 USB 主机发来数据在这里把数据交给 BLE 的 NUS 服务发送出去。注意ble_nus_data_send是一个异步函数数据是拷贝进 Softdevice 的缓冲区还是直接引用原地址取决于你传入的p_ctx参数如果需要对数据进行缓存务必开辟一块专用的数组而不是复用栈上变量。参数说明USB CDC 的端点大小一般配成 64 字节对应一个 USB Full Speed 包的负载上限。BLE 的 ATT 最大传输单元MTU默认是 23 字节如果一次要发超过 20 字节的用户数据需要在连接后发起 MTU 协商。#define APP_USBD_CDC_ACM_COMM_IFACE 0 #define APP_USBD_CDC_ACM_DATA_IFACE 1 #define APP_USBD_CDC_ACM_COMM_EPIN 0x81 #define APP_USBD_CDC_ACM_DATA_EPIN 0x82 #define APP_USBD_CDC_ACM_DATA_EPOUT 0x02参数说明这是 USB 描述符里端点的固定分配0x81表示 IN 端点 10x02表示 OUT 端点 2。如果和板载 Bootloader 的 USB 描述符冲突系统会枚举失败这就是为什么市面上很多复制版 Dongle 刷了第三方的 CDC 固件后电脑不识别的原因——端点地址被写死了。4.2 OpenThread 边界路由一条命令让 Dongle 变成 Thread 网络“守门员”nRF52840 的 802.15.4 无线电让它能作为 OpenThread Border Router 的 RCPRadio Co-Processor设备。RCP 模式下Dongle 只负责收发 802.15.4 帧复杂的网络层由主机上的ot-br-posix处理这样的好处是降低了 Dongle 侧的内存压力也方便在 PC 上抓包调试。# 编译 RCP 固件在 SDK 的 openthread 目录下 make -f examples/Makefile-nrf52840 USB1 # 将编译出的固件刷入 Dongle pyocd flash -t nrf52840 -f build/bin/ot-rcp.hex # 主机侧启动边界路由器 sudo ot-br-posix逻辑说明USB1会让 RCP 固件启用 USB CDC 作为 Spinel 协议传输层通过串口与主机通信。这里有个关键前提Nordic 的 OpenThread 例程底层使用了单独的 IEEE 802.15.4 协议栈不需要 BLE 的 Softdevice所以编译前要把工程里的 Softdevice 依赖全部关掉否则 Flash 空间不够。参数说明ot-br-posix启动后默认监听/dev/ttyACM0如果 Dongle 枚举成了/dev/ttyACM1需要加启动参数指定端口。4.3 Zigbee 网络的另类玩法用它做轻量级协调器除了 Thread802.15.4 还支撑 Zigbee 协议。Nordic 的 Zigbee 例程和 OpenThread 共用底层驱动但网络层配置完全不同。如果你只是想在局域网里控制几个 Zigbee 开关Dongle 完全可以承担 Zigbee Coordinator 的角色。// Zigbee 协调器初始化关键代码 zigbee_default_unknown_long_address_set(0x1234567890abcdef); zigbee_eui64_set(mac_address); zigbee_channel_set(ZIGBEE_DEFAULT_CHANNEL);逻辑说明Zigbee 协调器必须有一个唯一的 64 位 MAC 地址zigbee_default_unknown_long_address_set是兜底逻辑防止设备在没有烧录 MAC 地址的 Flash 区域时无法加入网络。zigbee_channel_set默认选 11因为 2.4GHz 频段上 Zigbee 和 Wi-Fi 信道重叠严重。参数说明如果家里路由器固定在 Wi-Fi 信道 1、6、11建议把 Zigbee 信道调整到 15、20 或 25能显著减少丢包。这块板子的 PCB 天线在 802.15.4 模式下增益一般穿墙能力不如专业 Zigbee 模组做协调器时要尽量摆放在房间中部。5. USB Dongle 避坑指南从刷成砖到救回来的几条血泪经验5.1 现象按复位键后电脑没任何反应设备彻底消失原因Bootloader 区域被覆盖或者进入 Bootloader 的按键检测代码被新固件里的中断处理抢占。最常见的是从网上直接下载了一个不带 Bootloader 保护的 hex 全量包刷进去把0x00000000起始的 MBR 和 Bootloader 全部覆盖。解决如果有 J-Link 或者另一个好的 Dongle可以通过 SWD 飞线直接连到芯片的调试引脚重新烧录。但很多复制版 Dongle 的 SWD 焊盘被省掉了此时只能借助 Nordic 的nrfjprog --recover命令前提是芯片上的访问端口保护APPROTECT还没被打开。如果 APPROTECT 开了并且 Bootloader 也废了这块板子基本只能当废料处理所以刷第三方固件前一定做好备份至少把原厂 hex 用 pyocd 导出存好这是唯一的后悔药。5.2 现象pyocd flash 报错Failed to load hex file原因目标 hex 文件不是 Nordic DFU 格式而是普通的 Intel Hex。pyocd 本身能解析 Intel Hex但如果你下载的“固件”是带 Softdevice 的合并包起始地址偏移可能超出芯片范围加载器直接拒绝写入。解决先确认 hex 文件的起始地址。用文本编辑器打开 hex 文件看第一行的:02000004扩展地址记录如果是0000说明从 0 地址开始如果刷之前板子里有 Softdevice需要手动把固件起始地址改成0x26000。常见做法是用mergehex工具把 Softdevice、Bootloader 和应用固件合并成一个完整包再刷。mergehex -m s140_nrf52_7.2.0_softdevice.hex bootloader.hex app.hex -o full.hex pyocd flash -t nrf52840 -f full.hex逻辑说明mergehex是 Nordic 提供的离线合并工具输入三个 hex 文件输出一个从0x00000000开始的完整镜像。这样刷进去后Bootloader 按键进 DFU 的逻辑仍然保留后续升级固件还能走 USB DFU。参数说明三个 hex 文件的 Flash 区间不能重叠Softdevice 占0x00000000~0x26000Bootloader 占高地址区间应用程序必须落在两者之间。5.3 现象USB 串口能枚举但发送数据没回应原因SDK 里的 CDC ACM 例程默认在收到数据后先缓存到 FIFO不会每收一字节就触发事件。如果你把包含协议头的多包数据一次性发过去应用层可能只触发了一次EVT_RX剩下数据还残留在 FIFO 里。解决在EVT_RX回调里循环读取直到 FIFO 为空再统一处理。while (true) { ret app_usbd_cdc_acm_read(m_cdc, buf, len); if (ret ! NRF_SUCCESS || len 0) { break; } // 处理 buf 中的 len 字节 }逻辑说明app_usbd_cdc_acm_read返回NRF_ERROR_NOT_FOUND时表示没有更多数据。这里的循环要注意栈缓冲区大小一次性读太多字节会覆盖栈数据最好把buf定义成静态变量。5.4 现象BLE 连接不稳定RSSI 显示正常但偶尔断连原因USB Dongle 的供电来自 USB 口部分老式电脑的 USB 口输出电压纹波大在 BLE 发射瞬间电流尖峰触发欠压复位导致连接意外断开。解决不要试图在固件里过滤掉欠压中断而是从源头解决供电。改用带屏蔽的 USB 线或者插在带独立供电的 USB HUB 上。作为一个低成本硬件它的电源设计确实简化到极致没有专门的 DC-DC 稳压纹波抑制全靠 SoC 内部的 LDO这是它在复杂电磁环境下的一个已知短板。5.5 现象OpenThread 固件里 Softdevice 初始化死机原因RCP 固件不需要 Softdevice但 SDK 的编译开关默认把它打开了。即使你在代码里不调用任何 BLE API只要 Softdevice 的时钟和内存配置还在802.15.4 协议栈启动时会争抢硬件资源。解决在sdk_config.h里把NRF_SD_BLE_API_ENABLE设为 0同时把NRF_FPRT_ENABLED关掉。检查main.c里是否调用了sd_softdevice_enable()OpenThread 例程里不应该出现这个函数。6. 验证方法与进阶技巧用 PC 端 Python 脚本做自动化巡检当 Dongle 刷好自定义固件后最实用的验证工具不是手机 App而是 PC 上跑的 Python 脚本。因为 Dongle 天然挂在 USB 上你可以直接用pyserial打开串口做数据回环测试也可以配合pyusb做 DFU 状态机检查比用手按按钮可靠得多。所谓自动化巡检本质是在每次烧录完新固件后自动执行 3 项检查第一USB 枚举是否正常且描述符匹配预期第二CDC 串口是否能收发特定测试数据第三BLE 广播包能否被相邻的 Dongle 扫描到。下面给出第二项的示例import serial import time ser serial.Serial(/dev/ttyACM0, 115200, timeout2) test_payload b\x01\x02\x03\x04\x05\x06\x07\x08 ser.write(test_payload) time.sleep(0.5) response ser.read(8) if response test_payload: print(PASS: USB CDC loopback OK) else: print(fFAIL: expected {test_payload.hex()}, got {response.hex()})逻辑说明这里的回环测试依赖固件将 USB 收到的数据原样发送回 USB。很多透传固件默认数据流向是单向的需要在EVT_RX回调里加一行app_usbd_cdc_acm_write()把数据原路写回。参数说明波特率参数对 CDC ACM 虚拟串口没有实际意义但pyserial要求必须传一个合法的波特率值。timeout2表示等待 2 秒如果你的固件在收到数据后还要做 BLE 转发再回传这 2 秒可能不够需要按实际链路延迟调整。进阶用法是把 USB 枚举测试也集成进来用pyusb在刷写前后各做一次设备计数防止烧录过程中设备掉线导致后续测试误判。import usb.core dev usb.core.find(idVendor0x1915, idProduct0x521F) if dev: # 进入 DFU 模式后请求读设备状态 ret dev.ctrl_transfer(0x21, 0x03, 0, 0, 6) print(DFU status:, ret.tobytes().hex())逻辑说明DFU 类请求0x03是 Get Status返回 6 字节的状态信息。如果你刷的固件没带 DFU 服务这段代码会抛USBError正好用来确认当前设备处于应用模式还是 Bootloader 模式。参数说明0x21是 USB 类特定请求的 bmRequestType其中0x20表示厂商类、0x01表示方向为设备到主机。不同 DFU 实现可能用0xA1需要先用 Wireshark 抓包确认板子的实际描述符。最后一个具体的技巧是关于如何检查 Softdevice 是否真正跑起来写一个 1ms 定时器累加计数每隔 100ms 把当前计数值通过 USB 发出来。如果你看到计数稳定递增说明系统时钟和中断调度正常如果计数跳跃或停止说明某个外设中断和 Softdevice 发生了优先级冲突。这个技巧不需要 J-Link不需要 RTT一根 USB 线就够了从那以后我每次改完 BLE 相关的配置都会强制跑一遍这个计数器巡检确认调度链路没被改挂再进入协议功能联调省掉了很多“看似能连上但一传数据就死机”的隐性坑。这块小板子就是这样空间局促、外设精简但只要你把它的 USB 和 DFU 链路吃透它能成为你案头最称手的低功耗协议实验平台。希望帮到你。本文还有配套的精品资源点击获取