1. 程式人生 > >藍芽BLE---DA14683藍芽資料收發詳解

藍芽BLE---DA14683藍芽資料收發詳解

DA14683自定義資料收發詳解

Date: 2018.11.26

Create: Jim

Wechat: life5270

在使用14683的SDK過程中,SDK裡面並未提供簡單的資料收發Demo,或者說例程中其實也有資料收發的Demo(如CTS服務),但是封裝了很多東西,新手初次接觸可能需要花大量時間去研究和裁剪,當然了,對高手來說都是小菜一碟。下面我們來看看怎麼新增一個自定義資料收發服務。

附件中有user_service.c和user_service.h這兩個檔案,定義了一個自定義的服務,包含一個特徵值,Read+Notify+Write no response。(如果新手不知道這些概念的意思,可到百度或Google瞭解)

 

程式碼地址:https://blog.csdn.net/JaLLs/article/details/84558112

請自行復制然後新增成.c和.h檔案。

匯入工程和新增檔案

匯入自己的工程或者SDK中的例程,pxp_reporter或者ble_peripheral都可以

然後把本人提供的user_service.c和user_service.h新增進自己的工程檔案

 

直接複製檔名然後ctr+v貼上進對應的資料夾即可。

user_service.h貼上進sdk->ble->service->include裡面

user_service.c

貼上進sdk->ble->service->src裡面

新增完上訴兩個檔案以後,如果使用的Demo是ble_peripheral,那我們到

ble_peripheral_config.h中關閉所有其他服務:

然後定義一個CFG_USER_SERVICE巨集定義,用來開啟或者關閉我們自己新增的服務。

完成以上操作以後,開啟ble_peripheral_task.c

新增user_service.h標頭檔案

然後到ble_peripheral_task任務中,新增以下程式碼:

到此,自定義服務就新增完畢了,此時編譯程式碼,燒錄進開發板,復位。

開啟手機藍芽通訊助手APP(NRF Connect),搜尋到配對名Dialog Peripherial,連線。

連線成功以後出現以下介面:

這個Unknown Service就是我們新增的自定義服務。

上面的兩個服務是GAP和GATT,可忽略不管。

點開這個Service,彈出這個服務中的具體特徵:

標出來的部分就是這個特徵值的屬性,可寫,可讀,可通知。

Notify表示可通知,和Indicate功能類似,但是Notify是無應答的通知,Indicate是有應答的,可以簡單理解為這兩個屬性都是用於晶片傳送資料給手機,Notify的時候無需收到手機的返回確認,Indicate需要接收手機的返回確認。這兩個使用之前都必須先在手機上使能。

Read表示手機主動讀取晶片的資料,當用戶點選Read的時候,晶片收到Read請求,就把相應的資料丟到空氣中,至於手機有沒有收到資料,晶片不管。

Write no response表示手機寫資料到晶片中,他和Write的功能類似,但是Write no response是無應答的,Write是有應答的。Write no response就是說手機只需要把資料傳送出去,至於晶片有沒有正確地收到,無所謂。

以上概念僅作粗淺介紹,詳情可自行百度或Google.

廢話不多說,點選下面這個按鈕使能晶片的Notify

然後再點選中間的按鈕:

 

出現如下資料傳送頁面:

輸入隨意16進位制數,點選SEND傳送。

這個時候晶片會收到資料,並且晶片會返回你傳送的資料到手機。

向右滑動Nrf connect的頁面可滑出此頁。

到此,實驗驗證就完成了,說明我們的自定義收發服務新增成功。

機智的你一定會發現還有兩個地方沒講,是的。請看:

這第一個按鈕是read屬性,點選這個read按鈕,同樣會讀取到資料,只不過這套例程我把資料固定了,使用者可以自定義成別的,如ADC採集到的值,感測器採集到的值等等自行發揮。

還有一個地方就是右下角這個按鈕:

這個實際上是描述符,在這套例程中,點選這個按鈕實際上可以檢查Notify的狀態,Notify是使能狀態還是關閉狀態都可以通過這個Read按鈕來檢驗。別以為它沒什麼作用,產品級的APP一般傳送資料前都需要先檢查Notify或Indicate是否已經使能了,這時候就可以通過這個來檢查。

驗證程式碼到這就結束了。真的結束了嗎?不想多瞭解一點具體程式碼的東西嗎?

請接著往下看。

 

User_service.c詳解

初始化函式

先從user_service初始化開始

這個函式是初始化服務的函式,具體它幹了什麼事我們定位到user_service_init這個函式實體中看看:

這個初始化函式的形參是user service的回撥函式。

再看看下面這幾句語句:

  1. user_svc->svc.read_req = handle_read_req;表示把read的操作放到handle_read_req中
  2. user_svc->svc.write_req = handle_write_req;表示把Write(no response)的操作都放到handle_write_req中
  3. user_svc->svc.cleanup = cleanup;資料收發完成以後會執行cleanup函式清除相關標誌
  4. user_svc->cb = cb;回撥函式,回撥函式包括read回撥和write(no response)回撥。

