BlueX BLE Master端應用程式範例解說

關鍵字 :BlueXBLE Master
  1. 動機:

看完Eric_kuang提供的”BlueX SDK 2.1 Eclipse 開發環境建置”後,想說之前都是使用nRF Connect進行BLE 連線與控制,這次想來挑戰使用BLE Master端進行與BLE Slave設備連線與控制,目前已經成功;因此分享大家我的成果與經驗;此篇博文將為各位介紹BlueX BLE Master 端程式範例解說,並概述BLE Master端如何進行掃描Slave端設備裝置將MAC Address、廣播封包、Service、Characteristic等等資料讀取,並測試Read以及Write功能,讓有需要的人能夠快速上手。

 

  1. 功能說明

之前有分享過”Qualcomm QCA4020 實例-2(BLE Lights應用)”此次我們透過創建的BLE Master進行控制此BLE Light開燈與關燈功能。

 我們會使用開發板上三個Button進行以下幾個功能

  1. Button 1 進行設備掃描、連線、讀取Service、Characteristic以及Read設備狀態。
  2. Button 2 進行Write設備控制。
  3. Button 3 進行設備斷連線。

 

程式碼 1為進行相關button設定,由於我們主要說明BLE Master所以這邊就不多加解釋。 

#define BTN1_PIN_NUM        15

#define BTN2_PIN_NUM        17

#define BTN3_PIN_NUM        23

void hal_button1_cb(void)

{

    LOG(LOG_LVL_INFO,"hal_button1_cb\n");

    xEventGroupSetBits(BLE_Register_Signal_Event, Scan_Device);

}

void hal_button2_cb(void)

{

    LOG(LOG_LVL_INFO,"hal_button2_cb\n");

    xEventGroupSetBits(BLE_Register_Signal_Event, Control_Device);

}

void hal_button3_cb(void)

{

     LOG(LOG_LVL_INFO,"hal_button3_cb\n");

     xEventGroupSetBits(BLE_Register_Signal_Event, Disconnect_Device);

}

void hal_init_buttons(void)

{  

     LOG(LOG_LVL_INFO,"hal_init_buttons\n");

    //1. io dir

     io_cfg_input(BTN1_PIN_NUM);

    io_cfg_input(BTN2_PIN_NUM);

    io_cfg_input(BTN3_PIN_NUM);

    //2. pull

    io_pin_pull_write(BTN1_PIN_NUM,IO_PULL_UP);

    io_pin_pull_write(BTN2_PIN_NUM,IO_PULL_UP);

    io_pin_pull_write(BTN3_PIN_NUM,IO_PULL_UP);

    //3. int cfg

    io_ext_int_cfg(BTN1_PIN_NUM,EXT_INT_TRIGGER_NEG_EDGE,hal_button1_cb);

    io_ext_int_cfg(BTN2_PIN_NUM,EXT_INT_TRIGGER_NEG_EDGE,hal_button2_cb);

    io_ext_int_cfg(BTN3_PIN_NUM,EXT_INT_TRIGGER_NEG_EDGE,hal_button3_cb);

    //4. en

    io_ext_int_en(BTN1_PIN_NUM,true);

    io_ext_int_en(BTN2_PIN_NUM,true);

    io_ext_int_en(BTN3_PIN_NUM,true);

}

 

程式碼 1

 

  

  1. 流程圖

此流程可讓看官們明瞭相關步驟的順序,後續將敘述BlueX BLE Master流程圖中各自步驟讓各位看官們瞭解,如何獲得相對應Characteristic的Attribute Handle,實現BLE Master對BLE Slave設備進行操作動作。

 

流程圖 (圖1)

 

 

 

  1. 創建BLE Master端規則與相關功能
  1. 由於我們目標是做BLE Master端,因此必須於osapp_utils_set_dev_init函式進行設定將規則設為GAP_ROLE_CENTRAL。

osapp_utils_set_dev_init(GAP_ROLE_CENTRAL, GAPM_CFG_ADDR_PUBLIC);

程式碼 2

 






 

  1. 創建相對應功能的返回函式

static osapp_msg_handler_table_t const handler_table[]=

{

    {GAPM_CMP_EVT,                  (osapp_msg_handler_t)osapp_gapm_cmp_evt_handler},

    {GAPM_ADV_REPORT_IND,           (osapp_msg_handler_t)osapp_gapm_scan_adv_report_ind_handler},

     {GATTC_DISC_SVC_IND,           (osapp_msg_handler_t)gattc_cmp_disc_handler},

     {GATTC_DISC_CHAR_IND,          (osapp_msg_handler_t)gattc_cmp_disc_char_handler},

     {GATTC_READ_IND,               (osapp_msg_handler_t)osapp_gattc_read_ind_handler},

     {GATTC_CMP_EVT,                (osapp_msg_handler_t)osapp_gattc_cmp_evt_handler},

};

