当前文档版本为 v1.0.1,您可以访问当前页面的 开发中 版本以获取最近可能的更新。

BLE App 开发指南

本文主要通过一些示例,介绍蓝牙应用开发过程中常用的方法以及可能遇到的问题。

1 基础指标

1.1 功耗

蓝牙在不同的工作模式下功耗如下表所示:

测试条件:

  • 基于例程 bluetooth\peripheral_hr, 配置文件 prj_lp_rcl.conf / prj_lp_xtl.conf

  • 发射功率:0 dBm,广播数据:33 Bytes,其它参数请参考项目文件

img

PAN1080/PAN1081 EVB 核心板功耗测试数据

img

PAN1080/PAN1081 EVB 核心板配置latency功耗测试数据

img

PAN1082 EVB 核心板功耗测试数据

img

PAN1083 EVB 核心板功耗测试数据

img

PAN3730 EVB 核心板功耗测试数据

注意:上述测试中,RCL 使用 1000ppm 的配置,实际项目中可根据方案实际情况,适当调整 ppm。 例如 1s interval 1000ppm 连接状态下的功耗相比于 2000ppm,平均电流大约可以减少20ua。

1.2 RSSI

PAN1080 在天线直连模式下测试,RSSI 精度在 ±2 dB。在蓝牙扫描模式下,使用板载天线测试不同距离下ibeacon的RSSI分布,测试数据如下表:

以下数据基于例程 bluetooth\central 进行接受空中ibeacon的测试 ,测试扫描使用的是channel37单通道(adv实际3通道发射功率会略有差异)。

类别

3米

5米

8米

10米

13米

15米

平均值(dBm)

-66.3253012

-67.98387097

-69.18181818

-73.05084746

-75.6

-76.76136364

总体方差

0.886422806

0.85179006

0.97404329

0.928343318

0.771669272

0.881654862

2 添加GATT服务

2.1 确认开发环境

参考ZDK 快速入门指南,确认软硬件开发环境,可以正常的编译、下载和调试ZDK提供的基础例程。

建议连接板载的 micro USB,通过串口工具监测 Log。

2.2 参考相关例程

蓝牙开发需要了解一些蓝牙协议相关的知识,可以参考蓝牙协议规范,网上也有很多协议的介绍,此处不作为重点。

当前SDK中提供了一些蓝牙相关的例程,涵盖了 beacon、central、peripheral、hid、mesh 等。

在进行蓝牙开发之前,建议先看一下相关的例程和文档,磨刀不误砍柴工,相信这些例程会对你的开发有所帮助。

image

蓝牙例程目录

2.3 新建一个蓝牙例程

在对蓝牙协议以及相关例程有一些了解之后,就可以尝试进行开发了,这里我们演示一个蓝牙收、发服务例程,具体服务描述如下:

Service or Characteristic

UUID

Properties

T/Rx Service

0x12345678-1234- 5678-1234-56789abcdef0

Data From Client to Server Characteristic

0x12345678-1234- 5678-1234-56789abcdef1

Write

Data From Server to Client Characteristic

0x12345678-1234- 5678-1234-56789abcdef2

Read, Notify

2.3.1 拷贝一份相似的工程

当然你也可以在原有例程上进行修改,初期并不建议开发者从头开始进行开发。

例程 peripheral_hr 演示了简单的健康温度计(Health Thermometer)服务,可以在建立连接后将采集温度并上报给主机,我们将以这个例程为基础,来进行修改和补充,首先拷贝一份 peripheral_hr 并重命名为 peripheral_trx

image

拷贝一份相似的工程

其中的 必要的文件为:

  • src\main.c: 主程序代码

  • CMakeList.txt: cmake 文件

  • prj.conf: 项目配置文件

其它几个文件都不是必须的:

  • prj_minimal.conf: 编译阶段可选的项目配置文件

  • README.rst: 项目说明文件

  • sample.yaml: 用于 zephyr tests 的文件

2.3.2 进行必要的调整

(1) 为了简单起见,我们删掉不必要的文件。

删掉 prj_minimal.confREADME.rstsample.yaml

image

只保留必要的文件

(2) 在 CMakeList.txt 文件中修改项目名称为 peripheral_trx

## SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(peripheral_trx)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE
  ${app_sources}
  )

zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth)

(3) 在 prj.conf 中修改项目的配置,关闭不必要的配置。

CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="Peripheral TRx"
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
  • CONFIG_BT=y: 使能蓝牙

  • CONFIG_BT_DEBUG_LOG=y: 使能蓝牙 log

  • CONFIG_BT_PERIPHERAL=y: 使能 peripheral

  • CONFIG_BT_DEVICE_NAME="Peripheral TRx": 修改蓝牙设备名称

(4) 在 main.c 中删掉不必要的代码,如原来代码中关于 BASHRSAUTH 相关的内容。

#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <sys/printk.h>
#include <sys/byteorder.h>
#include <zephyr.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>

static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
};

static void connected(struct bt_conn *conn, uint8_t err)
{
	if (err) {
		printk("Connection failed (err 0x%02x)\n", err);
	} else {
		printk("Connected\n");
	}
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	printk("Disconnected (reason 0x%02x)\n", reason);
}

BT_CONN_CB_DEFINE(conn_callbacks) = {
	.connected = connected,
	.disconnected = disconnected,
};

static void bt_ready(void)
{
	int err;

	printk("Bluetooth initialized\n");

	err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
	if (err) {
		printk("Advertising failed to start (err %d)\n", err);
		return;
	}

	printk("Advertising successfully started\n");
}

void main(void)
{
	int err;

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

	bt_ready();
}

上述代码比较简单,但有个点需要说明一下:

  • 你可以使用 BT_CONN_CB_DEFINE 来为连接事件注册回调函数,但却不需要对它进行额外的初始化;系统会将用这种方法定义的回调函数放到特定代码空间进行统一处理。

  • 初次接触的开发者可能会感到困惑,但它会很方便,特别是多个文件都需要处理这个回调函数时,只需要按照同样的方式处理即可。

(5) 重新编译和下载例程

此时我们重新编译 (rebuild),并下载。

下载成功,系统执行起来以后,串口工具将显示如下 Log:

*** Booting Zephyr OS version 2.7.0  ***
Bluetooth initialized
Advertising successfully started
[00:00:00.186,000] <inf> bt_hci_core: Identity: C4:3E:A1:88:FD:9A (random)
[00:00:00.186,000] <inf> bt_hci_core: HCI: version 5.1 (0x0a) revision 0x0003, manufacturer 0x07d1
[00:00:00.187,000] <inf> bt_hci_core: LMP: version 5.1 (0x0a) subver 0x0000

手机端可以通过 nRF Connect APP 进行搜索、连接蓝牙设备

注意,非HID相关的例程,不建议通过手机设置界面 查看和连接蓝牙设备,因为这可能涉及到一些手机系统的设备过滤和功能要求。

image

nRF Connect 搜索广播

image

nRF Connect 成功连接并显示Service信息

2.3.3 添加GATT服务

(1) 定义 UUID

/* TRx Service Variables */
#define BT_UUID_TRX_SERVICE_VAL \
	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)

static struct bt_uuid_128 trx_svc_uuid = BT_UUID_INIT_128(
	BT_UUID_TRX_SERVICE_VAL);

static struct bt_uuid_128 trx_c2s_uuid = BT_UUID_INIT_128(
	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1));

static struct bt_uuid_128 trx_s2c_uuid = BT_UUID_INIT_128(
	BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef2));

这里把 BT_UUID_TRX_SERVICE_VAL 单独列出来,是为了后面加到广播数据中,以暴露此服务。

(2) 定义 GATT 服务

可以使用 BT_GATT_SERVICE_DEFINE 来定义服务,它和前面介绍的 BT_CONN_CB_DEFINE 一样,将被系统放到特定代码空间,不需要额外初始化。

定义了三个 buffer 来存放 待收发的数据:trx_c2s_valuetrx_s2c_valuetrx_s2c_ntf_value

定义了三个接口,处理数据收发:write_trx_c2sread_trx_s2ctrx_s2c_ccc_cfg_changed

