ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

nRF52840双通道实战:BLE与USB虚拟串口从零调通

nRF52840双通道实战:BLE与USB虚拟串口从零调通 如果你的项目需要同时跟手机和电脑打交道比如一边通过BLE连App做配置一边通过USB连PC传数据通常的做法是找一块带蓝牙的MCU再外挂一颗USB转串口芯片。这个方案能用但占板、占串口、占中断调起来还会遇到两边驱动互相抢资源的问题。我当时做一台上位机与手机双端控制的硬件外设时直接把目光放在了nRF52840上原因很直接——这芯片一头是BLE 5.0射频另一头还带了完整的USB 2.0全速设备控制器一颗Cortex-M4F就能把两条通信链路都接起来。这篇文章不是照抄手册而是我在nRF52840开发板上把BLE和USB通信从零调到通的完整记录。内容包括芯片选型、SDK和SoftDevice的选型、广播与连接参数怎么调、USB虚拟串口枚举失败的排查套路以及最后BLE和USB同场协作的透传实例。适合刚拿到nRF52840开发板、想在BLE和USB两条路上都少走弯路的嵌入式开发者。1. 一个USB口和一个BLE射频口为什么都压在nRF52840上1.1 芯片资源到底够不够用先看硬指标。nRF52840用的是Cortex-M4F内核主频64MHz不算高但在这个主频下做协议处理是完全够用的。Flash有1MBRAM有256KB跑一个带BLE协议栈的完整应用再塞下USB CDC驱动空间依然宽裕。这个内存配置在几年前的MCU里属于偏大的现在做复杂应用也不用天天为内存不够发愁。射频部分支持BLE 5.0最大输出8dBm支持2Mbps、长距离模式Coded PHY和广播扩展。这些特性在以后做OTA升级或者远距离调试时非常有用不是单纯的参数堆料。USB部分容易被忽视但它反而是这颗芯片最值钱的地方之一。nRF52840内置的是USB 2.0全速设备控制器最高速度12Mbps。做虚拟串口、HID键盘鼠标、MSC存储设备速度都够。关键它不需要外接USB转串口芯片USB D和D-引脚是固定的板子上留个Type-C座子就能直接跟电脑通信。外设接口也不少多个SPI、UART、TWII2C、I2S、PDM、QSPI还有一组12位ADC。如果你要做传感器采集、音频输入或者驱动外部Flash基本不用扩展芯片。我看中的是它一套片子能同时搞定无线采集和有线传输省掉两颗芯片之间的握手逻辑。1.2 一芯双通道能做什么、不能做什么USB和BLE同时在线的场景比想象中多BLE无线配置 USB有线传输设备先通过BLE连手机App做参数配置配置完成后走USB高速传输数据。相当于给设备开了两条门一条给用户无感配置用一条给批量数据用。HID键盘鼠标双模USB连PC是有线键鼠BLE连平板是无线键鼠。很多人用nRF52840做这类双模外设。物联网网关节点USB从PC取数据和供电BLE收集附近传感器信息再整理后上传。调试利器USB CDC虚拟串口输出日志BLE air sniffer看无线数据包。两路同时工作调试效率翻倍。但也有做不到的。USB全速只有12Mbps想拿它传音频流或视频流不现实。BLE 5.0虽然支持2Mbps物理速率实际应用层吞吐量经过协议开销后通常也就几百Kbps到1Mbps出头不适合大批量文件传输。所以nRF52840更适合做控制类、传感器类、小数据量传输的场景而不是高带宽流媒体场景。1.3 与ESP32、STM32WB的对比取舍很多人会拿ESP32和STM32WB来对比。ESP32的优势是Wi-Fi加蓝牙双模生态大价格有竞争力但它的USB只是从机模式而且没有原生USB设备控制器通常还要外扩一个USB转串口芯片。STM32WB55有BLE和USB但USB外设不是所有型号都带而且BLE协议栈在Cube包里的集成方式相对复杂早期版本还有过功耗问题。nRF52840的优势在于Nordic在BLE协议栈上的沉淀。S140 SoftDevice是经过大量量产的闭源协议栈稳定性很高API设计得很清晰。BLE部分基本不用操心协议栈底层的崩溃问题可以把精力放在应用层。同时官方有nRF Connect for Desktop和nRF Connect for Mobile这套工具链排查问题非常顺手。如果你追求的是“USB有线 BLE无线 稳定协议栈 调试工具链完善”nRF52840是当前很平衡的选择。它的劣势是价格比ESP32贵而且某些批次芯片缺货备货周期要提前规划。2. 环境搭建可以快但这四处别省2.1 SDK和SoftDevice选型没你想的那么简单nRF52840的开发环境可以走两条路一是用传统的nRF5 SDK二是用较新的nRF Connect SDK基于Zephyr RTOS。如果你之前没有Zephyr开发经验我建议先从nRF5 SDK入手。nRF5 SDK 17.1.0是Nordic官方最后维护的经典版本资料最多、教程最多、遇到问题能在论坛上搜到很多方案。Zephyr版功能更全但学习曲线陡调试时很难分清是应用代码的问题还是RTOS调度的问题。我这次用的就是nRF5 SDK 17.1.0配合SoftDevice S140 v7.2.0。S140是nRF52840的蓝牙协议栈固件以十六进制文件形式存在支持BLE 5.0、支持外设和中心两种GAP角色也支持同时做多个连接。选S140就够用不需要选S112或S113那些是给内存更小的芯片用的精简版。2.2 SoftDevice烧进去之后为什么工程容易“翻车”SoftDevice不是普通的库文件它是一个运行在特权模式下的独立固件用户代码通过系统调用方式SDK里封装成sd_ble_gap_xxx这类的API请求它执行蓝牙操作。这就带来一个编译上的关键问题程序链接时SoftDevice占用的Flash和RAM区域必须被保留不能给应用代码用。具体来说S140 v7.2.0占用的Flash从0x00000000到0x00026000大约152KBRAM从0x20000000到0x2000F000约60KB。应用代码的起始地址要放在SoftDevice之后。如果你用官方例程这些已经在链接脚本里配好了。但如果自己新建工程忘记配置Flash起始地址或RAM起始地址编译能通过一运行就进HardFault芯片还经常连不上调试器。我在第一次搞的时候犯过这个错症状是程序能烧进去但跑起来之后只要调用第一个BLE API系统就挂。后来查链接脚本发现RAM区域的起始地址偏了导致协议栈的数据被应用覆盖。这里记住一句话从官方例程改代码永远比从零新建工程可靠。2.3 驱动装不上的真实原因开发板连电脑经常遇到设备管理器里出现感叹号或者完全识别不到设备。这个问题分几种情况板上用的是J-Link OB调试器电脑需要安装J-Link驱动这个驱动在Segger官网下载装好后应识别出J-Link设备。板上如果有USB转串口芯片FT231x、FT232R、CP2102这些需要对应厂商的驱动。FTDI芯片在Windows上有时会被识别为“USB Serial Converter”而不出来串口号解决方法是右击设备选择“更新驱动程序”手动指向FTDI驱动目录。新版Windows会强制驱动签名某些老版本FTDI驱动会被拦下来需要关闭驱动签名强制或找新版驱动。还有一个我踩过的坑某些开发板USB口旁边有跳线帽控制VBUS供电。如果跳线帽没插USB端口虽然能连接但芯片不认为有外部供电USB枚举会失败。别小看这点我在这上面浪费过半小时。2.4 先跑通SES工程和nrfjprog开发环境我推荐用Segger Embedded StudioSES。SES对Nordic的支持是原生级别的打开SDK例程就能编译下载省去很多Keil的配置步骤。Keil也能用但工程文件经常需要手动指定SoftDevice路径容易出错。代码写好之后用nrfjprog命令行工具操作芯片比在IDE里点按钮更可控。读芯片信息用一个指令nrfjprog --deviceversion能看到连接的芯片型号和SDK版本。再用nrfjprog --program s140_nrf52_7.2.0_softdevice.hex --chiperase nrfjprog --program your_app.hex --sectorerase nrfjprog --reset第一句烧录SoftDevice并整片擦除第二句烧录应用代码第三句复位运行。如果你的开发板连接正常这个流程应该很顺畅。如果读不到芯片优先检查驱动和连接线别急着怀疑板子坏了。3. 先搞懂BLE的“时序”再写代码就不慌3.1 广播参数像说话的音量调不好连不上BLE设备要被人发现首先要广播。广播参数直接决定了手机能不能快速搜到设备以及搜到之后连接够不够稳定。广播间隔是最常调整的参数。间隔越短发现越快但功耗高、空中的广播包也多会干扰其他设备。间隔越长越省电但手机扫描到设备的时间变长。官方例程里常用的快速广播间隔是100ms慢速广播间隔是1000ms。广播类型也要注意。BLE支持可连接广播、不可连接广播、可扫描广播等类型。做常规数据透传用可连接广播就够了。如果要做Beacon信标用不可连接广播。广播包里还要正确配置设备名称和标志位。SDK中的ble_advertising_init结构体里有advdata.flags和advdata.name_type两个字段flags要包含BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODEname_type设为全名或短名。如果flag配错手机能看到设备但连接时可能异常。我实测下来广播间隔设置在100ms到200ms之间手机App搜设备基本是一搜一个准。间隔太短到20ms时虽然发现快但连续多个广播包会占用较多射频时间反而可能影响后续的连接稳定性。3.2 连接事件和连接参数更新手机连上设备后通信就进入了连接事件模式。连接事件是主设备在每个连接间隔中与从设备交换数据的时间窗口。连接间隔和从设备延迟是决定通信延迟和功耗的两个核心参数。连接间隔代表主设备多久来一次。间隔短数据吞吐高但功耗高间隔长省电但延迟大。连接参数协商通过sd_ble_gap_conn_param_update发起手机端也可以主动发起更新。从设备延迟这个参数容易被忽略。它表示从设备可以跳过多少个连接事件而不必回复。比如从设备延迟设为9意味着主设备连发10次事件从设备只需响应一次适合低功耗传感器场景。但如果你在做实时性要求高的透传延迟就尽量设0。连接参数的协商不一定是主设备说了算。BLE规范允许发起参数更新请求但最终决定权在主设备通常是手机。很多情况下手机App会拒绝你的参数更新请求比如iOS在某些场景下会强制使用自己的连接参数。这时候你要么接受现实用默认参数跑要么在设计时就考虑在手机上主动发起连接参数更新。3.3 GATT服务搭建思路BLE的应用逻辑基本都建立在GATT框架上。GATT定义了服务Service、特征Characteristic和描述符Descriptor三层结构。一个服务里可以包含多个特征每个特征又可以有多个描述符。做透传功能通常自定义一个服务里面放两个特征一个用于接收数据Write一个用于发送数据Notify。接收特征要求对端可以写发送特征要求支持通知Notify功能。需要在特征配置里打开BLE_GATT_CHAR_PROPERTIES_WRITE和BLE_GATT_CHAR_PROPERTIES_NOTIFY。同时要给Notify特征添加一个客户端特征配置描述符CCCD这样手机端App通过写0x0001到这个描述符来订阅通知。没有CCCDNotify消息发不出去这也是新手最常踩的坑之一。SDK里的ble_nus.cNordic UART Service是一个现成的模板实现了一个RX特征和一个TX特征逻辑和透传很接近。我的建议是直接在这个服务基础上改把UUID换成自己的就能少写很多样板代码。3.4 主从切换与角色并存nRF52840在S140协议栈下既支持外设角色Peripheral被手机连接也支持中心角色Central主动连接其他外设。这意味着它不只是“被手机连”的设备还可以自己主动扫描连接其他BLE传感器。角色切换的关键API是sd_ble_gap_scan_start开始扫描和sd_ble_gap_connect发起连接。连接建立后原来的广播可以继续也可以停止。一个典型的双角色场景是nRF52840保持广播等待手机连接同时作为Central去连接一个心率的蓝牙传感器。手机通过BLE给nRF52840下发指令nRF52840再通过另一路BLE连接把指令转发给传感器。这里需要注意的是双角色同时工作会增加协议栈的复杂度和调度压力。如果你的应用逻辑很简单尽量保持单一角色。只有在确实需要同时管理两条连接时才启用双角色模式而且要仔细阅读S140的内存配置要求确保RAM分配充足。官方例程ble_app_multilink和ble_app_hrs里有双角色的实现参考我从里面捞了不少代码。3.5 用nRF Connect和sniffer验证验证BLE状态我最常用的工具是手机App nRF Connect和PC端的nRF Sniffer。nRF Connect App可以扫描设备、查看广播包内容、连接设备后浏览GATT服务、直接读写特征值。写完广播代码后先把手机贴近开发板看到广播名称和正确的服务UUID就证明广播层正常了。nRF Sniffer配一个nRF52840 Dongle或者开发板可以抓空中的BLE数据包实时看到连接事件、数据重传、连接参数协商过程。如果你怀疑某次通信卡顿是空中的重传造成的用抓包工具一看便知。我也用过BLE协议分析仪但论性价比官方的Sniffer方案就够用了。抓包这一层的经验是别等到出问题再抓包。我习惯在开发过程中就开着Sniffer随时记录正常的通信时序长什么样。这样等到通信出问题时对比一下正常状态的时序问题的范围能马上缩小到具体是广播阶段、连接阶段还是数据传输阶段。4. USB虚拟串口枚举失败才是真正的第一课4.1 app_usbd的驱动框架nRF5 SDK里USB设备功能的核心是app_usbd库它把USB协议栈的底层细节封装成了一套面向应用的API。app_usbd支持多种设备类CDC ACM虚拟串口、HID人机交互设备、MSC大容量存储等。做数据通信最方便的是CDC ACM电脑端不需要额外安装专门的驱动Windows和Linux都自带识别。USB设备的工作逻辑本质上是通过端点和主机交互。CDC ACM定义了两种端点类型一个是管理端点通常用于设置波特率、流控等控制命令一个是数据端点用于实际数据传输。SDK里的app_usbd_cdc_acm驱动已经把这两层封装好了。初学阶段最需要注意的是USB协议栈对时序非常敏感。调用app_usbd_start启动USB外设后要确保主循环及时处理USB事件否则主机枚举设备时可能因为超时而失败。这个“及时”的要求不像中断那么严格但对逻辑阻塞是零容忍的。4.2 CDC ACM实现步骤附代码用SDK建一个CDC ACM虚拟串口工程的步骤比我想象中简单得多。关键先配置好USB字符串描述符再初始化CDC ACM实例然后注册回调最后启动USB外设。第一步定义厂商、产品、序列号三个字符串描述符APP_USBD_STRING_DESC(udev_string_langid, APP_USBD_STRING_LANGID_ENGLISH, English); APP_USBD_STRING_DESC(udev_string_manufacturer, 20, Nordic Semiconductor); APP_USBD_STRING_DESC(udev_string_product, 20, nRF52840 USB CDC); APP_USBD_STRING_DESC(udev_string_serial, 12, 000000000001); APP_USBD_STRINGS_REGISTER(udev_string_desc, udev_string_langid, udev_string_manufacturer, udev_string_product, udev_string_serial);第二步创建一个CDC ACM实例并保存一个句柄static app_usbd_cdc_acm_t m_app_cdc_acm; static void* cdc_acm_ctx_data[APP_USBD_CDC_ACM_CTX_SIZE]; static uint8_t cdc_acm_tx_buffer[512]; static uint8_t cdc_acm_rx_buffer[512]; APP_USBD_CDC_ACM_GLOBAL_DEF(m_app_cdc_acm, cdc_acm_ctx_data, cdc_acm_tx_buffer, sizeof(cdc_acm_tx_buffer), cdc_acm_rx_buffer, sizeof(cdc_acm_rx_buffer));第三步注册事件回调处理CDC的端口使能、数据接收等事件static void cdc_acm_user_ev_handler(app_usbd_cdc_acm_t const* p_cdc_acm, app_usbd_cdc_acm_user_event_t event) { switch (event) { case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN: break; case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE: break; case APP_USBD_CDC_ACM_USER_EVT_RX_DONE: uint32_t len; app_usbd_cdc_acm_read(m_app_cdc_acm, cdc_acm_rx_buffer, len); // 在这里处理接收到的数据 break; default: break; } }第四步初始化并启动USB外设ret_code_t ret app_usbd_cdc_acm_init(m_app_cdc_acm, cdc_acm_config); APP_ERROR_CHECK(ret); ret app_usbd_cdc_acm_user_event_handler_set(m_app_cdc_acm, cdc_acm_user_ev_handler); APP_ERROR_CHECK(ret); ret app_usbd_start(); APP_ERROR_CHECK(ret);启动完成后把USB线插到电脑设备管理器里应该出现一个COM口Windows或者ttyACM0Linux。4.3 枚举失败的排查链路USB枚举失败是最容易劝退新手的环节。我把自己踩过的坑整理成了一条排查链路按这个顺序查能省大量时间。先查硬件供电。USB设备枚举的第一件事是检测到设备插入由D线上的上拉电阻触发。nRF52840内部集成了这个上拉但前提是芯片本身供电正常。用万用表量一下开发板的3.3V和VBUS电压保证VBUS在4.4V以上。再查时钟。USB全速模式对时钟精度要求比较高必须使用外部高频晶振HFXO不能依赖内部RC振荡器。nRF52840 DK板载了32MHz晶振一般没问题但如果你自己画板子在硬件初始化时忘了启动HFXO枚举就会失败。SDK例程里会在初始化时自动拉起但有些精简工程会漏。接着查描述符。USB描述符有任何长度错误或字段配错主机就会拒绝设备。这部分建议对照官方例程的usb_config.h文件检查看接口描述符数量、端点地址、端点属性是否一致。尤其注意端点地址不能冲突。最后查代码逻辑。app_usbd_start之后主循环里不要卡在某个阻塞操作太长时间。主机枚举有时间限制如果设备忙到无法响应请求枚举就会失败。我遇到过一次是因为主循环里有段Flash擦除操作耗时几百毫秒导致枚举超时。后来把Flash擦除挪到Idle状态才解决。4.4 Windows和Linux上的挂载差异同一份固件插到Windows和Linux上表现完全不同这个差异值得提前了解。Windows会显示成COM口只要驱动没问题就能直接通过串口工具收发。有个细节CDC ACM的波特率设置其实没有实际意义USB虚拟串口不受波特率控制但Windows串口工具要求你设置一个波特率随便填一个也能通信。Linux上通常显示为/dev/ttyACM0。默认情况下只有root或有串口权限的用户才能访问。如果普通用户无法打开需要把用户加入dialout组sudo usermod -aG dialout $USER然后重新登录一下就能访问串口了。如果插上后没有出现ttyACM0用dmesg | tail查看内核日志能直接看到设备是否被识别错误原因也在里面。我在Linux上做USB设备开发时喜欢用lsusb看设备枚举状态用cat /sys/kernel/debug/usb/devices看USB设备描述符树这样能快速确认设备有没有正确枚举。5. 把BLE和USB串成一个数据通道透传实例拆解5.1 整体链路设计这个实例是最典型也最实用的场景PC通过USB虚拟串口发送数据到nRF52840nRF52840通过BLE Notify把数据发送给手机反过来手机通过BLE Write发送数据到nRF52840nRF52840再通过USB虚拟串口发给PC。这是标准的串口转BLE透传。整体架构分三层物理层USB CDC ACM负责PC到MCU的数据通道BLE GATT负责手机到MCU的数据通道。数据缓冲层USB和BLE的速度不一样必须用队列做缓冲避免数据丢失。应用层透传逻辑把两边收到的数据交给另一边发出去。核心的数据结构是收发环形缓冲区。SDK里的app_uart或app_fifo库能直接用但我在工程里用的是简单的数组加读写指针效率更高也能加深对缓冲机制的理解。5.2 USB到BLE方向代码逻辑USB到BLE方向的逻辑比较直接。USB CDC收到数据后在回调函数里把数据写入一个发送队列然后在主循环中检查队列如果不为空就调用BLE的sd_ble_gatts_value_set设置特征值再通过sd_ble_gatts_hvx发送Notify通知。关键代码大概长这样static void cdc_acm_user_ev_handler(app_usbd_cdc_acm_t const* p_cdc_acm, app_usbd_cdc_acm_user_event_t event) { if (event APP_USBD_CDC_ACM_USER_EVT_RX_DONE) { uint32_t len sizeof(m_rx_buf); ret_code_t ret app_usbd_cdc_acm_read(m_app_cdc_acm, m_rx_buf, len); if (ret NRF_SUCCESS) { for (uint32_t i 0; i len; i) { fifo_write(m_usb_tx_fifo, m_rx_buf[i]); } } } } // 主循环 static void ble_transmit_loop(void) { if (!fifo_is_empty(m_usb_tx_fifo) m_is_connected m_notify_enabled) { uint8_t data[20]; uint32_t len 0; while (!fifo_is_empty(m_usb_tx_fifo) len 20) { fifo_read(m_usb_tx_fifo, data[len]); len; } ble_nus_data_send(m_nus, data, len); } }这里有几个细节值得注意。BLE单次Notify的载荷长度在nRF52840上默认是247字节但手机端不一定都支持长包为了兼容性我在实例中固定按20字节分包传输也就是传统的ATT_MTU23模式。如果你确认手机端支持GATT长包可以配置更高的MTU一次能发更多数据吞吐量能显著提升。Notify发送的时机也要控制好。协议栈要求连接事件期间才能发送HVX如果在间隔期间多次调用协议栈会返回NRF_ERROR_RESOURCES或BLE_ERROR_NO_TX_PACKETS。我的处理方式是定义m_notify_enabled标志在BLE事件回调中收到连接事件后再发送这样能避免大部分发送失败。5.3 BLE到USB方向代码逻辑手机发送数据给nRF52840BLE会把收到的数据通过on_write回调通知应用层。SDK的NUS服务在ble_nus_on_ble_evt里处理BLE_GATTS_EVT_WRITE事件把收到的数据传给应用的回调函数。我在回调中把数据写入另一个FIFO主循环里检查这个FIFO如果有数据则调用USB发送函数static void nus_data_handler(ble_nus_evt_t* p_evt) { if (p_evt-type BLE_NUS_EVT_RX_DATA) { for (uint32_t i 0; i p_evt-params.rx_data.length; i) { fifo_write(m_ble_tx_fifo, p_evt-params.rx_data.p_data[i]); } } } static void usb_transmit_loop(void) { if (!fifo_is_empty(m_ble_tx_fifo)) { uint8_t data[64]; uint32_t len 0; while (!fifo_is_empty(m_ble_tx_fifo) len sizeof(data)) { fifo_read(m_ble_tx_fifo, data[len]); len; } app_usbd_cdc_acm_write(m_app_cdc_acm, data, len); } }USB发送这边要注意app_usbd_cdc_acm_write不允许在上一次数据还没有发完时再次调用。因此在实际工程中我加了一个发送完成标志在上一个TX_DONE事件来之前不会发起下一次写入。这个异步约束是USB驱动最常见的陷阱直接连续调用会把驱动状态机搞乱。5.4 实测效果与性能把控实测下来BLE这条链路在默认MTU下手机端接收来自USB的数据速率大概在每秒几千字节左右。如果做以下优化吞吐能再上一个台阶协商更大的MTU把MTU从23提升到247单包载荷从20字节变成244字节。减少两次Notify之间的等待时间在可发送数据时连续调用发送接口。提高连接间隔从30ms降到15ms甚至7.5ms。USB这条链路因为12Mbps的带宽远大于BLE几乎不会成为瓶颈。但有个问题需要注意USB CDC如果收得很猛而BLE发得慢FIFO就会溢出。我在实测中故意用PC连续发送大量数据测试结果发现FIFO在32KB缓冲下依然会在几十秒后溢出。解决方案有两个。一是把FIFO加大但这种治标不治本。二是做流控USB收到数据后在CDC的PORT_OPEN事件中主动告诉主机“忙”或“不忙”。不过CDC ACM的标准流控在PC端是否生效取决于串口工具是否启用了流控实测中部分工具支持部分不支持。折中的方案是用主循环轮询的方式限制向协议栈提交数据的速度让BLE发送速率不要超过实际吞吐上限。我在工程里用一个100ms间隔的定时任务每次最多从FIFO取一部分数据分批次发给BLE。虽然降低了瞬时吞吐但保证了长时间运行的稳定性。这个取舍在透传类应用中很关键。6. 这些坑我替你踩过了锁定、驱动、引脚冲突恢复手册6.1 ApproTect与“永久锁定”的形成和恢复这一节是nRF52840最容易把人急哭的坑网上搜“nrf52840 永久锁定”能搜出一大堆求助帖。它本质上是芯片的访问端口保护机制被意外开启导致SWD调试接口无法连接IDE里报错DLL connect failed或者Unable to connect芯片就像“变砖”了一样。常见触发原因有两个一是在UICR寄存器中配置了APPROTECT保护位比如为了做代码防读保护把APPROTECT设成了0之后想再连调试器就进不去了二是使用了某些“防读取”的示例代码或自定义烧录脚本它们会在烧录后启动保护。遇到这种情况第一步要确认芯片是否真的进入了保护状态用命令查看nrfjprog --memrd 0x10001208 --n 4如果读不到说明当前访问受控。恢复动作核心是执行一次擦除整片并解锁保护nrfjprog --recover--recover会擦除整个Flash和UICR同时清除ApproTect保护。注意执行--recover是有代价的芯片里所有数据都会丢失包括出厂时烧录的SoftDevice。恢复后需要重新烧录SoftDevice和应用代码之前的调试断点、RTT配置也会全部清空。实测下来--recover能救回绝大多数“永久锁定”的nRF52840。我遇到过一次恢复成功后无法立即下载程序的情况处理办法是拔掉USB线重新插然后手动执行一次nrfjprog --eraseall再重新烧录就能恢复正常。如果--recover执行后依然报错连接失败这时候要检查电路板上SWD接口的电压转换逻辑以及J-Link与芯片之间有没有其他干扰设备。在极少数情况下芯片的ApproTect保护强度可能是不可恢复级别的那是硬件层面的最终保护这时候只能换芯片了。预防这个坑最简单的办法是不要随意修改UICR中的APPROTECT字段。如果你的产品确实需要开启代码读保护一定要在量产阶段再开启开发调试阶段保持默认值。6.2 引脚复用冲突与I2C上的教训nRF52840的引脚资源丰富但也不能随便拉一根线就用因为大量外设共用同一个引脚。最典型的冲突发生在I2C、SPI和UART之间。开发板上的I2C总线通常接在特定的P0引脚上。我在做一个传感器采集项目时把I2C的SCL接到了P0.06结果发现SDK的调试打印使用的是同一个引脚的UART功能两边的初始化顺序直接打架。表现为传感器读取偶尔超时串口输出乱码两者根本没法共存。排查办法是查看开发板原理图把所有已经占用功能的引脚列出来然后分配新功能时避开这些引脚。nRF52840的引脚没有太多硬性强制但以下引脚在DK板上有默认功能最好别碰引脚默认功能P0.00/P0.01NFC天线默认可配置为GPIOP0.02/P0.03外部32.768kHz晶振P0.18/P0.20USB D/D-固定P0.06/P0.08I2C在某些开发板上P0.05/P0.07SPI/调试接口视板子我个人的习惯是把所有外设的引脚分配写在一个头文件里集中管理比如#define I2C_SCL_PIN 28 #define I2C_SDA_PIN 29 #define LED_PIN 13 #define BUTTON_PIN 14这样在代码审查和移植时一眼就能看出有没有引脚冲突。另外还有一个容易踩的坑是引脚唤醒。如果某一个引脚被配置为低功耗唤醒引脚同时该引脚下拉电阻配置错误芯片可能会从睡眠状态频繁唤醒电流居高不下。排查功耗问题时不能只看外设的功耗还要怀疑引脚配置本身。6.3 开发阶段就能规避的检查清单写到这里想给正在带nRF52840项目的开发者一份开发阶段就能用上的检查清单。这些不是理论都是实际项目中踩过的坑总结出来的。烧录顺序方面SoftDevice必须优先于应用代码烧录且应用代码的链接地址要放在SoftDevice之后。如果整片擦除后只烧应用不烧SoftDevice系统一启动就死在协议栈初始化。USB调试线不要和SWD线同时使用同一个开发板的同一组USB端口有些开发板只有一个USB口它既用于供电又用于调试拔插时容易干扰正在进行的烧录。硬件设计方面USB D和D-引脚不要串电阻、不要加电容滤波USB全速对信号质量有要求直接短距离走线到Type-C座子。晶体和负载电容布局要靠近芯片引脚这会影响HFXO起振和USB时钟精度。软件逻辑方面主循环不能有长时间阻塞。USB枚举、BLE事件处理、Flash擦除操作都要避免在中断里长时间执行。我用了一个固定频率的调度器把可延迟的操作统一放到主循环里跑从而保证USB和BLE的任务都能被及时调度。调试阶段我强烈建议保留至少一个UART口做调试日志输出但这个UART不能跟USB虚拟串口共用同一个物理串口。很多人在调USB时想一边看USB收发日志一边看调试串口结果发现插上USB后调试串口也被占用了导致判断错乱。让调试日志独立占用一个串口能省下很多排查时间。最后一个建议是保留一个按键和一个LED。不要小看这个按键我在做USB枚举测试时就是靠按键触发nrfjprog --recover后的复位和手动烧录动作避免了每次拔插线缆的麻烦。LED用来指示系统主循环是否还在跑——如果一个系统连LED都不闪了那问题十有八九出在中断优先级配置上。
返回列表