手机APP的简介和开发流程
📎 原始文档:https://www.kdocs.cn/l/cum5StN3CwWL 手机APP的简介和开发流程
本文是关于手机APP功能的说明文档,会从APP应用的原理、示例、实例来介绍其开发流程,希望大家耐心看完。
- 初步了解手机APP应用
1.1 APP的应用场景
- 耳机 手机APP一般用于控制耳机的功能、修改音效,获取耳机的电量音量等信息并在APP界面显示出来,以及进行OTA升级。本文主要讲的是这种应用场景。

- 音箱 除可能实现跟耳机相同的功能之外,音箱可能还有灯效、SD卡U盘等功能,修改灯效,显示SD卡U盘内的歌曲列表,都是APP可以实现的功能。

- 穿戴 穿戴类常见的是智能手表,这种场景的APP的功能一般比较复杂,手机APP端可以显示手表上传感器的数据,比如心率、运动数据(步数),还可以给手表同步手机上的图片和音乐,表端就是则可以接收和显示手机APP发过来的通知、播放手机的音乐等。

- 视频 视频相关的APP比较重要的功能是在手机上回放视频,但这种APP一般用WIFI连接比较多。
1.2 APP应用的框架
框架中的两个大的个体:手机和耳机,它们之间通过蓝牙的BLE或者SPP进行数据传输,传输的内容是数据包,数据包是按照协议有序排列的一组数据。APP应用的功能,是由手机中安装的APP和耳机中的APP流程实现的。
2. APP应用中每个角色的作用
2.1 手机端

- 手机 手机的作用,是提供APP运行的环境,提供蓝牙的连接和传输功能,提供数据传输的接口(BLE、SPP等)。
手机无法决定APP实现的具体功能,但是能够限制APP功能的实现方式。由于苹果手机不支持SPP,所以在苹果手机上运行的APP一般使用BLE的方式来跟我们的进行数据交互,安卓手机BLE和SPP都支持,所以在安卓手机上运行的APP既可以用BLE通信,也可以用SPP通信,但是具体用哪种,或者是否两种都支持,则需要咨询APP。 - APP APP一般作为控制者存在,其作用一般为:控制耳机的功能,获取耳机的信息,提供耳机需要的数据。以上三个作用里面的具体功能,是由产品功能定义来决定的。
控制耳机的功能:一般为控制耳机的暂停播放、上下曲等,如果产品定义里面有ANC、低延时这些功能,可能还会涉及这些功能的开关切换。更复杂的可能还涉及对耳机功能的修改,常见的是修改耳机的按键功能,修改耳机的声音效果(EQ)。
获取耳机的信息:凡是可以用数据来表示的信息,APP都有可能来获取,例如获取耳机当前的播放状态(暂停还是播放中)、各个功能的开关状态、当前生效的EQ参数、音量等,当然具体APP会来获取什么信息,也是由产品定义决定的。
提供耳机需要的数据:根据产品定义的功能,提供数据给耳机执行,例如提供EQ参数给耳机用于切换音效,在OTA升级中给耳机提供升级文件的数据。
手机端数据包的加密和打包也是在APP上进行的,APP打包好数据包之后,会使用手机提供的数据传输接口将数据包发送出去。 - 数据传输接口BLE、SPP 数据传输接口由手机提供,使用者是APP,常见的接口就是我们目前用到的BLE和SPP(当然手机还有其他可以用来传数据的接口,比如WIFI、蜂窝网络、USB,但本文不涉及)。
APP将数据包给到数据传输接口后,会由接口择机发送出去;接口收到耳机发过来的数据之后,也会通知并将数据传给APP。
数据传输接口不仅提供数据传输的功能,还会提供BLE和SPP的连接功能。目前的APP应用中,都是由APP使用手机提供的接口来发起连接,耳机的连接是被动的,无法主动发起。
2.2 耳机端

