zephyr架构下扫描和解析Beacon数据

发布于:2025-05-02 ⋅ 阅读:(34) ⋅ 点赞:(0)

目录

概述

1.  软硬件环境

1.1 软件开发环境

1.2 硬件环境 

1.2.1 Beacon功能硬件环境

 1.2.2  Observer功能硬件环境

3 Beacon功能

3.1 广播数据定义

3.2 广播功能主函数

 3.3 编译运行代码

3 observer功能

3.1 使用的主要函数

3.2 功能实现

​3.4 编译和运行

3.5 Observer的源代码 


概述

本文主要介绍基于zephyr架构下的Bluetooth接口实现Beacon功能,同时实现了Observer用于解析Beacon的数据。

1.  软硬件环境

1.1 软件开发环境

nordic提供了基于zephyr平台sdk, 其提供了大量的demo可供开发者参考和使用,同时nordi还提供一个集成的软件库工具,方便开发者安装相应的SDK和编译工具链。集成环境同时包含了其他的一些软件,非常便于进行项目开发。

软件工具 功能 版本信息
nRF Connect SDK  nordic提供基于zephyr的代码库 v2.9.0 
nRF Connect SDK Toolchain 代码编译工具 v2.9.1
VS-CODE 集成开发环境 v1.99.3 
nRF Connect for Desktop nordic集成工具链 v5.1.0
nRF Connect 手机App

手机App下载地址:

https://nav.nordicsemi.com/search?query=nRF%20Connect

1.2 硬件环境 

1.2.1 Beacon功能硬件环境

本案例是在nRF52832开发板(nRF52-DK)上实现的,该开发板nRF52832的主要特点如下:

1)板载j-link调试接口

2)引出所有 IO接口,用户可根据实际应用,外载其他设备

3)支持4个LED

4)支持4路Key接口

5)板载UART调试接口,方便打印调试信息

 1.2.2  Observer功能硬件环境

使用nRF52840-DK开发作为Observer功能的硬件环境,该开发板的主要特点如下:

1)开发板上使用一颗nRF52840芯片

2)板载j-link调试接口

3)引出所有 IO接口,用户可根据实际应用,外载其他设备

4)支持4个LED

5)支持4路Key接口

6)板载UART调试接口,方便打印调试信息

3 Beacon功能

3.1 广播数据定义

 基于nrf52832-DK实现Beacon功能,其主要实现周期广播数据,其数据格式定义如下:

 定义广播的数据结构体:

3.2 广播功能主函数

代码102行: 调用bt_le_adv_start开始广播,

广播的类型为: BT_LE_ADV_NCONN_IDENTITY

代码116行: 获取当前Bluetooth的MAC

完整的程序代码如下:

/* main.c - Application main entry point */

/*
 * Copyright (c) 2015-2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/types.h>
#include <stddef.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/uuid.h>

 #include <zephyr/bluetooth/hci.h>


#if 0
#define DEVICE_NAME CONFIG_BT_DEVICE_NAME
#define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1)

static const struct bt_data ad[] = 
{
	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
	BT_DATA_BYTES(BT_DATA_SVC_DATA16,
		          0xaa, 0xfe,       /* Eddystone UUID */
		          0x10,             /* Eddystone-URL frame type */
		          0x00,             /* Calibrated Tx power at 0m */
		          0x00,             /* URL Scheme Prefix http://www. */
		          'z', 'e', 'p', 'h', 'y', 'r',
		          'p', 'r', 'o', 'j', 'e', 'c', 't',
		           0x08) /* .org */
};

/* Set Scan Response data */
static const struct bt_data sd[] = {
	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};

#else


static uint8_t user_manufacturer_data[] = 
{
    0x59, 0x20,	                             //  Company ID

    0xAA, 0x03, 0x11, 0x22, 0x33, 0x44,       // Mac address
    0x2a,                                     // status of device 
    0X21,                                     // voltage 
    0x00, 0x00, 0X10, 0,                      // steps of device
    
    0X20, 0X10,                               // Date time | Temperature 
    0x32                                      // POWER
};

