ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

沁恒 RISC-V 蓝牙应用中常用蓝牙参数的设定和修改
整理一下实际应用中蓝牙参数的设定和修改方式 ...... 矜辰所致 增加其他部分接口说明 ...... 2026/5/22 修正发射功率设置说明 ...... 2026/11/3

前言

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

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

相关博文:
与本文有关的博文比较多,所以大家可通过专栏目录自行查找对应知识点的文章介绍。
沁恒微蓝牙专栏目录:
【导航】沁恒微 RISC-V 蓝牙 入门教程目录 【快速跳转】
.
我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

目录

  • 前言
  • 一、 广播相关
    • 1.1 关闭/开启广播
    • 1.2 发射功率
    • 1.3 MAC 地址
    • 1.4 广播间隔
      • 1.4.1 一次广播时长说明
    • 1.5 广播内容
      • 1.5.1 修改广播名称
    • 1.6 广播类型
  • 二、连接相关
    • 2.1 连接参数
    • 2.2 MTU
    • 2.3 PHY
    • 2.4 断开当前连接
  • 三、扫描相关
    • 3.1 扫描时间
    • 3.2 扫描间隔
    • 3.3 扫描窗口
  • 四、其他接口
    • 4.1 协议栈剩余大小
    • 4.2 协议栈剩余缓存包
  • 结语

本文以 CH585 EVT 为例说明,仅供参考,同系列芯片大致相同,但是也不排除有某些特定型号的区别,具体以大家实际测试为准。
原理、流程、框架相关的东西都在以前博文中讲过了,不知道代码添加在哪里的朋友自行查阅上面相关博文。

一、 广播相关

1.1 关闭/开启广播

关闭广播:

uint8_tinitial_advertising_enable=FALSE;GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED,sizeof(uint8_t),&initial_advertising_enable);

开启广播:

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

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

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

此流程的示例如下:

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

1.2 发射功率

初始发功功率:

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

动态修改发射功率:

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

/* 1、需要先关闭广播,然后设置,最后在状态回调里面开启广播 2、#define TGAP_ADV_TX_POWER 25 //!< Indicates the maximum power level Range: -127 ≤ N ≤ +126 Units: dBm.Default 127(Host has no preference). 3、它是“上限”而不是“精确值”。控制器会在自己支持的功率档位里,选一个 ≤ N 的档位。所以写 4 不一定真输出 4.0 dBm,而是“不超过 4 dBm 的最高可用档”。 */intset_dbm=4GAP_SetParamValue(TGAP_ADV_TX_POWER,set_dbm);

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

1.3 MAC 地址

初始MAC地址:

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

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

动态修改 MAC 地址:

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

/* // 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_tGAP_ConfigDeviceAddr(uint8_taddrType,uint8_t*pStaticAddr);//示例:uint8_tmyAddr[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:

// What is the advertising interval when device is discoverable (units of 625us, 80=50ms)#defineDEFAULT_ADVERTISING_INTERVAL80

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

uint16_tadvInt=100;//想要修改的间隔(32~16348)// Set advertising intervalGAP_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[]

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

GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA,sizeof(scanRspData),scanRspData);GAPRole_SetParameter(GAPROLE_ADVERT_DATA,sizeof(advertData),advertData);

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

/* 参数1:任务 ID 参数2:广播包类型:确定是修改广播数据还是扫描响应数据 TRUE - advertisement data, FALSE - scan response data 参数3:数据长度 参数4:指向广播内容缓冲区的指针(要修改成的数据内容) */bStatus_tGAP_UpdateAdvertisingData(uint8_ttaskID,uint8_tadType,uint16_tdataLen,uint8_t*pAdvertData);

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

1.5.1 修改广播名称

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

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);}

测试图:

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

如下:

// GAP GATT Attributesstaticuint8_tattDeviceName[GAP_DEVICE_NAME_LEN]="Simple Peripheral";// Set the GAP CharacteristicsGGS_SetParameter(GGS_DEVICE_NAME_ATT,GAP_DEVICE_NAME_LEN,attDeviceName);

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

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

1.6 广播类型

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

