[经验分享] 关于RSL10 SDK中Event Kernel部分的代码分析(下)

cruelfox   2021-6-13 19:01 楼主

  首先,把上篇写的重点再回顾一下:
(1) 底层 Event Kernel 是通过消息驱动的,消息的接收者称为 task, 但并非是 RTOS 中的任务概念。可以把一个 task 看作是一组消息处理函数(回调函数)的集合。每当执行 Kernel_Schedule() 函数的时候,消息逐个以传递给匹配的回调函数的方式被处理。
(2) 一个BLE应用需要创建一个 type 为 TASK_APP 的 task, 和系统内部的 task 进行消息交换,以操作BLE stack.
(3) 消息ID是从接收方 task 的默认消息处理函数集合中选择哪一个的依据。消息ID(包含了task index和消息序号),在SDK中定义。用户可以自行定义包含 TASK_ID_APP 的消息。
(4) 除了消息ID,消息处理函数可以从参数中的发送方 task id、接收方 task id 获取额外的信息。消息包含的数据长度和格式是自由的,由发送和接收双方约定。
 

  这篇再用一个例子(peripheral_server)看看 BLE 是怎么工作的。

  在 BLE_Initialize() 函数里面,发送了一个 GAPM_RESET_CMD 消息:

	/* Reset the stack */
    cmd = KE_MSG_ALLOC(GAPM_RESET_CMD, TASK_GAPM, TASK_APP, gapm_reset_cmd);
    cmd->operation = GAPM_RESET;

    /* Send the message */
    ke_msg_send(cmd);

  这个消息由 TASK_APP 发送给 TASK_GAPM (task type),带有 struct gapm_reset_cmd 结构类型的数据。手册中能找到这个命令的说明:

gapm_reset.PNG

  此处发送 GAPM_RESET_CMD 消息产生软复位。当完成以后,GAPM 任务会回应一个 GAPM_CMP_EVT 消息。

 

  在 BLE_Initialize() 中调用 ke_msg_send() 时,消息只是放入了队列。要待后面 Kernel_Schedule() 执行时才会处理消息。况且此时 ke_create_task(TASK_APP) 都还没有调用,消息通路建立不起来。

  当 main()Kernel_Schedule() 得到执行之后,GAPM 软复位执行。若 GAPM_CMP_EVT 消息回应(给TASK_APP接收),则对应的回调函数是?
  用 GDB 查看一下 appm_default_state[] 这个表就清楚了:

(gdb) print appm_default_state
$1 = {{id = 65535, func = 0x1003d1 <Msg_Handler>}
{id = 3328,    func = 0x100d31 <GAPM_CmpEvt>}
{id = 3356,    func = 0x100ced <GAPM_ProfileAddedInd>}
{id = 3585,    func = 0x100fb1 <GAPC_ConnectionReqInd>}
{id = 3584,    func = 0x100e51 <GAPC_CmpEvt>}
{id = 3587,    func = 0x100f41 <GAPC_DisconnectInd>}
{id = 3594,    func = 0x100dd9 <GAPC_GetDevInfoReqInd>}
{id = 3601,    func = 0x100e55 <GAPC_ParamUpdatedInd>}
{id = 3599,    func = 0x100e81 <GAPC_ParamUpdateReqInd>}
{id = 9217,    func = 0x100605 <Batt_EnableRsp_Server>}
{id = 9220,    func = 0x1005e5 <Batt_LevelNtfCfgInd>}
{id = 3091,    func = 0x100715 <GATTC_ReadReqInd>}
{id = 3093,    func = 0x100861 <GATTC_WriteReqInd>}
{id = 2817,    func = 0x1006e5 <GATTM_AddSvcRsp>}
{id = 3072,    func = 0x1009a9 <GATTC_CmpEvt>}
{id = 3095,    func = 0x1009e1 <GATTC_AttInfoReqInd>}
{id = 3841,    func = 0x1003d5 <APP_Timer>}
{id = 3842, func = 0x100469 <LED_Timer>}}

 

  从定义查找 GAPM_CMP_EVT 的值, 算得: 3328

enum gapm_msg_id
{
    /* Default event */
    /// Command Complete event
    GAPM_CMP_EVT = TASK_FIRST_MSG(TASK_ID_GAPM),

因此当 GAPM_CMP_EVT 消息回应给 TASK_APP 时,GAPM_CmpEvt() 函数就会被调用。

int GAPM_CmpEvt(ke_msg_id_t const msg_id,
                struct gapm_cmp_evt const *param,
                ke_task_id_t const dest_id, ke_task_id_t const src_id)
{
    struct gapm_set_dev_config_cmd *cmd;

    switch (param->operation)
    {
        /* A reset has occurred, configure the device and
         * start a kernel timer for the application */
        case (GAPM_RESET):
        {
            if (param->status == GAP_ERR_NO_ERROR)
            {
                /* Set the device configuration */
                cmd = KE_MSG_ALLOC(GAPM_SET_DEV_CONFIG_CMD, TASK_GAPM, TASK_APP,
                                   gapm_set_dev_config_cmd);
                memcpy(cmd, gapmConfigCmd,
                       sizeof(struct gapm_set_dev_config_cmd));
                free(gapmConfigCmd);

                /* Send message */
                ke_msg_send(cmd);

                /* Start a timer to be used as a periodic tick timer for
                 * application */
                ke_timer_set(APP_TEST_TIMER, TASK_APP, TIMER_200MS_SETTING);

                /* Start LED timer */
                ke_timer_set(LED_TIMER, TASK_APP, TIMER_200MS_SETTING);
            }
        }
        break;

        /* Device configuration updated */
        case (GAPM_SET_DEV_CONFIG):
        {
            /* Start creating the GATT database */
            ble_env[0].state = APPM_CREATE_DB;

            /* Add the first required service in the database */
            if (!Service_Add())
            {
                for (unsigned int i = 0; i < NUM_MASTERS; i++)
                {
                    /* If there are no more services to add, go to the ready
                     * state */
                    ble_env[i].state = APPM_READY;
                }

                /* Start advertising since there are no services to add
                 * to the attribute database */
                Advertising_Start();
            }
        }
        break;

        default:
        {
            /* No action required for other operations */
        }
        break;
    }

    return (KE_MSG_CONSUMED);
}

它根据消息的数据进行选择,那么看一下手册对 GAPM_CMP_EVT 的说明

gapm_cmp.PNG   这个消息是回应操作完成的意思,通过消息数据中的 operation 字段表明是什么操作完成。因为前面执行的是复位,所以这次当取 GAPM_RESET 分支。


  程序又判断了一下状态,如果没有错误,就发送 GAPM_SET_DEV_CONFIG_CMD 消息,对 GAPM 进行配置。这个消息的数据很多,在先前初始化过的 gapmConfigCmd 全局变量中。
  接着,程序设置了两个定时器,各自分别将在 200ms 之后产生消息 APP_TEST_TIMER, 和 LED_TIMER. 用定时器是在 Event Kernel 环境下产生延迟操作的标准做法。
 

  根据手册说明,当 GAPM_SET_DEV_CONFIG_CMD 消息处理后,也会回应 GAPM_CMP_EVT 消息,此时,上面 GAPM_CmpEvt() 函数中的另一个条件分支得以执行:调用了 Service_Add() 函数。

bool Service_Add(void)
{
    /* Check if another should be added in the database */
    if (appm_add_svc_func_list[ble_env[0].next_svc] != NULL)
    {
        /* Call the function used to add the required service */
        appm_add_svc_func_list[ble_env[0].next_svc] ();

        /* Select the next service to add */
        ble_env[0].next_svc++;
        return (true);
    }

    return (false);
}

  这个 Service_Add() 函数的写法比较特别,经分析,在第一次调用它的时候,将调用 Batt_ServiceAdd_Server() 函数。省略一些细节列出其代码如下:

void Batt_ServiceAdd_Server(void)
{
    struct bass_db_cfg *db_cfg;
    struct gapm_profile_task_add_cmd *req =
        KE_MSG_ALLOC_DYN(GAPM_PROFILE_TASK_ADD_CMD,
                         TASK_GAPM,
                         TASK_APP,
                         gapm_profile_task_add_cmd,
                         sizeof(struct bass_db_cfg));

    /* Fill message */
......
    /* Send the message */
    ke_msg_send(req);
}

 

  可见,添加 Battery Service 是通过发送 GAPM_PROFILE_TASK_ADD_CMD 消息实现的。

profile_add.PNG   这个消息会产生两个回应,第一个回应将引起回调 GAPM_ProfileAddedInd() 函数。

int GAPM_ProfileAddedInd(ke_msg_id_t const msg_id,
                         struct gapm_profile_added_ind const *param,
                         ke_task_id_t const dest_id,
                         ke_task_id_t const src_id)
{
    /* If the application is creating its attribute database, continue to add
     * services; otherwise do nothing. */
    if (ble_env[0].state == APPM_CREATE_DB)
    {
    	PRINTF("__GAPM_PROFILE_ADDED_IND\n");
        /* Add the next requested service */
        if (!Service_Add())
        {
            for (unsigned int i = 0; i < NUM_MASTERS; i++)
            {
                /* If there are no more services to add, go to the ready state
                 * */
                ble_env[i].state = APPM_READY;
            }

            /* No more services to add, start advertising */
            Advertising_Start();
        }
    }

    return (KE_MSG_CONSUMED);
}

 

  其中再次调用 Service_Add() 函数,这是第二次,将会引起调用 CustomService_ServiceAdd() 函数。在这个函数中通过向 TASK_GATTM 发送 GATTM_ADD_SVC_REQ 消息添加自定义的服务。

add_svc.PNG   GATTM 处理消息后将会回应 GATTM_ADD_SVC_RSP, 再由 GATTC_AddSvcRsp() 函数接收。

int GATTM_AddSvcRsp(ke_msg_id_t const msg_id,
                    struct gattm_add_svc_rsp const *param,
                    ke_task_id_t const dest_id,
                    ke_task_id_t const src_id)
{
    for (unsigned int i = 0; i < NUM_MASTERS; i++)
    {
        cs_env[i].start_hdl = param->start_hdl;
    }

    PRINTF("__CUSTOM SERVICE ADDED\n");
    /* Add the next requested service  */
    if (!Service_Add())
    {
        /* All services have been added, go to the ready state
         * and start advertising */
        for (unsigned int i = 0; i < NUM_MASTERS; i++)
        {
            ble_env[i].state = APPM_READY;
        }
        Advertising_Start();
    }

    return (KE_MSG_CONSUMED);
}

 

  第三次调用 Service_Add() 的时候,已经没有可添加的服务了,于是 Advertising_Start() 函数得到执行。

  可以看 Advertising_Start() 的实现,里面向 GAPM 发送了 GAPM_START_ADVERTISE_CMD 消息。
  这样 BLE 进入 advertising 状态,无线广播开始。在应用程序看来这是“后台”在进行的。只有特定事件产生了消息,才会执行回调函数。例如,当有 BLE central 设备发起连接之后,GAPM 将回应 GAPC_CONNECTIEN_REQ_IND 消息,被 GAPC_ConnectionReqInd() 函数处理。

int GAPC_ConnectionReqInd(ke_msg_id_t const msg_id,
                          struct gapc_connection_req_ind const *param,
                          ke_task_id_t const dest_id,
                          ke_task_id_t const src_id)
{
    if (Connected_Peer_Num() < NUM_MASTERS)
    {
        uint8_t device_indx;

        /* Search for the first disconnected link */
        for (device_indx = 0; device_indx < NUM_MASTERS; device_indx++)
        {
            if (ble_env[device_indx].state != APPM_CONNECTED)
            {
                break;
            }
        }

        PRINTF("__GAPC_CONNECTION_REQ_IND\n");
        /* Set connection index */
        ble_env[device_indx].conidx = KE_IDX_GET(src_id);

        /* Check if the received connection handle was valid */
        if (ble_env[device_indx].conidx != GAP_INVALID_CONIDX)
        {
            /* Change peer state in the app environment structure */
            ble_env[device_indx].state = APPM_CONNECTED;

            /* Retrieve the connection info from the parameters */
            ble_env[device_indx].conhdl = param->conhdl;

            Send_Connection_Confirmation(device_indx);
            BLE_SetServiceState(true, device_indx);
        }
    }

    return (KE_MSG_CONSUMED);
}

 

  在其中调用的 Send_Connection_Confirmation() 函数中,通过发送 GAPC_CONNECTION_CFM 消息,确认建立 BLE 连接。

void Send_Connection_Confirmation(uint8_t device_indx)
{
    struct gapc_connection_cfm *cfm;

    /* Allocate connection confirmation message */
    cfm = KE_MSG_ALLOC(GAPC_CONNECTION_CFM,
                       KE_BUILD_ID(TASK_GAPC, ble_env[device_indx].conidx),
                       KE_BUILD_ID(TASK_APP, device_indx), gapc_connection_cfm);

    cfm->ltk_present = false;

    cfm->svc_changed_ind_enable = 0;
    cfm->pairing_lvl = GAP_PAIRING_BOND_UNAUTH;

    /* Send the message */
    ke_msg_send(cfm);
}

 

  还有许多 BLE 服务的访问,也是类似的,通过发送命令消息、接收应答消息实现。这里就不继续列举了。

  如此这般,就将步骤拆散到单独的回调函数里去了。你们觉得绕吗?

回复评论 (4)

楼主用了一个例(peripheral_server)大家讲了BLE 是怎么工作的了

不错

点赞  2021-6-13 21:23

请问解释各种命令的图片是来自那个资料

点赞  2021-7-2 18:15
引用: 爱吃土豆 发表于 2021-7-2 18:15 请问解释各种命令的图片是来自那个资料

文档压缩包里面,CEVA目录下。

点赞  2021-7-2 18:52

楼主追踪的两贴,我看完 了 ,于是 我看   完了。。已经绕混了。。

点赞  2021-7-3 00:12
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复