GATT 服务一般要包含:BT_GATT_PRIMARY_SERVICE, BT_GATT_CHARACTERISTIC,包含 notify / indicate 时,还需要用到 BT_GATT_CCC 来进行管理是否使能。

更多的示例,请参考例程 bluetooth\peripheral

#define TRX_MAX_LEN 20

static uint8_t trx_c2s_value[TRX_MAX_LEN + 1] = {
	'T', 'R', 'X', '_', 'C', '2', 'S'
};
static uint8_t trx_s2c_value[TRX_MAX_LEN + 1] = {
	'T', 'R', 'X', '_', 'S', '2', 'C'
};
static uint32_t trx_s2c_ntf_value;

static ssize_t write_trx_c2s(struct bt_conn *conn, const struct bt_gatt_attr *attr,
			 const void *buf, uint16_t len, uint16_t offset,
			 uint8_t flags)
{
	uint8_t *value = attr->user_data;

	if (offset + len > TRX_MAX_LEN) {
		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
	}

	memcpy(value + offset, buf, len);
	value[offset + len] = 0;

	printk("write (%d): ", len);
	for (uint8_t i = 0; i < len; i++)
	{
		printk("0x%02X ", value[i]);
	}
	printk("\n");

	return len;
}

static ssize_t read_trx_s2c(struct bt_conn *conn, const struct bt_gatt_attr *attr,
			void *buf, uint16_t len, uint16_t offset)
{
	const char *value = attr->user_data;

	return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
				 strlen(value));
}

static uint8_t simulate;

static void trx_s2c_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
	simulate = (value == BT_GATT_CCC_NOTIFY) ? 1 : 0;
}

/* TRx Primary Service Declaration */
BT_GATT_SERVICE_DEFINE(trx_svc,
	BT_GATT_PRIMARY_SERVICE(&trx_svc_uuid),
	BT_GATT_CHARACTERISTIC(&trx_c2s_uuid.uuid,
			       BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE,
			       NULL, write_trx_c2s, trx_c2s_value),
	BT_GATT_CHARACTERISTIC(&trx_s2c_uuid.uuid,
			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
			       BT_GATT_PERM_READ,
			       read_trx_s2c, NULL, trx_s2c_value),
	BT_GATT_CCC(trx_s2c_ccc_cfg_changed,
		    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

(3) 定义接口

这里定义了两个接口:

  • trx_init:初始化 notify attribute

  • trx_notify:上报数据给 client

static struct bt_gatt_attr *trx_ntf_attr;

static void trx_init(void)
{
	trx_ntf_attr = bt_gatt_find_by_uuid(trx_svc.attrs, trx_svc.attr_count, &trx_s2c_uuid.uuid);
}

static void trx_notify(void)
{
	if (simulate) {
		bt_gatt_notify(NULL, trx_ntf_attr, &trx_s2c_ntf_value, sizeof(trx_s2c_ntf_value));
		trx_s2c_ntf_value++;
	}
}

(4) 修改广播数据

添加了 TRx Service UUID,以便在广播数据中发现它。

static const 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, BT_UUID_TRX_SERVICE_VAL),
};

(5) 初始化 trx service 并 在连接建立且使能 notify 后上报数据

void main(void)
{
	int err;

	err = bt_enable(NULL);

	trx_init();

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

	bt_ready();

	while (1) {
		k_sleep(K_SECONDS(1));

		trx_notify();
	}
}

2.3.4 功能演示

(1) 扫描设备

image

扫描设备

(2) 建立连接

image

建立连接

串口工具将打印连接状态变化的信息:

Connected

(3) 写数据

image

写数据

串口工具将打印收到的数据:

write (5): 0x12 0x34 0x56 0x78 0x90

(4) 读数据

image

读数据

(5) Notify

image

Notify

image

Logs

3 蓝牙相关的接口说明

3.1 GAP

3.1.1 蓝牙地址使用

random addr

蓝牙初始化默认使用的是随机地址,随机地址每次蓝牙初始化都不一样。随机地址初始化如下:

