一. 声明本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下: 
第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。 第二篇:Transport层介绍,主要介绍蓝牙协议栈跟蓝牙芯片之前的硬件传输协议,比如基于UART的H4,H5,BCSP,基于USB的H2等 第三篇:传统蓝牙controller介绍,主要介绍传统蓝牙芯片的介绍,包括射频层(RF),基带层(baseband),链路管理层(LMP)等 第四篇:传统蓝牙host介绍,主要介绍传统蓝牙的协议栈,比如HCI,L2CAP,SDP,RFCOMM,HFP,SPP,HID,AVDTP,AVCTP,A2DP,AVRCP,OBEX,PBAP,MAP等等一系列的协议吧。 第五篇:低功耗蓝牙controller介绍,主要介绍低功耗蓝牙芯片,包括物理层(PHY),链路层(LL) 第六篇:低功耗蓝牙host介绍,低功耗蓝牙协议栈的介绍,包括HCI,L2CAP,ATT,GATT,SM等 第七篇:蓝牙芯片介绍,主要介绍一些蓝牙芯片的初始化流程,基于HCI vendor command的扩展 第八篇:附录,主要介绍以上常用名词的介绍以及一些特殊流程的介绍等。 另外,开发板如下所示,对于想学习蓝牙协议栈的最好人手一套。以便更好的学习蓝牙协议栈,相信我,学完这一套视频你将拥有修改任何协议栈的能力(比如Linux下的bluez,Android下的bluedroid)。 
------------------------------------------------------------------------------------------------------------------------------------------ CSDN学院链接(进入选择你想要学习的课程):https://edu.csdn.net/lecturer/5352?spm=1002.2001.3001.4144 蓝牙交流扣扣群:970324688 Github代码:https://github.com/sj15712795029/bluetooth_stack 入手开发板:https://item.taobao.com/item.htm?spm=a1z10.1-c-s.w4004-22329603896.18.5aeb41f973iStr&id=622836061708 ------------------------------------------------------------------------------------------------------------------------------------------ 二.搜索command以及产生的event蓝牙搜索没什么好说的吧,就是搜索周边设备,也叫inquiry,搜索command以及产生的event如下: 
整个流程如下: 1)首先协议栈给蓝牙芯片发送搜索command 2)芯片收到搜索命令上报协议栈一个command status的event 3)然后芯片上报协议栈搜索结果 4)最终芯片上报协议栈搜索完成 我们就以上4个步骤来一一分析下每个封包,既巩固我们前面所说的封包格式,又学到协议栈跟芯片的交互流程。 首先我们来说下搜索command,命令格式如下: 
参数: LAP: 
其中LAP的有效值在:https://www.bluetooth.com/specifications/assigned-numbers/baseband/ 
Inquiry_Length:搜索时间 
Num_Responses:最多返回的设备个数 
注意此部分如果Inquiry_Length跟Num_Responses有一方满足,那么就停止搜索。 搜索命令具体封包如下: 
搜索代码实现如下: err_t hci_inquiry(uint32_t lap, uint8_t inq_len, uint8_t num_resp,
err_t (*inq_result)(struct hci_pcb_t *pcb,struct hci_inq_res_t *inqres),
err_t (* inq_complete)(struct hci_pcb_t *pcb,uint16_t result))
{
struct bt_pbuf_t *p;
struct hci_inq_res_t *tmpres;
/* Free any previous inquiry result list */
while(pcb->ires != NULL)
{
tmpres = pcb->ires;
pcb->ires = pcb->ires->next;
bt_memp_free(MEMP_HCI_INQ, tmpres);
}
pcb->inq_complete = inq_complete;
pcb->inq_result = inq_result;
if((p = bt_pbuf_alloc(BT_TRANSPORT_TYPE, HCI_INQUIRY_PLEN, BT_PBUF_RAM)) == NULL)
{
BT_HCI_TRACE_ERROR("ERROR:file[%s],function[%s],line[%d] bt_pbuf_alloc fail\n",__FILE__,__FUNCTION__,__LINE__);
return BT_ERR_MEM; /* Could not allocate memory for bt_pbuf_t */
}
/* Assembling command packet */
p = hci_cmd_ass(p, HCI_INQUIRY, HCI_LINK_CONTROL, HCI_INQUIRY_PLEN);
/* Assembling cmd prameters */
bt_le_store_24((uint8_t *)p->payload,3,lap);
((uint8_t *)p->payload)[6] = inq_len;
((uint8_t *)p->payload)[7] = num_resp;
phybusif_output(p, p->tot_len,PHYBUSIF_PACKET_TYPE_CMD);
bt_pbuf_free(p);
return BT_ERR_OK;
}
其次,我们来看下command status的event,封包格式如下 
参数: Status:在9.6小节我们以及介绍errcode的值分别代表什么意思 
Num_HCI_Command_Packets: 
Command_Opcode:用于标示command status是返回哪个cmmand的,此部分应该跟我们HCI command的opcode一样,此部分也就是inquiry command. 
Command status封包格式如下: 
代码中没有针对此部分做特别处理,基本上算是打印: case HCI_COMMAND_STATUS:
switch(((uint8_t *)p->payload)[0])
{
case HCI_SUCCESS:
BT_HCI_TRACE_DEBUG("hci_event_input: Command Status\n");
break;
default:
BT_HCI_TRACE_DEBUG("hci_event_input: Command failed, %s\n", hci_get_error_code(((uint8_t *)p->payload)[0]));
bt_pbuf_header(p, -2); /* Adjust payload pointer not to cover
Num_HCI_Command_Packets and status parameter */
ocf = *((uint16_t *)p->payload) & 0x03FF;
ogf = *((uint16_t *)p->payload) >> 10;
bt_pbuf_header(p, -2); /* Adjust payload pointer not to cover Command_Opcode
parameter */
HCI_EVENT_CMD_COMPLETE(pcb,ogf,ocf,((uint8_t *)p->payload)[0],ret);
bt_pbuf_header(p, 4);
break;
}
BT_HCI_TRACE_DEBUG("Num_HCI_Command_Packets: 0x%x\n", ((uint8_t *)p->payload)[1]);
pcb->numcmd += ((uint8_t *)p->payload)[1]; /* Add number of completed command packets to the
number of command packets that the BT module
can buffer */
BT_HCI_TRACE_DEBUG("Command_Opcode: 0x%x 0x%x\n", ((uint8_t *)p->payload)[2], ((uint8_t *)p->payload)[3]);
break;
然后,我们来看下搜索结果的event,封包格式如下 
参数: Num_Responses:搜索到设备的个数,一般芯片会每次上来一个,然后以多个搜索结果event上来,但是也不排除有一次上来多个搜索结果的event. 
BD_ADDR:搜索到的蓝牙地址. 
Page_Scan_Repetition_Mode 
Reserved:保留参数 
Class_Of_Device:设备类型,具体参照:https://www.bluetooth.com/specifications/assigned-numbers/baseband/ 
Clock_Offset:时钟偏移 
注意此部分普通的搜索不会上来remote bluetooth name,需要额外去调用Remote Name Request command去请求,当然EIR除外,后续我们会说明EIR。 普通搜索的结果封包格式如下: 
代码处理如下: case HCI_INQUIRY_RESULT:
for(i=0; i<((uint8_t *)p->payload)[0]; i++)
{
resp_offset = i*14;
BT_HCI_TRACE_DEBUG("hci_event_input: Inquiry result %d\nBD_ADDR: 0x",i);
for(i = 0; i < BD_ADDR_LEN; i++)
{
BT_HCI_TRACE_DEBUG("%x",((uint8_t *)p->payload)[1+resp_offset+i]);
}
BT_HCI_TRACE_DEBUG("\n");
BT_HCI_TRACE_DEBUG("Page_Scan_Rep_Mode: 0x%x\n",((uint8_t *)p->payload)[7+resp_offset]);
BT_HCI_TRACE_DEBUG("Class_of_Dev: 0x%x 0x%x 0x%x\n",((uint8_t *)p->payload)[10+resp_offset],
((uint8_t *)p->payload)[11+resp_offset], ((uint8_t *)p->payload)[12+resp_offset]);
BT_HCI_TRACE_DEBUG("Clock_Offset: 0x%x%x\n",((uint8_t *)p->payload)[13+resp_offset],
((uint8_t *)p->payload)[14+resp_offset]);
bdaddr = (void *)(((uint8_t *)p->payload)+(1+resp_offset));
if((inqres = bt_memp_malloc(MEMP_HCI_INQ)) != NULL)
{
bd_addr_set(&(inqres->bdaddr), bdaddr);
inqres->psrm = ((uint8_t *)p->payload)[7+resp_offset];
inqres->psm = ((uint8_t *)p->payload)[9+resp_offset];
memcpy(inqres->cod, ((uint8_t *)p->payload)+10+resp_offset, 3);
inqres->co = *((uint16_t *)(((uint8_t *)p->payload)+13+resp_offset));
HCI_REG(&(pcb->ires), inqres);
HCI_EVENT_INQ_RESULT(pcb,inqres,ret); /*---通过这个函数回调到APP应用层 */
}
else
{
BT_HCI_TRACE_ERROR("ERROR:file[%s],function[%s],line[%d] bt_memp_malloc fail\n",__FILE__,__FUNCTION__,__LINE__);
}
}
break;
最后我们来看下搜索完成的event,封包格式如下: 
参数: Status:搜索完成状态 
Wireshark封包格式如下: 
代码处理如下: case HCI_INQUIRY_COMPLETE:
BT_HCI_TRACE_DEBUG("DEBUG:hci_event_input: Inquiry complete, 0x%x %s\n",((uint8_t *)p->payload)[0], hci_get_error_code(((uint8_t *)p->payload)[0]));
HCI_EVENT_INQ_COMPLETE(pcb,((uint8_t *)p->payload)[0],ret);/* 通过这个函数回调到APP应用层 */
break;
三.取消搜索command以及产生的event 正常取消搜索流程如下: 
这里要重点提一下,个人觉得不合理的地方,我觉得取消搜索,也应该来搜索完成的event,这样设计才合理,我手里的芯片是CSR8811,没有上报协议栈搜索完成的消息,我们暂时先以这个流程说明,后续有了其他芯片重点验证下这个case 回到主题,流程如下: 1)发送取消搜索的命令 2)收到command complete with inquiry cancel opcode 我们来整个分析下这两个步骤 步骤1:发送取消搜索命令,取消搜索的命令格式如下,很简单的一个命令,甚至连参数都没有 
Wireshark封包格式如下: 
代码如下: err_t hci_cancel_inquiry(void)
{
struct bt_pbuf_t *p;
struct hci_inq_res_t *tmpres;
/* Free any previous inquiry result list */
while(pcb->ires != NULL)
{
tmpres = pcb->ires;
pcb->ires = pcb->ires->next;
bt_memp_free(MEMP_HCI_INQ, tmpres);
}
if((p = bt_pbuf_alloc(BT_TRANSPORT_TYPE, HCI_CANCEL_INQUIRY_PLEN, BT_PBUF_RAM)) == NULL)
{
BT_HCI_TRACE_ERROR("ERROR:file[%s],function[%s],line[%d] bt_pbuf_alloc fail\n",__FILE__,__FUNCTION__,__LINE__);
return BT_ERR_MEM; /* Could not allocate memory for bt_pbuf_t */
}
/* Assembling command packet */
p = hci_cmd_ass(p, HCI_INQUIRY_CANCEL, HCI_LINK_CONTROL, HCI_CANCEL_INQUIRY_PLEN);
phybusif_output(p, p->tot_len,PHYBUSIF_PACKET_TYPE_CMD);
bt_pbuf_free(p);
return BT_ERR_OK;
}
步骤2:收到command complete with inquiry cancel opcode,command complete的封包格式如下: 
参数: Num_HCI_Command_Packets: 
Command_Opcode:这个opcode就不用介绍了吧,很熟悉啊· 
Return_Parameter(s):返回的参数,要看具体的命令返回什么参数,我们在取消搜索的时候看到返回status的参数,所以此部分应该是status。 
直接上Wireshark喽 
在代码中我们在event input中监测command complete处理如下 
另外,我送个彩蛋,就是我比较好奇他说如果BE/EDR处于搜索状态,那么就取消搜索,但是如果此时我们不在搜索中我们下取消搜索会怎么样呢,以下是我试的结果,贴上来下: 
我们会同样受到command complete,但是status结果是command disallowed.
|