/**
 *  blue tooth device name 
 */
#define DEVICE_USER_NAME        "BEA"
#define DEVICE_NAME_LEN	       (sizeof(DEVICE_USER_NAME) - 1)
#define BT_UUID_USER_VAL       BT_UUID_128_ENCODE(0x25b556c5, 0xf081, 0x4506, 0x1bb7, 0x608eb1234345)


static const struct bt_data ad[] = 
{
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    // MANUFACTURER 
    BT_DATA(BT_DATA_MANUFACTURER_DATA, user_manufacturer_data, sizeof(user_manufacturer_data)),
    // DEVICE NAME 
    BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_USER_NAME, DEVICE_NAME_LEN),
};


/* Set Scan Response data */
static const struct bt_data sd[] = 
{
    BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_USER_VAL),
};  

#endif 

void ble_interface_test( void );

static void bt_ready(int err)
{
	char addr_s[BT_ADDR_LE_STR_LEN];
	bt_addr_le_t addr = {0};
	size_t count = 1;

	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	/* Start advertising */
	err = bt_le_adv_start(BT_LE_ADV_NCONN_IDENTITY, ad, ARRAY_SIZE(ad),
			      sd, ARRAY_SIZE(sd));
	if (err) {
		printk("Advertising failed to start (err %d)\n", err);
		return;
	}


	/* For connectable advertising you would use
	 * bt_le_oob_get_local().  For non-connectable non-identity
	 * advertising an non-resolvable private address is used;
	 * there is no API to retrieve that.
	 */

	bt_id_get(&addr, &count);
	bt_addr_le_to_str(&addr, addr_s, sizeof(addr_s));

	printk("Beacon started, advertising as %s\n", addr_s);
}

int main(void)
{
	int err;

	printk("Starting Beacon Demo\n");

	/* Initialize the Bluetooth Subsystem */
	err = bt_enable(bt_ready);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
	}

    ble_interface_test();

    
	return 0;
}

/**
 *   get tx power 
 * /
/* Bits 7..0 : RADIO output power. */
// #define RADIO_TXPOWER_TXPOWER_Pos (0UL) /*!< Position of TXPOWER field. */
// #define RADIO_TXPOWER_TXPOWER_Msk (0xFFUL << RADIO_TXPOWER_TXPOWER_Pos) /*!< Bit mask of TXPOWER field. */
// #define RADIO_TXPOWER_TXPOWER_0dBm (0x00UL) /*!< 0 dBm */
// #define RADIO_TXPOWER_TXPOWER_Pos3dBm (0x03UL) /*!< +3 dBm */
// #define RADIO_TXPOWER_TXPOWER_Pos4dBm (0x04UL) /*!< +4 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg40dBm (0xD8UL) /*!< -40 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg20dBm (0xECUL) /*!< -20 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg16dBm (0xF0UL) /*!< -16 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg12dBm (0xF4UL) /*!< -12 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg8dBm (0xF8UL) /*!< -8 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg4dBm (0xFCUL) /*!< -4 dBm */
// #define RADIO_TXPOWER_TXPOWER_Neg30dBm (0xFFUL) /*!< Deprecated enumerator -  -40 dBm */
 
#include <hal/nrf_radio.h>

void get_radio_tx_power(void)
{
    // 获取当前无线电发射功率设置
    int8_t radio_power = nrf_radio_txpower_get(NRF_RADIO);
    
    // 转换为dBm值
    const char *power_str;
    switch(radio_power)
     {
        case RADIO_TXPOWER_TXPOWER_Pos: power_str = "+8 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Pos3dBm: power_str = "+3 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Pos4dBm: power_str = "+4 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg40dBm: power_str = "-40 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg20dBm: power_str = "-20 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg16dBm: power_str = "-163 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg12dBm: power_str = "-12 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg8dBm: power_str = "-8 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg4dBm: power_str = "-4 dBm"; break;
        case RADIO_TXPOWER_TXPOWER_Neg30dBm:  power_str = "-30 dBm"; break;
        // 其他功率等级...
        default: power_str = "Unknown";
    }
    
    printk("Radio TX power: %s, %d\n", power_str, radio_power);
}