有疑問吧?有就對了。

既然接收到資料會執行handle_read_req和handle_read_req,那為何還需要再註冊一個回撥函式cb ?

後面將會講到。

繼續看初始化函式:

num_attr = ble_gatts_get_num_attr(0, 1, 1);是計算特徵值的個數。

ble_uuid_create16(UUID_TX_RX_SERVICE, &uuid);是新增user service的UUID。

ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr);是新增user service服務

再往下看:

ble_uuid_create16(UUID_TX_RX_CHARACTER, &uuid);是建立讀寫特徵值的UUID

ble_gatts_add_characteristic(&uuid,GATT_PROP_READ|GATT_PROP_NOTIFY |\

                          GATT_PROP_WRITE_NO_RESP,ATT_PERM_RW, 10,

GATTS_FLAG_CHAR_READ_REQ,NULL, &user_svc->user_val_h);

這一句程式碼是新增特徵值,可以看到這裡的GATT_PROP分由READ,NOTIFY,WRITE NO RESPONSE組成,這就解釋了我們APP連線上裝置以後,點開服務可以看到以下屬性:

其實是對應得上的。

再看看後面一個形參:ATT_PERM_RW

這是給這個特徵值賦予了可讀可寫的許可權,所以手機發送資料給到晶片,然後晶片再發送回資料給到手機這條路才能暢通無阻。不信?那你把這個形參改成ATT_PERM_NONE試試?

繼續往後看:

這兩句程式碼是新增描述符的UUID和設定許可權。

最後一段則是真正的註冊服務了:

handle_write_req

前面初始化的時候有講到註冊handle_write_req的作用,當手機發送資料給到晶片時(正確地講應該是傳送資料給到user service),user service接收到資料以後,會進入這個handle_write_req函式。這個函式做了什麼工作?看看函式實體。

 

實際上它只做了一個判斷,判斷資料是由user service的哪個特徵值傳送過來的,然後做相應的處理。

以下這個判斷就是處理手機發送過來的資料的判斷:

可以看到呼叫了do_user_write這個函式,定位到函式實體:

前兩個判斷是錯誤判斷,暫時忽略。看後面兩句(登出的printf裡面的內容請忽略)

關鍵的一句在這:

user_svc->cb->recieve_date_cb(&user_svc->svc, conn_idx, value,length);

這句程式碼把接收到的資料和資料長度都賦值給我們前面初始化服務時註冊的回撥函數了。

在本例程中,手機write資料時的回撥函式是recieve_date_cb

我們再看看recieve_date_cb幹了什麼事?

recieve_date_cb呼叫了service_send_date(svc, conn_idx, value,length);這個函式,把手機發送過來的資料又發回給手機了,所以手機才能接收到返回的資料。

值得注意的是,如果你註冊服務時,Write屬性給的是GATT_PROP_WRITE而不是GATT_PROP_WRITE_NO_RESP,那麼請注意把紅框標出來的那句程式碼取消登出,別問為什麼,看字面意思再結合Write和Write no response的區別,就能理解了。

再回到handle_write_req這個函式,還有一些疑問吧? 那就對了。

既然再這個Handle裡面可以直接獲取到手機發送過來的資料和長度,那為何不在這個函式裡面直接把傳送返回資料給手機的函式或者使用者處理資料的程式碼加在這裡得了,還要註冊回撥函式幹嘛?

首先,這個例程的user service只有一個特徵值,倒也不復雜繁瑣,想加在這也行,但是假設這個user service有10個特徵值,你還會想把所有的資料處理程式碼都加在這嗎?

其次,程式碼結構和質量也是很重要的。

最後,大家做產品的時候可以試試,沒問題算我輸。

handle_read_req

這個Handle是手機讀資料時會執行的函式

程式碼邏輯和handle_write_req基本都是相似的。也就沒什麼好說的了。

關於UUID

這套例程中所用的UUID為自定義UUID

在新增自定義服務時,最好不要使用通用UUID,因為有些通用UUID,在手機上的BLE助手裡面是能識別出來的,接收到的資料會經過這個通用服務進行封裝,最後接收到的資料就是經過封裝的,而非原始資料。當然了,如果使用者自己寫APP來測試應該就不存在這個問題。

以下是部分通用服務UUID截圖,關於服務的UUID和特徵值,描述符的UUID也是有做區分的,具體可在ble_uuid.h中檢視:

 

簡單粗暴的上手方法

如果使用者不想研究那麼多BLE服務相關的程式碼,不想了解太多原理,那麼請看以下內容。

 

1. 手機Read資料時,晶片會執行1這個函式。手機Read到的內容是read_test這個陣列的內容。

2. 手機Write資料給晶片時,晶片會執行2這個函式。收到的資料存放在value這個指標中,length是接收到的資料長度。

3. 如果想傳送資料給手機,可以呼叫3這個函式。做個按鍵,按一次按鍵發一次資料給手機也可以,直接呼叫這個函式即可。