- 耳机 从功能上来说,耳机这个角色的作用与手机类似,同样提供APP流程运行的环境,提供蓝牙的连接和数据传输功能,提供数据传输的接口(BLE、SPP)。
而耳机上的功能也是由产品定义来决定,APP最终要控制的就是耳机的功能。 - APP流程 耳机上的APP流程其实就是一个功能模块,它的主要作用就是解析收到的数据,获取耳机的各个信息,打包要发送的数据。
对应手机APP的三个作用,耳机上的APP流程主要完成以下三件事情:
接收手机APP的控制命令,控制耳机的功能;
收集耳机的状态信息,打包发送给手机APP;
接收手机APP的数据,修改各项参数或进行OTA升级。 - 数据传输接口BLE、SPP 跟手机端一样的原理,APP流程需要用到的数据传输接口也是由耳机提供,数据传输接口给APP流程提供数据收发服务,但是与手机端不一样的是,在我们讨论的APP应用中,APP流程不能控制BLE和SPP主动发起连接,只能主动断开。
- 协议和数据包 协议在APP应用中十分重要,就像人与人交流时需要使用相同的语言一样,在APP应用中需要手机和耳机使用同样的”语言“交互。
使用相同的语言,就是要求手机和耳机使用相同的协议进行数据的打包、解析和交互。
协议的定义:协议(计算机学科概念)。
按照协议打包的一组数据,我们称之为数据包。
3.1 简单理解协议
1. 按照约定的方式交互 -- 按照约定好的格式来打包和解析数据包
2. 按照约定的步骤做事情 -- 约定好交互的先后顺序,谁先发起通信,如何交换数据
3. 按照预定的时间点做事情 -- 约定好在什么时间点该进行什么样的交互,发什么样的数据
3.2 数据包的打包
简单理解,打包就是把一些零散的东西通过某种方式合并成一个统一的整体,这里涉及到三个角色:零散的东西、某种方式、统一的整体。
- 零散的东西 零散的东西可以是任何信息、数据,每个零散个体可以有它自己的含义和作用,但是如果这些东西是零散杂乱无章地堆在那里,我们很难快速从中找到我们需要的信息。在APP应用中,协议确定了哪些数据是需要打包的,但如果手机将数据杂乱无章地发过来给耳机(尤其是因为数据都是同样的16进制数值),则耳机根本无法从中获取想要的数据。
- 某种方式 像第1点说的那样,如果手机将数据杂乱无章地发过来给耳机,耳机根本无法从中获取想要的数据。因此在实际应用中,手机APP和耳机会约定,传输的数据要按照一定的方式(规则)来进行排列组合,这个规则就是由协议决定的。
- 统一的整体 我们将需要的信息,按照协议规定的规则排列好之后,就形成了数据包。如此所有的数据交互都按照同样的规则打包、传输、解析,将零散的数据统一了起来,我们只需要根据协议定好的规则去解析数据包,就能拿到我们想要的信息。
3.3 数据包的解析
数据解析是数据打包的逆过程,从排列好的数据组合中,按照协议拆解数据,获取零散的信息。这个过程就不详述了。
3.4 数据包中可能包含的信息
- 命令:通过特定协商好的数值来表示需要执行的动作 例如,耳机和手机提前协商好,0x01这个命令用来切换低延时,0x02这个命令用来切换anc模式,0x03这个命令用来设置EQ参数,0x04这个命令用来获取当前的EQ参数。
- 参数:一个或者一些数据,用来表示某些信息,一般跟在命令后面 例如,耳机和手机提前协商好,跟在命令0x01后面的一个字节,如果这个字节是0x01,则是要打开低延时,如果是0x00,则是要关闭低延时。
- 数据校验信息:可能存在于数据包的任意位置 例如,在数据包中规定数据包必须以某个数据作为开头(包头),在数据包中规定某一个字节用来存放数据包的大小,在数据包中规定某一个字节用来存放数据包的CRC校验码,耳机或者APP收到数据包后,可以根据这些信息来确认收到的数据是否完整和正确。
- 一个数据包(协议)的示例(实际项目数据包格式需要和手机APP协商定好)
| 包头 | LEN | CMD | PARAM | CRC |
|---|---|---|---|---|
| 0x1234 | n | 一个字节 | n个字节 | 一个字节 |
3.5 数据包加密
有些APP可能会对数据包进行加密,加密方式也是要与手机APP沟通。
4. 一个简单的APP应用示例
4.1 结构图