void ble_interface_test( void )
{
    char addr_s[BT_ADDR_LE_STR_LEN];
    struct bt_le_ext_adv_info info;
    struct bt_le_ext_adv *adv;
    int8_t tx_power;
    int err;
    
    // 先获取扩展广播实例
    err = bt_le_ext_adv_get_info(adv, &info);
    if (!err) 
    {
        tx_power = info.tx_power;
        bt_addr_le_to_str(&info.addr, addr_s, sizeof(addr_s));

        printk("Advertising TX power: %d dBm\n", tx_power);
        printk("Advertising address:  %s \n", addr_s);
        printk("Advertising ID:       %d \n", info.id);
    } 
    else {
        printk("Failed to get advertising info (err %d)\n", err);
    }

    get_radio_tx_power();
}



/* End of this file */

 3.3 编译运行代码

调试终端看见如下log:

 使用手机App扫描设备,得到如下信息,说明Beacon功能以及实现了

3 observer功能

3.1 使用的主要函数

1) bt_le_scan_start

该函数是 Zephyr RTOS 蓝牙协议栈中的一个重要函数,用于启动低功耗蓝牙(BLE)扫描。

函数原型

int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb);

参数说明

  1. param: 指向扫描参数结构体的指针

    • 类型: struct bt_le_scan_param

    • 包含扫描类型、间隔、窗口等配置

  2. cb: 扫描回调函数

    • 类型: bt_le_scan_cb_t

    • 当发现设备时调用的回调函数

2) bt_le_scan_cb_register() 函数

bt_le_scan_cb_register() 是 Zephyr RTOS 蓝牙协议栈中的一个函数,用于注册或注销低功耗蓝牙(BLE)扫描回调函数。

函数原型

void bt_le_scan_cb_register(struct bt_le_scan_cb *cb);


参数说明

  • cb: 指向扫描回调结构体的指针

    • 类型: struct bt_le_scan_cb

    • 包含回调函数指针和其他相关信息

    • 如果传入 NULL,则表示注销所有已注册的回调

3.2 功能实现

1) device_found函数

扫描设备的回调函数,在该函数中根据BT_DATA_NAME_COMPLETE获取name数据,并解析对应的数据

2)回调函数和主要函数

代码127行:实现回调函数引用

代码142行: 注册回调函数

代码146行: 开始扫描设备,发现设备后,在device_found函数中解析Beacon数据

 3.4 编译和运行

编译代码,并运作nRF52840-DK板卡上,log中已经能够解析Beacon的数据包。

3.5 Observer的源代码 

