一、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 工具链安装
  1. GNU Arm Embedded Toolchain:用于编译 C/C++ 代码
  2. SEGGER J-Link Software:用于调试和烧录程序
  3. nRF Command Line Tools:Nordic 提供的命令行工具集
  4. 集成开发环境 (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 开发常用的调试工具包括:

  1. SEGGER J-Link:用于程序烧录和调试
  2. nRF Connect for Desktop:Nordic 提供的桌面应用,包含多种开发工具
  3. nRF Sniffer:用于蓝牙数据包捕获和分析
  4. Power Profiler Kit 2 (PPK2):用于功耗分析和优化

7.2 调试技巧

  1. 使用日志输出:在关键位置添加日志输出,帮助跟踪程序执行流程
  2. 断点调试:使用调试器设置断点,检查变量值和程序状态
  3. 低功耗调试:使用 PPK2 监测功耗,找出高功耗点
  4. 蓝牙协议分析:使用 nRF Sniffer 捕获和分析蓝牙数据包,诊断连接和通信问题

八、总结与展望

nRF52840 凭借其高性能、低功耗和丰富的蓝牙 / BLE 功能,成为物联网设备开发的理想选择。通过 C/C++ 语言结合 Nordic 的 SDK 和工具链,开发人员可以高效地实现各种物联网应用。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