- 動機:
看完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功能,讓有需要的人能夠快速上手。
- 功能說明
之前有分享過”Qualcomm QCA4020 實例-2(BLE Lights應用)”此次我們透過創建的BLE Master進行控制此BLE Light開燈與關燈功能。
我們會使用開發板上三個Button進行以下幾個功能
- Button 1 進行設備掃描、連線、讀取Service、Characteristic以及Read設備狀態。
- Button 2 進行Write設備控制。
- 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 |
- 流程圖
此流程可讓看官們明瞭相關步驟的順序,後續將敘述BlueX BLE Master流程圖中各自步驟讓各位看官們瞭解,如何獲得相對應Characteristic的Attribute Handle,實現BLE Master對BLE Slave設備進行操作動作。
|
流程圖 (圖1) |
- 創建BLE Master端規則與相關功能
- 由於我們目標是做BLE Master端,因此必須於osapp_utils_set_dev_init函式進行設定將規則設為GAP_ROLE_CENTRAL。
osapp_utils_set_dev_init(GAP_ROLE_CENTRAL, GAPM_CFG_ADDR_PUBLIC); |
程式碼 2 |
- 創建相對應功能的返回函式
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 |
- 當GAPM事件完成時會呼叫osapp_gapm_cmp_evt_handler函式,如:BLE設備連線成功
- 當掃描設備完成時會呼叫osapp_gapm_scan_adv_report_ind_handler函式
- 掃描設備Service完成時會呼叫gattc_cmp_disc_handler函式
- 掃描設備Characteristic完成時會呼叫gattc_cmp_disc_char_handler函式
- 操作Read功能成功時會呼叫osapp_gattc_read_ind_handler函式
- 操作Write功能成功時會呼叫osapp_gattc_cmp_evt_handler函式
- 建立掃描設備功能,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 |
- 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 |
- 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 |
- 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 |
- 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 |
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 |
- 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 |
- 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 |
- 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 |
- 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 |
- 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 |
- 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 |
- 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 |
- 我們創建一個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 |
- 實例功能說明
- 按鍵對應功能
- Button 1 進行設備掃描、連線、讀取Service、Characteristic以及Read設備狀態。
- Button 2 進行Write設備控制。
- Button 3 進行設備斷連線。
|
開發板按鈕(圖2) |
- 按下Button 1進行設備掃描、連線、讀取Service、Characteristic以及Read設備狀態,可從RTT軟體看出相關資訊。
|
掃描、連線、讀取Service、Characteristic以及Read設備狀態後的RTT資料 (圖3) |
- 按下Button 2進行Write設備控制(開燈、關燈)。
|
(圖4) |
|
|
關燈(圖5) |
開燈(圖6) |
- 按下Button 3進行設備斷連線。
|
(圖7) |
小結:
透過以上的程式教學,設計者可以透過任何已知的BLE協議進行UUID讀取以及數據資料Write與Read,其他更多的BLE功能就等大家去探索了,如果看官們在測試時發生什麼問題,歡迎聯繫我,一同討論。
喜歡我的帖子,請幫我按個”收藏”,我們下回見。
評論