TWS配对常见应用
📎 原始文档:https://www.kdocs.cn/l/caFGRJUNrPNp TWS配对常见应用
- AC697X/700x左右耳同时双击发起配对 20210415 HJY 700N V135以及更新版本、697N V236以及更新版本请看第4点
修改方法:选择双击配对方式,耳机是根据配对码是否一致进行配对的,配对时将两个耳机的配对码改为一致,超时后重新生成新的随机配对码。
配对方式要选择按键双击发起配对、声道选择不能选择CONFIG_TWS_LEFT_START_PAIR和
lmp_hci_write_local_address
CONFIG_TWS_RIGHT_START_PAIR
在bt_tws_start_search_sibling函数中按上图加如下代码:
u8 mac_addr[6];
syscfg_read(CFG_BT_MAC_ADDR, mac_addr, 6);
lmp_hci_write_local_address(mac_addr);
bt_set_tws_device_indicate(dk_tws_pair_code);
tws_api_set_quick_connect_addr(tws_set_auto_pair_code());
r_printf("tws_set_auto_pair_code=%x\n",tws_set_auto_pair_code());
bt_tws_connect_sibling(3);
printf("bt_cfg.tws_device_indicate = %x\n",bt_get_tws_device_indicate(NULL));
sys_timeout_add(NULL,chang_pair_code,3000);
void chang_pair_code(void *p)
{
u16 code;
u8 temp[2];
get_random_number(temp,2);
code = temp[1];
code = ((code<<8) | temp[0]);
if((tws_api_get_tws_state() & TWS_STA_SIBLING_DISCONNECTED)){
bt_set_tws_device_indicate(code);
g_printf("code=%x,%x\n",temp[0],temp[1]);
bt_tws_search_sibling_and_pair();
}
}
u16 dk_tws_pair_code = 0xdc81;
void bt_set_tws_device_indicate(u16 tws_pair_code)
{
bt_cfg.tws_device_indicate = tws_pair_code;
}
可配合第2点修改
2. AC697X/700x 左耳与多个右耳配对过,同时开机只有最后配对的耳机能连接成功20201224 HJY 使用场景:左耳分别与多只右耳连接过,下一次连接只允许最后连接的一对耳机回连成功。
3. 对耳连接时根据需要固定主机
普通SDK:
下图可以根据自己的需要修改判断条件(这里demo是左耳做主机)
关闭主从切换
可视化SDK:
按键发起配对的耳机做主机,记忆主从
这里demo是按键发起配对的耳机做主机,能够记忆主从角色,下次开机主从与最后一次连接一样
700N_1T2_V202_patch_06_02_固定主从.rar
功能实现方法
- 按键配对,关闭主从切换

#if TCFG_TWS_AUTO_ROLE_SWITCH_ENABLE
tws_api_auto_role_switch_enable();
#else
tws_api_auto_role_switch_disable();
#endif


2. 按键操作,发起配对和清除配对记录
3. 重点:添加下面的代码到apps\earphone\mode\bt\bt_tws.c
/**
* @brief 最后一次对耳连接时,本地主从角色
* @param TWS_ROLE_MASTER:上一次连接是做主机
* @param TWS_ROLE_SLAVE:上一次连接是做从机
*/
u8 tws_last_role = TWS_ROLE_MASTER;
/**
* @brief 标记是否本地按键触发配对
*
*/
u8 local_start_pair = 0;
//本地主从角色选择,两个耳机会互通彼此的角色选择
int tws_host_get_local_role()
{
printf("%s %x\n", __func__, tws_api_get_tws_state());
//本地按键触发配对或者手机已连接,做主机
if((tws_api_get_tws_state() & TWS_STA_PHONE_CONNECTED)){
printf("%s M\n", __func__);
return 0xff;
}
if(local_start_pair){
printf("%s m\n", __func__)
return TWS_ROLE_MASTER;
}
printf("%s %d\n", __func__, tws_last_role);
return tws_last_role;//使用上一次的角色
}
/**
* @brief TWS连接过程中,TWS流程根据此函数返回值判断是否进行主从切换
*
* @param remote_role_req 远端耳机请求的角色
* @param local_role_req 本地耳机请求的角色
* @return true 切换
* @return false 不切换
*/
bool tws_host_role_switch(int remote_role_req, int local_role_req)
{
printf("%s %d %d %x\n", __func__, remote_role_req, local_role_req, tws_api_get_tws_state());
if(tws_api_get_tws_state() & TWS_STA_PHONE_CONNECTED){
return true;//已经连接了手机,切换
}
if(remote_role_req == 0xff){
return false;//remote已连接手机,不切换
}
//当本地请求做主机,并且是因为触发按键配对或者上一次是主机,就执行切换
//返回true之后TWS会进行主从切换,让本地切换为主机
if(local_role_req == TWS_ROLE_MASTER && (local_start_pair == 1 || tws_last_role == TWS_ROLE_MASTER))
{
return true;
}
return false;
}
- 开机读上一次主从角色