程式碼 3
  1. 當GAPM事件完成時會呼叫osapp_gapm_cmp_evt_handler函式,如:BLE設備連線成功
  2. 當掃描設備完成時會呼叫osapp_gapm_scan_adv_report_ind_handler函式
  3. 掃描設備Service完成時會呼叫gattc_cmp_disc_handler函式
  4. 掃描設備Characteristic完成時會呼叫gattc_cmp_disc_char_handler函式
  5. 操作Read功能成功時會呼叫osapp_gattc_read_ind_handler函式
  6. 操作Write功能成功時會呼叫osapp_gattc_cmp_evt_handler函式
  1. 建立掃描設備功能,start_scan_cmd_send()此函式進行附近BLE設備廣播封包掃描,掃描到的設備資訊將會返回到osapp_gapm_scan_adv_report_ind_handler函式。

void start_scan_cmd_send()

{

     LOG_I("start_scan_cmd_send \n");

    struct gapm_start_scan_cmd *cmd = AHI_MSG_ALLOC(GAPM_START_SCAN_CMD,TASK_ID_GAPM,gapm_start_scan_cmd);

    cmd->op.code = GAPM_SCAN_ACTIVE;                 

    cmd->op.addr_src = GAPM_STATIC_ADDR;

    cmd->interval = 100;

    cmd->window = 50;

    cmd->mode = GAP_GEN_DISCOVERY;

    cmd->filt_policy = SCAN_ALLOW_ADV_ALL;

    cmd->filter_duplic = SCAN_FILT_DUPLIC_EN;

    osapp_ahi_msg_send(cmd,sizeof(struct gapm_start_scan_cmd),portMAX_DELAY);

}

程式碼 4

 

  1. osapp_gapm_scan_adv_report_ind_handler函式,掃描到的BLE設備廣播封包資料都會於此函示顯示,由於附近可能會有很多BLE設備因此我們進行RSSI過濾以及Local Name來進行設備名稱判斷,經由判斷與過濾後可以找到目標設備的BLE Mac Address,透過osapp_start_connect函式進行設備連線。

PS:找到設備後記得做停止掃描osapp_gapm_cancel函式,否則會一直掃描附近設備。

static void osapp_gapm_scan_adv_report_ind_handler(ke_msg_id_t const msgid, adv_report_t const *param,ke_task_id_t const dest_id,ke_task_id_t const src_id)

{

     if ((int8_t)(param->rssi) >= -70){    //將附近的RSSI>-70的設備進行顯示

         int8_t ret = 0;

         LOG_I(,"scaned adv addr:");

         //顯示BLE設備的BLE MAC Address與RSSI資訊

         LOG_I("MAC ADDR:%02x:%02x:%02x:%02x:%02x:%02x, rssi: %02d\r\n", param->adv_addr.addr[0],param->adv_addr.addr[1],param->adv_addr.addr[2],param->adv_addr.addr[3],param->adv_addr.addr[4],param->adv_addr.addr[5],(int8_t)(param->rssi));

         if(strstr((char *)(param->data),"QCA402"))//過濾Local Name名稱

         {

            HEXDUMP((char *)param->data, param->data_len, true, false);

              /*stop scan*/

            osapp_gapm_cancel();

              /*connect device*/

            osapp_start_connect((uint8_t *)&(param->adv_addr.addr)[0]);

         }

    }

}

程式碼 5

 

  1. osapp_gapm_cancel函式,停止掃描附近BLE設備。

void osapp_gapm_cancel(){

    struct gapm_cancel_cmd *cmd = AHI_MSG_ALLOC(GAPM_CANCEL_CMD,TASK_ID_GAPM,gapm_cancel_cmd);

    cmd->operation = GAPM_CANCEL;

    osapp_ahi_msg_send(cmd,sizeof(struct gapm_cancel_cmd),portMAX_DELAY);

}

程式碼 6

 

  1. osapp_start_connect函式,透過找尋到的BLE Mac Address進行設備連線,設備連線成功會進入osapp_gapm_cmp_evt_handler函式。