4.2 功能介绍:
耳机的功能:一个按键,单击暂停播放,双击下一曲,三击上一曲,长按开关机,内置3种EQ模式,支持修改EQ模式和EQ参数,支持双备份OTA升级。
手机APP的功能:可以控制耳机执行暂停播放和上下曲,可以修改耳机的音量,可以修改耳机的EQ模式和EQ参数,可以获取耳机的播放状态,可以获取耳机的音量,可以获取耳机的EQ模式和EQ参数,可以给耳机进行OTA升级。
手机和耳机约定使用SPP进行通信,同时约定使用如下的协议进行数据传输:
| 包头 | LEN | CMD | PARAM | CRC |
|---|---|---|---|---|
| 0x1234 | n | 一个字节 | n个字节 | 一个字节 |
CMD命令列表:
| 0x01 | 控制耳机的暂停播放,后接1个字节PARAM数据,0x00表示要控制耳机暂停,0x01表示要控制耳机播放,耳机执行成功后需要给手机回复相同数据。 |
|---|---|
| 0x02 | 控制耳机端上下曲功能,后接1个字节PARAM数据,0x00表示要控制耳机下一曲,0x01表示要控制耳机上一曲,耳机执行成功后需要给手机回复相同数据。 |
| 0x03 | 修改耳机的音量,后接1个字节PARAM数据,表示需要修改的音量大小,范围是0~16,耳机执行成功后需要给手机回复相同数据。 |
| 0x04 | 修改耳机的EQ模式,后接1个字节PARAM数据,表示需要修改的模式,范围0~2,耳机执行成功后需要给手机回复相同数据。 |
| 0x05 | 修改耳机的EQ参数,后接10个字节PARAM数据,每个字节表示一个EQ频点的增益,增益的范围是-12~12,耳机执行成功后需要给手机回复相同数据。 |
| 0x06 | 获取耳机当前的播放状态,后面没有PARAM数据,耳机收到该指令,要按照协议格式给手机回复耳机状态,回复时CMD填0x06,耳机正在播放时PARAM填写0x01,否则PARAM填写0x00。耳机本地播放状态变化时,也需要主动上报变化后的状态。 |
| 0x07 | 获取耳机当前的音量大小,后面没有PARAM数据,耳机收到该指令,要按照协议格式给手机回复耳机音量,回复时CMD填0x07,PARAM填写音量大小,范围是0~16。耳机本地音量变化时,也需要主动上报变化后的音量。 |
| 0x08 | 获取耳机当前的EQ模式,后面没有PARAM数据,耳机收到该指令,要按照协议格式给手机回复耳机当前的EQ模式,回复时CMD填0x08,PARAM填写EQ模式,范围是0~2。耳机本地EQ模式变化时,也需要主动上报变化后的模式。 |
| 0x09 | 获取耳机当前的EQ参数,后面没有PARAM数据,耳机收到该指令,要按照协议格式给手机回复耳机当前的EQ参数,回复时CMD填0x09,PARAM填写10个字节的EQ参数,每个字节表示一个EQ频点的增益,增益的范围是-12~12。 |
| 0x0a | 通知耳机进入OTA升级模式,后接4个字节PARAM数据,表示升级文件的大小,耳机执行成功后需要给手机回复相同数据。 |
| 0x0b | 表示这包是升级文件的数据,后接255字节的升级文件数据,耳机收到数据后,要按照协议格式给手机回复升级文件数据的接收状态,回复时CMD填0x0b,PARAM填写状态,范围0~2,0表示写入成功,1表示写入失败,2表示数据校验不通过。 |
4.3 数据包打包demo
/**
* @brief 计算crc
*
* @param ptr
* @param len
* @return u8
*/
static u8 crc8(u8 *ptr, u8 len)
{
u8 i, crc;
crc = 0;
while (len--) {
crc ^= *ptr++;
for (i = 0; i < 8; i++) {
if (crc & 0x01) {
crc = (crc >> 1) ^ 0x8c;
} else {
crc >>= 1;
}
}
}
return crc;
}
/**
* @brief 打包
*
* @param tx_buff 数据包缓存
* @param cmd 命令
* @param data 需要打包的参数
* @param len 需要打包的参数长度
* @return u16 数据包的长度
*/
u16 data_package(u8 *tx_buff, u8 cmd, u8 *data, u8 len)
{
u16 length = len + 5;
u8 *buff = tx_buff;
if(!buff){
return;
}
memset(buff, 0x00, length);
buff[0] = 0x34;
buff[1] = 0x12;
buff[2] = len;
buff[3] = cmd;
if(date && len){
memcpy(buff + 4, data, len);
}
buff[length - 1] = crc8(buff, length - 1);
return length;
}
u8 tx_buff[255 + 5];
void data_package_send_test(void)
{
u16 tx_len = 0;
u8 cmd = 0x01;
u8 data = 0x01;
tx_len = data_package(tx_buff, cmd, &data, 1);
//send_data(tx_buff, tx_len);
}
4.4 数据包解析demo
/**
* @brief 解析
*
* @param rx_data 收到的数据包
* @param rx_len 收到的数据包长度
* @param data 参数缓存指针
* @param data_len 参数长度指针
* @return u8 cmd命令
*/
u8 data_parse(u8 *rx_data, u16 rx_len, u8 **data, u8 *data_len)
{
if(!rx_data || rx_len < 5){
return 0xff; //指针异常或者数据包长度异常
}
u8 cmd = 0xff;
u8 *buff = rx_data;
u8 len = buff[2];
if(len + 5 > rx_len){
return 0xff; //参数长度异常
}
if(data_len){
*data_len = len;
}
u8 crc = crc8(buff, 4 + len);
u8 rx_crc = buff[4 + len];
if(buff[0] == 0x34 && buff[1] == 0x12 && crc == rx_crc){
if(len && data){ //命令后面有数据
*data = (u8 *)malloc(len);
memset(*data, 0x00, len);
memcpy(*data, buff + 4, len);
}
cmd = buff[3];
}
return cmd;
}
/**
* @brief demo
*
* @param rx_data 收到的数据包
* @param rx_len 收到的数据包长度
*/
void rx_data_parse(u8 *rx_data, u16 rx_len)
{
u8 *data = NULL; //data_parse会给这个指针malloc内存,用完要释放掉
u8 data_len = 0;
u8 cmd = data_parse(rx_data, rx_len, &data, &data_len);
switch(cmd){
//todo
default;
break;
}
if(data){ //data_parse会给这个指针malloc内存,用完要释放掉
free(data);
}
}
- 耳机端的APP流程