static int set_random_address(const bt_addr_t *addr)
{
	struct net_buf *buf;
	int err;

	BT_DBG("%s", bt_addr_str(addr));

	/* Do nothing if we already have the right address */
	if (!bt_addr_cmp(addr, &bt_dev.random_addr.a)) {
		return 0;
	}

	buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_RANDOM_ADDRESS, sizeof(*addr));
	if (!buf) {
		return -ENOBUFS;
	}

	net_buf_add_mem(buf, addr, sizeof(*addr));

	err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_RANDOM_ADDRESS, buf, NULL);
	if (err) {
		return err;
	}

	bt_addr_copy(&bt_dev.random_addr.a, addr);
	bt_dev.random_addr.type = BT_ADDR_LE_RANDOM;
	return 0;
}

静态的随机地址,在bt_le_adv_start调用,地址的高字节的高2bit必须要强制写1。初始化如下所示:

bt_addr_le_t set_addr = {
	.type = BT_ADDR_LE_RANDOM,
	.a = { { 0x01, 0x02, 0x03, 0x04, 0x05, 0xC6 } },
};

bt_static_address_init(&set_addr.a);
public addr

public地址来源有三种模式:

  • 用户自定义

  • INFO区(PANLINK烧录)

  • EFUSE(芯片的ID)

用户自定义模式:

//在工程的"prj.conf"文件中使能CONFIG_BT_USER_DEFINED_PUBLIC_MAC

CONFIG_BT_USER_DEFINED_PUBLIC_MAC=y

//地址初始化

int err;
struct ble_cfg_t *ble_cfg = (struct ble_cfg_t *)param;

bt_set_user_public_mac(ble_cfg->ble_addr);

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

bt_ready(param);

INFO区和EFUSE:

//在工程的"prj.conf"文件中使能CONFIG_BT_SET_PUBLIC_MAC

CONFIG_BT_SET_PUBLIC_MAC=y

bt_addr_t pub_addr_info;
bt_addr_t pub_addr_efuse;