/*
 * Copyright (c) 2022 Nordic Semiconductor ASA
 * Copyright (c) 2015-2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>

#define NAME_LEN 30

static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
			 struct net_buf_simple *ad)
{
    char addr_str[BT_ADDR_LE_STR_LEN];

    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
    //  printk("Device found: %s (RSSI %d), type %u, AD data len %u\n",
    //          addr_str, rssi, type, ad->len);

    int len =  ad->len;
    if( ad->data[len-3] == 'B' && ad->data[len-2] == 'E'  && ad->data[len-1] == 'A' )   
    {
        printk("\r\n"); 
        for(int i = 0; i < ad->len; i++ )
        {
            printk(" %02x ", ad->data[i]);
        }  
        printk("\r\n");     
    }        
}

#if defined(CONFIG_BT_EXT_ADV)
static bool data_cb(struct bt_data *data, void *user_data)
{
    char *name = user_data;
    uint8_t len;

    switch (data->type) 
    {
        case BT_DATA_NAME_SHORTENED:
        case BT_DATA_NAME_COMPLETE:
            len = MIN(data->data_len, NAME_LEN - 1);
            (void)memcpy(name, data->data, len);
            name[len] = '\0';
            // printk("step-1: BT_DATA_NAME_COMPLETE \r\n");
            // for(int i = 0; i < data->data_len; i++ )
            // {
            //     printk(" %02x ", data->data[i]);
            // }  
            // printk("\r\n");    
            return false;
        case BT_DATA_MANUFACTURER_DATA:    
            //  printk(" step-2: BT_DATA_MANUFACTURER_DATA \r\n");
            // for(int i = 0; i < data->data_len; i++ )
            // {
            //     printk(" %02x ", data->data[i]);
            // }   
            // printk("\r\n");         
            return true;
        default:
        	return true;
    }
}

static const char *phy2str(uint8_t phy)
{
	switch (phy) 
    {
        case BT_GAP_LE_PHY_NONE: return "No packets";
        case BT_GAP_LE_PHY_1M: return "LE 1M";
        case BT_GAP_LE_PHY_2M: return "LE 2M";
        case BT_GAP_LE_PHY_CODED: return "LE Coded";
        default: return "Unknown";
	}
}

static void scan_recv(const struct bt_le_scan_recv_info *info,
		              struct net_buf_simple *buf)
{
	char le_addr[BT_ADDR_LE_STR_LEN];
	char name[NAME_LEN];
	uint8_t data_status;
	uint16_t data_len;

	(void)memset(name, 0, sizeof(name));

	data_len = buf->len;
	bt_data_parse(buf, data_cb, name);

	data_status = BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS(info->adv_props);

	bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
    if( memcmp(name, "BEA",3) == 0 )
    {
    	// printk("[DEVICE]: %s, AD evt type %u, Tx Pwr: %i, RSSI %i "
    	//        "Data status: %u, AD data len: %u Name: %s "
    	//        "C:%u S:%u D:%u SR:%u E:%u Pri PHY: %s, Sec PHY: %s, "
    	//        "Interval: 0x%04x (%u ms), SID: %u\n",
    	//        le_addr, info->adv_type, info->tx_power, info->rssi,
    	//        data_status, data_len, name,
    	//        (info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_SCANNABLE) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_DIRECTED) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_SCAN_RESPONSE) != 0,
    	//        (info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0,
    	//        phy2str(info->primary_phy), phy2str(info->secondary_phy),
    	//        info->interval, info->interval * 5 / 4, info->sid);
        
        printk("\r\n");
        printk( " Name: %s, [DEVICE]: %s AD evt type %u Tx Pwr: %i, RSSI %i \r\n" , name, le_addr, info->adv_type, info->tx_power, info->rssi);  
    }

    // printk(" LOG DATA: \r\n");    

    // for(int i = 0; i < buf->len; i++ )
    // {
    //     printk(" %02x ", buf->data[i]);
    // }    
    
    // printk("\r\n");  
}

static struct bt_le_scan_cb scan_callbacks = {
	.recv = scan_recv,
};
#endif /* CONFIG_BT_EXT_ADV */

int observer_start(void)
{
	struct bt_le_scan_param scan_param = {
		.type       = BT_LE_SCAN_TYPE_PASSIVE,
		.options    = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
		.interval   = BT_GAP_SCAN_FAST_INTERVAL,
		.window     = BT_GAP_SCAN_FAST_WINDOW,
	};
	int err;

#if defined(CONFIG_BT_EXT_ADV)
	bt_le_scan_cb_register(&scan_callbacks);
	printk("Registered scan callbacks\n");
#endif /* CONFIG_BT_EXT_ADV */

	err = bt_le_scan_start(&scan_param, device_found);
	if (err) {
		printk("Start scanning failed (err %d)\n", err);
		return err;
	}
	printk("Started scanning...\n");

	return 0;
}