void osapp_start_connect(uint8_t *addr){

    struct gapm_start_connection_cmd* cmd = AHI_MSG_ALLOC_DYN(GAPM_START_CONNECTION_CMD, TASK_ID_GAPM, gapm_start_connection_cmd, sizeof(struct gap_bdaddr));

    cmd->op.code = GAPM_CONNECTION_DIRECT;

    cmd->op.addr_src = GAPM_STATIC_ADDR;

    cmd->op.state = 0;

    cmd->scan_interval = 0x20;

    cmd->scan_window = 0x20;

    cmd->con_intv_min = 400;

    cmd->con_intv_max = 400;

    cmd->con_latency = 0;

    cmd->superv_to = 300;

    cmd->ce_len_min = 0;

    cmd->ce_len_max = 0;

    cmd->nb_peers = 1;

    cmd->peers[0].addr_type = 0;

    memcpy(cmd->peers[0].addr.addr,addr, GAP_BD_ADDR_LEN);

    os_ahi_msg_send(cmd,portMAX_DELAY);

}

程式碼 7

 

 

 

  1. osapp_gapm_cmp_evt_handler函式,連線成功後會進入GAPM_CONNECTION_DIRECT,我們利用ble_disc_service與ble_disc_characteristic兩個函式進行找尋BLE設備的Service以及Characteristic,以及相對應的Characteristic handle可達到後續的Read與Write功能。

 

static void osapp_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) {

        case GAPM_SET_DEV_CONFIG:

            LOG_W(" operation-GAPM_SET_DEV_CONFIG:0x%x",param->operation);

            break;

        case GAPM_CONNECTION_SELECTIVE:

            break;

         case GAPM_RESET:

            LOG_W(" operation-GAPM_RESET:0x%x",param->operation);

            if( param->status == GAP_ERR_NO_ERROR ) {

               // Set Device configuration

              osapp_set_dev_config();

            }

              break;

         case GAPM_CONNECTION_DIRECT:

              ble_disc_service();

              ble_disc_characteristic();

              break;

         case GAPM_SCAN_PASSIVE:

              break;

         case GAPM_ADV_UNDIRECT:

             LOG_I("adv status:%d",param->status);

             break;

        default:

            LOG_W("gapm_cmp_evt operation:0x%x",param->operation);

            break;

  }

}

程式碼 8

 

  1. ble_disc_service函式,進行Service掃描,掃描成功的資訊會返回至gattc_cmp_disc_handler函式。

static void ble_disc_service(){

  struct gattc_disc_cmd *disc_cmd = AHI_MSG_ALLOC(GATTC_DISC_CMD,TASK_ID_GATTC,gattc_disc_cmd);

     static uint16_t disc_seq_num = 0;

     disc_cmd->operation = GATTC_DISC_ALL_SVC;

     disc_cmd->uuid_len    = ATT_UUID_16_LEN;

     disc_cmd->seq_num = disc_seq_num++;

     disc_cmd->start_hdl   = 1;

     disc_cmd->end_hdl = 0xFFFF;

     os_ahi_msg_send(disc_cmd,portMAX_DELAY);

}

程式碼 9

 

  1. gattc_cmp_disc_handler函式,可將找到設備的Service的UUID。

static void gattc_cmp_disc_handler(ke_msg_id_t const msgid, struct gattc_disc_svc_ind const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id){

     LOG_I("gattc_cmp_disc_handler");

     LOG_I("uuid_len = %d",param->uuid_len);

     LOG_I("start_hdl = %d",param->start_hdl);

     LOG_I("end_hdl = %d",param->end_hdl);

     if(param->uuid_len == ATT_UUID_16_LEN) {  

LOG_I("ATT_UUID_16_LEN uuid = 0x%02 %02x",param->uuid[1],param->uuid[0]);

     }

     else if(param->uuid_len == ATT_UUID_32_LEN) {

         LOG_RAW("ATT_UUID_32_LEN uuid = 0x");

         for(uint8_t uuid_t = 4; uuid_t > 0; uuid_t--) {

              LOG_RAW("%02x ",param->uuid[uuid_t]);

         }

         LOG_RAW("\n");

     }

     else if(param->uuid_len == ATT_UUID_128_LEN) {

         LOG_RAW("ATT_UUID_128_LEN uuid = 0x");

         for(uint8_t uuid_t = 15; uuid_t > 0; uuid_t--) {

              LOG_RAW("%02x ",param->uuid[uuid_t]);

         }

         LOG_RAW("\n");

     }

     Disc_Char_Handler_End_Hdl = param->end_hdl;

}

程式碼 10

 

  1. ble_disc_characteristic,進行Characteristic掃描,掃描成功的資訊會返回至gattc_cmp_disc_handler函式。

