DA14580 主机从扫描到建立连接全过程分析(含代码)

本文详细剖析了蓝牙主机如何从扫描广播数据到找到目标从机并建立连接的过程。主机首先回调user_on_adv_report_ind()函数,通过memcmp()比较广播数据中的128bitUUID来筛选合适的从机。匹配到目标设备后,主机发起连接请求,并设置连接超时。当连接请求被从机接受,主机进入已连接状态,启用配置文件和服务,交换MTU。若连接超时,则重新启动扫描。整个流程展示了蓝牙设备连接的完整步骤。

主机扫描到广播事件,回调user_on_adv_report_ind( )函数。该函数里先使用memcmp()函数对收到的广播数据和本地预存的数据进行对比,这里是对比服务的128bit UUID。
在主机的user_config.h文件下,预定义了USER_ADVERTISE_DATA:

#define USER_ADVERTISE_DATA    "\x11\x07\xb7\x5c\x49\xd2\x04\xa3\x40\x71\xa0\xb5\x35\x85\x3e\xb0\x83\x07"

把这个128bit数据和从广播收到的数据比对,如果匹配则说明找到了需要连接的从机。

/**
 *********************主机手收到广播数据后的回调函数*********************************************
 * @brief Handles advertise reports
 * @param[in]   param Parameters of disconnect message
 * @return void
 ****************************************************************************************
 */
void user_on_adv_report_ind(struct gapm_adv_report_ind const * param)
{
    if(!memcmp(&param->report.data[3], USER_ADVERTISE_DATA, USER_ADVERTISE_DATA_LEN))
    {
    //打印即将连接到的从机的地址,该地址在广播数据里
        arch_printf("Connect with %02x %02x %02x %02x %02x %02x",
            param->report.adv_addr.addr[5],
            param->report.adv_addr.addr[4],
            param->report.adv_addr.addr[3],
            param->report.adv_addr.addr[2],
            param->report.adv_addr.addr[1],
            param->report.adv_addr.addr[0]);
            
    //设置连接参数
    app_easy_gap_start_connection_to_set(param->report.adv_addr_type, (uint8_t *)&param->report.adv_addr.addr, MS_TO_DOUBLESLOTS(USER_CON_INTV));
    user_gapm_cancel();//取消当前GAP任务,也就是取消正在进行的扫描
    }
}

我们看一下server端定义的service,SPS_SERVICE_UUID是128bit的,{0xb7, 0x5c, 0x49, 0xd2, 0x04, 0xa3, 0x40, 0x71, 0xa0, 0xb5, 0x35, 0x85, 0x3e, 0xb0, 0x83, 0x07}

/*
 * SPSS PROFILE ATTRIBUTES DEFINITION
 *******************这是server端定义的服务和特征UUID***********************************************
 */
#define SPS_SERVICE_UUID    {0xb7, 0x5c, 0x49, 0xd2, 0x04, 0xa3, 0x40, 0x71, 0xa0, 0xb5, 0x35, 0x85, 0x3e, 0xb0, 0x83, 0x07}

#define SPS_SERVER_TX_UUID  {0xb8, 0x5c, 0x49, 0xd2, 0x04, 0xa3, 0x40, 0x71, 0xa0, 0xb5, 0x35, 0x85, 0x3e, 0xb0, 0x83, 0x07}
#define SPS_SERVER_RX_UUID  {0xba, 0x5c, 0x49, 0xd2, 0x04, 0xa3, 0x40, 0x71, 0xa0, 0xb5, 0x35, 0x85, 0x3e, 0xb0, 0x83, 0x07}
#define SPS_FLOW_CTRL_UUID  {0xb9, 0x5c, 0x49, 0xd2, 0x04, 0xa3, 0x40, 0x71, 0xa0, 0xb5, 0x35, 0x85, 0x3e, 0xb0, 0x83, 0x07}

#define SPS_SERVER_TX_CHAR_LEN      160
#define SPS_SERVER_RX_CHAR_LEN      160
#define SPS_FLOW_CTRL_CHAR_LEN      1

上面在回调函数user_on_adv_report_ind()的末尾调用了 user_gapm_cancel(),取消当前GAP任务,也就是正进行的扫描任务。该函数是向ke内核发送一个取消的消息。

static void user_gapm_cancel(void)
{
    // Disable Advertising
    struct gapm_cancel_cmd *cmd = app_gapm_cancel_msg_create();
    // Send the message
    app_gapm_cancel_msg_send(cmd);
}