uint8_t addr_zero[6] = {0};
uint8_t addr_all_0xff[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

hwinfo_get_info_mac(pub_addr_info.val, 6);

hwinfo_get_efuse_mac(pub_addr_efuse.val, 6);

if (memcmp(pub_addr_efuse.val, addr_zero, 6) != 0) {
    bt_public_address_set(&pub_addr_efuse);
} else if (memcmp(pub_addr_info.val, addr_all_0xff, 6) != 0) {
    bt_public_address_set(&pub_addr_info);
}

当efuse和info区域同时存在地址值时,优先使用efuse区域中的内容,efuse区域不存在地址值就使用info区域中的。两个区域都不存在地址就可以使用用户自定义的地址。

3.2 GATT

16-bit或128-bit的UUID唯一决定了 Service/Characteristic.

  • 16-bit UUID 用于标准 Bluetooth Service/Characteristic

  • 128-bit UUID 用于厂商自定义的一些服务

3.2.1 Bluetooth Service

GAP Service

这是一个基础服务,默认开启,不建议用户修改。

如果确实需要修改,建议使用 CONFIG_BT_GAP_SERVICE=n 关掉这个服务,然后在应用层重新实现,这样可以避免代码更新导致的冲突。

源码路径:zephyr\subsys\bluetooth\host\gatt.c

Service or Characteristics

Kconfig

Description

ROM

SRAM

GAP Service

CONFIG_BT_GAP_SERVICE

Enable GAP service (Enabled by default)

Device Name Characteristic

N/A

Device name characteristic (Always enable)

-

-

Appearance Characteristic

N/A

Appearance characteristic (Always enable)

-

-

Central Address Resolution Characteristic

CONFIG_BT_CENTRAL
CONFIG_BT_PRIVACY

Depends on Central Role & Privacy Feature

-

-

Service Changed Characteristic

CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS

Configure peripheral preferred connection parameters

GATT Service

这是一个基础服务,默认开启,不建议用户修改。

如果确实需要修改,建议使用 CONFIG_BT_GATT_SERVICE=n 关掉这个服务,然后在应用层重新实现,这样可以避免代码更新导致的冲突。

源码路径:zephyr\subsys\bluetooth\host\gatt.c

Service or Characteristics

Kconfig

Description

ROM

SRAM

GATT Service

CONFIG_BT_GATT_SERVICE

Enable GATT service (Enabled by default)

Service Changed Characteristic

CONFIG_BT_GATT_SERVICE_CHANGED

GATT Service Changed support

Client Supported Features & Database Hash Characteristic

CONFIG_BT_GATT_CACHING

GATT Caching support

Server Supported Features Characteristic

CONFIG_BT_EATT

Enhanced ATT Bearers support [EXPERIMENTAL]

Battery Service

源码路径:zephyr\subsys\bluetooth\services\bas.c

Service or Characteristics

Kconfig

Description

ROM

SRAM

Battery Service

CONFIG_BT_BAS

Enable GATT Battery service

Battery Level Characteristic

N/A

BAS Battery level characteristic (Always enable)

-

-

APIs

  • bt_bas_get_battery_level

  • bt_bas_set_battery_level

需要包含头文件 #include <bluetooth/services/bas.h>

Device Information Service

源码路径:zephyr\subsys\bluetooth\services\dis.c

Service or Characteristics

Kconfig

Description

ROM

SRAM

Device Information Service

CONFIG_BT_DIS

Enable GATT Device Information service

CONFIG_BT_DIS_SETTINGS

Enable Settings usage in Device Information Service

CONFIG_BT_DIS_STR_MAX

Maximum size in bytes for DIS strings

-

-

Model Number String Characteristic

N/A

DIS Model number string characteristic (Always enable)

-

-

CONFIG_BT_DIS_MODEL

Model name

-

-

Manufacturer Name String Characteristic

N/A

DIS Manufacturer name string characteristic (Always enable)

-

-

CONFIG_BT_DIS_MANUF

Manufacturer name

-

-

Firmware Revision String Characteristic

CONFIG_BT_DIS_FW_REV

Enable DIS Firmware Revision characteristic

CONFIG_BT_DIS_FW_REV_STR

Firmware revision

-

-

Hardware Revision String Characteristic

CONFIG_BT_DIS_HW_REV

Enable DIS Hardware Revision characteristic

CONFIG_BT_DIS_HW_REV_STR

Hardware revision

-

-

PnP ID Characteristic

CONFIG_BT_DIS_PNP

Enable PnP_ID characteristic

CONFIG_BT_DIS_PNP_PID

Product ID

-

-

CONFIG_BT_DIS_PNP_VER

Product Version

-

-

CONFIG_BT_DIS_PNP_VID

Vendor ID

-

-

CONFIG_BT_DIS_PNP_VID_SRC

Vendor ID source

-

-

Serial Number String Characteristic

CONFIG_BT_DIS_SERIAL_NUMBER

Enable DIS Serial number characteristic

CONFIG_BT_DIS_SERIAL_NUMBER_STR

Serial Number

-

-

Software Revision String Characteristic

CONFIG_BT_DIS_SW_REV

Enable DIS Software Revision characteristic

CONFIG_BT_DIS_SW_REV_STR

Software revision

-

-

Heart Rate Service

源码路径:zephyr\subsys\bluetooth\services\hrs.c

Service or Characteristics

Kconfig

Description

ROM

SRAM

Heart Rate Service

CONFIG_BT_HRS

Enable GATT Heart Rate service

CONFIG_BT_HRS_DEFAULT_PERM_RW

Read and write allowed

CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN

Require encryption and authentication for access

CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT

Require encryption for access

Measurement Interval Characteristic

N/A

HRS Measurement interval characteristic (Always enable)

-

-

Body Sensor Location Characteristic

N/A

HRS Body sensor location characteristic (Always enable)

-

-

Control Point Characteristic

N/A

HRS Control Point characteristic (Always enable)

-

-

APIs

  • bt_hrs_notify

需要包含头文件 #include <bluetooth/services/hrs.h>

Object Transfer Service

源码路径:zephyr\subsys\bluetooth\services\ots\*

Service or Characteristics

Kconfig

Description

ROM

SRAM

Object Transfer Service

CONFIG_BT_OTS

Object Transfer Service (OTS) [EXPERIMENTAL]

CONFIG_BT_OTS_DIR_LIST_OBJ

Enables the Directory Listing Object

CONFIG_BT_OTS_DIR_LIST_OBJ_NAME

The object name of the Directory Listing Object

CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU

Size of RX MTU for Object Transfer Channel

CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU

Size of TX MTU for Object Transfer Channel

CONFIG_BT_OTS_MAX_INST_CNT

Maximum number of available OTS instances

CONFIG_BT_OTS_MAX_OBJ_CNT

Maximum number of objects that each service instance can store

CONFIG_BT_OTS_OACP_CREATE_SUPPORT

Support OACP Create Operation

CONFIG_BT_OTS_OACP_DELETE_SUPPORT

Support OACP Delete Operation

CONFIG_BT_OTS_OACP_PATCH_SUPPORT

Support patching of objects

CONFIG_BT_OTS_OACP_READ_SUPPORT

Support OACP Read Operation

CONFIG_BT_OTS_OACP_WRITE_SUPPORT

Support OACP Write Operation

CONFIG_BT_OTS_OBJ_MAX_NAME_LEN

Maximum object name length

CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT

Support object name write

CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT

Support OLCP Go To Operation

CONFIG_BT_OTS_SECONDARY_SVC

Register OTS as Secondary Service

OTS Feature Characteristic

N/A

OTS Feature characteristic (Always enable)

-

-

OTS Object Name Characteristic

N/A

OTS Object name characteristic (Always enable)

-

-

OTS Object Type Characteristic

N/A

OTS Object type characteristic (Always enable)

-

-

OTS Object Size Characteristic

N/A

OTS Object size characteristic (Always enable)

-

-

OTS Object ID Characteristic

N/A

OTS Object ID characteristic (Always enable)

-

-

OTS Object Properties Characteristic

N/A

OTS Object properties characteristic (Always enable)

-

-

OTS Object Action Control Point Characteristic

N/A

OTS Object action control point characteristic (Always enable)

-

-

OTS Object List Control Point Characteristic

N/A

OTS Object list control point characteristic (Always enable)

-

-

APIs

  • bt_ots_init

  • bt_ots_obj_add

  • bt_ots_obj_delete

  • bt_ots_svc_decl_get

  • bt_ots_free_instance_get

  • bt_ots_obj_id_to_str

需要包含头文件 #include <bluetooth/services/ots.h>

3.3 Notify

参考 bt_gatt_indicate 参数计算

int notify_send(const void *data, uint16_t len)
{
    return bt_gatt_notify(NULL, &cvs.attrs[1], data, len);
}

3.3 Indicate

static void indicate_cb(struct bt_conn *conn,
						struct bt_gatt_indicate_params *params, uint8_t err)
{
	printk("Indication %s\n", err != 0U ? "fail" : "success");
}

static void indicate_destroy(struct bt_gatt_indicate_params *params)
{
	printk("Indication complete\n");
}

int indicate_send(const void *data, uint16_t len)
{
    static struct bt_gatt_attr *ind_attr;
    static struct bt_gatt_indicate_params ind_params;

    vnd_ind_attr = bt_gatt_find_by_uuid(svc.attrs, svc.attr_count,
                            			&chr_uuid.uuid);

    ind_params.attr = ind_attr;
	ind_params.func = indicate_cb;
	ind_params.destroy = indicate_destroy;
	ind_params.data = data;
	ind_params.len  = len;

    return bt_gatt_indicate(NULL, &ind_params);
}

3.4 Attribute Index

我们在使用 bt_gatt_indicatebt_gatt_notify 时,需要传入一个参数 attr,这个参数与 GATT Service 的定义有关。下面介绍一下这个参数如何填写。

Service 是多个 Attribute 的集合,为了方便定义,我们使用宏进行组织服务,每个宏包含1或2个Attribute,如下:

描述

数量

BT_GATT_PRIMARY_SERVICE

Primary Service Declaration Macro.

1

BT_GATT_SECONDARY_SERVICE

Secondary Service Declaration Macro.

1

BT_GATT_INCLUDE_SERVICE

Include Service Declaration Macro.

1

BT_GATT_CHARACTERISTIC

Characteristic and Value Declaration Macro.

2

BT_GATT_CCC

Client Characteristic Configuration Declaration Macro.

1

BT_GATT_CEP

Characteristic Extended Properties Declaration Macro.

1

BT_GATT_CUD

Characteristic User Format Descriptor Declaration Macro.

1

BT_GATT_CPF

Characteristic Presentation Format Descriptor Declaration Macro.

1

BT_GATT_DESCRIPTOR

Descriptor Declaration Macro.

1

如下是 Battery Service 的定义,源码位置:\subsys\bluetooth\services\bas.c

BT_GATT_SERVICE_DEFINE(bas,
	BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS),
	BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL,
			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
			       BT_GATT_PERM_READ, read_blvl, NULL,
			       &battery_level),
	BT_GATT_CCC(blvl_ccc_cfg_changed,
		    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

其 Notify Characteristic Attribute index 为 1 (BT_GATT_PRIMARY_SERVICE)。

同样的,如下 hog_svc

BT_GATT_SERVICE_DEFINE(hog_svc,
	BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_INFO, BT_GATT_CHRC_READ,
			       BT_GATT_PERM_READ, read_info, NULL, &info),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT_MAP, BT_GATT_CHRC_READ,
			       BT_GATT_PERM_READ, read_report_map, NULL, NULL),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT,
			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
			       BT_GATT_PERM_READ_AUTHEN,
			       read_input_report, NULL, NULL),
	BT_GATT_CCC(input_ccc_changed,
		    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
	BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ,
			   read_report, NULL, &input),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_REPORT,
			       BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
			       BT_GATT_PERM_READ_AUTHEN,
			       read_input_report_consumer, NULL, NULL),
	BT_GATT_CCC(input_ccc_changed_consumer,
		    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
	BT_GATT_DESCRIPTOR(BT_UUID_HIDS_REPORT_REF, BT_GATT_PERM_READ,
			   read_report_consumer, NULL, &input_consumer),
	BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT,
			       BT_GATT_CHRC_WRITE_WITHOUT_RESP,
			       BT_GATT_PERM_WRITE,
			       NULL, write_ctrl_point, &ctrl_point),
);
  • 第一个 Notify Characteristic Attribute index 为 5

    BT_GATT_PRIMARY_SERVICE+BT_GATT_CHARACTERISTIC+BT_GATT_CHARACTERISTIC = 1+2+2

  • 第二个 Notify Characteristic Attribute index 为 9

    BT_GATT_PRIMARY_SERVICE+BT_GATT_CHARACTERISTIC+BT_GATT_CHARACTERISTIC + BT_GATT_CHARACTERISTIC + BT_GATT_CCC + BT_GATT_DESCRIPTOR = 1+2+2+2+1+1