static void ble_disc_characteristic(){

  struct gattc_disc_cmd *disc_cmd = AHI_MSG_ALLOC(GATTC_DISC_CMD,TASK_ID_GATTC,gattc_disc_cmd);

     static uint16_t disc_seq_num = 0;

     disc_cmd->operation = GATTC_DISC_ALL_CHAR;

     disc_cmd->uuid_len    = ATT_UUID_16_LEN;

     disc_cmd->seq_num = disc_seq_num++;

     disc_cmd->start_hdl   = 1;

     disc_cmd->end_hdl = 0xFFFF;

     os_ahi_msg_send(disc_cmd,portMAX_DELAY);

}

程式碼 11

 

  1. gattc_cmp_disc_char_handler函式,可將找到設備的Characteristic的UUID與handle,我們可以透過此函式找到所需要的Characteristic pointer attribute handle,進行後續的Read與Write功能。

我們找到UUID 0x4031的pointer attribute handle,利用osapp_gatt_write函式執行Write功能。然而UUID 0x4032的pointer attribute handle,利用osapp_gatt_read函式執行Read功能。

static void gattc_cmp_disc_char_handler(ke_msg_id_t const msgid, struct gattc_disc_char_ind const *param, ke_task_id_t const dest_id, ke_task_id_t const src_id){

    LOG_I("gattc_cmp_disc_char_handler");

     LOG_I("uuid_len = %d",param->uuid_len);

     LOG_I("prop = %d",param->prop);

     LOG_I("attr_hdl = %d",param->attr_hdl);

     LOG_I("pointer_hdl = %d",param->pointer_hdl);

     if(param->uuid_len == ATT_UUID_16_LEN) {

          if(((uint16_t*)&(param->uuid))[0] == 0x4031) {

              WRITE_HANDLE = param->pointer_hdl;

          }

          else if(((uint16_t*)&(param->uuid))[0] == 0x4032){

              READ_HANDLE = param->pointer_hdl;

         }

     }

     else if(param->uuid_len == ATT_UUID_32_LEN)

     {

         LOG_RAW("ATT_UUID_32_LEN uuid = 0x");

         for(uint8_t uuid_t = 4; uuid_t > 0; uuid_t--) {

              LOG_RAW("%02x",param->uuid[uuid_t]);

         }

         LOG_RAW("\n");

     }

     else if(param->uuid_len == ATT_UUID_128_LEN) {

         LOG_RAW("ATT_UUID_128_LEN uuid = 0x");

         for(uint8_t uuid_t = 16; uuid_t > 0; uuid_t--) {

              LOG_RAW("%02x ",param->uuid[uuid_t-1]);

         }

         LOG_RAW("\n");

     }

     if(Disc_Char_Handler_End_Hdl == param->pointer_hdl) {

           LOG_I("scan end READ_HANDLE = %d",READ_HANDLE);

           osapp_gatt_read(GATTC_READ,READ_HANDLE,peer_conn_id);

     }

}

程式碼 12

 

  1. osapp_gatt_read函式,op帶入GATTC_READ操作指令,hdl帶入找尋到的UUID 0x4032的pointer attribute handle,由於只要連線一個設備所以conn_id帶入0,函式成功後會將讀取到的資訊返回到osapp_gattc_read_ind_handler函式。

 

void osapp_gatt_read(uint8_t op, uint8_t hdl, uint8_t conn_id){

    static uint16_t seq_num = 0;

    struct gattc_read_cmd* cmd  = AHI_MSG_ALLOC_DYN(GATTC_READ_CMD, KE_BUILD_ID(TASK_ID_GATTC, conn_id), gattc_read_cmd, 5);

    cmd->operation = op;

    cmd->nb                             = 1;

    cmd->req.simple.offset              = 0;

    cmd->req.simple.length              = 0;

    cmd->req.simple.handle              = hdl;

    os_ahi_msg_send(cmd,portMAX_DELAY);

}

程式碼 13

 

  1. osapp_gattc_read_ind_handler可以將讀到的封包數值顯示出來,我們已知QCA4020 BLE Light協議,因此知道數值[2]的值為Light的狀態。

 

static void osapp_gattc_read_ind_handler(ke_msg_id_t const msgid,struct gattc_read_ind const *param,ke_task_id_t const dest_id,ke_task_id_t const src_id){

   uint8_t state = ke_state_get(dest_id);

         for(uint8_t i = 0; i < param->length; i++) {

             LOG_RAW("0x%02x", param->value[i]);

         }

         LOG_RAW("\r\n");

         if(param->length > 0) {

         Light_Status = param->value[2];

         }

}

程式碼 14

 

  1. osapp_gatt_write 函式,執行Write功能,op帶入GATTC_WRITE_NO_RESPONSE操作指令,hdl帶入找尋到的UUID 0x4031的pointer attribute handle,由於只要連線一個設備所以conn_id帶入0,length為要傳輸數據的長度,pdat為傳輸之數據,成功後返回osapp_gattc_cmp_evt_handler函式。

 