- 打开耳机的SPP功能
6.1 SPP功能开关
#define USER_SUPPORT_PROFILE_SPP 1
6.2 SPP连接状态和数据接口
标准UUID
用到的回调函数注册接口
extern void spp_data_deal_handle_register(void (*handler)(u8 packet_type, u16 channel, u8 *packet, u16 size)); /*支持串口功能的数据处理接口*/
回调函数demo
void spp_data_handler(u8 packet_type, u16 ch, u8 *packet, u16 size)
{
switch (packet_type) {
case 1: //SPP连接成功
log_info("spp connect\n");
break;
case 2: //SPP连接断开
log_info("spp disconnect\n");
break;
case 7: //SPP收到数据
log_info("spp_rx:");
put_buf(packet,size);
break;
}
}
//蓝牙初始化成功后再调用注册接口注册回调函数
/*spp_data_deal_handle_register(spp_data_handler);*/
SPP发数据接口
/**
USER_CTRL_SPP_SEND_DATA命令有参数,参数会先存起来,
param_len是数据长度,param发送数据指针
返回0,表示准备成功,会PENDing发完才返回
返回3,表示上一包数据没发完
要求:size <= 512,data必须是全局或者静态数组
*/
user_send_cmd_prepare(USER_CTRL_SPP_SEND_DATA, size, data);
自定义UUID
修改自定义UUID
添加回调函数(无需注册,底层会直接调用)
int mutil_handle_data_deal(u8 local_id, u8 packet_type, u16 channel, u8 *packet, u16 size)
{
printf("%s[packet_type = %d, packet:]\n",__func__, packet_type);
put_buf(packet, size);
switch (packet_type){
case 0x01:
log_info("SPP_CONNECT");
break;
case 0x02:
log_info("SPP_DISCONNECT");
break;
case 0x07:
log_info("RECEIVE_DATA");
break;
default:
break;
}
}
发数接口
extern int multi_spp_send_data(u8 local_cid, u8 rfcomm_cid, u8 *buf, u16 len);
//调用方法
int user_spp_send_data(u8 *data, u16 len)
{
return multi_spp_send_data(0x0A, 0, data, len);
}
可视化SDK接口
添加回调函数
//spp收数回调
void app_spp_recieve_callback(void *hdl, void *remote_addr, u8 *buf, u16 len)
{
log_info("%s %x\n", __func__, (u32)hdl);
put_buf(buf, len);
}
//spp连接状态回调
void app_spp_state_callback(void *hdl, void *remote_addr, u8 state)
{
log_info("%s %x state %d\n", __func__, (u32)hdl, state);
}
//发数结束、spp空闲回调
void app_spp_send_wakeup_callback(void *hdl)
{
log_info("%s %x\n", __func__, (u32)hdl);
}
在multi_protocol_common_callback_init函数中添加代码
注意:app_spp_hdl_alloc(u8 local_id);// local_id:0:标准SPP uuid;0xa:自定义SPP uuid,自定义id方法同普通SDK
if (multi_protocol_spp_hdl == NULL) {
multi_protocol_spp_hdl = app_spp_hdl_alloc(0);//0:标准SPP uuid;0xa:自定义SPP uuid
if (multi_protocol_spp_hdl == NULL) {
log_info("multi_protocol_spp_hdl alloc err !!\n");
return;
}
}
app_spp_recieve_callback_register(multi_protocol_spp_hdl, app_spp_recieve_callback);
app_spp_state_callback_register(multi_protocol_spp_hdl, app_spp_state_callback);
app_spp_wakeup_callback_register(multi_protocol_spp_hdl, app_spp_send_wakeup_callback);
发数接口
app_spp_data_send(multi_protocol_spp_hdl, data, len);
- 打开BLE功能
7.1 BLE功能开关
#define TCFG_USER_BLE_ENABLE 1
/**********以下内容会由SDK根据宏定义修改,不了解的话严禁随意修改**********/
const int config_stack_modules = BT_BTSTACK_CLASSIC | BT_BTSTACK_LE;
const int config_btctler_modules = BT_MODULE_CLASSIC | BT_MODULE_LE;
const int config_btctler_le_roles = (LE_ADV | LE_SLAVE);
/**********以上内容会由SDK根据宏定义修改,不了解的话严禁随意修改**********/
7.2 BLE接口
接口文件:include_lib\btstack\le\ble_api.h
关键的初始化函数:void ble_profile_init(void)
7.3 BLE修改UUID服务
工具:make_gatt_services:https://kdocs.cn/l/cj0hbnJgvG3r
工具压缩包内有说明文档和demo
app开发profile服务UUID汇总(仅供参考,具体服务由APP定)
可视化SDK(支持两路RCSP的版本)修改UUID的方法.
通过上面的方法生成profile后,用下面的方法合入代码
//收数回调,底层蓝牙收到数据会调用,功能跟att_write_callback类似
void bt_rcsp_custom_recieve_callback(u16 ble_con_hdl, void *remote_addr, u8 *buf, u16 len, uint16_t att_handle)
{
printf("%s %d\n", __func__, att_handle);
put_buf(buf, len);
}
extern void *rcsp_server_ble_hdl;
extern void *rcsp_server_ble_hdl1;
extern int app_ble_profile_set(void *_hdl, const uint8_t *db);
app_ble_profile_set(NULL, NULL);
app_ble_profile_set(rcsp_server_ble_hdl, multi_profile_data);
app_ble_profile_set(rcsp_server_ble_hdl1, multi_profile_data);