// GAP_ADVERTISEMENT_TYPE_DEFINES GAP Advertising Event Types#defineGAP_ADTYPE_ADV_IND0x00//!< Connectable undirected event typet#defineGAP_ADTYPE_ADV_HDC_DIRECT_IND0x01//!< Connectable high duty cycle directed event type#defineGAP_ADTYPE_ADV_SCAN_IND0x02//!< Scannable undirected event type#defineGAP_ADTYPE_ADV_NONCONN_IND0x03//!< Non-Connectable undirected event type#defineGAP_ADTYPE_ADV_LDC_DIRECT_IND0x04//!< Connectable low duty cycle directed event type/* 依次为: 可连接、非定向广播 可被扫描 + 可被连接最常用 高占空比、可连接、定向广播 设备需要瞬间秒连时用 可扫描、非定向、不可连接 只发数据、不做连接的设备展 不可连接、不可扫描、纯广播 iBeacon / 传感器 低占空比、可连接、定向广播 低功耗定向连接 */uint8_tmy_test_adv_type=GAP_ADTYPE_ADV_SCAN_IND;GAPRole_SetParameter(GAPROLE_ADV_EVENT_TYPE,sizeof(uint8_t),&my_test_adv_type);

二、连接相关

2.1 连接参数

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

从机端

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

// Minimum connection interval (units of 1.25ms, 6=7.5ms)#defineDEFAULT_DESIRED_MIN_CONN_INTERVAL6// Maximum connection interval (units of 1.25ms, 100=125ms)#defineDEFAULT_DESIRED_MAX_CONN_INTERVAL100// Slave latency to use parameter update#defineDEFAULT_DESIRED_SLAVE_LATENCY0// Supervision timeout value (units of 10ms, 100=1s)#defineDEFAULT_DESIRED_CONN_TIMEOUT100

必须满足:

Supervision Timeout > (1 + Slave Latency) × Connection Interval × 2
超时时间 (ms)>(1+延迟次数)×连接间隔 (ms)×2

uint16_tdesired_min_interval=DEFAULT_DESIRED_MIN_CONN_INTERVAL;uint16_tdesired_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);

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

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

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里打印,连接间隔以最后一次协商结果为最终结果,如下图 :

主机端

// Minimum connection interval (units of 1.25ms)#defineDEFAULT_UPDATE_MIN_CONN_INTERVAL20// Maximum connection interval (units of 1.25ms)#defineDEFAULT_UPDATE_MAX_CONN_INTERVAL100// Slave latency to use parameter update#defineDEFAULT_UPDATE_SLAVE_LATENCY0// Supervision timeout value (units of 10ms)#defineDEFAULT_UPDATE_CONN_TIMEOUT600if(events&START_PARAM_UPDATE_EVT){// start connect parameter updateGAPRole_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。

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

// Update MTU,最大为 BLE_BUFF_MAX_LEN - 4attExchangeMTUReq_treq={.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 协商一样的方式。
1。 BLE_BUFF_MAX_LEN=516(>=516都可以)2.//...// Register receive scan request callbackGAPRole_BroadcasterSetCB(&Broadcaster_BroadcasterCBs);//增加代码GATT_InitClient();// Setup a delayed profile startuptmos_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_treq={.clientRxMTU=512,//这只是最大值};GATT_ExchangeMTU(peripheralConnList.connHandle,&req,Peripheral_TaskID);}else{PRINT("no connected...\r\n");}return(events^MY_ChangePower_EVT);}

测试结果:

程序中协商后获取 MTU 接口:

/** * @brief This function is used to get the MTU size of a connection. * * @param connHandle - connection handle. * * @return connection MTU size.<BR> */externuint16_tATT_GetMTU(uint16_tconnHandle);

2.3 PHY

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

// start phy updatePRINT("PHY Update %x...\n",GAPRole_UpdatePHY(peripheralConnList.connHandle,0,GAP_PHY_BIT_LE_2M,GAP_PHY_BIT_LE_2M,0));

2.4 断开当前连接

bStatus_tGAPRole_TerminateLink(uint16_tconnHandle);

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

三、扫描相关

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

3.1 扫描时间

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

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

3.2 扫描间隔

表示 多久扫一次:

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

3.3 扫描窗口

表示 每次扫多久:

/* Default 16. (n * 0.625 mSec) */GAP_SetParamValue(TGAP_DISC_SCAN_WIND,32);

扫描间隔不是扫描停止到下次扫描开始的时间啊,不要搞错了,他是两次扫描开始的间隔,所以由 扫描间隔需要>=扫描窗口,如下图:

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

快速发现设备(功耗大)

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

低功耗扫描(省电)

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

四、其他接口

4.1 协议栈剩余大小

PRINT("协议栈剩余: %d\n",tmos_memory_getlen());

测试(单位:字节):

4.2 协议栈剩余缓存包

在某些时候发送太快,协议栈允许一定的缓存,宏定义为BLE_BUFF_NUM- 控制器缓存的包数量( 默认:5 ),在程序允许期间可通过下面接口获取:

PRINT("剩余包: %d",LL_GetNumberOfUnAckPacket(centralConnHandle));//参数为句柄,从机主机注意区分修改一下

结语

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

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

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进