void osapp_gatt_write(uint8_t op, uint8_t hdl, uint8_t conn_id, uint8_t length, const uint8_t* pdat){

    static uint16_t seq_num = 0;

    struct gattc_write_cmd* cmd  = AHI_MSG_ALLOC_DYN(GATTC_WRITE_CMD, KE_BUILD_ID(TASK_ID_GATTC, conn_id), gattc_write_cmd, length);

    memset(cmd, 0, (sizeof(struct gattc_write_cmd)+length));

    cmd->operation = op;

    cmd->auto_execute = 1;

    cmd->seq_num = seq_num++;

    cmd->handle = hdl;

    cmd->offset = 0;

    cmd->length = length;

    cmd->cursor = 0;

    memcpy(cmd->value, pdat, length);

    os_ahi_msg_send(cmd,portMAX_DELAY);

}

程式碼 15

 

  1. osapp_gattc_cmp_evt_handler函式,可知道Write功能是否發送成功。

static void osapp_gattc_cmp_evt_handler(ke_msg_id_t const msgid, struct gapc_cmp_evt const *param,ke_task_id_t const dest_id,ke_task_id_t const src_id){

    switch(param->operation)

    {

        case GAPC_DISCONNECT:

          LOG_RAW("GAPC_DISCONNECT");

          break;

        case GATTC_SDP_DISC_SVC:

          LOG_RAW("GATTC_SDP_DISC_SVC ");

          break;

        case GATTC_WRITE:

          LOG_RAW("%s, %dGATTC_WRITE  \n",__FUNCTION__,__LINE__);

          break;

        case GATTC_WRITE_NO_RESPONSE:

          LOG_RAW("GATTC_WRITE_NO_RESPONSE");

          break;

        default:

            LOG_W("gattc_cmp_evt operation:0x%x",param->operation);

            break;

    }

}

程式碼 16

 

  1. 我們創建一個Task進行按鍵按下時相對應功能。

BLE_Master_task(void *params)

{

     uint32_t sig_mask = 0;

     while(1) {

         sig_mask = xEventGroupWaitBits(BLE_Register_Signal_Event, (Disconnect_Device|  

                      Scan_Device | Control_Device), pdTRUE,pdFALSE,portMAX_DELAY);

         if(sig_mask & Disconnect_Device){

              osapp_disconnect(peer_conn_id);

         }

         else if(sig_mask & Control_Device) {

              if(Light_Status == 1) {

                  Light_Status = 0;

                  static uint8_t write_data[4]={0x00,0x02,0x10,0x00};

osapp_gatt_write(GATTC_WRITE_NO_RESPONSE, WRITE_HANDLE, peer_conn_id, 4, &write_data[0]);

              }

              else {

                       static uint8_t write_data[4]={0x00,0x02,0x10,0x01};

osapp_gatt_write(GATTC_WRITE_NO_RESPONSE, WRITE_HANDLE, peer_conn_id, 4, &write_data[0]);

                  Light_Status = 1;

              }

         }

         else if(sig_mask & Scan_Device){

              osapp_gapm_cancel();

              start_scan_cmd_send();

         }

     }

程式碼 17

 

  1. 實例功能說明
  1. 按鍵對應功能
    1. Button 1 進行設備掃描、連線、讀取Service、Characteristic以及Read設備狀態。
    2. Button 2 進行Write設備控制。
    3. Button 3 進行設備斷連線。

 

開發板按鈕(圖2)

 

  1. 按下Button 1進行設備掃描、連線、讀取Service、Characteristic以及Read設備狀態,可從RTT軟體看出相關資訊。

 

掃描、連線、讀取Service、Characteristic以及Read設備狀態後的RTT資料

(圖3)

 

  1. 按下Button 2進行Write設備控制(開燈、關燈)。

(圖4)

 

關燈(圖5)

開燈(圖6)

 

  1. 按下Button 3進行設備斷連線。

 

(圖7)

 

小結:

透過以上的程式教學,設計者可以透過任何已知的BLE協議進行UUID讀取以及數據資料Write與Read,其他更多的BLE功能就等大家去探索了,如果看官們在測試時發生什麼問題,歡迎聯繫我,一同討論。 

喜歡我的帖子,請幫我按個”收藏”,我們下回見。

 

★博文內容均由個人提供,與平台無關,如有違法或侵權,請與網站管理員聯繫。

★文明上網,請理性發言。內容一周內被舉報5次,發文人進小黑屋喔~

評論