沁恒微 RISC-V 蓝牙应用中常用蓝牙参数的设定和修改

复制代码
整理一下实际应用中蓝牙参数的设定和修改方式  ...... 矜辰所致

前言

之前的文章,我们讲了很多理论基础,例程分析,在实际应用中,根据不同的需求我们需要设定修改不同的蓝牙参数。

所以本文我们就来整理下 实际应用中 工程里 常用的参数设置与修改方式 。

相关博文:

与本文有关的博文比较多,所以大家可通过专栏目录自行查找对应知识点的文章介绍。

沁恒微蓝牙专栏目录:
【导航】沁恒微 RISC-V 蓝牙 入门教程目录 【快速跳转】

.

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

目录

  • 前言
  • [一、 广播相关](#一、 广播相关)
    • [1.1 关闭/开启广播](#1.1 关闭/开启广播)
    • [1.2 发射功率](#1.2 发射功率)
    • [1.3 MAC 地址](#1.3 MAC 地址)
    • [1.4 广播间隔](#1.4 广播间隔)
      • [1.4.1 一次广播时长说明](#1.4.1 一次广播时长说明)
    • [1.5 广播内容](#1.5 广播内容)
      • [1.5.1 修改广播名称](#1.5.1 修改广播名称)
    • [1.6 广播类型](#1.6 广播类型)
  • 二、连接相关
    • [2.1 连接参数](#2.1 连接参数)
    • [2.2 MTU](#2.2 MTU)
    • [2.3 PHY](#2.3 PHY)
    • [2.4 断开当前连接](#2.4 断开当前连接)
  • 三、扫描相关
    • [3.1 扫描时间](#3.1 扫描时间)
    • [3.2 扫描间隔](#3.2 扫描间隔)
    • [3.3 扫描窗口](#3.3 扫描窗口)
  • 结语

本文以 CH585 EVT 为例说明,仅供参考,同系列芯片大致相同,但是也不排除有某些特定型号的区别,具体以大家实际测试为准。

原理、流程、框架相关的东西都在以前博文中讲过了,不知道代码添加在哪里的朋友自行查阅上面相关博文。

一、 广播相关

1.1 关闭/开启广播

关闭广播:

c 复制代码
uint8_t  initial_advertising_enable = FALSE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable);

开启广播:

c 复制代码
/*
如果当前已经在广播模式, 不要再次开启广播了
如果不确定当前状态,可以操作前先获取当前广播状态
非 0 为广播
uint8_t adv_status;
GAPRole_GetParameter(GAPROLE_ADVERT_ENABLED, &adv_status );
*/
uint8_t  initial_advertising_enable = TRUE;
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable);

注意!对于与广播参数有关的动态修改,需要先关闭广播 -> 修改配置 -> 再开启广播。

这个 关闭/开启 广播步骤对应用实际效果来说,基本是无感的,并不会有明显的延时,在实际运行中需要修改广播参数的情况,必须按照 关闭广播 -> 修改配置 -> 再开启广播的流程。(首先,大部分是需要这个步骤的,即便存在有什么东西修改不需要这个步骤的按照这个来,也不会出错,反正我自己目前测试是不知道有哪个步骤不需要重新开启广播,所以实际使用按照这个步骤,准没错。)

此流程的示例如下:

不能直接在设置完以后的代码处直接开启广播,必须按照上面示例,在协议栈回复了状态以后才能再次开启广播!!!

1.2 发射功率

初始发功功率:

设置默认发射功率,如下操作(如果不设置,EVT 默认发射功率为:LL_TX_POWEER_0_DBM):

动态修改发射功率:

如果要在广播期间修改发射功率使用如下函数(此过程需要关闭广播 - 设置 - 再开启广播):

c 复制代码
/*#define LL_TX_POWEER_4_DBM              0x3B
第二个参数根据上面那个对应表格来即可
需要先关闭广播,然后设置,最后在状态回调里面开启广播
*/
GAP_SetParamValue(TGAP_ADV_TX_POWER,0x3B);

连接时候的发射功率修改,后期遇到了再来更新。

1.3 MAC 地址

初始MAC地址:

芯片上电默认使用芯片本身的公共地址。

如果想修改上电的 MAC 地址用于测试,需要定义一个宏BLE_MAC == TRUE ,便可以在代码中自己设定自定义地址,如下图:

动态修改 MAC 地址:

如果要动态修改 MAC 地址使用如下函数(此过程需要关闭广播 - 设置 - 再开启广播):

c 复制代码
/*
// GAP_ADDR_TYPE_DEFINES GAP Address Types
#define ADDRTYPE_PUBLIC                         0x00  //!< Use the BD_ADDR
#define ADDRTYPE_STATIC                         0x01  //!< Static address
#define ADDRTYPE_PRIVATE_NONRESOLVE             0x02  //!< Generate Non-Resolvable Private Address
#define ADDRTYPE_PRIVATE_RESOLVE                0x03  //!< Generate Resolvable Private Address
第一个参数选上面宏定义
需要先关闭广播,然后设置,最后在状态回调里面开启广播
*/
bStatus_t GAP_ConfigDeviceAddr( uint8_t addrType, uint8_t *pStaticAddr );
//示例:
uint8_t myAddr[6] = {0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x00};
GAP_ConfigDeviceAddr(ADDRTYPE_STATIC, myAddr);

详细说明可参考博主之前的博文:BLE 蓝牙 MAC 地址相关说明

1.4 广播间隔

BLE 广播间隔的标准范围是 20 ms ~ 10.24 s,设置范围为 32~16348(以 0.625 ms 单位)。

示例默认为 80:

c 复制代码
// What is the advertising interval when device is discoverable (units of 625us, 80=50ms)
#define DEFAULT_ADVERTISING_INTERVAL         80

修改方式如下(此过程需要关闭广播 - 设置 - 再开启广播):

c 复制代码
uint16_t advInt = 100;//想要修改的间隔(32~16348)

// Set advertising interval
GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, advInt);
GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, advInt);

减少广播间隔时,可以使得建立连接速度更快,但是这样也会使得芯片功耗增大。

1.4.1 一次广播时长说明

一次广播事件,就是在 3 个广播信(37/38/39)上各发一遍广播包,发完就停,直到下一个广播间隔再发一轮,不是持续发射。

如果有扫描响应,就回广播响应,一次广播事件流程如下:

信道 37:发广播 → 看有没有扫描请求 → 有就回扫描响应

信道 38:发广播 → 看有没有扫描请求 → 有就回扫描响应

信道 39:发广播 → 看有没有扫描请求 → 有就回扫描响应

单次广播事件时长 是很短的 ,一般时间最长 可连接广播+扫描响应 的广播时间估算下来 4ms 左右 ,甚至更少 。剩余的时间就可以用来休眠节约功耗 ,比如 20ms 广播间隔,剩余的 16ms 就可以休眠 。

1.5 广播内容

根据示例有两个数组,表示广播包和广播响应的内容 scanRspData[]advertData[]

修改方式如下(此过程需要关闭广播 - 设置 - 再开启广播):

c 复制代码
GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData);
GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData);

或者通过下面函数,直接生效:

c 复制代码
/*
参数1:任务 ID
参数2:广播包类型:确定是修改广播数据还是扫描响应数据 
TRUE - advertisement data, 
FALSE  - scan response data
参数3:数据长度
参数4:指向广播内容缓冲区的指针(要修改成的数据内容)
*/
bStatus_t GAP_UpdateAdvertisingData( uint8_t taskID, uint8_t adType, uint16_t dataLen, uint8_t *pAdvertData );

广播包格式需要遵循标准格式,可参考之前博文:BLE 蓝牙空中报文格式与解析(广播包)

1.5.1 修改广播名称

广播名称是属于上面广播内容的一部分的,我们使用上面方式二做个示例说明:

c 复制代码
if(events & MY_ChangePower_EVT)
{
    PRINT("Set Set...\r\n");
    GAP_UpdateAdvertisingData(Peripheral_TaskID,FALSE,sizeof(my_change_scanRspData), my_change_scanRspData);
    return (events ^ MY_ChangePower_EVT);
}

测试图:

额外说明,在工程里面涉及到名字的地方有两个,一个就是上面的广播包/扫描响应包中,还有一个是需要存在服务特征值中,链接以后通过特征值读取的。

如下:

c 复制代码
// GAP GATT Attributes
static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "Simple Peripheral";

// Set the GAP Characteristics
GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);

虽然这两个地方的名字没有明文规定必须一致,但是强烈建议实际产品中保持一致!!

苹果 IOS 中如果不一致,会存在一些问题。

1.6 广播类型

广播类型一般不会在使用中更改,在初始化的时候设置,例程并没有设置,默认广播类型为 GAP_ADTYPE_ADV_IND (可连接、非定向广播),设置其他广播类型,如下 :

c 复制代码
// GAP_ADVERTISEMENT_TYPE_DEFINES GAP Advertising Event Types
#define GAP_ADTYPE_ADV_IND                      0x00  //!< Connectable undirected event typet
#define GAP_ADTYPE_ADV_HDC_DIRECT_IND           0x01  //!< Connectable high duty cycle directed event type
#define GAP_ADTYPE_ADV_SCAN_IND                 0x02  //!< Scannable undirected event type
#define GAP_ADTYPE_ADV_NONCONN_IND              0x03  //!< Non-Connectable undirected event type
#define GAP_ADTYPE_ADV_LDC_DIRECT_IND           0x04  //!< Connectable low duty cycle directed event type
/*
依次为:
可连接、非定向广播  可被扫描 + 可被连接最常用
高占空比、可连接、定向广播 设备需要瞬间秒连时用
可扫描、非定向、不可连接  只发数据、不做连接的设备展
不可连接、不可扫描、纯广播  iBeacon / 传感器
低占空比、可连接、定向广播  低功耗定向连接
*/

uint8_t  my_test_adv_type = GAP_ADTYPE_ADV_SCAN_IND;
GAPRole_SetParameter(GAPROLE_ADV_EVENT_TYPE, sizeof(uint8_t), &my_test_adv_type);

二、连接相关

2.1 连接参数

连接参数相关详细说明可参考博文:BLE 蓝牙连接参数详解

从机端

下面是从机例程与连接参数有关的宏定义:

c 复制代码
// Minimum connection interval (units of 1.25ms, 6=7.5ms)
#define DEFAULT_DESIRED_MIN_CONN_INTERVAL    6

// Maximum connection interval (units of 1.25ms, 100=125ms)
#define DEFAULT_DESIRED_MAX_CONN_INTERVAL    100

// Slave latency to use parameter update
#define DEFAULT_DESIRED_SLAVE_LATENCY        0

// Supervision timeout value (units of 10ms, 100=1s)
#define DEFAULT_DESIRED_CONN_TIMEOUT         100

必须满足:

Supervision Timeout > (1 + Slave Latency) × Connection Interval × 2

超时时间 (ms)>(1+延迟次数)×连接间隔 (ms)×2

c 复制代码
uint16_t desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;
uint16_t desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;

GAPRole_SetParameter(GAPROLE_MIN_CONN_INTERVAL, sizeof(uint16_t), &desired_min_interval);
GAPRole_SetParameter(GAPROLE_MAX_CONN_INTERVAL, sizeof(uint16_t), &desired_max_interval);

这只是从机端希望的连接间隔,主机会在这个范围内,给一个合适的值(由主机决定),如果这个范围主机给不了,主机也会给从机一个连接间隔,从机不会拒绝 。

连接上以后从机还能发起协商请求:

c 复制代码
if(events & SBP_PARAM_UPDATE_EVT)
{
    // Send connect param update request
    // When the current connection parameters already meet the requirements for update, return 0x18(InvalidRange)
    GAPRole_PeripheralConnParamUpdateReq(peripheralConnList.connHandle,
                                         DEFAULT_DESIRED_MIN_CONN_INTERVAL,
                                         DEFAULT_DESIRED_MAX_CONN_INTERVAL,
                                         DEFAULT_DESIRED_SLAVE_LATENCY,
                                         DEFAULT_DESIRED_CONN_TIMEOUT,
                                         Peripheral_TaskID);

    return (events ^ SBP_PARAM_UPDATE_EVT);
}

不管协商几次,主机协商还是从机自己协商,协商的结果会在从机回调函数 peripheralParamUpdateCB 里打印,连接间隔以最后一次协商结果为最终结果,如下图 :

主机端

c 复制代码
// Minimum connection interval (units of 1.25ms)
#define DEFAULT_UPDATE_MIN_CONN_INTERVAL    20

// Maximum connection interval (units of 1.25ms)
#define DEFAULT_UPDATE_MAX_CONN_INTERVAL    100

// Slave latency to use parameter update
#define DEFAULT_UPDATE_SLAVE_LATENCY        0

// Supervision timeout value (units of 10ms)
#define DEFAULT_UPDATE_CONN_TIMEOUT         600

if(events & START_PARAM_UPDATE_EVT)
{
    // start connect parameter update
    GAPRole_UpdateLink(centralConnHandle,
                       DEFAULT_UPDATE_MIN_CONN_INTERVAL,
                       DEFAULT_UPDATE_MAX_CONN_INTERVAL,
                       DEFAULT_UPDATE_SLAVE_LATENCY,
                       DEFAULT_UPDATE_CONN_TIMEOUT);
    return (events ^ START_PARAM_UPDATE_EVT);
}

2.2 MTU

关于 MTU 交互, 一些理论知识请参考博主之前博文:沁恒微 CH585 芯片 Ble 传输速率测试说明 里面 MTU,BLE_BUFF_MAX_LEN 和 单包数据长度小节相关说明。

这里举几个例子,先看看最大的情况:

如果 BLE_BUFF_MAX_LEN = 516 字节

那么:MTU 最大为 512 字节

用户一包数据最大为 509 字节,可以定义一个 509 字节大小的数组用于发送

但是因为空中物理帧限制,一包空中数据最大 251 字节,减去 2 字节 LL header,减去 4 字节 L2CAP header,再减去 3 字节 ATT header,用户数据一帧最大发出 244 字节。

所以这种勤快它会分成 3 包 : 244 B + 244 B + 21 B 发送

对于适合单帧不分包的情况:

BLE_BUFF_MAX_LEN = 251

MTU 最大为 247

用户一包数据最大为 244 字节 (通过特征值或者通知数据交互)

对于默认的情况:

BLE_BUFF_MAX_LEN = 27

MTU 最大为 23

用户一包数据最大为 20

设置 MTU ,需要主从机均支持修改 MTU,对于应用来说,就是 主机从机的 BLE_BUFF_MAX_LEN 要足够,如果我们想协商 512 的 MTU ,那么需要 BLE_BUFF_MAX_LEN 至少为 516 。

从机端

首先是宏定义 BLE_BUFF_MAX_LEN

然后通过手机连接从机,手机端发起 MTU 交互,就能够成功:

主机端

主机端同样的,和从机一样的方式先定义 BLE_BUFF_MAX_LEN

然后再程序需要设置的地方手动调用如下代码:

c 复制代码
// Update MTU,最大为 BLE_BUFF_MAX_LEN - 4
attExchangeMTUReq_t req = {
    .clientRxMTU = BLE_BUFF_MAX_LEN - 4,//这只是最大值
};

GATT_ExchangeMTU(centralConnHandle, &req, centralTaskId);

从机主动交互 MTU

如果想要从机主动交互 MTU ,我们可以使用如下方式:

  1. 先定义 BLE_BUFF_MAX_LEN
  2. 从机初始化的地方 Peripheral_Init 加上 GATT_InitClient();
  3. 和主机示例手动调用 MTU 协商一样的方式。
c 复制代码
1。
BLE_BUFF_MAX_LEN=516(>=516 都可以)
2.
//...
// Register receive scan request callback
GAPRole_BroadcasterSetCB(&Broadcaster_BroadcasterCBs);
//增加代码
GATT_InitClient();
// Setup a delayed profile startup
tmos_set_event(Peripheral_TaskID, SBP_START_DEVICE_EVT);
//...
3.
if(events & MY_ChangePower_EVT)
{
    if(peripheralConnList.connHandle != GAP_CONNHANDLE_INIT)//如果存在连接状态,才设置
    {
        PRINT("Set Set...\r\n");
        attExchangeMTUReq_t req = {
            .clientRxMTU = 512,//这只是最大值
        };
        GATT_ExchangeMTU(peripheralConnList.connHandle, &req, Peripheral_TaskID);
    }
    else{
        PRINT("no connected...\r\n");
    }
    return (events ^ MY_ChangePower_EVT);
}

测试结果:

2.3 PHY

PHY 协商,主机和从机一样的函数:

c 复制代码
    // start phy update
PRINT("PHY Update %x...\n", GAPRole_UpdatePHY(peripheralConnList.connHandle, 0, 
                GAP_PHY_BIT_LE_2M, GAP_PHY_BIT_LE_2M, 0));

2.4 断开当前连接

c 复制代码
bStatus_t GAPRole_TerminateLink( uint16_t connHandle );

上面传入的是句柄,这个在主机从机示例里面可以找到,比如主机端使用的句柄变量是 centralConnHandle ,从机使用的句柄变量是 peripheralConnList.connHandle

三、扫描相关

扫描相关,是针对主机或者观察者角色而言的。

3.1 扫描时间

表示设备扫描持续时间,扫描多久后停止:

c 复制代码
// Scan duration in 0.625ms
#define DEFAULT_SCAN_DURATION               2400
/*
扫描持续时间
Default 10.24seconds.
Setting this parameter to 0 turns off the timeout
设置为0,永不超时
*/
GAP_SetParamValue( TGAP_DISC_SCAN, DEFAULT_SCAN_DURATION );

3.2 扫描间隔

表示 多久扫一次:

c 复制代码
/*
Default 16. (n * 0.625 mSec)
扫描间隔,需要>=扫描窗口
*/
GAP_SetParamValue(TGAP_DISC_SCAN_INT, 64);

3.3 扫描窗口

表示 每次扫多久:

c 复制代码
/*
Default 16. (n * 0.625 mSec)
*/
GAP_SetParamValue(TGAP_DISC_SCAN_WIND, 32);

上面 3 个参数是主机扫描的核心参数,举几个组合示例 :

快速发现设备(功耗大)

c 复制代码
TGAP_DISC_SCAN        = 0;       // 无限扫描
TGAP_DISC_SCAN_INT    = 10ms;    // 扫描间隔
TGAP_DISC_SCAN_WIND   = 10ms;    // 窗口 = 间隔 → 一直扫描

低功耗扫描(省电)

c 复制代码
TGAP_DISC_SCAN        = 5000ms;  // 扫5秒自动停
TGAP_DISC_SCAN_INT    = 100ms;   // 每100ms扫一次
TGAP_DISC_SCAN_WIND   = 10ms;    // 每次只扫10ms

结语

本文整理了一些蓝牙应用中需要用到的功能接口函数以供大家查阅。

在后期如果有更新或者新的一些说明,博主也会更新博文内容。

好了,本文就到这里。谢谢大家!

相关推荐
zhaoshuzhaoshu2 天前
BLE(蓝牙低功耗)连接过程详解
物联网·蓝牙·无线
wotaifuzao3 天前
从128-bit到16-bit:BLE UUID背后的带宽战争与架构设计
性能优化·蓝牙·uuid·低功耗蓝牙·架构设计·嵌入式开发·ble
嵌入式小企鹅9 天前
蓝牙学习系列(二):BLE协议栈解析
学习·蓝牙·ble·蓝牙协议栈·协议栈
wzfj123451 个月前
bt-l2cap 深入理解重点接口 l2c_link_check_send_pkts
蓝牙·bluetooth·bt
babytiger1 个月前
ble扫描相关的问题,蓝牙 MAC 是否可以确定厂商?
蓝牙·ble
whik11941 个月前
ESP32-C3-DevKitM-1开发板深度上手评测
wifi·嵌入式·esp32·arduino·蓝牙·开发板·乐鑫
Darkershadow1 个月前
蓝牙学习之发送 Mesh Provisioning Service advertising
学习·蓝牙·ble·mesh
byte轻骑兵2 个月前
从HCI报文透视LE Audio重连流程(3):音频流建立、同步与终止
音视频·蓝牙·le audio·cig/cis·广播音频
summerkissyou19872 个月前
android-蓝牙-广播启动-startAdvertising和startAdvertisingSet区别
android·蓝牙