ARTICLE DETAIL

资讯详情

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

nRF52840蓝牙5.0 LED控制实战:Keil开发环境搭建与GPIO编程指南

nRF52840蓝牙5.0 LED控制实战:Keil开发环境搭建与GPIO编程指南 1. 为什么选择nRF52840做蓝牙LED控制1.1 这颗芯片到底强在哪nRF52840是Nordic家的一款多协议SoC核心是ARM Cortex-M4F带浮点单元主频64MHzFlash 1MB、RAM 256KB。这个配置放在低功耗蓝牙芯片里算是相当宽裕的我第一次拿到它的开发板时最直观的感受就是这不像一颗蓝牙芯片更像一颗带蓝牙的MCU。它支持蓝牙5.0理论上是蓝牙4.2的两倍速、四倍广播距离、八倍广播数据量。但真正让我在项目里选它的原因不是这些纸面参数而是几个很实际的东西第一GPIO数量多P0口32个、P1口16个加起来48个可编程引脚做LED控制这种小项目绰绰有余后面要扩展按键、传感器、屏幕也不会捉襟见肘第二它自带USB控制器可以直接用USB供电和调试省掉一个调试器第三它的外设很全PWM、SPI、I2C、UART、ADC、QSPI都有一颗芯片能撑起一个完整的小产品。很多人做蓝牙LED控制会先想到STM32加一个蓝牙模块比如STM32F103加HC-05那种组合。这种方案不是不行但你要多写一套串口AT指令的解析代码还要处理模块和MCU之间的通信时序调试起来是两个系统的事。nRF52840把蓝牙协议栈和MCU集成在一颗芯片里代码层面是统一的出了问题也只需要在一个工程里排查。1.2 蓝牙5.0在LED控制场景里的实际意义有人会问控制一个LED而已蓝牙4.0和5.0有区别吗从功能上讲确实都能实现。但蓝牙5.0带来的几个特性在实际产品里是有价值的。广播距离的提升意味着手机和设备的连接更稳定尤其是在有遮挡的环境里。我之前做过一个灯带项目用蓝牙4.0的模块手机走到隔壁房间就断连换成nRF52840之后同样的室内环境连接稳定性明显好很多。广播数据量的提升则允许你在广播包里直接携带LED的状态信息手机不用建立连接就能读取当前灯的状态这在一些低功耗场景下很有用。另外nRF52840的蓝牙协议栈支持多连接一个设备可以同时被多个手机连接。做LED控制的时候这意味着你可以用一台手机控制另一台手机同时监控状态或者做多设备的组网控制。1.3 Keil环境的取舍开发nRF52840可以用Keil MDK、Segger Embedded Studio、GCC命令行等多种环境。我选Keil的原因很直接它的调试体验在Windows平台上是最顺手的尤其是配合J-Link调试器的时候断点、单步、变量监视、外设寄存器查看都很流畅。Keil的Pack Installer可以直接安装Nordic的Device Family Pack安装完之后芯片的寄存器定义、启动文件、外设驱动都自动配好了不用手动去折腾链接脚本和启动代码。当然Keil也有它的坑比如License的问题、Pack版本兼容性的问题这些后面会专门讲。但整体来说对于从STM32转过来的人来说Keil的上手成本是最低的因为界面和操作逻辑几乎一样。2. 开发环境搭建的完整流程2.1 Keil MDK的安装与License处理Keil MDK的安装本身不复杂官网下载安装包一路Next就行。但有几个细节需要注意。安装路径不要有中文和空格这是老生常谈但每年都有人踩的坑。我建议直接装在C:\Keil_v5这种路径下避免后续编译时出现莫名其妙的路径错误。安装过程中会提示安装Pack可以先跳过等装完主程序再通过Pack Installer统一安装。关于LicenseKeil MDK是商业软件官方提供32KB代码限制的免费版。对于nRF52840的LED控制项目来说32KB其实够用一个基础的BLE工程编译出来大概在20KB左右。但如果你的项目要加DFU升级、要加多个蓝牙服务很容易就超了。正版License的获取途径这里不展开但我要提醒一句网上流传的各种注册工具存在安全风险而且版本更新后经常失效不建议在生产项目里使用。安装完成后打开Keil通过Pack Installer搜索NordicSemiconductor找到nRF_DeviceFamilyPack安装最新版本。截至我写这篇的时候8.27.0是比较稳定的版本。安装Pack的时候注意看它依赖的CMSIS版本如果CMSIS版本太低Pack会装不上或者编译报错。2.2 nRF5 SDK的下载与目录结构Nordic的SDK更新节奏比较快nRF5 SDK最新的稳定版本是17.1.0。下载下来是一个压缩包解压后目录结构大概是这样的nRF5_SDK_17.1.0/ ├── components/ # 协议栈、驱动、库文件 ├── config/ # 配置文件 ├── examples/ # 示例工程 ├── external/ # 第三方库 ├── modules/ # 模块化组件 └── documentation/ # 文档对于LED控制项目我们主要关注examples/ble_peripheral/目录下的示例。其中ble_app_blinky是最接近我们需求的例子它实现了一个蓝牙LED控制服务可以通过手机APP控制LED的开关和闪烁。SDK的路径同样不要有中文和空格。我一般会把SDK放在C:\nRF5_SDK_17.1.0然后在Keil工程里用相对路径引用。如果你移动了SDK的位置工程里的路径引用会失效需要手动修改所以建议一开始就放好位置。2.3 J-Link驱动与调试器连接nRF52840开发板通常板载J-Link OB调试器比如PCA10056nRF52840 DK。你需要安装Segger的J-Link驱动包安装完之后设备管理器里会出现JLink CDC UART Port和JLink Driver。连接开发板之前先确认跳线帽的位置。PCA10056上有一个Debug out和Debug in的选择默认应该是Debug in也就是板载调试器连接板载芯片。如果你要用外部J-Link调试自己的板子需要把跳线帽改到Debug out。第一次连接的时候Keil可能会提示找不到设备。这时候检查几个地方USB线是不是数据线有些线只能充电、驱动有没有装好、开发板有没有上电。如果都正常在Keil的Options for Target-Debug里选择J-LINK / J-TRACE Cortex然后点Settings应该能看到SW Device里识别到nRF52840。2.4 工程模板的创建与配置我不建议直接从零新建工程那样要手动添加的文件太多容易漏。更高效的做法是复制ble_app_blinky示例然后在此基础上修改。复制的时候要注意示例工程里引用了SDK的公共文件这些文件是通过相对路径引用的。如果你把工程复制到SDK目录之外路径就会断。有两种解决方案一是把工程放在SDK目录内比如examples/ble_peripheral/下面新建一个文件夹二是用Keil的Manage Project Items手动调整路径。我一般选第一种简单省事。复制完之后用Keil打开.uvprojx文件先编译一次确认能通过。如果编译报错大概率是Pack版本或者SDK路径的问题先解决这些再往下走。3. GPIO与LED控制的底层原理3.1 nRF52840的GPIO结构nRF52840的GPIO分为P0和P1两个端口每个端口最多32个引脚。P0.00到P0.31P1.00到P1.15。每个引脚都可以独立配置为输入、输出、上拉、下拉、开漏等模式。和STM32不同nRF52840的GPIO没有复用功能这个概念。它的外设引脚是通过PSEL寄存器来分配的也就是说你可以把UART的TX引脚分配到任意一个GPIO上灵活性很高。这一点在做PCB布局的时候特别有用不用为了迁就外设的固定引脚而绕线。GPIO的配置通过nrf_gpio_cfg_output()、nrf_gpio_cfg_input()这些函数来完成。底层操作的是NRF_P0-PIN_CNF[]寄存器每个引脚有一个32位的配置寄存器包含方向、输入缓冲、上拉、下拉、驱动能力、感测等位域。3.2 LED的驱动方式与限流电阻计算开发板上的LED通常是低电平点亮也就是GPIO输出低电平时LED亮。这是因为LED的阳极接VCC阴极通过限流电阻接GPIO。当GPIO输出低电平时形成回路LED导通。限流电阻的计算很简单假设LED的正向压降是2.0V红色LED典型值工作电流5mA供电3.3V那么电阻R (3.3 - 2.0) / 0.005 260Ω。实际选用270Ω或330Ω都可以电流小一点LED暗一些但更省电对GPIO的驱动压力也小。nRF52840的GPIO驱动能力可以配置标准驱动是2mA高驱动是10mA。对于LED来说标准驱动就够了。如果你要驱动大功率LED或者灯带就需要加三极管或者MOS管来扩流不能直接用GPIO驱动。3.3 蓝牙LED控制的服务设计蓝牙LED控制的核心是定义一个GATT服务包含一个可写的特征值。手机往这个特征值写数据设备收到后根据数据内容控制LED。Nordic的SDK里有一个现成的LED Button ServiceLBS定义了一个LED特征值和一个Button特征值。LED特征值支持写操作写入0x00关闭LED0x01打开LED。这个服务在ble_lbs.c和ble_lbs.h里实现。如果你要自己定义服务需要做几件事定义UUID、定义特征值的属性读、写、通知、实现写回调函数、在广播数据里加入服务的UUID。UUID可以用在线的UUID生成器生成也可以用Nordic的nrf_ble_uuid工具。写回调函数是核心当手机写入数据时协议栈会调用这个回调。在回调里解析数据然后调用nrf_gpio_pin_write()控制GPIO。注意回调函数是在中断上下文里执行的不要在里面做耗时操作否则会影响蓝牙的响应。4. 从零搭建LED控制工程的实操步骤4.1 工程文件的组织与关键配置复制ble_app_blinky工程到你的工作目录后用Keil打开。先看Options for Target里的几个关键配置。Target标签页里Xtal设置为32768Hz这是外部低速晶振的频率蓝牙协议栈需要它来维持连接时序。IRAM1的起始地址是0x20000000大小0x40000256KB。IROM1的起始地址是0x00000000大小0x1000001MB。C/C标签页里Define中需要包含BOARD_PCA10056、CONFIG_GPIO_AS_PINRESET、NRF52840_XXAA这些宏。Include Paths里要包含SDK的components、modules、examples等目录。Debug标签页里选择J-LINK勾选Run to main()。Utilities标签页里勾选Use Debug Driver然后点Settings在Flash Download里确认Programming Algorithm是nRF52840_xxAA。这些配置在示例工程里通常已经设好了但如果你复制工程后改了路径可能需要手动调整。我建议每次复制工程后都检查一遍避免编译通过但下载失败的情况。4.2 主程序的初始化流程main.c是整个工程的入口初始化流程大致是这样的int main(void) { // 初始化板级支持包配置LED和按键的GPIO bsp_init(); // 初始化蓝牙协议栈 ble_stack_init(); // 初始化GAP参数设置设备名、连接参数等 gap_params_init(); // 初始化GATT注册服务 gatt_init(); // 初始化LED Button Service lbs_init(); // 初始化广播参数 advertising_init(); // 开始广播 advertising_start(); // 进入主循环 for (;;) { // 处理电源管理 idle_state_handle(); } }bsp_init()会配置开发板上的4个LED和4个按键。在bsp_board.h里可以找到LED的引脚定义PCA10056的LED1到LED4分别对应P0.13、P0.14、P0.15、P0.16。ble_stack_init()是蓝牙初始化的核心它会初始化协议栈、注册事件回调、使能协议栈。这个函数里有一个nrf_sdh_ble_default_cfg_set()调用设置协议栈的默认配置比如连接数、事件长度等。4.3 LED控制代码的编写在ble_lbs.c里找到led_write_handler函数这是LED特征值的写回调。示例代码是这样的static void led_write_handler(uint16_t conn_handle, ble_lbs_t *p_lbs, uint8_t led_state) { if (led_state) { bsp_board_led_on(LEDBUTTON_LED); } else { bsp_board_led_off(LEDBUTTON_LED); } }LEDBUTTON_LED是宏定义对应开发板上的LED1。如果你想控制多个LED可以扩展这个函数比如用led_state的不同的位来控制不同的LEDstatic void led_write_handler(uint16_t conn_handle, ble_lbs_t *p_lbs, uint8_t led_state) { for (uint8_t i 0; i 4; i) { if (led_state (1 i)) { bsp_board_led_on(i); } else { bsp_board_led_off(i); } } }这样手机写入0x0F就点亮所有LED写入0x01只点亮LED1。这种位控制的方式在多做LED控制的时候很实用一个字节可以控制8个LED。4.4 广播数据的配置广播数据决定了手机能搜索到什么设备。在advertising_init()里advdata结构体定义了广播内容。示例代码里包含了设备名和LED Button Service的UUID。设备名在gap_params_init()里通过ble_gap_conn_sec_mode_t和sd_ble_gap_device_name_set()设置。默认是Nordic_Blinky你可以改成自己的名字比如My_LED。广播间隔在adv_params里设置默认是APP_ADV_INTERVAL单位是0.625ms。示例里是64也就是40ms。广播间隔越短手机发现设备越快但功耗越高。对于LED控制这种对功耗不敏感的场景40ms到100ms都可以。4.5 编译、下载与验证编译之前先确认Options for Target里的Output标签页勾选了Create HEX File这样编译后会生成hex文件方便用nRF Connect Programmer烧录。点编译按钮如果一切正常Output窗口会显示0 Error(s), 0 Warning(s)。如果有错误先看错误信息里的文件路径通常是路径引用的问题。下载的时候确保开发板通过USB连接J-Link识别正常。点Download按钮Keil会擦除芯片、烧录程序、复位运行。如果下载失败检查Flash Download里的算法配置以及芯片是否被读保护了。下载成功后开发板上的LED1应该会闪烁表示正在广播。用手机上的nRF Connect或者LightBlue搜索应该能看到Nordic_Blinky设备。连接后找到LED Button Service往LED特征值写0x01LED1应该点亮。5. 调试技巧与常见问题排查5.1 Keil调试模式下的变量查看Keil的调试模式功能很强但很多人只用了断点和单步。我分享几个实用的技巧。查看结构体变量的时候默认的Watch窗口只显示结构体的地址不展开内容。你可以在Watch窗口里输入结构体变量名.成员名来查看单个成员或者右键选择Expand来展开整个结构体。如果结构体指针输入(*指针名).成员名或者指针名-成员名。查看外设寄存器在Peripherals菜单里选择System Viewer然后找到对应的外设比如GPIO。这里可以实时看到GPIO的配置寄存器和输出寄存器的值调试GPIO问题的时候特别有用。逻辑分析仪功能也很实用。在View-Analysis Windows-Logic Analyzer里可以添加要观察的变量设置好采样率运行程序后就能看到变量的变化波形。比如观察LED的状态变量就能看到它什么时候翻转。5.2 蓝牙连接不稳定的排查思路蓝牙连接不稳定是新手最常遇到的问题。排查的时候按这个顺序来。先看供电。nRF52840在蓝牙发射的时候瞬间电流可能达到十几毫安如果供电不足或者电源纹波太大会导致连接断开。用示波器看VDD的波形如果有明显的跌落就要检查电源电路。再看晶振。蓝牙协议栈依赖32.768kHz的低速晶振来维持连接时序。如果晶振不起振或者频偏太大连接会不稳定甚至连不上。用示波器或者频率计测量晶振引脚确认频率在32.768kHz附近误差在±20ppm以内。然后看天线匹配。如果你用的是自己画的板子天线部分的匹配网络很重要。nRF52840的参考设计里有一个π型匹配网络元件值需要根据实际PCB调整。没有网络分析仪的话至少确保天线部分的走线是50欧姆阻抗周围不要有金属遮挡。最后看连接参数。连接间隔、从机延迟、监督超时这些参数会影响连接的稳定性。连接间隔太短会增加功耗太长会影响响应速度。对于LED控制连接间隔设置在30ms到50ms之间比较合适。5.3 常见编译错误与解决方法错误信息原因解决方法cannot open source input file nrf.hInclude路径没配好在C/C标签页添加SDK的components目录undefined symbol ble_lbs_init源文件没加入工程在Manage Project Items里添加ble_lbs.cregion RAM overflowedRAM不够减小协议栈的RAM配置或优化全局变量L6218E: Undefined symbol库文件没链接确认链接了协议栈的库文件Flash Download failed芯片被读保护用nRF Connect Programmer执行Erase All编译错误里最常见的是路径问题和文件遗漏。复制工程的时候如果只复制了.uvprojx文件而没有复制源文件编译就会报找不到文件。建议整个工程文件夹一起复制。5.4 实操心得与避坑经验第一个坑是Pack版本。Nordic的Device Family Pack更新比较频繁不同版本之间可能有兼容性问题。我遇到过用最新版Pack编译旧版SDK的工程报了一堆寄存器定义找不到的错误。解决办法是看SDK的Release Notes里面会写明推荐的Pack版本。第二个坑是协议栈的RAM配置。nRF52840的蓝牙协议栈需要一块连续的RAM区域通过nrf_sdh_ble_default_cfg_set()来配置。如果RAM配得太小协议栈初始化会失败配得太大应用层的RAM就不够用。默认配置是NRF_SDH_BLE_GATT_MAX_MTU_SIZE247字节、NRF_SDH_BLE_GATTS_ATTR_TAB_SIZE1408字节对于LED控制来说够用了。第三个坑是GPIO的默认状态。nRF52840的GPIO在复位后默认是输入、无上拉下拉。如果你在初始化之前就操作GPIO可能没有效果。确保在bsp_init()之后再操作LED。第四个坑是广播数据长度。蓝牙5.0的广播包最大31字节如果你往广播数据里塞太多东西会截断或者广播失败。设备名不要太长UUID用16位的短UUID能省不少空间。6. 功能扩展与进阶方向6.1 用PWM实现LED调光基础的LED控制只有开和关用PWM可以实现亮度调节。nRF52840有4个PWM外设每个支持4个通道总共可以控制16路PWM输出。用PWM控制LED的步骤初始化PWM外设、配置PWM频率和占空比、把PWM输出连接到GPIO。Nordic的SDK里有一个nrfx_pwm驱动封装了PWM的配置接口。PWM频率建议设置在1kHz以上避免人眼看到闪烁。占空比从0到100%对应亮度从暗到亮。手机端可以发送一个字节的亮度值设备端把这个值映射到PWM的占空比。6.2 多设备组网控制nRF52840支持多连接可以同时被多个手机连接。但如果你想做的是多个设备之间的组网比如一个手机控制多个灯就需要用到蓝牙Mesh或者自定义的广播协议。蓝牙Mesh是蓝牙SIG的标准协议nRF5 SDK for Mesh是Nordic提供的Mesh协议栈。Mesh的配置比较复杂需要先配网再控制适合灯控这种多节点的场景。如果不想用Mesh也可以用自定义的广播协议。手机通过广播发送控制指令设备收到广播后执行动作不需要建立连接。这种方式简单但安全性差一些适合对安全要求不高的场景。6.3 低功耗优化LED控制如果用在电池供电的产品上低功耗就很重要。nRF52840的低功耗模式可以把电流降到微安级别。优化的思路有几个一是降低广播频率广播间隔从40ms改到500ms甚至1s二是缩短连接间隔连接建立后尽快传输数据然后断开三是用__WFE()指令让CPU在空闲时进入睡眠四是关闭不用的外设比如UART、SPI。实测下来广播间隔1s、连接间隔100ms的情况下平均电流大概在50微安左右一颗CR2032纽扣电池可以撑几个月。6.4 固件升级功能的加入产品化的时候固件升级DFU是必备功能。Nordic的DFU支持空中升级手机通过蓝牙把新的固件传给设备设备自己完成升级。DFU的配置需要在工程里加入nrf_dfu相关的文件配置Bootloader和DFU服务。升级的时候设备先跳转到Bootloader接收新固件写入Flash然后跳转回应用层。DFU的配置比较复杂涉及分区表、Bootloader设置、密钥生成等。建议先用Nordic的示例工程跑通流程再往自己的工程里移植。7. 从示例工程到产品固件的距离ble_app_blinky这个示例工程跑通之后很多人会觉得蓝牙LED控制不过如此。但真正把它做成一个能出货的产品中间还有不少路要走。首先是代码的健壮性。示例代码里几乎没有错误处理比如协议栈初始化失败怎么办、广播启动失败怎么办、连接断开后怎么处理。产品固件里这些都要考虑到要有重试机制、要有错误日志、要有异常恢复。其次是功耗。示例工程的功耗优化很粗糙广播间隔、连接参数都是默认值。产品固件要根据实际使用场景调优比如没人连接的时候降低广播频率连接后尽快传输数据然后进入低功耗。然后是OTA升级。没有OTA的产品一旦出货就只能召回。DFU功能要在第一版就加上哪怕暂时用不到也要预留好分区和Bootloader。最后是认证。蓝牙产品要过BQB认证无线产品要过无线电认证。这些认证对射频指标、协议一致性都有要求需要在设计阶段就考虑进去。我个人的经验是从示例工程到可量产固件工作量大概是示例工程的5到10倍。但这个投入是值得的因为示例工程帮你验证了技术路线剩下的就是工程化的工作。7.1 代码分层与模块化示例工程的代码都堆在main.c里产品固件需要做分层。我一般会分成这几层硬件抽象层HAL、驱动层、服务层、应用层。硬件抽象层封装GPIO、PWM、UART这些外设的操作驱动层实现具体的传感器、LED驱动服务层实现蓝牙GATT服务应用层处理业务逻辑。层与层之间通过接口调用不要跨层访问。这样分层的好处是换硬件平台的时候只需要改HAL层业务逻辑不用动。加新功能的时候在服务层加新的GATT服务应用层调用就行。7.2 状态机的设计LED控制看起来简单但加上蓝牙连接状态、DFU状态、低功耗状态之后状态就多了。用状态机来管理这些状态代码会清晰很多。我一般会定义一个app_state_t枚举包含IDLE、ADVERTISING、CONNECTED、DFU这些状态。在蓝牙事件回调里切换状态在主循环里根据状态执行不同的操作。状态机的关键是状态转换要清晰每个状态只处理自己关心的事件不关心的事件传递给下一个状态或者忽略。这样代码的可维护性会好很多。7.3 日志系统的搭建调试产品固件的时候日志系统很重要。nRF52840有UART、RTTReal Time Transfer两种日志输出方式。RTT的速度快不占用UART引脚推荐用RTT。Nordic的SDK里有一个NRF_LOG模块支持RTT和UART输出。配置日志级别、日志模块、日志格式就可以在调试的时候看到详细的运行信息。日志级别建议在开发阶段用DEBUG量产固件用ERROR或者OFF。日志本身会占用Flash空间和CPU时间量产固件里要关掉或者精简。7.4 生产测试的考虑产品量产的时候每台设备都要做功能测试。测试内容包括GPIO是否正常、LED是否能亮、蓝牙是否能广播、连接是否正常、功耗是否达标。测试固件和产品固件可以分开。测试固件里加入测试模式通过特定的广播数据或者GPIO状态进入。测试完成后再烧录产品固件。测试工装可以用nRF52840 DK来做通过UART或者GPIO和被测设备通信。测试结果记录到数据库方便追溯。8. 写在最后的一些实操体会nRF52840这颗芯片我用了三年多从最简单的LED控制到复杂的Mesh组网都做过。它给我的感觉是上手容易精通难。示例工程跑通可能只需要半天但要把它的低功耗、射频、协议栈都吃透需要不少时间的积累。Keil环境的好处是调试方便坏处是License和Pack版本的问题比较烦人。如果你不介意换环境Segger Embedded Studio对Nordic的芯片支持更好而且是免费的。但如果你已经熟悉Keil继续用也没问题只是要留意Pack版本的兼容性。GPIO控制是嵌入式开发的基本功nRF52840的GPIO灵活性很高但也要注意它的驱动能力和电平特性。直接驱动LED没问题驱动继电器或者电机就要加驱动电路。蓝牙5.0的特性在LED控制场景里最实用的还是广播距离和广播数据量的提升。如果你做的是需要远距离控制或者广播携带数据的场景这些特性会很有价值。最后说一个我踩过的坑nRF52840的Flash读写要注意对齐。Nordic的NVMC外设要求写入的地址和长度都是4字节对齐的不对齐会触发硬件错误。我当初存LED的状态到Flash里没注意对齐调试了半天才发现问题。这个坑在SDK的文档里有写但很容易被忽略。
返回列表