3.5 INFO区 API说明

  1. ssize_t hwinfo_get_efuse_mac(uint8_t *buffer, size_t length);

说明:获取EFUSE区域mac地址,其中buffer为存放的变量地址,length必须为固定值6。

  1. ssize_t hwinfo_get_info_mac(uint8_t *buffer, size_t length);

说明:获取INFO区域mac地址,其中buffer为存放的变量地址,length必须为固定值6。INFO区mac地址为0x1B。

  1. ssize_t hwinfo_get_triple(uint8_t read_type, uint8_t *buffer, size_t length);

说明:获取INFO区域三元组变量,read_type为获取三元组变量的类型。其中BLE_INFO_TRIPLE_PID_TYPE和BLE_INFO_TRIPLE_PID_SIZE为PID的类型和长度,BLE_INFO_TRIPLE_MAC_TYPE和BLE_INFO_TRIPLE_MAC_SIZE为MAC的类型和长度,BLE_INFO_TRIPLE_KEY_TYPE和BLE_INFO_TRIPLE_KEY_SIZE为KEY的类型和长度。

  1. ssize_t hwinfo_read(size_t addr, uint8_t *buffer, size_t length);

说明:通用INFO区域读取函数,其中buffer为存放的变量地址,length为读取变量,不要超过4096。

