找回密码
 立即注册

使用微信账号登录

只需一步,快速开始

传统蓝牙HCI搜索流程介绍(bluetooth inquiry)

2020-8-10 09:34| 发布者: 蓝牙协议栈开发| 查看: 1923| 评论: 0|原作者: 于忠军

摘要: 一. 声明本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。第二篇:Transport层介绍,主要介 ...

一. 声明

本专栏文章我们会以连载的方式持续更新,本专栏计划更新内容如下:

第一篇:蓝牙综合介绍 ,主要介绍蓝牙的一些概念,产生背景,发展轨迹,市面蓝牙介绍,以及蓝牙开发板介绍。

第二篇: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.


路过

雷人

握手

鲜花

鸡蛋

相关阅读

最新评论

小黑屋|手机版|我爱蓝牙网 - 52Bluetooth

GMT+8, 2024-4-29 09:38 , Processed in 0.207601 second(s), 36 queries , Gzip On, MemCached On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

返回顶部