简介
提起这个NRF52840芯片相比大家都已经很熟悉了,这款芯片在蓝牙的通讯上做的非常好,同时它还支持多种开发环境还有各种各样的开发教程。在我的印象中除去ESP32系列的蓝牙就这款芯片属于最流行的了。对常用的开发方式的话,我建议使用Arduino来进行开发。 我也是很多年前机缘巧合拿到了一个Sparkfun的NRF-52840MINI的开发板, 最近有空就研究了一下在Arduino下进行BLE开发的步骤同时借此机会分享给大家。

环境搭建
这个开发板的环境搭建稍微有一点不同,因为NRF52840的适配很多公司都已经做好了,所以Sparkfun 在设计的时候是完全没有必要再次将NRF52840的所以什么SDK等等的再重新设计一遍。他们公司的做法是:
一、首先安装NRF52840的支持。

二、修改Arduino 内的boards.txt 来增加SparkFun-NRF52840-MINI 在Arduinoboard中就可以选中如下所示。

路径如下:
C:\Users\你的用户名\AppData\Local\Arduino15\packages\adafruit\hardware\nrf52\1.7.0
添加内容如下
#**********************************************
# SparkFun Pro nRF52840 Mini
#**********************************************
sparkfunnrf52840mini.name=SparkFun Pro nRF52840 Mini
# DFU Mode with CDC only
sparkfunnrf52840mini.vid.0=0x1B4F
sparkfunnrf52840mini.pid.0=0x002A
# DFU Mode with CDC + MSC (UF2)
sparkfunnrf52840mini.vid.1=0x1B4F
sparkfunnrf52840mini.pid.1=0x0029
# Application with CDC + MSC
sparkfunnrf52840mini.vid.2=0x1B4F
sparkfunnrf52840mini.pid.2=0x8029
# CircuitPython
sparkfunnrf52840mini.vid.2=0x1B4F
sparkfunnrf52840mini.pid.2=0x802A
sparkfunnrf52840mini.bootloader.tool=bootburn
# Upload
sparkfunnrf52840mini.upload.tool=nrfutil
sparkfunnrf52840mini.upload.protocol=nrfutil
sparkfunnrf52840mini.upload.use_1200bps_touch=true
sparkfunnrf52840mini.upload.wait_for_upload_port=true
#sparkfunnrf52840mini.upload.native_usb=true
# Build
sparkfunnrf52840mini.build.mcu=cortex-m4
sparkfunnrf52840mini.build.f_cpu=64000000
sparkfunnrf52840mini.build.board=NRF52840_FEATHER
sparkfunnrf52840mini.build.core=nRF5
sparkfunnrf52840mini.build.variant=sparkfun_nrf52840_mini
sparkfunnrf52840mini.build.extra_flags=-DNRF52840_XXAA {build.flags.usb}
sparkfunnrf52840mini.build.vid=0x1B4F
sparkfunnrf52840mini.build.pid=0x5284
sparkfunnrf52840mini.build.usb_manufacturer="SparkFun"
sparkfunnrf52840mini.build.usb_product="nRF52840 Mini Breakout"
# SofDevice Menu
# Ram & ROM size varies depending on SoftDevice (check linker script)
sparkfunnrf52840mini.menu.softdevice.s140v6=s140 6.1.1 r0
sparkfunnrf52840mini.menu.softdevice.s140v6.build.sd_flags=-DS140
sparkfunnrf52840mini.menu.softdevice.s140v6.build.sd_name=s140
sparkfunnrf52840mini.menu.softdevice.s140v6.build.sd_version=6.1.1
sparkfunnrf52840mini.menu.softdevice.s140v6.build.sd_fwid=0x00B6
sparkfunnrf52840mini.menu.softdevice.s140v6.build.ldscript=nrf52840_s140_v6.ld
sparkfunnrf52840mini.menu.softdevice.s140v6.upload.maximum_size=815104
sparkfunnrf52840mini.menu.softdevice.s140v6.upload.maximum_data_size=248832
# Debug Menu
sparkfunnrf52840mini.menu.debug.l0=Level 0 (Release)
sparkfunnrf52840mini.menu.debug.l0.build.debug_flags=-DCFG_DEBUG=0 -Os
sparkfunnrf52840mini.menu.debug.l1=Level 1 (Error Message)
sparkfunnrf52840mini.menu.debug.l1.build.debug_flags=-DCFG_DEBUG=1 -Os
sparkfunnrf52840mini.menu.debug.l2=Level 2 (Full Debug)
sparkfunnrf52840mini.menu.debug.l2.build.debug_flags=-DCFG_DEBUG=2 -Os
sparkfunnrf52840mini.menu.debug.l3=Level 3 (Segger SystemView)
sparkfunnrf52840mini.menu.debug.l3.build.debug_flags=-DCFG_DEBUG=3 -Os三- 增加BSP (variants)的Pin定义。
此时此时所有的配置工作就已经都做完了。可以将Arduino IDE进行重启。那么重启后Arduino IDE加载的时候便会加载我们新配置的适配。此时便可以在board中选中我们新配置的这个开发板了。如下所示。

