用C语言实现无线耳机的TWS配对编程:解决双耳连接不稳定的问题
TWS双耳连不上、单耳出声、手机里冒出两个设备条目,根因大多不是射频芯片坏了,而是左右耳内部没同步成一对。 手机只认主耳,副耳靠对耳链路跟主耳对齐状态;只要配对表、角色、时钟、重连逻辑有一处乱,双耳就失步。C代码里要把“配对同步”和“手机连接”切成两层。
一、上电先选主副耳
出盒别两只都去抢手机连接,先在对耳私有通道(NFMI/2.4G私有BLE/BR-EDR ACL)里选举:
typedef enum { ROLE_NONE, ROLE_MASTER, ROLE_SLAVE } tws_role_t;
tws_role_t elect_role(int batt_pct, int rssi_to_phone){
/* 电量高、离手机近的当主耳 */
if(batt_pct >= 50 && rssi_to_phone > -75)
return ROLE_MASTER;
return ROLE_SLAVE;
}
工程做法一般是交换:电量、RSSI、链路质量、是否先出盒。 规则要加迟滞——两耳差距小的时候沿用上一次角色,别每毫秒切一次,否则主副来回跳就是断连源头。
二、配对信息双耳同步
最常见“两个蓝牙条目”就是左右耳存了不一致的bonding key。 主耳和手机完成配对后,把LTK、IRK、设备地址、服务UUID同步给副耳:
typedef struct {
uint8_t phone_addr[6];
uint8_t ltk[16];
uint8_t irk[16];
uint8_t bonded;
} tws_pair_info_t;
void master_sync_pairinfo(const tws_pair_info_t*p){
tws_peer_send(PEER_PAIR_SYNC, (uint8_t*)p, sizeof(*p));
}
void slave_on_peer_msg(uint8_t cmd, uint8_t*buf, int len){
if(cmd==PEER_PAIR_SYNC && len==sizeof(tws_pair_info_t)){
memcpy(&g_pair, buf, len);
bt_stack_load_bond(&g_pair); // 副耳装入同一份密钥
}
}
副耳不自己走配对流程,只等主耳派发;这样手机侧看到的是“一个设备”,不会出两个条目。
三、主耳建链,副耳监听同步
经典BR/EDR A2DP原生是一对一,常见架构是手机↔主耳建ACL,副耳走监听或主耳选择性转发补齐丢包。 控制面(音量、播放状态、电量上报)走对耳链路:
void tws_connect_flow(void){
if(local_role()==ROLE_MASTER){
bt_connect_phone(g_pair.phone_addr);
}else{
peer_wait_master_ready(); // 副耳不主动连手机
audio_sync_clock(base_clock());
}
}
副耳千万别在bt_connect_phone里也调一次——双连模式要看芯片是否支持TWS+/双路直连,否则就是“两个设备”。
四、断线重连状态机
单耳掉线后无脑重连=重连风暴,控制器假死、功耗炸。 区分“用户主动断开”和“异常掉线”:
typedef enum { LINK_IDLE, LINK_ON,
LINK_RECONN, LINK_USER_OFF } link_st_t;
#define MAX_RETRY 5
static int retry;
static uint32_t delay_ms = 1000;
void on_phone_disconnect(int reason){
if(reason==USER_DISCONNECT){
link_st = LINK_USER_OFF; return;
}
link_st = LINK_RECONN;
retry = 0; delay_ms = 1000;
schedule_reconnect();
}
void schedule_reconnect(void){
if(retry++ >= MAX_RETRY){ link_st=LINK_IDLE; return; }
tws_timer(delay_ms, [](){
if(phone_in_range()) // 先确认可见
bt_connect_phone(g_pair.phone_addr);
delay_ms = delay_ms*2>30000 ? 30000 : delay_ms*2;
});
}
退避1/2/4/8/16s,超5次放弃等用户。 副耳不单独重连手机,只跟主耳重新同步;主耳重连成功再把新信道映射/时钟基准发给副耳。
五、主副切换不停流
主耳放回盒子/低电,副耳接管:
void on_master_in_case(void){
if(local_role()==ROLE_SLAVE){
local_role_set(ROLE_MASTER);
bt_connect_phone(g_pair.phone_addr); // 用同一bond
audio_continue();
}
}
切换前提是对耳链路已把LTK和当前ACL状态同步过,否则副耳连手机会走“新配对”,手机弹配对框。
六、落坑清单
左右耳各自存bond→手机里两个条目
副耳也调bt_connect_phone→单耳抢占
断连while重试→重连风暴
主副角色无迟滞→每秒切换一次
出盒不重新选举→低电耳当主耳,很快掉
对耳通道不通就连手机→副耳永久沉默
用户点断开还自动重连→语义错误
时钟漂移不重同步→副耳音画错位
盒内不停重连→耗电发热
七、落地顺序
定义对耳私有通道(NFMI或BLE对端GATT)
上电交换电量/RSSI选主副,加迟滞
主耳配对,LTK/IRK同步副耳
主耳连手机A2DP/AVRCP,副耳监听
控制指令走对耳:音量/播放/电量
断连状态机分USER_OFF/RECONN
指数退避+扫描到再连
主耳入盒→副耳用同bond接管
盒内清除pending_reconnect,复位角色
TWS稳定连接的本质不是“两只都连上手机”,是双耳先内部成组、再统一对手机出场。主耳管外链、副耳跟状态、对耳通道做同步、重连走退避——这四件事在C状态机里拆开,单耳掉线/两个蓝牙条目/反复重连基本就消失了。量产项目里80%的“连接不稳”都出在配对信息没双耳对齐,不是天线问题。