- 对耳连上清标志位,存主从角色

- 主从切换,存主从角色

- 按键发起配对置标志位,配对超时清标志位

- VM索引(不要与SDK已有索引重复)
#define CFG_TWS_LOCAL_ROLE 7

其他相关问题修改
- 清除配对流程的bug导致清除对耳配对后主机搜不到从机,修改如下


- 按键配对不能与第三个耳机配对(交叉配对),或同一对耳机重复发起配对会配不上,需要打开下面的功能
要提高交叉配对成功率,需要加长配对时间
- 无蓝牙模式提示音导致蓝牙流程异常(比如开机对耳不回连)

- 单耳连上手机之后,还能被其他耳机搜索配对上的问题

- 单耳连接了手机,发起TWS配对超时后,还能被其他耳机搜索配对上的问题

- 主动断开手机
//新增断连函数
void bt_disconn_all_phone_dev(void)
{
void *device;
void *devices[2];
int num = btstack_get_conn_devices(devices, 2);
if(num == 0){
return;
}
for (u8 i = 0; i < num; i++)
{
device = devices[i];
btstack_device_detach(device);
}
}
//按键执行下面的函数去断连,进入手机配对模式
void bt_tws_enter_pairing(void)
{
clr_device_in_page_list();
bt_cmd_prepare(USER_CTRL_PAGE_CANCEL, 0, NULL);
page_mode_active = 0;
bt_disconn_all_phone_dev();
}
自定义条件固定主机
修改如图的两个函数内容
图中tws_host_role_switch是从机调用
remote_info = 主机的tws_host_get_local_role返回值
local_info = 从机的tws_host_get_local_role返回值
注意:要关闭自动主从切换,参考功能实现方法
4. AC700N_V1.3.5、697N_V236,tws同时按按键进行配对修改 支持单耳连接了手机的状态下进行对耳配对,可使用V1.3.5的btctrler.a替换到旧版本(V1.3.x)修改
【金山文档】 tws同时按按键进行配对修改
https://kdocs.cn/l/culxpi7I3u9Z
按键调用bt_tws_search_sibling_and_pair()函数进行配对。
5. 关闭自动配对,只能通过测试盒进行配对 修改方法:
- 关闭自动配对:
#define CONFIG_TWS_PAIR_BY_CLICK 0 /* 按键发起配对 */
#define CONFIG_TWS_PAIR_BY_AUTO 1 /* 开机自动配对 */
#define CONFIG_TWS_PAIR_BY_FAST_CONN 2 /* 开机快速连接,连接速度比自动配对快,不支持取消配对操作 */
#define CONFIG_TWS_PAIR_MODE 3//配置为3,只是为了不让这个宏等于0、1、2,配置为4也可以达到目的,只要不是0、1、2就行
- 关闭交叉配对
#define CONFIG_DEVELOPER_MODE 0 //开发者模式debug ,开启后可以进行按键双击配对
//#define CONFIG_TWS_AUTO_PAIR_WITHOUT_UNPAIR /* 不取消配对也可以配对新的耳机 */
- 关闭或不使用清除配对功能
//#define CONFIG_TWS_REMOVE_PAIR_ENABLE /* 不连手机的情况下双击按键删除配对信息 */
4. TWS未配对,不允许执行tws_api_create_connection函数 
6. 加快交叉配对速度(未连接手机时) 
if(bt_user_priv_var.auto_connection_counter || !(gtws.state & BT_TWS_POWER_ON)){
connect_and_connectable_switch(0);
}
else{
connect_and_connectable_switch(2);
}
如果还不够快就把这个值加大
- 可视化SDK两边耳机同时操作按键发起配对 修改代码