对应的开发板的原理图如下所示。

低功耗蓝牙控制开发
由于这个开发板并不是Arduino官方的开发板,所以并不能使用ArduinoBL的库,而是使用bluefruit.h的这个库进行开发,两个开发的库都是基本流程是一样的并没有很大的区别。我们主要做的开发任务是初始化一个BLE的服务,然后在手机上可以控制开发板上的LED灯的变化。同时如果按下的开发板上的button的按键的话,当手机上订阅了这个服务的话,那么开发板会主动发送Notify通知给手机。
项目的主要步骤是:
一、 首先初始化LED引脚
pinMode(LED_PIN, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP);
二、初始化蓝牙
// 初始化 Bluefruit BLE 协议栈
Bluefruit.begin();
// 设置发射功率为 +4dBm
Bluefruit.setTxPower(4);
// 设置 BLE 设备名称,手机扫描时显示此名称
Bluefruit.setName("ButtonLED");
// 启动 LED 服务
ledService.begin();三、初始化对应服务的特征值和开启广播。
// 配置 LED 特征值:可读可写 ledCharacteristic.setProperties( CHR_PROPS_READ | CHR_PROPS_WRITE); // 权限设为开放(无需配对即可访问) ledCharacteristic.setPermission( SECMODE_OPEN, SECMODE_OPEN); // 固定长度 1 字节(0=关,1=开) ledCharacteristic.setFixedLen(1); // 注册写入回调,手机写入时触发 ledCharacteristic.setWriteCallback(led_write_callback); ledCharacteristic.begin(); // 初始值设为 0(LED 熄灭) uint8_t value = 0; ledCharacteristic.write(&value, 1); // 配置按键特征值:可读可通知 buttonCharacteristic.setProperties( CHR_PROPS_READ | CHR_PROPS_NOTIFY); // 权限设为开放 buttonCharacteristic.setPermission( SECMODE_OPEN, SECMODE_OPEN); // 固定长度 1 字节(0=未按下,1=按下) buttonCharacteristic.setFixedLen(1); buttonCharacteristic.begin(); // 初始值设为 0(按键未按下) buttonCharacteristic.write(&value, 1); // 开始 BLE 广播 startAdvertising();
四、在主循环中检测按键是否按下
void loop() {
// 读取按键状态:上拉输入,按下时 LOW,转换为 1 表示按下
uint8_t buttonState =
(digitalRead(BUTTON_PIN) == LOW) ? 1 : 0;
// 仅在按键状态发生变化时更新
if (buttonState != lastButtonState) {
lastButtonState = buttonState;
// 更新特征值并通知已订阅的手机端
buttonCharacteristic.write(&buttonState, 1);
buttonCharacteristic.notify(&buttonState, 1);
Serial.print("Button: ");
Serial.println(buttonState);
}
// 短暂延时,避免频繁轮询
delay(20);
}五、如果是手机上向对应的服务写入数据的话,触发回调函数来开灯或者关灯
// 手机写入 LED 特征值时的回调函数
void led_write_callback(uint16_t conn_hdl,
BLECharacteristic* chr,
uint8_t* data,
uint16_t len) {
// 数据长度不足则忽略
if (len < 1) return;
// data[0] 非零则点亮 LED,为零则熄灭
if (data[0]) {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED ON");
} else {
digitalWrite(LED_PIN, LOW);
Serial.println("LED OFF");
}
}完整代码如下所示
// 按键控制 LED 的 BLE 外设示例
// 功能:手机通过 BLE 写入控制 LED 开关,板载按键状态通过 BLE 通知发送给手机
#include <bluefruit.h>
// 按键引脚(使用内部上拉,按下时为 LOW)
#define BUTTON_PIN 13
// 板载 LED 引脚
#define LED_PIN LED_BUILTIN
// BLE LED 服务 UUID
BLEService ledService("19B10010-E8F2-537E-4F6C-D104768A1214");
// LED 特征值 — 手机可读写,用于控制 LED 开关
BLECharacteristic ledCharacteristic(
"19B10011-E8F2-537E-4F6C-D104768A1214");
// 按键特征值 — 手机可读取和接收通知,用于获取按键状态
BLECharacteristic buttonCharacteristic(
"19B10012-E8F2-537E-4F6C-D104768A1214");
// 记录上一次按键状态,用于检测变化
uint8_t lastButtonState = 0;
// 手机写入 LED 特征值时的回调函数
void led_write_callback(uint16_t conn_hdl,
BLECharacteristic* chr,
uint8_t* data,
uint16_t len) {
// 数据长度不足则忽略
if (len < 1) return;
// data[0] 非零则点亮 LED,为零则熄灭
if (data[0]) {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED ON");
} else {
digitalWrite(LED_PIN, LOW);
Serial.println("LED OFF");
}
}
// 启动 BLE 广播,让手机能发现并连接本设备
void startAdvertising(void) {
// 先停止之前的广播
Bluefruit.Advertising.stop();
// 添加广播标志:仅 BLE,通用发现模式
Bluefruit.Advertising.addFlags(
BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
// 添加发射功率
Bluefruit.Advertising.addTxPower();
// 广播中包含 LED 服务的 UUID,方便手机筛选
Bluefruit.Advertising.addService(ledService);
// 扫描响应中包含设备名称
Bluefruit.ScanResponse.addName();
// 断开连接后自动重新开始广播
Bluefruit.Advertising.restartOnDisconnect(true);
// 设置广播间隔:快速模式 32(20ms),慢速模式 244(152.5ms)
Bluefruit.Advertising.setInterval(32, 244);
// 快速广播持续 30 秒后切换到慢速
Bluefruit.Advertising.setFastTimeout(30);
// 开始广播,0 表示无限期广播
Bluefruit.Advertising.start(0);
}
void setup() {
// LED 引脚设为输出,按键引脚设为输入并启用内部上拉电阻
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// 初始化串口,用于调试输出
Serial.begin(115200);
// 等待串口就绪
while (!Serial) {
delay(10);
}
Serial.println("Starting BLE...");
// 初始化 Bluefruit BLE 协议栈
Bluefruit.begin();
// 设置发射功率为 +4dBm
Bluefruit.setTxPower(4);
// 设置 BLE 设备名称,手机扫描时显示此名称
Bluefruit.setName("ButtonLED");
// 启动 LED 服务
ledService.begin();
// 配置 LED 特征值:可读可写
ledCharacteristic.setProperties(
CHR_PROPS_READ | CHR_PROPS_WRITE);
// 权限设为开放(无需配对即可访问)
ledCharacteristic.setPermission(
SECMODE_OPEN,
SECMODE_OPEN);
// 固定长度 1 字节(0=关,1=开)
ledCharacteristic.setFixedLen(1);
// 注册写入回调,手机写入时触发
ledCharacteristic.setWriteCallback(led_write_callback);
ledCharacteristic.begin();
// 初始值设为 0(LED 熄灭)
uint8_t value = 0;
ledCharacteristic.write(&value, 1);
// 配置按键特征值:可读可通知
buttonCharacteristic.setProperties(
CHR_PROPS_READ | CHR_PROPS_NOTIFY);
// 权限设为开放
buttonCharacteristic.setPermission(
SECMODE_OPEN,
SECMODE_OPEN);
// 固定长度 1 字节(0=未按下,1=按下)
buttonCharacteristic.setFixedLen(1);
buttonCharacteristic.begin();
// 初始值设为 0(按键未按下)
buttonCharacteristic.write(&value, 1);
// 开始 BLE 广播
startAdvertising();
Serial.println("BLE Ready");
}
void loop() {
// 读取按键状态:上拉输入,按下时 LOW,转换为 1 表示按下
uint8_t buttonState =
(digitalRead(BUTTON_PIN) == LOW) ? 1 : 0;
// 仅在按键状态发生变化时更新
if (buttonState != lastButtonState) {
lastButtonState = buttonState;
// 更新特征值并通知已订阅的手机端
buttonCharacteristic.write(&buttonState, 1);
buttonCharacteristic.notify(&buttonState, 1);
Serial.print("Button: ");
Serial.println(buttonState);
}
// 短暂延时,避免频繁轮询
delay(20);
}在项目编译和烧录之后我们使用蓝牙工具来扫描附近的蓝牙设备如下所示。

连接上去之后我们可以找到对应的蓝牙服务。

为什么这里的服务有的回事unknow的呢? 这是因为蓝牙的服务的特征码是我们自定义的,而不是使用固定的比如温度计等等的特征码,因此无法正常识别。
向LED特征值写入00,LED灯熄灭,写入01的话LED灯打开。 同时我们订阅下方的按钮状态来接受开发板对按钮状态的通知。
可以看到,当按钮按下的时候,其手机会收到对应的消息通知。 至此双向的通讯完毕。
总结
NRF52840是一款非常经典的蓝牙芯片就类似ESP32或者8051这种经典单片机一样,同时它支持多种的开发方式的选择。具备重组且大量的教程和资源非常适合初学者来学习蓝牙控制。
我要赚赏金
