前言现在为止也开发了许多杰理TWS蓝牙耳机、音响项目SDK的案子在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习本章详细讲解杰理SDK开发中接入APP修改CID、VID、PID参数在我们进行杰理蓝牙耳机、蓝牙音响等蓝牙产品软件开发时往往根据现在的发展。客户们都需要加入APP功能甚至离线语音功能加入APP功能后APP就可以方便修改我们产品的按键功能、LED灯效、EQ等一下配置。本章就详细讲解如何在杰理SDK中开发中对接客户APP中的CID、VID、PID修改CID、VID、PID值使得与APP完好接入注意文中解释APP均是走杰理RCSP协议原理杰理JL蓝牙芯片SDK中用于处理BLE广播数据与APP通信的核心模块。它实现了BLE广播包ADV和扫描响应包Scan Response的组装以及连接后通过私有协议RCSP向APP提供设备详细信息的命令处理。通俗易懂的形容的话就是手机APP端要想连接上并控制杰理芯片蓝牙产品必须知道三件事情1、这个设备是谁通过BLE广播包Advertising表明身份2、设备叫什么、有什么能力通过扫描响应包Scan Response补充信息3、连接后如何持续读取/控制设备状态通过私有协议接口如rcsp(杰理私有协议)按需上报电量、按键、LED等在SDK中查找对应函数实现如上功能static int make_set_adv_data(void) 设置广播包ADV Datastatic int make_set_rsp_data(void) 设置扫描响应包RSP Datastatic u32 JL_opcode_get_adv_info(void *priv, u8 OpCode, u8 OpCode_SN, u8 *data, u16 len) APP连接后通过命令读取完整设备信息static update_adv_data(void) 在某种场景如连接后动态更新特征值重新组织广播核心数据什么是小端序、大端序小端序低有效字节存储在低地址先发送大端序高有效字节存储在低地址先发送例如 16 进制数 0x1234小端序内存0x34, 0x12大端序内存0x12, 0x34蓝牙广播为什么使用小端序BLE 协议栈的链路层、GAP 层由来已久多字节特性值、UUID 等通常以小端序传输所以杰理在广播包中沿用了这个传统使得标准手机 BLE 栈无需额外转换就能读取 Company ID、VID 等。RCSP协议RCSP或许是“杰理远程控制服务协议”是杰理自定义的 BLE 通信通道通过某个特征值收发命令。JL_opcode_get_adv_info就是这条通道上的一个命令处理函数用于 APP 主动查询设备信息。名词讲解CID Company ID说明蓝牙SIG分配给每个厂商的公司标识符杰理的公式ID是0x05D6。广播时必现包含正确的CID才能被APP识别为杰理设备VID Vendor ID说明厂商自定义的厂商标识用于区分不同客户或方案商。例如同一杰理芯片不同耳机品牌可分配不同VIDPID product ID说明厂商自定义的产品标识用于区分同一厂商下不同产品型号VER Version说明固件/协议的版本号通常用于标识当前广播协议的版本以便APP做兼容处理。在杰理SDK中修改CID、VID、PID参数广播包构造函数讲解static int make_set_adv_data(void)函数代码讲解static int make_set_adv_data(void) { u8 i; u8 *buf adv_data; // 广播数据第0字节长度 0x1E (30字节后续数据总长) buf[0] 0x1E; // 第1字节AD Type 0xFF (厂商自定义数据) buf[1] 0xFF; // 接下来2字节为 Company ID (CID)小端格式0x05D6 代表杰理科技 buf[2] 0xD6; // 低字节 buf[3] 0x05; // 高字节 // VID 和 PID从外部配置文件中读取如提供的 json 中的 ver_info u16 vid get_vid_pid_ver_from_cfg_file(GET_VID_FROM_EX_CFG); buf[4] vid 0xFF; // VID 低字节 buf[5] vid 8; // VID 高字节 u16 pid get_vid_pid_ver_from_cfg_file(GET_PID_FROM_EX_CFG); buf[6] pid 0xFF; // PID 低字节 buf[7] pid 8; // PID 高字节 // 协议版本 设备类型如 TWS 耳机及传输通道标记 #if (MUTIl_CHARGING_BOX_EN) buf[8] 0x21; // 某种标志比如带充电仓的 TWS #else buf[8] 0x20; // 普通 TWS if (RCSP_SPP get_defalut_bt_channel_sel()) { buf[8] | 2; // 若使用 SPP 通道则置位 } #endif // 填充 MAC 地址6字节倒序存入 buf[9]~buf[14] rcsp_adv_fill_mac_addr(buf[9]); // 连接标志是否已连接 buf[15] __this-connect_flag; // 左、右、充电仓电量各1字节最高位为充电状态低7位为电量百分比 buf[16] __this-bat_percent_L ? (((!!__this-bat_charge_L) 7) | (__this-bat_percent_L 0x7F)) : 0; buf[17] __this-bat_percent_R ? (((!!__this-bat_charge_R) 7) | (__this-bat_percent_R 0x7F)) : 0; buf[18] __this-bat_percent_C ? (((!!__this-bat_charge_C) 7) | (__this-bat_percent_C 0x7F)) : 0; memset(buf[19], 0x00, 4); // 保留4字节 buf[19] __this-seq_rand; // 随机序列号用于防重放 // 通道标记 #if (MUTIl_CHARGING_BOX_EN) buf[20] 1; #else if (RCSP_SPP get_defalut_bt_channel_sel()) { buf[20] 1; } #endif // 计算广播数据的哈希校验结果取单字节填充到后面的字段 u8 t_buf[16]; btcon_hash(buf[2], 16, buf[15], 4, t_buf); // hash 输入为 公司ID~MAC地址 前16字节 与 连接标志电量 4字节 for (i 0; i 8; i) { buf[23 i] t_buf[2 * i 1]; // 取哈希结果的奇数位字节 } __this-modify_flag 0; adv_data_len 31; // 将数据提交给BLE协议栈 ble_user_cmd_prepare(BLE_CMD_ADV_DATA, 2, 31, buf); r_printf(ADV data():); put_buf(buf, 31); return 0; }修改这里将VID和PID数据手动写入不从配置文件上读取VID和PID值如图所示修改 VID0X002F PID0X003D验证效果如果VID和PID数据输入正确那么APP上显示的图案就是我们真实的产品图VID和PID没用输入正确APP上显示的图片就是错误也可以下载NRF软件查看耳机端ADV发送的数据如图所示扫描响应包构造函数讲解static int make_set_rsp_data(void)static int make_set_rsp_data(void) { u8 offset 0; u8 *buf scan_rsp_data; u8 *edr_name bt_get_local_name(); // 获取本地蓝牙名称 // Flags 字段 (AD Type 0x01) #if TCFG_BLE_BRIDGE_EDR_ENALBE offset make_eir_packet_val(buf[offset], offset, HCI_EIR_DATATYPE_FLAGS, 0x0a, 1); #else offset make_eir_packet_val(buf[offset], offset, HCI_EIR_DATATYPE_FLAGS, 0x06, 1); #endif // 完整本地名称 (AD Type 0x09) offset make_eir_packet_data(buf[offset], offset, HCI_EIR_DATATYPE_COMPLETE_LOCAL_NAME, (void *)edr_name, strlen(edr_name)); scan_rsp_data_len 31; ble_user_cmd_prepare(BLE_CMD_RSP_DATA, 2, 31, buf); return 0; }原理扫描响应让APP在扫描结果中直接显示设备名称而不占用广播包宝贵的自定义数据容量。更新广播数据包函数static int update_adv_data(u8 *buf)static int update_adv_data(u8 *buf) // buf 指向 adv_data 的厂商自定义数据起始不含 Length 和 Type { u8 i; buf[1] 0xD6; // 重新填入 公司ID buf[0] 0x05; // 更新 VID/PID u16 vid get_vid_pid_ver_from_cfg_file(GET_VID_FROM_EX_CFG); buf[3] vid 0xFF; buf[2] vid 8; u16 pid get_vid_pid_ver_from_cfg_file(GET_PID_FROM_EX_CFG); buf[5] pid 0xFF; buf[4] pid 8; // 协议版本 buf[6] 0x20; if (RCSP_SPP get_defalut_bt_channel_sel()) { buf[6] | 2; } // 更新 MAC使用 swapX 交换字节序 swapX(bt_get_mac_addr(), buf[7], 6); // 连接标志 buf[13] __this-connect_flag; // 更新电量 buf[14] __this-bat_percent_L ? (((!!__this-bat_charge_L) 7) | (__this-bat_percent_L 0x7F)) : 0; buf[15] __this-bat_percent_R ? (((!!__this-bat_charge_R) 7) | (__this-bat_percent_R 0x7F)) : 0; buf[16] __this-bat_percent_C ? (((!!__this-bat_charge_C) 7) | (__this-bat_percent_C 0x7F)) : 0; buf[17] __this-seq_rand; return 0; }原理用于设备状态变化时如电量、连接状态更新已建立的广播包内容实现不重新构造整个包的情况下修改关键字节。看手机系统安卓手机连接后会跑这里来在这里需要我们将VID和PID参数也进行修改本人都是手动修改各位小伙伴也可以在配置文件中进行修改如图所示命令处理函数static u32 JL_opcode_get_adv_info(void *priv, u8 OpCode, u8 OpCode_SN, u8 *data, u16 len)static u32 JL_opcode_get_adv_info(void *priv, u8 OpCode, u8 OpCode_SN, u8 *data, u16 len) { u8 buf[256]; // 用于组装返回的数据包 u8 offset 0; // 当前写入位置 u32 ret 0; u32 mask READ_BIG_U32(data); // APP发来的请求掩码每一位代表一种要获取的属性 rcsp_printf(FEATURE MASK : %x\n, mask); // 如果请求电池信息 (位0) if (mask BIT(ATTR_TYPE_BAT_VALUE)) { rcsp_printf(ATTR_TYPE_BAT_VALUE\n); u8 bat[3]; bt_adv_get_bat(bat); // 获取电池信息比如电量、充电状态等3字节 // 按 LTV 添加属性Type ATTR_TYPE_BAT_VALUEValue bat长度3 offset add_one_attr(buf, sizeof(buf), offset, ATTR_TYPE_BAT_VALUE, bat, 3); } // 如果使能了EDR名称获取且请求了名称 (位1) #if RCSP_ADV_NAME_SET_ENABLE if (mask BIT(ATTR_TYPE_EDR_NAME)) { rcsp_printf(ATTR_TYPE_EDR_NAME\n); offset add_one_attr(buf, sizeof(buf), offset, ATTR_TYPE_EDR_NAME, (void *)_s_info.edr_name, strlen(_s_info.edr_name)); } #endif // 按键设置、LED设置、MIC模式、工作模式等逻辑同上 #if RCSP_ADV_KEY_SET_ENABLE if (mask BIT(ATTR_TYPE_KEY_SETTING)) { rcsp_printf(ATTR_TYPE_KEY_SETTING\n); offset add_one_attr(buf, sizeof(buf), offset, ATTR_TYPE_KEY_SETTING, (void *)_s_info.key_setting, sizeof(_s_info.key_setting)); } #endif // ... (LED, MIC, WORK_MODE 等类似省略) // 产品信息VID/PID/版本 #if RCSP_ADV_PRODUCT_MSG_ENABLE if (mask BIT(ATTR_TYPE_PRODUCT_MESSAGE)) { rcsp_printf(ATTR_TYPE_PRODUCT_MESSAGE\n); u16 vid get_vid_pid_ver_from_cfg_file(GET_VID_FROM_EX_CFG); u16 pid get_vid_pid_ver_from_cfg_file(GET_PID_FROM_EX_CFG); u8 tversion[6]; tversion[0] 0x05; tversion[1] 0xD6; // JL Company ID tversion[3] vid 0xFF; tversion[2] vid 8; // 注意这里是小端顺序先低字节后高字节 tversion[5] pid 0xFF; tversion[4] pid 8; offset add_one_attr(buf, sizeof(buf), offset, ATTR_TYPE_PRODUCT_MESSAGE, (void *)tversion, 6); } #endif rcsp_printf_buf(buf, offset); // 调试打印 // 通过 RCSP 协议发送成功响应数据为 buf长度为 offset ret JL_CMD_response_send(OpCode, JL_PRO_STATUS_SUCCESS, OpCode_SN, buf, offset); return ret; }此函数处理APP通过私有协议发来的“获取广播信息”命令。APP连接后可通过该命令按位掩码mask请求设备的各种属性如电池、EDR名称、按键设置等。底层使用LTV格式组织响应数据。辅助函数与初始化// 返回扫描响应数据指针和长度供BLE协议栈读取 u8 *ble_get_scan_rsp_ptr(u16 *len) { if (len) *len scan_rsp_data_len; return scan_rsp_data; } // 返回广播数据指针和长度 u8 *ble_get_adv_data_ptr(u16 *len) { if (len) *len adv_data_len; return adv_data; } // 返回GATT profile数据通常用于服务发现 u8 *ble_get_gatt_profile_data(u16 *len) { *len sizeof(profile_data); return (u8 *)profile_data; } // 广播参数及数据初始化开机时调用 static void advertisements_setup_init() { uint8_t adv_type 0; // 可连接、通用广播 uint8_t adv_channel 7; // 使用37、38、39三个信道 u16 adv_interval 0x30; // 广播间隔单位0.625ms此处约30ms // 设置广播参数 ble_user_cmd_prepare(BLE_CMD_ADV_PARAM, 3, adv_interval, adv_type, adv_channel); // 组装广告数据和扫描响应数据失败则报错 int ret 0; ret | make_set_adv_data(); ret | make_set_rsp_data(); if (ret) { puts(advertisements_setup_init fail !!!!!!\n); return; } }配置文件json.txt文件如图所示配置文件提供了各设备特定的参数ver_info定义了 VID(2字节)、PID(2字节)、VER(2字节)编译时转换为二进制数据函数中u16 get_vid_pid_ver_from_cfg_file(u8 type)从中读取。reset_io/pilot_lamp_io/power_io硬件IO配置与广播无关但影响按键、指示灯行为。link_key传统蓝牙配对信息的初始值用于回连。ver_info_ext可能包含 AuthKey、协议标识等用于APP校验设备的合法性。通过 json 可灵活适配不同产品无需修改代码。小伙伴也可以在这里进行修改这样本人习惯手动修改了总结整个广播体系遵循BLE规范开机/待连接组装31字节广播包含公司ID、型号、电量等和扫描响应设备名称启动广播。手机发现APP解析广播→识别杰理设备0x05D6→根据VID/PID区分型号→显示名称和基本信息。连接建立BLE连接后停止广播进入私有协议通信阶段。APP可通过u16 get_vid_pid_ver_from_cfg_file(u8 type)等命令按需查询详细属性电池、按键等采用LTV格式响应。状态变化电量、连接状态改变时调用static int update_adv_data(u8 *buf)更新广播内容确保未连接前其他设备也能看到最新状态。这种设计平衡了功耗不连接时只发最小必要信息和功能连接后提供完整交互是蓝牙音箱、TWS耳机等产品的典型实现方案。制作不易喜欢的小伙伴给个小赞赞喜欢我的小伙伴点个关注有不懂的地方和需要的资源随时问我哟