内核收到该消息后,rwip_schedule()会发出GAPM_CMP_EVT事件,该事件触发回调函数gapm_cmp_evt_handler( )。
程序进一步进入gapm_cmp_evt_handler( )函数下的 case GAPM_SCAN_ACTIVE:或 case GAPM_SCAN_PASSIVE:语句,执行
EXECUTE_CALLBACK_PARAM(app_on_scanning_completed, param->status);

/**
 ****************************************************************************************
 * @brief Handles GAP manager command complete events.
 *
 * @param[in] msgid     Id of the message received.
 * @param[in] param     Pointer to the parameters of the message.
 * @param[in] dest_id   ID of the receiving task instance (TASK_GAP).
 * @param[in] src_id    ID of the sending task instance.
 *
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
int gapm_cmp_evt_handler(ke_msg_id_t const msgid,
                                struct gapm_cmp_evt const *param,
                                ke_task_id_t const dest_id,
                                ke_task_id_t const src_id)
{
    switch(param->operation)
    {
        // reset completed
        case GAPM_RESET:
        {
            if(param->status != GAP_ERR_NO_ERROR)
            {
                ASSERT_ERR(0); // unexpected error
            }
            else
            {
                // set device configuration
                app_easy_gap_dev_configure ();
            }
        }
        break;

        // device configuration updated
        case GAPM_SET_DEV_CONFIG:
        {
            if(param->status != GAP_ERR_NO_ERROR)
            {
                ASSERT_ERR(0); // unexpected error
            }
            else
            {
                EXECUTE_CALLBACK_VOID(app_on_set_dev_config_complete);
            }
        }
        break;

        // Advertising finished
        case GAPM_ADV_UNDIRECT:
        {
           EXECUTE_CALLBACK_PARAM(app_on_adv_undirect_complete, param->status); 
        }
        break;
        
        // Directed advertising finished
        case GAPM_ADV_DIRECT:
        {
            EXECUTE_CALLBACK_PARAM(app_on_adv_direct_complete, param->status);
        }
        break;

        case GAPM_SCAN_ACTIVE:
        case GAPM_SCAN_PASSIVE:
        {
            EXECUTE_CALLBACK_PARAM(app_on_scanning_completed, param->status);
        }
        break;
        
        case GAPM_CONNECTION_DIRECT:
            if (param->status == GAP_ERR_CANCELED)
            {
                EXECUTE_CALLBACK_VOID(app_on_connect_failed);
            }
        break;

        case GAPM_CANCEL:
        {
            if(param->status != GAP_ERR_NO_ERROR)
            {
                ASSERT_ERR(0); // unexpected error
            }
            if (app_process_catch_rest_cb!=NULL)
            {
                app_process_catch_rest_cb(msgid,param,dest_id,src_id);
            }
         }
        break;
         
        default:
            if (app_process_catch_rest_cb!=NULL)
            {
                app_process_catch_rest_cb(msgid,param,dest_id,src_id);
            }    
        break;
    }

    return (KE_MSG_CONSUMED);
}

其中app_on_scanning_completed定义等于user_on_scanning_completed( )。也就是说,会回调user_on_scanning_completed( )函数处理。

该函先判断GAP的任务状态是否是GAP_ERR_CANCELED,如果是则说明找到一个符合条件的广播者,并且GAP已经取消了扫描任务,此时需要设置连接参数并发出连接消息 ,然后设置连接超时计时器。如果状态不是GAP_ERR_CANCELED,说明未有找到广播者,重新启动新的扫描。

/**
 ****************************************************************************************
 * @brief Called upon scan completion
 * @return void
 ****************************************************************************************
 */
#define USER_CON_TIMEOUT     700 //7 sec
void user_on_scanning_completed (uint8_t status)
{
    if(status == GAP_ERR_CANCELED)
    {
        app_easy_gap_start_connection_to();
        connection_timer = app_easy_timer(USER_CON_TIMEOUT, user_gapm_cancel);//设置连接超时时间,这里是7s。7s后如果未能连接上,则取消当前连接任务。
    }
    else
    {
        user_scan_start();
    }
    return;
}

连接消息发出后,会有两种结果一是收到来自 GAPC 的连接请求 (GAPC_CONNECTION_REQ_IND) 指示事件,二是定时器超时,取消连接任务。

当收到连接请求指示事件(GAPC_CONNECTION_REQ_IND) 的时候,会触发gapc_connection_req_ind_handler( )函数。该函数会读取当前TASK_APP任务状态,并把状态从APP_CONNECTABLE设置为APP_CONNECTED。
该函数最后通过EXECUTE_CALLBACK_PARAM1_PARAM2(app_on_connection, connection_idx, param),调用了user_on_connection()函数。

/**
 ****************************************************************************************
 * @brief Handles connection complete event from the GAP. Will enable profile.
 *
 * @param[in] msgid     Id of the message received.
 * @param[in] param     Pointer to the parameters of the message.
 * @param[in] dest_id   ID of the receiving task instance (TASK_GAP).
 * @param[in] src_id    ID of the sending task instance.
 *
 * @return If the message was consumed or not.
 ****************************************************************************************
 */
int gapc_connection_req_ind_handler(ke_msg_id_t const msgid,
                                           struct gapc_connection_req_ind const *param,
                                           ke_task_id_t const dest_id,
                                           ke_task_id_t const src_id)
{
    // Connection Index
    if (ke_state_get(dest_id) == APP_CONNECTABLE)
    {
        uint8_t connection_idx=KE_IDX_GET(src_id);
        ASSERT_WARNING(connection_idx<APP_EASY_MAX_ACTIVE_CONNECTION);
        app_env[connection_idx].conidx = connection_idx;
        
        if (connection_idx != GAP_INVALID_CONIDX)
        {
            app_env[connection_idx].connection_active=true;
            ke_state_set(TASK_APP, APP_CONNECTED);
            // Retrieve the connection info from the parameters
            app_env[connection_idx].conhdl = param->conhdl;
            app_env[connection_idx].peer_addr_type = param->peer_addr_type;
            memcpy(app_env[connection_idx].peer_addr.addr, param->peer_addr.addr, BD_ADDR_LEN);
            #if (BLE_APP_SEC)
            // send connection confirmation
                app_easy_gap_confirm(connection_idx, (enum gap_auth) app_sec_env[connection_idx].auth, GAP_AUTHZ_NOT_SET);
            #else // (BLE_APP_SEC)
                app_easy_gap_confirm(connection_idx, GAP_AUTH_REQ_NO_MITM_NO_BOND, GAP_AUTHZ_NOT_SET);  
            #endif
        }
        EXECUTE_CALLBACK_PARAM1_PARAM2(app_on_connection, connection_idx, param);
    }
    else
    {
        // APP_CONNECTABLE state is used to wait the GAP_LE_CREATE_CONN_REQ_CMP_EVT message
        ASSERT_ERR(0);
    }

    return (KE_MSG_CONSUMED);
}

在user_on_connection( )函数里,主机(client)调用app_easy_timer_cancel( )取消连接超时计时器,调用app_prf_enable( )启用配置文件和服务并初始化, 调用user_gattc_exc_mtu_cmd( ) 函数发出交换MTU。

/**
 *******主机(client)*************主机(client)**************主机(client)***************
 * @brief Handles connection event
 * @param[in]   connection_idx Connection index
 * @param[in]   param Parameters of connection
 * @return void
 ****************************************************************************************
 */
void user_on_connection(uint8_t connection_idx, struct gapc_connection_req_ind const *param)
{
    if (app_env[connection_idx].conidx != GAP_INVALID_CONIDX)
    {
        app_easy_timer_cancel(connection_timer);//取消定时器任务
        app_prf_enable (param->conhdl);//启用配置文件和服务并初始化
        user_gattc_exc_mtu_cmd(connection_idx);//发出交换MTU指令
        if ((user_default_hnd_conf.security_request_scenario==DEF_SEC_REQ_ON_CONNECT) && (BLE_APP_SEC))
        {//如果启用了安全选项,则发出启用安全的请求消息
             app_easy_security_request(connection_idx);
        }
    arch_printf("Device connected\r\n");//打印消息,设备已经连接
    }
    else
    {
        // No connection has been established, restart scanning  无连接建立,重新发出消息启动扫描
        user_scan_start();
    }    
}

以上分析了主机从扫描到广播者发出的广播数据开始到建立连接的全过程:扫描到广播数据---->通过匹配服务的UUID找到连接对象---->发出建立连接请求(向从机发出)---->收到连接请求消息指示(从机同意了)---->把状态从可连接切换为已连接,启用配置文件和服务并初始化、交换MTU,---->连接建立成功。
至此,设备建立连接完毕,主机(client)可以修改从机(server)的服务特征值(可写的情况下),从机也可以发出NOTIFY或INDICATION来和主机交互数据。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydgd118

您的鼓励是我最大的动力!谢赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值