nRF52840 开发实战:基于 C/C++ 的低功耗蓝牙物联网应用
一、nRF52840 硬件架构与核心特性
1.1 芯片概述
nRF52840 是 Nordic Semiconductor 推出的一款高性能、多协议系统级芯片 (SoC),专为低功耗无线应用设计。它集成了 ARM Cortex-M4F 处理器、2.4GHz 无线收发器、丰富的外设接口和内存资源,支持 Bluetooth 5、Bluetooth Mesh、Zigbee、Thread 等多种无线协议。
主要特性参数:
- 处理器:64MHz ARM Cortex-M4F,支持浮点运算
- 内存:1MB Flash,256KB RAM
- 无线连接:支持 Bluetooth 5.2、IEEE 802.15.4、ANT 和专有 2.4GHz 协议
- 接收灵敏度:-96dBm (BLE),-103dBm (802.15.4)
- 发射功率:+8dBm
- 功耗:接收模式 5.4mA,发射模式 (0dBm) 5.9mA
- 外设接口:SPI、I2C、UART、ADC、PWM、GPIO 等
- 安全特性:AES-128/256 硬件加密、真随机数生成器
1.2 硬件架构详解
1.2.1 处理器与内存子系统
nRF52840 的核心是 ARM Cortex-M4F 处理器,提供高达 64MHz 的运行频率和浮点运算能力。内存方面,1MB 的 Flash 可存储应用程序和数据,256KB 的 RAM 支持复杂应用的运行。
// 内存布局示例
#define FLASH_START_ADDRESS 0x00000000
#define FLASH_SIZE 0x100000 // 1MB
#define RAM_START_ADDRESS 0x20000000
#define RAM_SIZE 0x40000 // 256KB
// 访问外设寄存器示例
#define NRF_UART0_BASE 0x40002000
#define NRF_UART0 ((NRF_UART_Type*)NRF_UART0_BASE)
typedef struct {
__IO uint32_t STARTRX;
__IO uint32_t STOPRX;
__IO uint32_t STARTTX;
__IO uint32_t STOPTX;
__IO uint32_t RESERVED0[3];
__IO uint32_t SUSPEND;
__IO uint32_t RESERVED1[56];
__IO uint32_t INTEN;
__IO uint32_t INTENSET;
__IO uint32_t INTENCLR;
__IO uint32_t RESERVED2[93];
__IO uint32_t ERRORSRC;
__IO uint32_t RESERVED3[31];
__IO uint32_t ENABLE;
__IO uint32_t RESERVED4;
__IO uint32_t PSELRTS;
__IO uint32_t PSELTXD;
__IO uint32_t PSELCTS;
__IO uint32_t PSELRXD;
__IO uint32_t RXD;
__IO uint32_t TXD;
__IO uint32_t RESERVED5[1];
__IO uint32_t BAUDRATE;
__IO uint32_t RESERVED6[17];
__IO uint32_t CONFIG;
} NRF_UART_Type;
1.2.2 无线收发器
nRF52840 的无线收发器支持多种无线协议,通过协议栈软件进行配置。它具有出色的接收灵敏度和发射功率,可实现长距离通信和低功耗操作的平衡。
1.2.3 外设接口
芯片提供了丰富的外设接口,满足不同应用场景的需求:
- UART:用于串口通信,支持硬件流控制
- SPI:高速同步串行接口,支持主从模式
- I2C:支持标准、快速和高速模式的两线串行接口
- ADC:12 位精度,最多支持 8 个输入通道
- PWM:支持多个通道的脉宽调制输出
- GPIO:最多支持 32 个通用输入输出引脚
1.3 低功耗设计
nRF52840 在低功耗设计方面表现出色,支持多种电源模式:
- 运行模式:处理器和外设正常工作
- 就绪模式:处理器暂停,但保留上下文和 RAM 内容
- 休眠模式:处理器和大部分外设停止工作,RAM 内容保留
- 关机模式:所有功能停止,RAM 内容丢失
// 低功耗模式切换示例
#include "nrf_pwr_mgmt.h"
// 初始化电源管理
void power_management_init(void)
{
ret_code_t err_code;
err_code = nrf_pwr_mgmt_init();
APP_ERROR_CHECK(err_code);
}
// 进入低功耗休眠模式
void enter_sleep_mode(void)
{
// 准备进入休眠
nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
}
二、开发环境搭建
2.1 硬件准备
- nRF52840 开发板(如 nRF52840 DK、nRF52840 Dongle)
- USB 数据线
- 调试器(如 SEGGER J-Link)
- 开发主机(Windows、Linux 或 macOS)
2.2 软件开发环境
2.2.1 工具链安装
- GNU Arm Embedded Toolchain:用于编译 C/C++ 代码
- SEGGER J-Link Software:用于调试和烧录程序
- nRF Command Line Tools:Nordic 提供的命令行工具集
- 集成开发环境 (IDE):可选择 VS Code、CLion 或 Segger Embedded Studio
2.2.2 nRF Connect SDK 安装
nRF Connect SDK 是 Nordic 提供的官方软件开发工具包,集成了 Zephyr RTOS、蓝牙协议栈和各种驱动程序。
# 安装nRF Connect SDK
# 1. 安装West工具
pip3 install west
# 2. 创建工作目录并初始化
mkdir nrf_sdk && cd nrf_sdk
west init -m https://github.com/nrfconnect/sdk-nrf --mr v2.4.0
west update
# 3. 安装工具链和依赖
cd nrf
./scripts/setup/setup.sh
2.2.3 VS Code 配置
在 VS Code 中安装以下扩展:
- C/C++
- Cortex-Debug
- nRF Connect for VS Code
配置 tasks.json 和 launch.json 文件以支持编译和调试:
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Build application",
"type": "shell",
"command": "west build -b nrf52840dk_nrf52840 .",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": "$gcc"
},
{
"label": "Flash application",
"type": "shell",
"command": "west flash",
"dependsOn": ["Build application"]
}
]
}
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug (J-Link)",
"cwd": "${workspaceFolder}",
"executable": "${workspaceFolder}/build/zephyr/zephyr.elf",
"request": "launch",
"type": "cortex-debug",
"servertype": "jlink",
"device": "nRF52840_xxAA",
"interface": "swd",
"svdFile": "${workspaceFolder}/nrfx/mdk/nrf52840.svd",
"runToMain": true,
"preLaunchTask": "Build application"
}
]
}
三、C/C++ 开发基础
3.1 基于 Zephyr RTOS 的开发
nRF Connect SDK 基于 Zephyr RTOS,提供了内核、驱动、服务和应用框架。下面是一个简单的 Zephyr 应用示例:
// main.c
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
// 获取LED0的设备树节点
#define LED0_NODE DT_ALIAS(led0)
// 检查设备树节点是否存在
#if !DT_NODE_HAS_STATUS(LED0_NODE, okay)
#error "Unsupported board: led0 devicetree alias is not defined"
#endif
// 获取LED0的GPIO端口和引脚号
#define LED0_GPIO_LABEL DT_GPIO_LABEL(LED0_NODE, gpios)
#define LED0_GPIO_PIN DT_GPIO_PIN(LED0_NODE, gpios)
#define LED0_GPIO_FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios)
void main(void)
{
const struct device *dev;
bool led_is_on = true;
int ret;
// 获取GPIO设备
dev = device_get_binding(LED0_GPIO_LABEL);
if (dev == NULL) {
return;
}
// 配置LED引脚为输出模式
ret = gpio_pin_configure(dev, LED0_GPIO_PIN, GPIO_OUTPUT_INACTIVE | LED0_GPIO_FLAGS);
if (ret < 0) {
return;
}
// 闪烁LED
while (1) {
ret = gpio_pin_set(dev, LED0_GPIO_PIN, (int)led_is_on);
if (ret < 0) {
return;
}
led_is_on = !led_is_on;
k_sleep(K_MSEC(500));
}
}
3.2 外设驱动开发
3.2.1 GPIO 驱动
// gpio_example.c
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
// 配置LED和按钮引脚
#define LED0_NODE DT_ALIAS(led0)
#define BTN0_NODE DT_ALIAS(sw0)
// 检查设备树节点是否存在
#if !DT_NODE_HAS_STATUS(LED0_NODE, okay)
#error "Unsupported board: led0 devicetree alias is not defined"
#endif
#if !DT_NODE_HAS_STATUS(BTN0_NODE, okay)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
// 获取LED和按钮的GPIO信息
#define LED0_GPIO_LABEL DT_GPIO_LABEL(LED0_NODE, gpios)
#define LED0_GPIO_PIN DT_GPIO_PIN(LED0_NODE, gpios)
#define LED0_GPIO_FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios)
#define BTN0_GPIO_LABEL DT_GPIO_LABEL(BTN0_NODE, gpios)
#define BTN0_GPIO_PIN DT_GPIO_PIN(BTN0_NODE, gpios)
#define BTN0_GPIO_FLAGS (GPIO_INPUT | DT_GPIO_FLAGS(BTN0_NODE, gpios))
// 按钮按下回调函数
static void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
static bool led_state = false;
const struct device *led_dev = device_get_binding(LED0_GPIO_LABEL);
// 切换LED状态
led_state = !led_state;
gpio_pin_set(led_dev, LED0_GPIO_PIN, led_state);
}
// 按钮回调结构体
static struct gpio_callback button_cb_data;
void main(void)
{
const struct device *led_dev, *btn_dev;
int ret;
// 获取LED和按钮设备
led_dev = device_get_binding(LED0_GPIO_LABEL);
btn_dev = device_get_binding(BTN0_GPIO_LABEL);
if (led_dev == NULL || btn_dev == NULL) {
return;
}
// 配置LED引脚为输出
ret = gpio_pin_configure(led_dev, LED0_GPIO_PIN, GPIO_OUTPUT_INACTIVE | LED0_GPIO_FLAGS);
if (ret < 0) {
return;
}
// 配置按钮引脚为输入并启用中断
ret = gpio_pin_configure(btn_dev, BTN0_GPIO_PIN, BTN0_GPIO_FLAGS);
if (ret < 0) {
return;
}
// 设置按钮中断回调
gpio_init_callback(&button_cb_data, button_pressed, BIT(BTN0_GPIO_PIN));
ret = gpio_add_callback(btn_dev, &button_cb_data);
if (ret < 0) {
return;
}
// 启用按钮中断
ret = gpio_pin_interrupt_configure(btn_dev, BTN0_GPIO_PIN, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
return;
}
// 主循环可以处理其他任务
while (1) {
// 低功耗等待
k_sleep(K_MSEC(100));
}
}
3.2.2 UART 驱动
// uart_example.c
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/uart.h>
// 获取UART设备树节点
#define UART0_NODE DT_CHOSEN(zephyr_console)
// 检查设备树节点是否存在
#if !DT_NODE_HAS_STATUS(UART0_NODE, okay)
#error "Unsupported board: uart0 devicetree node is not defined"
#endif
// UART缓冲区大小
#define UART_BUF_SIZE 256
// UART接收缓冲区和索引
static uint8_t rx_buf[UART_BUF_SIZE];
static uint32_t rx_buf_pos = 0;
// UART设备指针
static const struct device *uart_dev;
// UART接收回调函数
static void uart_cb(const struct device *dev, void *user_data)
{
uint8_t c;
// 检查是否有数据可读
while (uart_irq_update(uart_dev) && uart_irq_rx_ready(uart_dev)) {
// 读取一个字符
if (uart_fifo_read(uart_dev, &c, 1) == 1) {
// 回显字符
uart_poll_out(uart_dev, c);
// 处理换行符
if (c == '\r') {
uart_poll_out(uart_dev, '\n');
// 处理接收到的命令
if (rx_buf_pos > 0) {
// 在这里添加命令处理代码
uart_poll_out(uart_dev, '>');
uart_poll_out(uart_dev, ' ');
// 重置缓冲区
rx_buf_pos = 0;
}
} else if (c == '\b') {
// 处理退格键
if (rx_buf_pos > 0) {
rx_buf_pos--;
}
} else {
// 存储字符到缓冲区
if (rx_buf_pos < UART_BUF_SIZE - 1) {
rx_buf[rx_buf_pos++] = c;
}
}
}
}
}
void main(void)
{
int ret;
// 获取UART设备
uart_dev = device_get_binding(DT_LABEL(UART0_NODE));
if (uart_dev == NULL) {
return;
}
// 配置UART参数
struct uart_config cfg = {
.baudrate = 115200,
.parity = UART_CFG_PARITY_NONE,
.stop_bits = UART_CFG_STOP_BITS_1,
.data_bits = UART_CFG_DATA_BITS_8,
.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
};
ret = uart_configure(uart_dev, &cfg);
if (ret < 0) {
return;
}
// 设置UART回调
uart_irq_callback_user_data_set(uart_dev, uart_cb, NULL);
// 启用UART接收中断
uart_irq_rx_enable(uart_dev);
// 发送欢迎信息
const char *welcome = "\r\n=== nRF52840 UART Example ===\r\n> ";
uart_poll_out(uart_dev, welcome, strlen(welcome));
// 主循环
while (1) {
// 可以处理其他任务
k_sleep(K_MSEC(100));
}
}
四、蓝牙 / BLE 开发
4.1 BLE 协议栈概述
nRF52840 支持 Nordic 的 SoftDevice 协议栈,提供了完整的 BLE 协议实现。SoftDevice 是一个预编译的二进制文件,占用部分 Flash 和 RAM 资源,为应用程序提供 API 接口。
4.2 BLE 应用开发
4.2.1 BLE 外设模式示例
下面是一个简单的 BLE 外设示例,实现了一个温度传感器服务:
// ble_peripheral_example.c
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <sys/printk.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
// 温度服务UUID: 00001809-0000-1000-8000-00805f9b34fb
static struct bt_uuid_16 uuid_temp_svc = BT_UUID_INIT_16(0x1809);
// 温度测量特征UUID: 00002a1c-0000-1000-8000-00805f9b34fb
static struct bt_uuid_16 uuid_temp_meas = BT_UUID_INIT_16(0x2a1c);
// 温度值(单位: 0.01摄氏度)
static uint16_t temperature = 2500; // 25.00°C
// 温度特征值读取回调
static ssize_t read_temperature(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
// 准备温度数据: [标志位(1字节), 温度值(2字节)]
uint8_t temp_data[3] = {0x01, temperature & 0xFF, (temperature >> 8) & 0xFF};
return bt_gatt_attr_read(conn, attr, buf, len, offset, temp_data, sizeof(temp_data));
}
// 温度服务属性表
static struct bt_gatt_attr attrs[] = {
// 服务声明
BT_GATT_PRIMARY_SERVICE(&uuid_temp_svc),
// 温度测量特征
BT_GATT_CHARACTERISTIC(&uuid_temp_meas.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ,
read_temperature, NULL, NULL),
// 客户端特征配置描述符(用于启用通知)
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};
// GATT服务定义
static struct bt_gatt_service temp_svc = BT_GATT_SERVICE(attrs);
// 蓝牙初始化回调
static void bt_ready(int err)
{
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
// 注册温度服务
err = bt_gatt_service_register(&temp_svc);
if (err) {
printk("Failed to register temperature service (err %d)\n", err);
return;
}
// 配置并开始广播
struct bt_le_adv_param *param = BT_LE_ADV_PARAM_DEFAULT;
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x09, 0x18), // 温度服务UUID
};
err = bt_le_adv_start(param, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising started\n");
}
// 连接回调
static void conn_cb(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %d)\n", err);
} else {
printk("Connection established\n");
}
}
// 断开连接回调
static void disconn_cb(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %d)\n", reason);
// 重新开始广播
struct bt_le_adv_param *param = BT_LE_ADV_PARAM_DEFAULT;
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x09, 0x18), // 温度服务UUID
};
int err = bt_le_adv_start(param, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
}
}
// 连接回调结构体
static struct bt_conn_cb conn_callbacks = {
.connected = conn_cb,
.disconnected = disconn_cb,
};
void main(void)
{
int err;
// 注册连接回调
bt_conn_cb_register(&conn_callbacks);
// 初始化蓝牙栈
err = bt_enable(bt_ready);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
// 模拟温度变化
while (1) {
// 每5秒更新一次温度
k_sleep(K_SECONDS(5));
// 温度在23-27°C之间随机变化
temperature = 2300 + (rand() % 400);
printk("New temperature: %.2f°C\n", (float)temperature / 100.0);
}
}
4.2.2 BLE 中心模式示例
下面是一个简单的 BLE 中心模式示例,用于扫描并连接到温度传感器外设:
// ble_central_example.c
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <sys/printk.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
// 温度服务UUID
static struct bt_uuid_16 uuid_temp_svc = BT_UUID_INIT_16(0x1809);
// 温度测量特征UUID
static struct bt_uuid_16 uuid_temp_meas = BT_UUID_INIT_16(0x2a1c);
// 连接对象
static struct bt_conn *default_conn;
// 发现的温度特征句柄
static uint16_t temp_meas_handle;
static uint16_t temp_meas_cccd_handle;
// 扫描过滤回调
static bool scan_filter_match(struct bt_data *data, void *user_data)
{
// 检查广播数据中是否包含温度服务UUID
if (data->type == BT_DATA_UUID16_ALL || data->type == BT_DATA_UUID16_PARTIAL) {
const uint16_t *uuids = (const uint16_t *)data->data;
uint8_t count = data->len / sizeof(uint16_t);
for (uint8_t i = 0; i < count; i++) {
if (bt_le16_to_cpu(uuids[i]) == 0x1809) {
return true;
}
}
}
return false;
}
// 扫描回调
static void scan_cb(const struct bt_le_scan_result *result, void *user_data)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(result->addr, addr, sizeof(addr));
printk("Device found: %s (RSSI %d)\n", addr, result->rssi);
// 连接到发现的设备
if (result->rssi > -70) { // 只连接信号强度大于-70dBm的设备
struct bt_conn *conn;
int err;
printk("Connecting to %s\n", addr);
// 停止扫描
bt_le_scan_stop();
// 创建连接
err = bt_conn_le_create(result->addr, BT_CONN_LE_CREATE_CONN,
BT_LE_CONN_PARAM_DEFAULT, &conn);
if (err) {
printk("Connection failed (err %d)\n", err);
// 重新开始扫描
bt_le_scan_start(BT_LE_SCAN_PASSIVE, scan_cb);
return;
}
default_conn = conn;
}
}
// 发现服务回调
static uint8_t discover_svc(struct bt_gatt_disc_params *params,
const struct bt_gatt_attr *attr,
struct bt_gatt_service *svc)
{
if (!attr) {
printk("Service discovery complete\n");
return BT_GATT_DISCOVER_COMPLETE;
}
if (bt_uuid_cmp(attr->uuid, &uuid_temp_svc.uuid) == 0) {
printk("Temperature service found\n");
params->uuid = &uuid_temp_meas.uuid;
params->start_handle = attr->handle + 1;
params->type = BT_GATT_DISCOVER_CHARACTERISTIC;
return BT_GATT_DISCOVER_CONTINUE;
}
return BT_GATT_DISCOVER_CONTINUE;
}
// 发现特征回调
static uint8_t discover_chrc(struct bt_gatt_disc_params *params,
const struct bt_gatt_attr *attr,
struct bt_gatt_chrc *chrc)
{
if (!attr) {
printk("Characteristic discovery complete\n");
return BT_GATT_DISCOVER_COMPLETE;
}
if (bt_uuid_cmp(attr->uuid, &uuid_temp_meas.uuid) == 0) {
printk("Temperature measurement characteristic found\n");
temp_meas_handle = chrc->value_handle;
// 查找CCCD描述符
params->uuid = BT_UUID_GATT_CCC;
params->start_handle = chrc->value_handle + 1;
params->type = BT_GATT_DISCOVER_DESCRIPTOR;
return BT_GATT_DISCOVER_CONTINUE;
}
return BT_GATT_DISCOVER_CONTINUE;
}
// 发现描述符回调
static uint8_t discover_desc(struct bt_gatt_disc_params *params,
const struct bt_gatt_attr *attr,
void *user_data)
{
if (!attr) {
printk("Descriptor discovery complete\n");
// 启用温度通知
uint8_t value[2] = {BT_GATT_CCC_NOTIFY, 0x00};
int err = bt_gatt_write_without_response(default_conn,
temp_meas_cccd_handle,
value, sizeof(value),
false);
if (err) {
printk("Failed to enable notifications (err %d)\n", err);
} else {
printk("Notifications enabled\n");
}
return BT_GATT_DISCOVER_COMPLETE;
}
temp_meas_cccd_handle = attr->handle;
printk("CCCD descriptor found at handle 0x%04X\n", temp_meas_cccd_handle);
return BT_GATT_DISCOVER_CONTINUE;
}
// 温度特征值变化回调
static void temp_meas_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *data, uint16_t length,
uint16_t offset, uint8_t flags)
{
if (!data) {
printk("Notification disabled\n");
return;
}
// 解析温度数据
const uint8_t *temp_data = data;
uint16_t temp_value = (temp_data[2] << 8) | temp_data[1];
float temperature = (float)temp_value / 100.0;
printk("Temperature update: %.2f°C\n", temperature);
}
// 特征值回调结构体
static struct bt_gatt_cb gatt_callbacks = {
.notify = temp_meas_cb,
};
// 连接回调
static void conn_cb(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %d)\n", err);
default_conn = NULL;
// 重新开始扫描
bt_le_scan_start(BT_LE_SCAN_PASSIVE, scan_cb);
return;
}
if (conn != default_conn) {
return;
}
printk("Connection established\n");
// 注册GATT回调
bt_gatt_cb_register(&gatt_callbacks);
// 开始服务发现
static struct bt_gatt_disc_params disc_params;
memset(&disc_params, 0, sizeof(disc_params));
disc_params.uuid = &uuid_temp_svc.uuid;
disc_params.func = discover_svc;
disc_params.start_handle = BT_ATT_FIRST_HANDLE;
disc_params.end_handle = BT_ATT_LAST_HANDLE;
disc_params.type = BT_GATT_DISCOVER_PRIMARY;
int err_disc = bt_gatt_discover(default_conn, &disc_params);
if (err_disc) {
printk("Discovery failed (err %d)\n", err_disc);
}
}
// 断开连接回调
static void disconn_cb(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %d)\n", reason);
if (conn == default_conn) {
default_conn = NULL;
// 重新开始扫描
bt_le_scan_start(BT_LE_SCAN_PASSIVE, scan_cb);
}
}
// 连接回调结构体
static struct bt_conn_cb conn_callbacks = {
.connected = conn_cb,
.disconnected = disconn_cb,
};
void main(void)
{
int err;
// 注册连接回调
bt_conn_cb_register(&conn_callbacks);
// 初始化蓝牙栈
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
// 配置扫描参数
struct bt_le_scan_param scan_param = {
.type = BT_LE_SCAN_TYPE_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = 0x0010, // 10ms
.window = 0x0010, // 10ms
};
// 设置扫描过滤器
struct bt_le_scan_filters filters = {
.match_data = scan_filter_match,
};
// 开始扫描
err = bt_le_scan_start(&scan_param, scan_cb);
if (err) {
printk("Scanning failed to start (err %d)\n", err);
return;
}
printk("Scanning started\n");
// 主循环
while (1) {
k_sleep(K_SECONDS(1));
}
}
五、低功耗优化技术
5.1 电源管理模式
nRF52840 提供多种电源管理模式,开发人员可以根据应用需求选择合适的模式:
// 电源管理模式示例
#include <zephyr.h>
#include <power/reboot.h>
#include <power/power_state.h>
// 进入系统OFF模式
void enter_system_off_mode(void)
{
// 保存关键数据到非易失性存储器
// 进入系统OFF模式
sys_reboot(SYS_REBOOT_POWER_OFF);
}
// 进入低功耗模式
void enter_low_power_mode(void)
{
// 禁用不需要的外设
// ...
// 配置唤醒源
// ...
// 进入低功耗模式
enum power_state state = SYS_POWER_STATE_SLEEP_2;
int err = sys_soc_power_state_set(state);
if (err) {
printk("Failed to enter low power mode (err %d)\n", err);
}
}
5.2 外设功耗优化
// ADC低功耗配置示例
#include <zephyr.h>
#include <drivers/adc.h>
// ADC设备指针
static const struct device *adc_dev;
// 配置ADC为低功耗模式
void adc_low_power_config(void)
{
// 获取ADC设备
adc_dev = device_get_binding(DT_LABEL(DT_NODELABEL(adc0)));
if (adc_dev == NULL) {
printk("ADC device not found\n");
return;
}
// 配置ADC采样率和分辨率以降低功耗
struct adc_channel_cfg channel_cfg = {
.gain = ADC_GAIN_1_6,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = 0,
.input_positive = 0,
.differential = 0,
};
// 配置低功耗采样率
channel_cfg.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40);
int err = adc_channel_setup(adc_dev, &channel_cfg);
if (err) {
printk("Failed to setup ADC channel (err %d)\n", err);
}
}
5.3 软件优化策略
// 低功耗软件优化示例
#include <zephyr.h>
// 任务调度优化
void optimize_task_scheduling(void)
{
// 使用工作队列代替线程处理非紧急任务
static struct k_work_delayable work;
// 初始化工作
k_work_init_delayable(&work, work_handler);
// 安排延迟工作
k_work_schedule(&work, K_MSEC(1000));
}
// 外设按需启用/禁用
void enable_peripherals_on_demand(void)
{
// 仅在需要时启用外设
const struct device *uart_dev = device_get_binding("UART_0");
// 发送数据前启用UART
uart_irq_rx_enable(uart_dev);
// 数据传输完成后禁用UART
uart_irq_rx_disable(uart_dev);
}
六、实际应用案例
6.1 智能家居传感器节点
下面是一个智能家居温湿度传感器节点的完整示例,结合了前面介绍的各种技术:
// smart_home_sensor.c
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/gpio.h>
#include <drivers/adc.h>
#include <drivers/sensor.h>
#include <sys/printk.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
#include <power/reboot.h>
// 温湿度服务UUID
static struct bt_uuid_128 uuid_env_svc = BT_UUID_INIT_128(
0x00, 0x00, 0x18, 0x1A, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB);
// 温度特征UUID
static struct bt_uuid_128 uuid_temp_char = BT_UUID_INIT_128(
0x00, 0x00, 0x2A, 0x6E, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB);
// 湿度特征UUID
static struct bt_uuid_128 uuid_hum_char = BT_UUID_INIT_128(
0x00, 0x00, 0x2A, 0x6F, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB);
// 电池电量特征UUID
static struct bt_uuid_16 uuid_batt_char = BT_UUID_INIT_16(0x2A19);
// 温湿度传感器设备
static const struct device *sensor_dev;
// 电池ADC设备
static const struct device *adc_dev;
static struct adc_sequence adc_seq;
static uint16_t adc_buffer[1];
// 连接对象
static struct bt_conn *default_conn;
// 特征值
static uint8_t battery_level = 100;
static int16_t temperature = 2500; // 25.00°C
static uint16_t humidity = 5000; // 50.00%
// 温度特征值读取回调
static ssize_t read_temperature(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
uint8_t temp_data[2] = {temperature & 0xFF, (temperature >> 8) & 0xFF};
return bt_gatt_attr_read(conn, attr, buf, len, offset, temp_data, sizeof(temp_data));
}
// 湿度特征值读取回调
static ssize_t read_humidity(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
uint8_t hum_data[2] = {humidity & 0xFF, (humidity >> 8) & 0xFF};
return bt_gatt_attr_read(conn, attr, buf, len, offset, hum_data, sizeof(hum_data));
}
// 电池电量特征值读取回调
static ssize_t read_battery(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset, &battery_level, sizeof(battery_level));
}
// 环境服务属性表
static struct bt_gatt_attr attrs[] = {
// 服务声明
BT_GATT_PRIMARY_SERVICE(&uuid_env_svc.uuid),
// 温度特征
BT_GATT_CHARACTERISTIC(&uuid_temp_char.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ,
read_temperature, NULL, NULL),
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
// 湿度特征
BT_GATT_CHARACTERISTIC(&uuid_hum_char.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ,
read_humidity, NULL, NULL),
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
// 电池电量特征
BT_GATT_CHARACTERISTIC(&uuid_batt_char.uuid,
BT_GATT_CHRC_READ,
BT_GATT_PERM_READ,
read_battery, NULL, NULL),
};
// GATT服务定义
static struct bt_gatt_service env_svc = BT_GATT_SERVICE(attrs);
// 读取电池电量
static void read_battery_level(void)
{
int err;
// 执行ADC转换
err = adc_read(adc_dev, &adc_seq);
if (err) {
printk("ADC read failed (err %d)\n", err);
return;
}
// 计算电池电压 (假设使用1.2V参考,12位ADC)
uint32_t voltage = adc_buffer[0] * 1200 / 4096;
// 转换为百分比 (假设3.0V为0%,4.2V为100%)
battery_level = (voltage - 3000) * 100 / 1200;
// 限制在0-100%范围内
if (battery_level > 100) battery_level = 100;
if (battery_level < 0) battery_level = 0;
printk("Battery voltage: %dmV, level: %d%%\n", voltage, battery_level);
}
// 读取温湿度数据
static void read_sensor_data(void)
{
struct sensor_value temp, hum;
int err;
// 读取传感器数据
err = sensor_sample_fetch(sensor_dev);
if (err) {
printk("Failed to fetch sample (err %d)\n", err);
return;
}
err = sensor_channel_get(sensor_dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
if (err) {
printk("Failed to get temperature (err %d)\n", err);
return;
}
err = sensor_channel_get(sensor_dev, SENSOR_CHAN_HUMIDITY, &hum);
if (err) {
printk("Failed to get humidity (err %d)\n", err);
return;
}
// 转换为0.01单位
temperature = sensor_value_to_double(&temp) * 100;
humidity = sensor_value_to_double(&hum) * 100;
printk("Temperature: %.2f°C, Humidity: %.2f%%\n",
sensor_value_to_double(&temp),
sensor_value_to_double(&hum));
}
// 蓝牙初始化回调
static void bt_ready(int err)
{
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
// 注册环境服务
err = bt_gatt_service_register(&env_svc);
if (err) {
printk("Failed to register environment service (err %d)\n", err);
return;
}
// 配置并开始广播
struct bt_le_adv_param *param = BT_LE_ADV_PARAM_DEFAULT;
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x1A, 0x18, 0x00, 0x00), // 环境服务UUID
};
err = bt_le_adv_start(param, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising started\n");
}
// 连接回调
static void conn_cb(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %d)\n", err);
} else {
printk("Connection established\n");
default_conn = bt_conn_ref(conn);
}
}
// 断开连接回调
static void disconn_cb(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %d)\n", reason);
if (conn == default_conn) {
bt_conn_unref(default_conn);
default_conn = NULL;
// 重新开始广播
struct bt_le_adv_param *param = BT_LE_ADV_PARAM_DEFAULT;
struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0x1A, 0x18, 0x00, 0x00), // 环境服务UUID
};
int err = bt_le_adv_start(param, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
}
}
}
// 连接回调结构体
static struct bt_conn_cb conn_callbacks = {
.connected = conn_cb,
.disconnected = disconn_cb,
};
void main(void)
{
int err;
// 初始化传感器设备
sensor_dev = device_get_binding(DT_LABEL(DT_NODELABEL(sht31)));
if (sensor_dev == NULL) {
printk("Sensor device not found\n");
return;
}
// 初始化电池ADC
adc_dev = device_get_binding(DT_LABEL(DT_NODELABEL(adc0)));
if (adc_dev == NULL) {
printk("ADC device not found\n");
return;
}
// 配置ADC通道
struct adc_channel_cfg adc_channel = {
.gain = ADC_GAIN_1_6,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = 3,
.input_positive = 3,
.differential = 0,
};
err = adc_channel_setup(adc_dev, &adc_channel);
if (err) {
printk("Failed to setup ADC channel (err %d)\n", err);
return;
}
// 配置ADC序列
adc_seq.channels = BIT(3);
adc_seq.buffer = adc_buffer;
adc_seq.buffer_size = sizeof(adc_buffer);
adc_seq.resolution = 12;
// 注册连接回调
bt_conn_cb_register(&conn_callbacks);
// 初始化蓝牙栈
err = bt_enable(bt_ready);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
// 主循环
while (1) {
// 读取传感器数据
read_sensor_data();
// 读取电池电量
read_battery_level();
// 如果有连接,发送通知
if (default_conn) {
uint8_t temp_data[2] = {temperature & 0xFF, (temperature >> 8) & 0xFF};
uint8_t hum_data[2] = {humidity & 0xFF, (humidity >> 8) & 0xFF};
// 发送温度通知
bt_gatt_notify(default_conn, &attrs[2], temp_data, sizeof(temp_data));
// 发送湿度通知
bt_gatt_notify(default_conn, &attrs[5], hum_data, sizeof(hum_data));
}
// 进入低功耗模式
k_sleep(K_SECONDS(10)); // 每10秒采样一次
}
}
七、开发调试工具与技巧
7.1 调试工具
nRF52840 开发常用的调试工具包括:
- SEGGER J-Link:用于程序烧录和调试
- nRF Connect for Desktop:Nordic 提供的桌面应用,包含多种开发工具
- nRF Sniffer:用于蓝牙数据包捕获和分析
- Power Profiler Kit 2 (PPK2):用于功耗分析和优化
7.2 调试技巧
- 使用日志输出:在关键位置添加日志输出,帮助跟踪程序执行流程
- 断点调试:使用调试器设置断点,检查变量值和程序状态
- 低功耗调试:使用 PPK2 监测功耗,找出高功耗点
- 蓝牙协议分析:使用 nRF Sniffer 捕获和分析蓝牙数据包,诊断连接和通信问题
八、总结与展望
nRF52840 凭借其高性能、低功耗和丰富的蓝牙 / BLE 功能,成为物联网设备开发的理想选择。通过 C/C++ 语言结合 Nordic 的 SDK 和工具链,开发人员可以高效地实现各种物联网应用。
更多推荐
所有评论(0)