作者信息
本文主要分析了 Apache NimBLE v1.5 版本的 BLE HCI 層設計,并分析了官方倉庫自帶 UART 對接例程;關于 BLE 層次結構可以先看一下這篇參考文檔。
NimBLE 目錄結構
1NimBLE
2├───apps/*Bluetooth示例應用程序*/
3├───docs/*官方文檔及API說明*/
4├───ext
5├───nimble
6│├───controller/*Controller實現(xiàn)*/
7│├───doc/*當前包含transport層說明文檔*/
8│├───drivers/*Nordic系列Phy驅(qū)動*/
9│├───host/*HostStack(主機控制器)實現(xiàn)*/
10│├───include
11│├───src
12│└───transport/*HCI傳輸抽象層*/
13└───porting/*OS抽象層及系統(tǒng)配置*/
-
觀察目錄,可以看出 NimBLE 是實現(xiàn)了 Host 與 Controller 分離的,在官方介紹中也有說明。
-
nimble/controller 則是 Controller 相關代碼;nimble/host 對應 Host 代碼;nimble/transport 就是 HCI 層代碼;這一版本 nimble/doc 下還包含了 HCI 層的說明文檔 transport.md。
-
順帶一提,porting 目錄下是 OS 抽象層及系統(tǒng)配置;rt-thread 的移植就實現(xiàn)在 porting/npl 下。
NimBLE HCI 層
主機控制接口層(Host Controller Interface,簡寫 HCI):HCI是可選的,主要用于2顆芯片實現(xiàn)BLE協(xié)議棧的場合(一個當作 Host 一個當作 Controller),用來規(guī)范兩者之間的通信協(xié)議和通信命令等。
NimBLE HCI 層主要是了解 nimble/transport 下的內(nèi)容。首先看一下官方文檔 nimble/doc/transport.md 中對 transport 層的說明,主要看下面這張圖:
HCI 層包括這4接口:
Host 從 Controller 端接收接口 ble_transport_to_hs_evt 和 ble_transport_to_hs_acl;以及 Host 向 Controller 發(fā)送接口 ble_transport_to_ll_cmd 和 ble_transport_to_ll_acl。
在目錄下,官方流出的接口定義主要包含在下面幾個文件中:
1transport
2└───inlucde
3├───nimble
4│└───transport
5│└───monitor.h
6├───transport_impl.h
7└───transport.h
其中比較重要的是 transport_impl.h 文件,從名字也可以看出這是一個需要實現(xiàn)的接口定義文件,其主要內(nèi)容如下:
1/*InitfunctionstobeimplementedfortransportactingasHS/LLside*/
2externvoidble_transport_ll_init(void);
3externvoidble_transport_hs_init(void);
4/*APIstobeimplementedbyHS/LLsideoftransports*/
5externintble_transport_to_ll_cmd_impl(void*buf);
6externintble_transport_to_ll_acl_impl(structos_mbuf*om);
7externintble_transport_to_hs_evt_impl(void*buf);
8externintble_transport_to_hs_acl_impl(structos_mbuf*om);
這些接口在 transportinclude imble ransportmonitor.h 中被引用:
1staticinlineint
2ble_transport_to_ll_cmd(void*buf)
3{
4returnble_transport_to_ll_cmd_impl(buf);
5}
6staticinlineint
7ble_transport_to_ll_acl(structos_mbuf*om)
8{
9returnble_transport_to_ll_acl_impl(om);
10}
11staticinlineint
12ble_transport_to_hs_evt(void*buf)
13{
14returnble_transport_to_hs_evt_impl(buf);
15}
16staticinlineint
17ble_transport_to_hs_acl(structos_mbuf*om)
18{
19returnble_transport_to_hs_acl_impl(om);
20}
看完這個文件,大概明白了,之前官方文檔圖中提到的 HCI 4個接口,在這里與對應的 _impl() 接口綁定了起來。而 impl() 接口就是官方提供給開發(fā)者具體實現(xiàn)對接的接口。
由于 Host 和 Contoller 是雙向交互的,所以發(fā)送與接收 HCI 包接口是要完整實現(xiàn)的,也就是大多數(shù)情況下上述 4 個 *impl() 接口都需要全部實現(xiàn)。
分析 UART 對接 HCI 層官方例程
官方提供了一個使用 UART 對接 HCI 層的例程,源碼文件為
nimble/transport/uart/src/hci_uart.c
首先找到熟悉的接口:代碼中顯式實現(xiàn)了 ble_transport_to_hs_evt_impl 以及 ble_transport_to_hs_acl_impl ,這兩個接口中基本上就是使用 uart 向 host 發(fā)送數(shù)據(jù)包,涉及到某個板子 uart 發(fā)送數(shù)據(jù)的具體細節(jié),這里不過多關注。
看完上面兩個接口,其實 HCI 中向 Host 發(fā)送數(shù)據(jù)包功能算實現(xiàn)完了,且當前文件中沒有實現(xiàn)其他的 impl 接口,很容易能想到,這是寫的 Controller 端的 HCI 層代碼。
有了這個定性信息,可以開始分析如何從 uart 中接收 Host 層發(fā)來的數(shù)據(jù)包。
通過 uart 初始化函數(shù)找到 uart 接收回調(diào)函數(shù):
1//uart初始化函數(shù)
2rc=hal_uart_init_cbs(MYNEWT_VAL(BLE_TRANSPORT_UART_PORT),
3hci_uart_tx_char,NULL,
4hci_uart_rx_char,NULL);
1//uart接收回調(diào)函數(shù)
2staticinthci_uart_rx_char(void*arg,uint8_tdata)
3{
4hci_h4_sm_rx(&hci_uart_h4sm,&data,1);
5return0;
6}
可以看到每接收一個字符,都使用 hci_h4_sm_rx 進行接收。該函數(shù)聲明在 transportcommonhci_h4include imble ransporthci_h4.h 文件下,是關于 H4 的一個函數(shù),大概看一下具體定義,接收字符后有一個組幀判斷的過程。看一下 hci_h4.h 下比較關鍵的兩個接口:
1voidhci_h4_sm_init(structhci_h4_sm*h4sm,
2conststructhci_h4_allocators*allocs,
3hci_h4_frame_cb*frame_cb);
4inthci_h4_sm_rx(structhci_h4_sm*h4sm,constuint8_t*buf,uint16_tlen);
hci_h4_sm_init() 中出現(xiàn)了一個 hci_h4_frame_cb *frame_cb 參數(shù),這是一個函數(shù)指針參數(shù),初步猜測用于回調(diào)函數(shù)的注冊。且在
hci_uart.c 代碼中,也找到了 hci_h4_sm_init() 相關調(diào)用:
244:hci_h4_sm_init(&hci_uart_h4sm,&hci_h4_allocs_from_hs,hci_uart_frame_cb)
這里將 hci_uart_frame_cb 注冊成了回調(diào)函數(shù),源碼中定義如下:
1staticint
2hci_uart_frame_cb(uint8_tpkt_type,void*data)
3{
4switch(pkt_type){
5caseHCI_H4_CMD:
6returnble_transport_to_ll_cmd(data);
7caseHCI_H4_ACL:
8returnble_transport_to_ll_acl(data);
9default:
10assert(0);
11break;
12}
13return-1;
14}
hci_uart_frame_cb 基本上是對一個完整的 HCI 包的處理,根據(jù)不同的類型使用 ble_transport_to_ll_cmd 和 ble_transport_to_ll_acl 傳輸給 Link Layer 層( Link Layer 是 Controller 上的一個層次,更加確定這是 Controller 上的 HCI 層實現(xiàn) )。
結合 hci_uart.c 中對 hci_h4_sm 的使用,以及對 hci_h4_sm 相關接口源碼的分析,hci_h4_sm 其實是官方提供的一個類似保證包完整性的東西,用于判斷一幀完整的 HCI 數(shù)據(jù)包,并且提供組包完成回調(diào)函數(shù)的機制。
看到這里,大概脈絡應該已經(jīng)捋清楚了。這是一個 Controller 上的 UART HCI 層對接實現(xiàn):
1、向 Host 發(fā)送 HCI 包:主要通過顯式實現(xiàn) ble_transport_to_hs_evt_impl 以及 ble_transport_to_hs_acl_impl 接口實現(xiàn),具體何時被調(diào)用,協(xié)議棧已經(jīng)自動處理好。
2、從 Host 接收 HCI 包:主要是使用的 hci_h4 中的組包接口,hci_h4_sm 即一個組包的狀態(tài)機實例,通過 hci_h4_sm_rx 接收 uart 接收到的字符,在判斷 hci 包完整接收時調(diào)用提前注冊好的 回調(diào)函數(shù) hci_h4_frame_cb 。hci_h4_frame_cb 里則實現(xiàn)了將 uart 接收到的包傳遞給 LL 層,進而 Controller 可以對 Host 傳下來的命令或數(shù)據(jù)做出響應動作。
完整的 HCI 層實現(xiàn)
Controller 使用 UART 做 HCI 層數(shù)據(jù)傳輸
開始說到無論在什么情況下都要完成 HCI 層中 4個主要接口的實現(xiàn),當前 hci_uart.c 中只找到了兩個接口的實現(xiàn),還有另外兩個接口在哪呢。使用全局搜索在 nimblecontrollersrcle_ll.c 下找到了另外兩個接口的實現(xiàn):
1/*TransportAPIsforLLside*/
2int
3ble_transport_to_ll_cmd_impl(void*buf)
4{
5returnble_ll_hci_cmd_rx(buf,NULL);
6}
7int
8ble_transport_to_ll_acl_impl(structos_mbuf*om)
9{
10returnble_ll_hci_acl_rx(om,NULL);
11}
因為目前默認在 Controller 端實現(xiàn) HCI 層,剩下未實現(xiàn)的兩個接口在 Controller 源碼下實現(xiàn)了。
Host 使用 UART 做 HCI 層數(shù)據(jù)傳輸
那么假設當前需要實現(xiàn) Host 端的 HCI 層,且還是需要使用 UART 對接。那么需要實現(xiàn)對接 UART 的接口應該剛好和 hci_uart.c 中相反。
需要在對接源碼中,實現(xiàn) ble_transport_to_ll_cmd_impl 和 ble_transport_to_ll_acl_impl 發(fā)送接口,具體同樣也是使用 UART 發(fā)送包。相應的,在 nimblehostsrcle_hs.c 下找到了以下兩個接口的實現(xiàn):
1/*TransportAPIsforHSside*/
2int
3ble_transport_to_hs_evt_impl(void*buf)
4{
5returnble_hs_hci_rx_evt(buf,NULL);
6}
7int
8ble_transport_to_hs_acl_impl(structos_mbuf*om)
9{
10returnble_hs_rx_data(om,NULL);
11}
也就實現(xiàn)了 Host端 HCI 層的完整搭建。
-
在使用 UART 對接 HCI 層時,是默認當前環(huán)境只跑了 Host 或 Controller 中的一端,因此 nimblecontrollersrcle_ll.c 和nimblehostsrcle_hs.c 不會同時參與編譯,自然也不會引起一些 *impl() 接口重復定義的錯誤。
不需要其他接口進行 HCI 傳輸
當 Host 與 Controller 跑在同一環(huán)境中,不需要對接具體的數(shù)據(jù)傳輸接口, nimblecontrollersrcle_ll.c 和nimblehostsrcle_hs.c 同時參與編譯,此時無需再編寫額外的代碼,即可完整實現(xiàn)整個 HCI 層。
總結
在較早的版本中,NimBLE 對接 HCI 層需要實現(xiàn)以下接口(參考文檔):
1intble_hci_trans_hs_cmd_tx(uint8_t*cmd);
2intble_hci_trans_hs_acl_tx(structos_mbuf*om);
3voidble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn*cmd_cb,
4void*cmd_arg,
5ble_hci_trans_rx_acl_fn*acl_cb,
6void*acl_arg);
7uint8_t*ble_hci_trans_buf_alloc(inttype);
8voidble_hci_trans_buf_free(uint8_t*buf);
9intble_hci_trans_set_acl_free_cb(os_mempool_put_fn*cb,void*arg);
10intble_hci_trans_reset(void);
可以看出來有一些麻煩,除了處理數(shù)據(jù)傳輸方向,還有處理數(shù)據(jù)包的內(nèi)存申請與釋放,實現(xiàn)起來較為復雜。
當前版本中官方對 HCI 層進行了重構,在 transport.c 中統(tǒng)一處理了數(shù)據(jù)包的內(nèi)存申請與釋放;讓開發(fā)者專注于處理數(shù)據(jù)的發(fā)送與接收方式,并且提供了 H4 類型的 HCI 包接收工具,更加方便開發(fā)者的對接使用。
審核編輯:湯梓紅
-
BLE
+關注
關注
12文章
660瀏覽量
59391 -
HCI
+關注
關注
0文章
29瀏覽量
12948 -
Apache
+關注
關注
0文章
64瀏覽量
12463
原文標題:NimBLE HCI 層分析
文章出處:【微信號:RTThread,微信公眾號:RTThread物聯(lián)網(wǎng)操作系統(tǒng)】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論