7.4 BLE广播
常用的接口,demo可参考数传
/*************************************************************************************************/
/*!
* \brief 开关BLE广播.
*
* \function ble_cmd_ret_e ble_op_adv_enable(int enable).
*
* \param [in] enable 广播使能 :0(dis) or 1(en).
*
* \return see ble_cmd_ret_e.
*
* \note !!!注意:开广播前必现先配置好广播的参数.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_adv_enable(int enable) */
#define ble_op_adv_enable(enable) \
ble_user_cmd_prepare(BLE_CMD_ADV_ENABLE, 1, (int)enable)
/*************************************************************************************************/
/*!
* \brief 配置广播参数.
*
* \function ble_cmd_ret_e ble_op_set_adv_param(u16 adv_interval,u8 adv_type).
*
* \param [in] adv_interval 广播周期,Range: 0x0020 to 0x4000 (unit: 0.625ms).
* \param [in] adv_type 广播类型,Range: 0x00 to 0x04.
* \param [in] adv_channel 广播类型通道,range:Range: 0x01 to 0x07.
*
* \return see ble_cmd_ret_e.
*
* \note !!!注意:设置的时候必现在广播关闭的状态下.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_set_adv_param(u16 adv_interval,u8 adv_type) */
#define ble_op_set_adv_param(adv_interval,adv_type,adv_channel) \
ble_user_cmd_prepare(BLE_CMD_ADV_PARAM, 3, (int)adv_interval, (int)adv_type, (int)adv_channel)
/*************************************************************************************************/
/*!
* \brief 配置广播 Advertising Data内容.
*
* \function ble_cmd_ret_e ble_op_set_adv_data(u8 adv_len,const *u8 adv_data).
*
* \param [in] adv_len adv 数据包长度,Range: 0x00 to 0x1f.
* \param [in] adv_data (全局变量地址),adv数据包地址.
*
* \return see ble_cmd_ret_e.
*
* \note !!!注意:设置的时候必现在广播关闭的状态下.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_set_adv_data(u8 adv_len,const *u8 adv_data) */
#define ble_op_set_adv_data(adv_len,adv_data) \
ble_user_cmd_prepare(BLE_CMD_ADV_DATA, 2, (int)adv_len, (void*)adv_data)
/*************************************************************************************************/
/*!
* \brief 配置广播 Scan Response Data内容.
*
* \function ble_cmd_ret_e ble_op_set_rsp_data(u8 rsp_len,const *u8 rsp_data).
*
* \param [in] rsp_len rsp 包长度,Range: 0x00 to 0x1f.
* \param [in] adv_data (全局变量地址),rsp数据包地址.
*
* \return see ble_cmd_ret_e.
*
* \note !!!注意:设置的时候必现在广播关闭的状态下.
*/
/*************************************************************************************************/
/* ble_cmd_ret_e ble_op_set_rsp_data(u8 rsp_len,const *u8 rsp_data) */
#define ble_op_set_rsp_data(rsp_len,rsp_data) \
ble_user_cmd_prepare(BLE_CMD_RSP_DATA, 2, (int)rsp_len, (void*)rsp_data)
7.5 BLE连接状态和数据接口
- BLE连接状态 连接状态会反馈到cbk_packet_handler函数中
static void cbk_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size)
{
//......
switch (packet_type) {
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)) {
//......
case HCI_EVENT_LE_META:
switch (hci_event_le_meta_get_subevent_code(packet)) {
//......
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE://BLE连接成功
//......
}
//......
case HCI_EVENT_DISCONNECTION_COMPLETE://BLE连接断开
//......
}
- 耳机接收手机APP发过来的数据 手机APP发给耳机的数据最终会进入att_write_callback这个函数,用户需要在对应的UUID handle下接收处理数据
static int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);
/***以下是demo***/
case ATT_CHARACTERISTIC_00000001_0000_1001_8001_00805F9B07D0_01_VALUE_HANDLE:
log_info("\nTUYA msg receive, handle = 0x%x, size = %d\n", handle, buffer_size);
receive_data(buffer, buffer_size);
break;
/***以上是demo***/
可视化SDK(支持两路RCSP的版本)的收数接口
见:可视化SDK(支持两路RCSP的版本)修改UUID的方法
3. 手机APP获取耳机的数据 手机APP获取耳机数据的方式有两种:
第一种是靠手机APP自己来读(READ),需要在att_read_callback函数对应的UUID handle下回填数据
static uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size)
/***以下是demo***/
case ATT_CHARACTERISTIC_2a00_01_VALUE_HANDLE:
att_value_len = gap_device_name_len;
if ((offset >= att_value_len) || (offset + buffer_size) > att_value_len) {
break;
}
if (buffer) {
memcpy(buffer, &gap_device_name[offset], buffer_size);
att_value_len = buffer_size;
log_info("\n------read gap_name: %s \n", gap_device_name);
}
return att_value_len;
/***以上是demo***/
第二种是靠耳机通知手机APP(NOTIFY),需要通知到指定的UUID handle
/***以下是demo***/
static int app_send_user_data(u16 handle, u8 *data, u16 len, u8 handle_type)
{
u32 ret = APP_BLE_NO_ERROR;
if (!con_handle) {
return APP_BLE_OPERATION_ERROR;
}
if (!att_get_ccc_config(handle + 1)) {
log_info("fail,no write ccc!!!,%04x\n", handle + 1);
return APP_BLE_NO_WRITE_CCC;
}
ret = ble_op_att_send_data(handle, data, len, handle_type);
if (ret == BLE_BUFFER_FULL) {
ret = APP_BLE_BUFF_FULL;
}
if (ret) {
log_info("app_send_fail:%d !!!!!!\n", ret);
}
return ret;
}
//使用方法:
att_set_ccc_config(ATT_CHARACTERISTIC_00000002_0000_1001_8001_00805F9B07D0_01_VALUE_HANDLE + 1, ATT_OP_NOTIFY);
app_send_user_data(ATT_CHARACTERISTIC_00000002_0000_1001_8001_00805F9B07D0_01_VALUE_HANDLE, data, len, ATT_OP_NOTIFY);
/***以上是demo***/
其他具体内容可以参看数传功能
8. 数传
8.1 SDK打开数传功能
- #define CONFIG_APP_BT_ENABLE APP功能必须定义这个宏
- #define TRANS_DATA_EN 1 使能数传功能
- #define CONFIG_DOUBLE_BANK_ENABLE 1
控制单双备份OTA的宏。1是双备份,0是单备份。数传功能要做OTA的话只能也必须用双备份,升级文件使用db_update_data.bin。 - #define CONFIG_DB_UPDATE_DATA_GENERATE_EN 1 用于生成db_update_data.bin。
- #define CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE 1
手机OTA升级必须打开这个宏,ufw只生成1份4K对齐的代码。 - .ini文件参考配置(一般耳机SDK已经配置好,这里只是给大家参考):
#if CONFIG_DOUBLE_BANK_ENABLE
BR22_TWS_DB = YES;//dual bank flash framework enable
FLASH_SIZE = CONFIG_FLASH_SIZE;//flash_size cfg
BR22_TWS_VERSION = 0; //default fw version
#if CONFIG_DB_UPDATE_DATA_GENERATE_EN
DB_UPDATE_DATA = YES; //generate db_update_data.bin
#endif
#if 1//CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE //如果支持双备份升级默认4K对齐,否则会因为对齐方式不一致升级过程中将vm区域擦除
FORCE_4K_ALIGN = YES; // force aligin with 4k bytes
SPECIAL_OPT = 0;// only generate one flash.bin
#endif
#else
NEW_FLASH_FS = YES;//enable single bank flash framework
#endif //CONFIG_DOUBLE_BANK_ENABLE
8.2 相关文件
apps\common\third_party_profile\jieli\trans_data_demo\le_trans_data.c
apps\common\third_party_profile\jieli\trans_data_demo\le_trans_data.h
apps\common\third_party_profile\jieli\trans_data_demo\spp_trans_data.c
apps\common\third_party_profile\jieli\trans_data_demo\spp_trans_data.h
9. 杰理之家
9.1 SDK打开杰理之家的方法
- #define CONFIG_APP_BT_ENABLE APP功能必须定义这个宏
- #define RCSP_ADV_EN 1 杰理之家必须开这个宏,用于使能RCSP协议
- #define JL_EARPHONE_APP_EN 1 需要使用杰理之家APP的话,必须开这个宏
- #define BT_CONNECTION_VERIFY 0
是否支持杰理之家加密认证:0是支持,1是不支持 - #define RCSP_UPDATE_EN 1
可选是否支持rcsp升级(杰理之家OTA、RCSP OTA) - #define CONFIG_DOUBLE_BANK_ENABLE 1
可选。控制单双备份OTA的宏。1是双备份,0是单备份。如果是512K的芯片只能用单备份了,升级文件使用update.ufw。 - #define CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE 1
手机OTA升级必须打开这个宏,ufw只生成1份4K对齐的代码。 - .ini文件参考配置(一般耳机SDK已经配置好,这里只是给大家参考):
#if CONFIG_DOUBLE_BANK_ENABLE
BR22_TWS_DB = YES;//dual bank flash framework enable
FLASH_SIZE = CONFIG_FLASH_SIZE;//flash_size cfg
BR22_TWS_VERSION = 0; //default fw version
#if CONFIG_DB_UPDATE_DATA_GENERATE_EN
DB_UPDATE_DATA = YES; //generate db_update_data.bin
#endif
#if 1//CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE //如果支持双备份升级默认4K对齐,否则会因为对齐方式不一致升级过程中将vm区域擦除
FORCE_4K_ALIGN = YES; // force aligin with 4k bytes
SPECIAL_OPT = 0;// only generate one flash.bin
#endif
#else
NEW_FLASH_FS = YES;//enable single bank flash framework
#endif //CONFIG_DOUBLE_BANK_ENABLE
9.2 相关文件
带rcsp的文件,主要有:
apps\common\third_party_profile\jieli\JL_rcsp\bt_trans_data\le_rcsp_adv_module.c
apps\common\third_party_profile\jieli\JL_rcsp\bt_trans_data\le_rcsp_adv_module.h
apps\common\third_party_profile\jieli\JL_rcsp\bt_trans_data\rcsp_adv_spp_user.c
apps\common\third_party_profile\jieli\JL_rcsp\adv_rcsp_protocol\rcsp_adv_bluetooth.c
9.3 DEMO 程序
杰理之家_AC695N_soundbox_sdk_release_3.0.5
9.4 APP连接操作
操作视频
10. 涂鸦 做好的demo:蓝牙音频APP相关应用.otl
10.1 SDK打开涂鸦功能
- #define CONFIG_APP_BT_ENABLE APP功能必须定义这个宏
- #define TUYA_DEMO_EN 1 使能涂鸦功能
- #define CONFIG_DOUBLE_BANK_ENABLE 1
控制单双备份OTA的宏。1是双备份,0是单备份。涂鸦要做OTA的话只能也必须用双备份,升级文件使用db_update_data.bin。 - #define CONFIG_DB_UPDATE_DATA_GENERATE_EN 1 用于生成db_update_data.bin。
- #define CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE 1
手机OTA升级必须打开这个宏,ufw只生成1份4K对齐的代码。 - .ini文件参考配置(一般耳机SDK已经配置好,这里只是给大家参考):
#if CONFIG_DOUBLE_BANK_ENABLE
BR22_TWS_DB = YES;//dual bank flash framework enable
FLASH_SIZE = CONFIG_FLASH_SIZE;//flash_size cfg
BR22_TWS_VERSION = 0; //default fw version
#if CONFIG_DB_UPDATE_DATA_GENERATE_EN
DB_UPDATE_DATA = YES; //generate db_update_data.bin
#endif
#if 1//CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE //如果支持双备份升级默认4K对齐,否则会因为对齐方式不一致升级过程中将vm区域擦除
FORCE_4K_ALIGN = YES; // force aligin with 4k bytes
SPECIAL_OPT = 0;// only generate one flash.bin
#endif
#else
NEW_FLASH_FS = YES;//enable single bank flash framework
#endif //CONFIG_DOUBLE_BANK_ENABLE
10.2 相关文件
带tuya的文件,主要有:
apps\earphone\tuya\tuya_demo.c
apps\earphone\tuya\tuya_demo.h
apps\earphone\tuya\tuya_app.c
apps\common\third_party_profile\tuya_protocol\app\demo\tuya_ble_app_demo.c
apps\common\third_party_profile\tuya_protocol\app\demo\tuya_ble_app_demo.h
11. ANCS
11.1 SDK打开ANCS功能
(以下以jl701n_soundbox_release_v1.2.1做为例子)
//ANCS 需要使能BLE
//相关文件:apps\soundbox\board\br28\board_jl701n_demo\board_jl701n_demo_cfg.h
#define TCFG_USER_BLE_ENABLE 1 //BLE功能使能
//需要打开ANCS的使能
//相关文件:apps\common\third_party_profile\jieli\trans_data_demo\le_trans_data.c
//添加需要的头文件
#include "ble_user.h"
//ANCS profile enable
#define TRANS_ANCS_EN 1
//相关文件:apps\soundbox\log_config\lib_btctrler_config.c
const uint64_t config_btctler_le_features = LE_ENCRYPTION;
//相关文件:apps\common\third_party_profile\jieli\trans_data_demo\le_trans_data.c
const int config_le_sm_support_enable = 1; //是否支持加密配对
jl701n_soundbox_release_v1.2.1:替换库文件 :【金山文档】 添加ANCS_jl701n_soundbox_release_v1.2.1
- #define CONFIG_APP_BT_ENABLE APP功能必须定义这个宏
- #define TRANS_DATA_EN 1 使能数传功能
- #define CONFIG_DOUBLE_BANK_ENABLE 1
控制单双备份OTA的宏。1是双备份,0是单备份。数传功能要做OTA的话只能也必须用双备份,升级文件使用db_update_data.bin。 - #define CONFIG_DB_UPDATE_DATA_GENERATE_EN 1 用于生成db_update_data.bin。
- #define CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE 1
手机OTA升级必须打开这个宏,ufw只生成1份4K对齐的代码。 - .ini文件参考配置(一般耳机SDK已经配置好,这里只是给大家参考):
#if CONFIG_DOUBLE_BANK_ENABLE
BR22_TWS_DB = YES;//dual bank flash framework enable
FLASH_SIZE = CONFIG_FLASH_SIZE;//flash_size cfg
BR22_TWS_VERSION = 0; //default fw version
#if CONFIG_DB_UPDATE_DATA_GENERATE_EN
DB_UPDATE_DATA = YES; //generate db_update_data.bin
#endif
#if 1//CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE //如果支持双备份升级默认4K对齐,否则会因为对齐方式不一致升级过程中将vm区域擦除
FORCE_4K_ALIGN = YES; // force aligin with 4k bytes
SPECIAL_OPT = 0;// only generate one flash.bin
#endif
#else
NEW_FLASH_FS = YES;//enable single bank flash framework
#endif //CONFIG_DOUBLE_BANK_ENABLE
11.2 相关文件
apps\common\third_party_profile\jieli\trans_data_demo\le_trans_data.c
apps\common\third_party_profile\jieli\trans_data_demo\le_trans_data.h
apps\common\third_party_profile\jieli\trans_data_demo\spp_trans_data.c
apps\common\third_party_profile\jieli\trans_data_demo\spp_trans_data.h
11.3 demo程序
| 系列 | 链接 |
|---|---|
| AC695N | 【金山文档】 ac695n_ANCS测试_soundbox_sdk_3.1.0 |
- 其他第三方APP(APP_PROTOCOL) 公版已实现的一些第三方APP功能
12.1 SDK打开其他第三方APP功能
- #define CONFIG_APP_BT_ENABLE APP功能必须定义这个宏
- #define AI_APP_PROTOCOL 1
- 修改app_protocol_api.h
#define APP_PROTOCOL_GMA_CODE 0/*阿里天猫协议接口ID*/
#define APP_PROTOCOL_AMA_CODE 0/*亚马逊的AMA协议接口ID*/
#define APP_PROTOCOL_DMA_CODE 0/*百度DMA协议接口ID*/
#define APP_PROTOCOL_TME_CODE 0/*腾讯酷狗TME协议接口ID*/
#define APP_PROTOCOL_MMA_CODE 0/*小米MMA协议接口ID*/
#define APP_PROTOCOL_REALME_CODE 0/*oppo真我的协议接口ID*/
#define APP_PROTOCOL_GFPS_CODE 1/*谷歌快对的协议接口ID*/
#define APP_PROTOCOL_NECM_CODE 0/*网易云音乐协议接口ID*/
- #define CONFIG_DOUBLE_BANK_ENABLE 1
控制单双备份OTA的宏。1是双备份,0是单备份。APP要做OTA的话只能也必须用双备份,升级文件使用db_update_data.bin。 - #define CONFIG_DB_UPDATE_DATA_GENERATE_EN 1 用于生成db_update_data.bin。
- #define CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE 1
手机OTA升级必须打开这个宏,ufw只生成1份4K对齐的代码。 - .ini文件参考配置(一般耳机SDK已经配置好,这里只是给大家参考):
#if CONFIG_DOUBLE_BANK_ENABLE
BR22_TWS_DB = YES;//dual bank flash framework enable
FLASH_SIZE = CONFIG_FLASH_SIZE;//flash_size cfg
BR22_TWS_VERSION = 0; //default fw version
#if CONFIG_DB_UPDATE_DATA_GENERATE_EN
DB_UPDATE_DATA = YES; //generate db_update_data.bin
#endif
#if 1//CONFIG_ONLY_GRENERATE_ALIGN_4K_CODE //如果支持双备份升级默认4K对齐,否则会因为对齐方式不一致升级过程中将vm区域擦除
FORCE_4K_ALIGN = YES; // force aligin with 4k bytes
SPECIAL_OPT = 0;// only generate one flash.bin
#endif
#else
NEW_FLASH_FS = YES;//enable single bank flash framework
#endif //CONFIG_DOUBLE_BANK_ENABLE
- 设计说明文档 杰理第三方APP通信协议设计说明文档.pdf
12.2 相关文件
app_protocol开头的文件,主要有:
apps\common\third_party_profile\interface\app_protocol_api.c
apps\common\third_party_profile\interface\app_protocol_api.h
apps\common\third_party_profile\interface\app_protocol_common.c
13. 其他资料 app开发profile服务UUID汇总
升级相关.otl
固件升级介绍.md
杰理所有升级方式汇总
蓝牙音频APP相关应用.otl
APP自定义按键的demo.otl
BLE速率提升:
BLE从机修改MTU大小
IOT(BLE-AD-MCU)资料链接入口汇总.otl
14. 认证码烧录
应用场景
如果您需要在烧写固件程序的同时烧写认证码(序列号, 三元组) 等第三方数据, 可参考本
文步骤导入认证码到烧写器中。
认证码数据需要使用 csv 文件方式导入, 建议认证码最大长度不超过 127bytes。
认证码烧写步骤说明.pdf
15. APP问题排查调试方法
15.1 连接类问题
排查BLE UUID、SPP UUID是否正确
排查BLE广播数据是否正确
排查连接上之后,上报数据是否符合协议要求
15.2 APP界面显示异常问题
排查耳机上报数据是否符合协议要求
排查耳机是否正确按照协议解析手机下发的数据
排查耳机是否给手机回复正确数据
15.3 耳机功能异常问题
排查耳机是否正确按照协议解析手机下发的数据
TWS的场景要将主机收到的数据同步给从机(也可由主机解析后再研判是否需要主从同步执行)
16. 各种APP常见问题汇总
- 杰理之家
- BLE和EDR相同地址导致OTA升级失败问题