配置工具配置按键功能
或者tws未连接状态下执行函数
app_send_message(APP_MSG_TWS_START_PAIR, 0);
- 实现TWS右耳分时和两个左耳连接 功能描述:有A耳机、B耳机、C耳机,取出A、B时,AB连接后去连接手机,然后B关机,取出C,AC能回连上并且正常连接同一台手机播歌,AC关机后再开机也能正常回连手机。
典型应用:AB是对耳,C是mic,AC连接时使用C的mic通话
实现方法:将B的TWS配对信息通过测试盒同步给C,同步的信息包含:TWS local地址和remote地址,通过代码区分B和C。
在user_cfg.c添加代码缓存remote地址
static u8 remote_addr[6];
void bt_get_tws_remote_addr(u8 *addr)
{
memcpy(addr, remote_addr, 6);
}
void bt_set_tws_remote_addr(u8 *addr)
{
memcpy(remote_addr, addr, 6);
}
在tws配对时记录remote地址
修改测试盒配对函数
#define TWS_TYPE_EAR 0
#define TWS_TYPE_MIC 1
#define CURR_TWS_TYPE TWS_TYPE_MIC//选择当前耳机的角色(也就是上面提到的B、C)
bool chargestore_set_tws_remote_info(u8 *data, u8 len)
{
u8 i;
bool ret = true;
int w_ret = 0;
u8 remote_addr[6];
u8 common_addr[6];
u8 local_addr[6];
CHARGE_STORE_INFO *charge_store_info = (CHARGE_STORE_INFO *)data;
if (len > sizeof(CHARGE_STORE_INFO)) {
log_error("len err\n");
return false;
}
//以下,新增
u8 *mutil_data = (u8 *)&charge_store_info->search_aa;
if(mutil_data[0] == TWS_TYPE_EAR && charge_store_info->local_channel == 'L' && CURR_TWS_TYPE == TWS_TYPE_MIC){
//本机是mic,跟左耳进测试盒配对,复制左耳的local mac和remote mac
//左耳的local mac --> mic的local mac
//左耳的remote mac --> mic的remote mac
if (sizeof(local_addr) != syscfg_write(CFG_TWS_LOCAL_ADDR, charge_store_info->tws_local_addr, sizeof(local_addr))) {
w_ret = -1;
}
if (sizeof(remote_addr) != syscfg_write(CFG_TWS_REMOTE_ADDR, mutil_data + 1, sizeof(remote_addr))) {
w_ret = -1;
}
goto __pair_over;
}
if(mutil_data[0] == TWS_TYPE_EAR && charge_store_info->local_channel == 'R' && CURR_TWS_TYPE == TWS_TYPE_MIC){
//本机是mic,跟右耳进测试盒配对,复制右耳的remote mac和local mac
//右耳的local mac --> mic的remote mac
//右耳的remote mac --> mic的local mac
if (sizeof(local_addr) != syscfg_write(CFG_TWS_REMOTE_ADDR, charge_store_info->tws_local_addr, sizeof(local_addr))) {
w_ret = -1;
}
if (sizeof(remote_addr) != syscfg_write(CFG_TWS_LOCAL_ADDR, mutil_data + 1, sizeof(remote_addr))) {
w_ret = -1;
}
goto __pair_over;
}
if(mutil_data[0] == TWS_TYPE_MIC){
//耳机跟mic配对,不操作,直接报成功
goto __pair_over;
}
//以上,新增
//set remote addr
syscfg_read(CFG_TWS_REMOTE_ADDR, remote_addr, sizeof(remote_addr));
if (memcmp(remote_addr, charge_store_info->tws_local_addr, sizeof(remote_addr))) {
ret = false;
}
//新增:正常的对耳配对,记录remote地址
void bt_set_tws_remote_addr(u8 *addr);
bt_set_tws_remote_addr(charge_store_info->tws_local_addr);
if (sizeof(remote_addr) != syscfg_write(CFG_TWS_REMOTE_ADDR, charge_store_info->tws_local_addr, sizeof(remote_addr))) {
w_ret = -1;
}
bt_get_tws_local_addr(local_addr);
#if (CONFIG_TWS_COMMON_ADDR_SELECT != CONFIG_TWS_COMMON_ADDR_USED_LEFT)
//set common addr
syscfg_read(CFG_TWS_COMMON_ADDR, common_addr, sizeof(common_addr));
for (i = 0; i < sizeof(common_addr); i++) {
if (common_addr[i] != (u8)(local_addr[i] + charge_store_info->tws_local_addr[i])) {
ret = false;
}
common_addr[i] = local_addr[i] + charge_store_info->tws_local_addr[i];
}
if (sizeof(common_addr) != syscfg_write(CFG_TWS_COMMON_ADDR, common_addr, sizeof(common_addr))) {
w_ret = -3;
}
#endif
#ifndef CONFIG_NEW_BREDR_ENABLE
if (__this->channel == 'L') {
if (tws_le_get_connect_aa() != tws_le_get_pair_aa()) {
ret = false;
}
tws_api_set_connect_aa_allways(tws_le_get_pair_aa());
} else {
if (tws_le_get_connect_aa() != charge_store_info->pair_aa) {
ret = false;
}
tws_api_set_connect_aa_allways(charge_store_info->pair_aa);
}
#endif
__pair_over://新增
if (__this->testbox_status && (0 == w_ret)) {
u8 cmd = CMD_BOX_TWS_REMOTE_ADDR;
chargestore_api_write(&cmd, 1);
__this->tws_paired_flag = 1;
/* r_printf("tws_paired_flag=%d\n",__this->tws_paired_flag ); */
}
return ret;
}
u16 chargestore_get_tws_remote_info(u8 *data)
{
u16 ret_len = 0;
u16 device_ind;
CHARGE_STORE_INFO *charge_store_info = (CHARGE_STORE_INFO *)data;
#if TCFG_USER_TWS_ENABLE
bt_get_tws_local_addr(charge_store_info->tws_local_addr);
if (__this->keep_tws_conn_flag) {
memcpy(charge_store_info->tws_mac_addr, bt_get_mac_addr(), 6);
} else {
bt_get_vm_mac_addr(charge_store_info->tws_mac_addr);
}
#ifndef CONFIG_NEW_BREDR_ENABLE
charge_store_info->search_aa = tws_le_get_search_aa();
charge_store_info->pair_aa = tws_le_get_pair_aa();
#endif
//以下,新增
u8 *mutil_data = (u8 *)&charge_store_info->search_aa;
mutil_data[0] = CURR_TWS_TYPE;
void bt_get_tws_remote_addr(u8 *addr);
bt_get_tws_remote_addr(mutil_data + 1);
//以上,新增
#if (CONFIG_TWS_CHANNEL_SELECT == CONFIG_TWS_AS_LEFT_CHANNEL) \
||(CONFIG_TWS_CHANNEL_SELECT == CONFIG_TWS_AS_RIGHT_CHANNEL) \
||(CONFIG_TWS_CHANNEL_SELECT == CONFIG_TWS_EXTERN_DOWN_AS_LEFT) \
||(CONFIG_TWS_CHANNEL_SELECT == CONFIG_TWS_EXTERN_UP_AS_LEFT)
charge_store_info->local_channel = tws_api_get_local_channel();
#else
charge_store_info->local_channel = 'U';
#endif
charge_store_info->device_ind = bt_get_tws_device_indicate(NULL);
if (__this->testbox_status) {
charge_store_info->reserved_data = 0;
} else {
charge_store_info->reserved_data = chargestore_crc8(data, sizeof(CHARGE_STORE_INFO) - 2);
}
#else
bt_get_vm_mac_addr(charge_store_info->tws_mac_addr);
#endif
ret_len = sizeof(CHARGE_STORE_INFO);
return ret_len;
}
- 单耳连接了手机的情况下发起TWS配对 普通SDK参考:
AC700N软件问题整理.doc 第86点
可视化SDK:
连接了手机的那个耳机执行:
void tws_api_set_pair_code(u16 pair_code);//指定配对用的配对码
int tws_api_wait_pair_when_phone_connect(int timeout_ms);//timeout_ms:0是不超时,非0是指定等待ms
没连手机的耳机执行:
void tws_api_set_pair_code(u16 pair_code);//指定配对用的配对码
int tws_api_auto_pair(int timeout);//timeout:0是不超时,非0是指定等待ms
例:
//如果使用tws_api_set_pair_code重新设置了配对码,则需要左右耳都执行
void tws_enter_pair_mode(void)
{
tws_api_set_pair_code(0x1234);
if(tws_api_get_tws_state() & TWS_STA_PHONE_CONNECTED){
tws_api_wait_pair_when_phone_connect(5000);
}
else{
tws_api_auto_pair(5000);
}
}
- tws连接成功后,修改TWS的声道 可视化SDK:
//channel:'L'、'R'
syscfg_write(CFG_TWS_CHANNEL, &channel, 1);
tws_api_set_local_channel(channel);
//如果当前在播放音乐,还要修改音频声道
if(g_a2dp_player) {
a2dp_player_set_audio_channel(g_a2dp_player);
}
- tws连接成功后,解除tws配对
tws_api_remove_pairs();
12、主动进行TWS主从切换
添加下面的代码,调用user_start_role_switch();进行主从切换
普通SDK接口
//非通话状态下用
void tws_conn_switch_role();
//通话状态下用
void test_esco_role_switch(u8 flag);
void tws_role_switch_api(void)
{
if (get_call_status() != BT_CALL_HANGUP) {
test_esco_role_switch(1);
}
else{
tws_conn_switch_role();
}
}
static int role_sw_check_timer = 0;
void user_ctrl_tws_role_switch(void *p)
{
if(role_sw_check_timer){
sys_timer_del(role_sw_check_timer);
role_sw_check_timer = 0;
}
printf("role_switch_check");
if(app_var.goto_poweroff_flag){
puts("goto_poweroff ing\n");
return;
}
if(get_tws_phone_connect_state() == 0 || get_tws_sibling_connect_state() == 0){
puts("phone or tws disconnected\n");
return;
}
if(tws_api_get_role() == TWS_ROLE_SLAVE){
puts("TWS_ROLE_SLAVE\n");
return;
}
int deal_state = tws_api_get_tws_state();
if (bt_is_sniff_close()) {
user_send_cmd_prepare(USER_CTRL_ALL_SNIFF_EXIT, 0, NULL);
role_sw_check_timer = sys_timer_add(NULL, user_ctrl_tws_role_switch, 500);
} else {
tws_role_switch_api();
}
}
void user_start_role_switch(void)
{
user_ctrl_tws_role_switch(NULL);
}
可视化SDK接口
void tws_api_role_switch();
static int role_sw_check_timer = 0;
void user_ctrl_tws_role_switch(void *p)
{
if(role_sw_check_timer){
sys_timer_del(role_sw_check_timer);
role_sw_check_timer = 0;
}
printf("role_switch_check");
if(app_var.goto_poweroff_flag){
puts("goto_poweroff ing\n");
return;
}
int deal_state = tws_api_get_tws_state();
if((deal_state & TWS_STA_PHONE_DISCONNECTED) || get_tws_sibling_connect_state() == 0){
puts("phone or tws disconnected\n");
return;
}
if(tws_api_get_role() == TWS_ROLE_SLAVE){
puts("TWS_ROLE_SLAVE\n");
return;
}
if (bt_is_sniff_close()) {
bt_cmd_prepare(USER_CTRL_ALL_SNIFF_EXIT, 0, NULL);
role_sw_check_timer = sys_timer_add(NULL, user_ctrl_tws_role_switch, 500);
} else {
tws_api_role_switch();
}
}
void user_start_role_switch(void)
{
user_ctrl_tws_role_switch(NULL);
}
13、TWS根据信号强弱进行主从切换(非可视化仅播歌状态下支持)的切换阈值修改
添加下面的函数到SDK的bt_tws.c文件中,底层会调用,先看看a2dp_role_switch_check_debug打印的rssi情况,再去修改a2dp_role_switch_check_rssi和tws_esco_rs_rssi_check函数中的判断条件。
调整判断条件可以让耳机更容易或者更难进行主从切换
u8 a2dp_role_switch_check_debug(int master_rx_num, int slave_rx_num, int master_rssi, char slave_rssi, int tws_rssi, int msec)
{
#if 0//def CONFIG_DEBUG_ENABLE//需要查看实时信号强度时打开,用于调试切换阈值
static int rx_rssi_print_cnt = 0;
if (++rx_rssi_print_cnt == 10) {
rx_rssi_print_cnt = 0;
printf("rx: %d, %d, rssi: %d, %d, %d, msec:%d\n",
master_rx_num, slave_rx_num,
master_rssi, slave_rssi, tws_rssi, msec);
}
return 1;
#endif
return 0;
}
u8 a2dp_role_switch_check_rssi(int master_rssi, char slave_rssi)
{
if (master_rssi < -58 && slave_rssi > -70 && master_rssi + 13 <= slave_rssi) {
return 1;//返回1进行主从切换
}
return 0;
}
bool tws_esco_rs_rssi_check(char m_rssi, char s_rssi)
{
static char old_s_rssi, old_m_rssi;
if (((old_s_rssi - s_rssi) < 20 && (old_s_rssi - s_rssi) > -20) && ((old_m_rssi - m_rssi) < 20 && (old_m_rssi - m_rssi) > -20)) {
if (m_rssi < -60 && s_rssi > -70 && m_rssi + 25 <= s_rssi) {
return TRUE;
}
}
old_s_rssi = s_rssi;
old_m_rssi = m_rssi;
return FALSE;
}
14、默认不配对 tws ,按键手动自由配对与删除
该应用需要关闭主从自动切换。
可视化 SDK:
添加 APP_MSG_TWS_START_REMOVE_PAIR // 如果没有配对则发起配对,如果配对了则取消配对
static int dual_conn_app_event_handler(int *msg) {
// ..... 此处省略其他代码
case APP_MSG_TWS_START_REMOVE_PAIR:
if (msg[1] == APP_KEY_MSG_FROM_TWS) {
break;
}
g_printf("tws_api_get_tws_state:%x", tws_api_get_tws_state());
if ((tws_api_get_tws_state() & TWS_STA_TWS_PAIRED)
&& (tws_api_get_tws_state() & TWS_STA_SIBLING_CONNECTED)) {
g_printf("tws_api_remove_pairs");
tws_api_remove_pairs();
} else { // 没有连接也要发起配对
g_printf("tws_api_wait_pair_by_code");
tws_api_search_sibling_by_code();
if (g_dual_conn.timer) {
sys_timeout_del(g_dual_conn.timer);
}
g_dual_conn.timer
= sys_timeout_add(NULL, msg_tws_start_pair_timeout, TCFG_TWS_PAIR_TIMEOUT * 1000);
}
break;
// ..... 此处省略其他代码
}
配对连接后,主从关系控制,(注:A、B 两个都连接了手机情况下禁止配对,或者断开其中一台后再配对):
/* 从机跑这里,返回 ture 为需要进行 role_switch 变成主机 */
bool tws_host_role_switch(int remote_info, int local_info)
{
g_printf("remote_info:0x%x local_info:0x%x", remote_info, local_info);
if (remote_info & TWS_STA_PHONE_CONNECTED && !(local_info & TWS_STA_PHONE_CONNECTED)) { /*对方连手机, 本地没连手机*/
g_printf("remote connect phone, local not connect phone");
return FALSE;
} else if (!(remote_info & TWS_STA_PHONE_CONNECTED) && (local_info & TWS_STA_PHONE_CONNECTED)) { /*对方没连手机,本地连手机*/
g_printf("remote not connect phone, local connect phone");
return TRUE;
} else if (remote_info & TWS_STA_PHONE_CONNECTED && (local_info & TWS_STA_PHONE_CONNECTED)) { /*对方连手机,本地连手机, 需要断开*/
//do detach, 两个设备分别都连接了手机需要断开
g_printf("remote connect phone, local connect phone");
return FALSE;
}
if (remote_info & TWS_STA_LE_AUDIO_CONNECTED) { //对方已连手机le_audio
g_printf("remote is le_audio connected");
return FALSE;
}
if ((local_info & TWS_STA_HAVE_PAGE_INFO) && !(remote_info & TWS_STA_HAVE_PAGE_INFO)) {
//tws超时断开同时与手机也超时断开,再次tws连接上后,还需要继续回连手机,有回连信息端要继续做回主机
g_printf("local have page info");
return TRUE;
}
return FALSE;
}
维持设备一直处于 tws wait pair 状态:
BT_STATUS_FIRST_CONNECTED 事件后打开 tws wait pair
TWS_EVENT_CONNECTION_DETACH 事件后,即 tws 断开后打开 tws wait pair: