NXP

NXP QN902X蓝牙QPPS 流程的理解

2019-07-12 11:28发布


首先烧录程序,完成后用light blue查看服务。

在profile中只有一个服务,UUID为128位的UUID,但是可以转换为16位UUID:FEE9,在这个服务下包含有8个特征,其中第一个特征UUID为9600结尾的一个128位UUID,是用于接收APP发送的数据的特征,在APP端显示为可写的一个特征值。后面是7个用于发送数据到APP的特征值,特征值UUID后四位由9601递增至9607,一共有七个特征值。这个特征值的数目实际是可以修改的,可以改为1~7,在usr_config.h中有如下定义,更改QPPS_NOTIFY_NUM的数值就可以改变发送特征值的数目。
  1. ///Quintic private profile Role
  2. #define CFG_PRF_QPPS
  3. #define QPPS_NOTIFY_NUM     7
  4. //#define CFG_TASK_QPPS     TASK_PRF1
复制代码
app向第一个特征值中写入数据,发送,则系统会调用到gatt_write_cmd_ind_handler函数处理APP端的操作命令。
  1. static int gatt_write_cmd_ind_handler(ke_msg_id_t const msgid,
  2.                                       struct gatt_write_cmd_ind const *param,
  3.                                       ke_task_id_t const dest_id,
  4.                                       ke_task_id_t const src_id)
  5. {
  6.     uint8_t status = PRF_ERR_OK;

  7.     if (param->conhdl == qpps_env.conhdl)
  8.     {
  9.         // Client Char. Configuration
  10.                 uint8_t char_index = param->handle - (qpps_env.shdl + QPPS_IDX_VAL_NTF_CFG);
  11.         if ((param->handle > (qpps_env.shdl + QPPS_IDX_VAL_CHAR)) && ((char_index % 3) == 0))
  12.         {
  13.             uint16_t value = 0x0000;

  14.             //Extract value before check
  15.             memcpy(&value, &(param->value), sizeof(uint16_t));

  16.             if ((value == PRF_CLI_STOP_NTFIND) || (value == PRF_CLI_START_NTF))
  17.             {
  18.                 if (value == PRF_CLI_STOP_NTFIND)
  19.                 {
  20.                     qpps_env.features &= ~(QPPS_VALUE_NTF_CFG << (char_index / 3));
  21.                 }
  22.                 else //PRF_CLI_START_NTF
  23.                 {
  24.                     qpps_env.features |= QPPS_VALUE_NTF_CFG << (char_index / 3);
  25.                 }
  26.             }
  27.             else
  28.             {
  29.                 status = PRF_APP_ERROR;
  30.             }

  31.             if (status == PRF_ERR_OK)
  32.             {
  33.                 uint8_t *old_value;
  34.                 atts_size_t length;

  35.                 attsdb_att_get_value(param->handle, &length, &old_value);
  36.                 if (value != co_read16p(old_value))
  37.                 {
  38.                     //Update the attribute value
  39.                     attsdb_att_set_value(param->handle, sizeof(uint16_t), (uint8_t *)&value);

  40.                     if(param->last)
  41.                     {
  42.                         //Inform APP of configuration change
  43.                         struct qpps_cfg_indntf_ind * ind = KE_MSG_ALLOC(QPPS_CFG_INDNTF_IND,
  44.                                                                         qpps_env.appid, TASK_QPPS,
  45.                                                                         qpps_cfg_indntf_ind);

  46.                         ind->char_index = (char_index / 3);
  47.                         memcpy(&ind->cfg_val, &value, sizeof(uint16_t));

  48.                         ke_msg_send(ind);
  49.                     }
  50.                 }
  51.             }
  52.         }
  53.         else if (param->handle == (qpps_env.shdl + QPPS_IDX_RX_DATA_VAL))
  54.         {
  55.                         if (param->length <= QPP_DATA_MAX_LEN)
  56.                         {
  57.                                 //inform APP of configuration change
  58.                                 struct qpps_data_val_ind * ind = KE_MSG_ALLOC_DYN(QPPS_DAVA_VAL_IND,
  59.                                                                                                                                   qpps_env.appid,
  60.                                                                                                                                   TASK_QPPS,
  61.                                                                                                                                   qpps_data_val_ind, param->length);

  62.                                 memcpy(&ind->conhdl, &(qpps_env.conhdl), sizeof(uint16_t));
  63.                                 //Send received data to app value
  64.                                 ind->length = param->length;
  65.                                 memcpy(ind->data, param->value, param->length);

  66.                                 ke_msg_send(ind);
  67.                         }
  68.                         else
  69.                         {
  70.                                 status = QPPS_ERR_RX_DATA_EXCEED_MAX_LENGTH;
  71.                         }
  72.         }
  73.                 else
  74.                 {
  75.                         status = QPPS_ERR_INVALID_PARAM;
  76.                 }
  77.     }

  78.     if (param->response)
  79.     {
  80.         //Send write response
  81.         atts_write_rsp_send(qpps_env.conhdl, param->handle, status);
  82.     }

  83.     return (KE_MSG_CONSUMED);
  84. }
复制代码
由于app对特征值9600的写入操作实际上是对特征中QPPS_IDX_RX_DATA_VAL一值进行了写入操作,所以函数中以下代码就是经过了一系列的接收操作后,我们能在程序中看到的第一入口。程序中构建了一条标识为QPPS_DAVA_VAL_IND的消息,在信息中填入了从app处获取到的写入内容。
  1. if (param->handle == (qpps_env.shdl + QPPS_IDX_RX_DATA_VAL))
  2.         {
  3.                         if (param->length <= QPP_DATA_MAX_LEN)
  4.                         {
  5.                                 //inform APP of configuration change
  6.                                 struct qpps_data_val_ind * ind = KE_MSG_ALLOC_DYN(QPPS_DAVA_VAL_IND,
  7.                                                                                                                                   qpps_env.appid,
  8.                                                                                                                                   TASK_QPPS,
  9.                                                                                                                                   qpps_data_val_ind, param->length);

  10.                                 memcpy(&ind->conhdl, &(qpps_env.conhdl), sizeof(uint16_t));
  11.                                 //Send received data to app value
  12.                                 ind->length = param->length;
  13.                                 memcpy(ind->data, param->value, param->length);

  14.                                 ke_msg_send(ind);
  15.                         }
  16.                         else
  17.                         {
  18.                                 status = QPPS_ERR_RX_DATA_EXCEED_MAX_LENGTH;
  19.                         }
  20.         }
  21.                 else
  22.                 {
  23.                         status = QPPS_ERR_INVALID_PARAM;
  24.                 }
  25.     }
复制代码
通过查看QPPS_DAVA_VAL_IND,得知该消息处理函数为app_qpps_data_ind_handler。根据函数描述,app发送数据到开发板后,会在串口中输出数据,打开串口,设置好波特率(9600),可以看到app端发送数据的字节长度和第一个数据的16进制。如果需要取出完整数据,根据param->length把param->data数组的数据都取出来,就是完整的数据了。
  1. int app_qpps_data_ind_handler(ke_msg_id_t const msgid,
  2.                               struct qpps_data_val_ind *param,
  3.                               ke_task_id_t const dest_id,
  4.                               ke_task_id_t const src_id)
  5. {
  6.     if (param->length > 0)
  7.     {
  8.         QPRINTF("len=%d, I%02X", param->length, param->data[0]);
  9.     }
  10.     QPRINTF(" ");

  11.     return (KE_MSG_CONSUMED);
  12. }
复制代码
对于发送数据,昆天科做了一个很有意思的算法,让app在配置完所有的notify后持续发送递增数据到每一个发送特征值中,所以开启这个发送的办法在与打开所有的notify。我们看一下打开notify的操作,这个操作会触发gatt_write_cmd_ind_handler处理函数,代码片如下:
  1. uint8_t char_index = param->handle - (qpps_env.shdl + QPPS_IDX_VAL_NTF_CFG);
  2.         if ((param->handle > (qpps_env.shdl + QPPS_IDX_VAL_CHAR)) && ((char_index % 3) == 0))
  3.         {
  4.             uint16_t value = 0x0000;

  5.             //Extract value before check
  6.             memcpy(&value, &(param->value), sizeof(uint16_t));

  7.             if ((value == PRF_CLI_STOP_NTFIND) || (value == PRF_CLI_START_NTF))
  8.             {
  9.                 if (value == PRF_CLI_STOP_NTFIND)
  10.                 {
  11.                     qpps_env.features &= ~(QPPS_VALUE_NTF_CFG << (char_index / 3));
  12.                 }
  13.                 else //PRF_CLI_START_NTF
  14.                 {
  15.                     qpps_env.features |= QPPS_VALUE_NTF_CFG << (char_index / 3);
  16.                 }
  17.             }
  18.             else
  19.             {
  20.                 status = PRF_APP_ERROR;
  21.             }

  22.             if (status == PRF_ERR_OK)
  23.             {
  24.                 uint8_t *old_value;
  25.                 atts_size_t length;

  26.                 attsdb_att_get_value(param->handle, &length, &old_value);
  27.                 if (value != co_read16p(old_value))
  28.                 {
  29.                     //Update the attribute value
  30.                     attsdb_att_set_value(param->handle, sizeof(uint16_t), (uint8_t *)&value);

  31.                     if(param->last)
  32.                     {
  33.                         //Inform APP of configuration change
  34.                         struct qpps_cfg_indntf_ind * ind = KE_MSG_ALLOC(QPPS_CFG_INDNTF_IND,
  35.                                                                         qpps_env.appid, TASK_QPPS,
  36.                                                                         qpps_cfg_indntf_ind);

  37.                         ind->char_index = (char_index / 3);
  38.                         memcpy(&ind->cfg_val, &value, sizeof(uint16_t));

  39.                         ke_msg_send(ind);
  40.                     }
  41.                 }
  42.             }
  43.         }