3.6 ADV Filter 功能说明

当设备处于scan 的状态去扫描一些指定的广播包的时候,为了提高扫描的效率,PAN1080提供了一个硬件的接口去过滤掉空中其他的广播报文。例如mesh 入网的时候,只扫描和入网相关的广播报文。

3.6.1 过滤扫描广播类型

蓝牙spec 规定了很多广播类型,例如不可连接不可扫描的广播包,代码定义相关接口如下

#define PAN_SVC_CALL(opcode, p_data)
SVC_LL_ADV_TYPE_FILTER           			# opcode filter adv type

typedef struct {
	uint8_t enable;
	uint8_t type;
} adv_type_fi_t;						   # P_data related

#define FIL_ADV_IND_TYPE                    (1 << 0) # adv_type_fi_t.type related
#define FIL_ADV_NONCONN_IND_TYPE            (1 << 2) # adv_type_fi_t.type related
#define FIL_SCAN_RSP_TYPE                   (1 << 4) # adv_type_fi_t.type related
#define FIL_ADV_SCAN_IND_TYPE               (1 << 6) # adv_type_fi_t.type related
#define FIL_ADV_EXT_IND_OR_AUX_ADV_TYPE     (1 << 7) # adv_type_fi_t.type related

具体操作方法:

adv_type_fi_t fi;
fi.enable = 1;
fi.type = FIL_ADV_NONCONN_IND_TYPE;
PAN_SVC_CALL(SVC_LL_ADV_TYPE_FILTER, (void *)&fi);