- AC695N_soundbox_sdk_release_3.0.5/AC695N_soundbox_sdk_relea se_3.0.6 无法连接问题 需要把SPP打开
- BLE和EDR相同地址导致OTA升级失败问题
c.RCSP选择连接方式

- 涂鸦
- 耳机主从切换会导致APP断连,目前无法解决,BLE不支持主从切换
- 唤醒siri爆ram

- 双备份flash空间优化
- 提示音使用aac格式,关掉wts

- VM改为8K(改了之后要先强制升级)

- 关闭ble加密配对

- 提示音使用单备份方案(提示音不放在代码区) 注意:此方法提示音不放在代码区,因此手机APP双备份升级时无法更新提示音,需要更新提示音时只能使用串口升级
此方法把提示音放在flash的一个独立区域
修改提示音路径
download.bat去掉 tone.cfg
修改ini文件(或者isd_config_rule.c)
TON_ADR值确定方式:
双备份flash结构如下
| uboot | |
|---|---|
| code1 | |
| code2 | |
| VM | |
| BTIF | |
| 其他预留区 | 自定义区域1 |
| 自定义区域2 | |
| ...... | |
| 4K预留区 |
- 假设“其他预留区“中的自定义区域个数为0,也就是自定义预留区只需要用来放提示音,那么TON_ADR = flash大小 - 4K - TON_LEN。 举例:假设实际flash为1MB(1MB = 1024KB = 1024 * 1024byte = 1048576byte = 0x100000byte),tone.cfg的大小为33K(4K对齐后占用36K空间),则
TON_ADR=0x100000-4K-36K
=0x100000-0x1000-0x9000
=0xF6000 - 假设“其他预留区“中已有其他区域,比如开了ANC之后还有ANC的区域(占用4K),则TON_ADR = flash大小 - 4K - TON_LEN - 其他区域(ANC区域)大小。 举例:假设实际flash为1MB(1MB = 1024KB = 1024 * 1024byte = 1048576byte = 0x100000byte),tone.cfg的大小为33K(4K对齐后占用36K空间),ANC的区域大小4K,则
TON_ADR=0x100000-4K-36K-4K
=0x100000-0x1000-0x9000-0x1000
=0xF5000
4. 重置的demo
void local_tuya_key_func_reset(void)
{
u8 key_value_record[6][6] = {0};
key_table_l[0][0] = tuya_key_event_swith(0);
key_table_l[0][1] = tuya_key_event_swith(1);
key_table_l[0][4] = tuya_key_event_swith(2);
key_table_r[0][0] = tuya_key_event_swith(0);
key_table_r[0][1] = tuya_key_event_swith(1);
key_table_r[0][4] = tuya_key_event_swith(2);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 6; j++) {
key_value_record[i][j] = key_table_l[i][j];
key_value_record[i + 3][j] = key_table_r[i][j];
}
}
syscfg_write(TUYA_SYNC_KEY_INFO, key_value_record, sizeof(key_value_record));
}
//用这个函数重置
void tuya_reset(void)
{
if(tws_api_get_role() == TWS_ROLE_MASTER){
tuya_ble_gap_disconnect();
}
user_send_cmd_prepare(USER_CTRL_DEL_ALL_REMOTE_INFO, 0, NULL);
tuya_ble_device_factory_reset();
local_tuya_key_func_reset();
char eq_info[11] = {0};
syscfg_write(CFG_RCSP_ADV_EQ_DATA_SETTING, eq_info, 11);
u8 name[LOCAL_NAME_LEN];
memset(name, 0x00, sizeof(name));
syscfg_read_string(CFG_BT_NAME, name, sizeof(name), 0);
syscfg_write(CFG_BT_NAME, name, LOCAL_NAME_LEN);
void sys_enter_soft_poweroff(void *priv);
sys_timeout_add((void *)1, sys_enter_soft_poweroff, 1000);
}
以上接口可以放在按键那里,也可以放在涂鸦指令(如果有的话)那里
代码demo见:蓝牙音频APP相关应用.otl
3. 酷狗 1. 700N合入新的PID和信息组 添加下面的代码到app_protocol_api.c,库里面会调用。
#define ECHD_KeyX "15207009984421a6586f9fc3fe7e4329d2809ea51125f8ed" //ECHD_KeyX
#define ECHD_KeyY "b09d42b81bc5bd009f79e4b59dbbaa857fca856fb9f7ea25" //ECHD_Keyy
#define TME_PID "1000"
#define TME_BID "0001"
static const u8 key_x[] = {ECHD_KeyX};
static const u8 key_y[] = {ECHD_KeyY};
const char * Get_TME_PubKey_x()
{
put_buf(key_x, sizeof(key_x));
return key_x;
}
const char * Get_TME_PubKey_y()
{
put_buf(key_y, sizeof(key_y));
return key_y;
}
u32 kugou_pid = 0;
u8 *Get_TME_pid()
{
kugou_pid = atoi(TME_PID);
printf("%s %d\n", __func__, kugou_pid);
return (u8 *)&kugou_pid;
}
u32 kugou_bid = 0;
u8 *Get_TME_bid()
{
u32 temp = 0;
temp = atoi(TME_BID);
kugou_bid = temp & 0xffff;
printf("%s %d\n", __func__, kugou_bid);
return (u8 *)&kugou_bid;
}
百度
小爱同学
天猫精灵
亚马逊
Realme Link
谷歌快连
网易云音乐