复制代码
当app开启notify的时候,会带来一个value,value代表着是开启还是关闭notify,所以首先要进行判断是哪一个特征中开启或者关闭了notify,然后判断是否需要进行开启和关闭操作(检查之前状态是否与当前操作冲突),如果需要开启或者关闭,则会发出一个标识为QPPS_CFG_INDNTF_IND的消息,消息中填入了操作的特征序号和值。
消息QPPS_CFG_INDNTF_IND的处理函数为app_qpps_cfg_indntf_ind_handler,此消息为指示性消息,notify已经在刚才 的处理中开启,所以本函数主要任务是指示了哪一个消息被开启或关闭,在判断所有的notify被开启后,函数调用了另一个函数app_test_send_data(app_qpps_env->tx_char_num - 1);,发送数据到9601~9607七个特征值去。
  1. int app_qpps_cfg_indntf_ind_handler(ke_msg_id_t const msgid,
  2.                                     struct qpps_cfg_indntf_ind *param,
  3.                                     ke_task_id_t const dest_id,
  4.                                     ke_task_id_t const src_id)
  5. {
  6.     if (app_qpps_env->conhdl == param->conhdl)
  7.     {
  8.         if (param->cfg_val == PRF_CLI_START_NTF)
  9.         {
  10.             QPRINTF("param->char_index %d ",param->char_index);
  11.                                                 app_qpps_env->features |= (QPPS_VALUE_NTF_CFG << param->char_index);
  12.             // App send data if all of characteristic have been configured
  13.                                                 QPRINTF("app_qpps_env->features 0x%X : 0x%X ",app_qpps_env->features,app_qpps_env->tx_char_num);
  14.             if (get_bit_num(app_qpps_env->features) == app_qpps_env->tx_char_num)
  15.             {
  16.                 app_qpps_env->char_status = app_qpps_env->features;
  17.                 app_test_send_data(app_qpps_env->tx_char_num - 1);
  18.             }
  19.         }
  20.         else
  21.         {
  22.             app_qpps_env->features &= ~(QPPS_VALUE_NTF_CFG << param->char_index);
  23.             app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG << param->char_index);
  24.         }
  25.     }

  26.     return (KE_MSG_CONSUMED);
  27. }
复制代码
函数通过对cnt计数和一些条件判断,对特征值依次发送数据。
  1. static void app_test_send_data(uint8_t max)
  2. {
  3.     uint8_t cnt;

  4.     for (cnt = 0; (max != 0) && cnt < app_qpps_env->tx_char_num; cnt++)
  5.     {
  6.         if ((app_qpps_env->char_status >> cnt) & QPPS_VALUE_NTF_CFG)
  7.         {
  8.             static uint8_t val[] = {0, '0', '1', '2','3','4','5','6','7','8','9','8','7','6','5','4','3','2','1','0'};

  9.             // Increment the first byte for test 
  10.             val[0]++;

  11.             max--;
  12.             // Allow next notify until confirmation received in this characteristic
  13.             app_qpps_env->char_status &= ~(QPPS_VALUE_NTF_CFG << cnt);
  14.             app_qpps_data_send(app_qpps_env->conhdl, cnt, sizeof(val), val);
  15.         }
  16.                                 QPRINTF("send to cnt:%d,max %d ",cnt,max);
  17.     }
  18.                 QPRINTF("app_qpps_env->char_status: %X ",app_qpps_env->char_status);
  19. }
复制代码
发送数据完成,系统会发出QPPS_DATA_SEND_CFM消息作为响应,于是在消息对应的处理函数app_qpps_data_send_cfm_handler中,再次发起消息发送。
  1. int app_qpps_data_send_cfm_handler(ke_msg_id_t const msgid,
  2.                                    struct qpps_data_send_cfm *param,
  3.                                    ke_task_id_t const dest_id,
  4.                                    ke_task_id_t const src_id)
  5. {
  6.     if (app_qpps_env->conhdl == param->conhdl && param->status == PRF_ERR_OK)
  7.     {
  8.         // Allow new notify
  9.         app_qpps_env->char_status |= (1 << param->char_index);
  10.                                 QPRINTF("app_qpps_env->char_status %X ",app_qpps_env->char_status);

  11.         // Send next group data until current data have been sent
  12.         if (get_bit_num(app_qpps_env->char_status) == (app_qpps_env->tx_char_num - 1))
  13.         {
  14.                                         QPRINTF("t:app_qpps_env->char_status %X ",app_qpps_env->char_status);
  15.             app_test_send_data(app_qpps_env->tx_char_num - 1);
  16.         }
  17.     }
  18.     else
  19.     {
  20.         QPRINTF("QPPS send error %d. ", param->status);
  21.     }

  22.     return (KE_MSG_CONSUMED);
  23. }
复制代码