3.6.2 过滤扫描广播数据

蓝牙spec 规定了广播数据的格式类型如下,PAN1080 可以过滤AD structure 1 的 length 和 AD Type,意味着只有符合我们指定的组合的广播包才会被接收,我们可以也可以添加多组。

adv数据结构

#define PAN_SVC_CALL(opcode, p_data)
SVC_LL_ADV_DATA_FILTER           			# opcode filter adv data

typedef struct {
	uint8_t enable;
	uint8_t index;                         # increase 1 if you has enabled a filter obj
	uint8_t len;
	uint8_t type;
} adv_date_filter_t;

具体操作方法:

adv_date_filter_t fi;
adv_date_filter_t.enable = 1;
adv_date_filter_t.index = 0;
adv_date_filter_t.len = 0; // ignor len
adv_date_filter_t.type = 0x2B //ble mesh message

PAN_SVC_CALL(SVC_LL_ADV_DATA_FILTER, (void *)&fi);

4 系统中断优先级相关

系统优先级是一个需要注意的问题,因为 108X 系列的芯片是单核芯片,既要跑蓝牙协议栈也要跑复杂的任务,因此合适的优先级策略将会直接影响整个系统功能和性能。不同的中断本身原理不同,组合起来的情况非常复杂,这儿分几个典型场景解释一下怎样使用。

4.1 应用没有高精度定时的任务

该任务表示实际的场景可能是简单蓝牙的任务,上层定时器允许几百us误差,或者中断允许几百us延迟响应。

CONFIG_BT_CTLR_EVENTS_PRIO=1      #蓝牙调度事件处理的优先级
CONFIG_BT_CTLR_IRQ_PRIO=0         #蓝牙中断的优先级
CONFIG_CORTEX_M_SYSTICK_PRIO=2    #操作系统调度的优先级

上述的配置也是系统软件的默认的配置。

优点:功耗最低

缺点:需要牺牲一点上层事件的实时性

4.2 应用需要高精度的定时任务

该任务表示应用需要一个非常高精度的定时,例如键盘方案中,需要定时器调节灯效,需要 10us 的精度定时响应效果

CONFIG_BT_CTLR_EVENTS_PRIO=2      #蓝牙调度事件处理的优先级
CONFIG_BT_CTLR_IRQ_PRIO=1         #蓝牙中断的优先级
CONFIG_CORTEX_M_SYSTICK_PRIO=3    #操作系统调度的优先级
CONFIG_BT_LL_PREEMPTED_TIME_FOR_ONE_MSEL=220 #蓝牙提前产生中断的时间
##伪代码
APP_TIME_IQR_PRIO=0          #应用程序开启的定时器

上述的配置原理是通过CONFIG_BT_LL_PREEMPTED_TIME_FOR_ONE_MSEL设置蓝牙中断提前的时间(< 500us),即使蓝牙优先级不够高,但是牺牲了时间换了精度

优点:上层应用可以获得最佳的响应性能

确定:功耗最高