ESP32 VHCI实现BLE广播,就是这么神奇
零. 聲明
本專欄文章我們會以連載的方式持續更新,本專欄計劃更新內容如下:
第一篇:ESP-IDF基本介紹,主要會涉及模組,芯片,開發板的介紹,環境搭建,程序編譯下載,啟動流程等一些基本的操作,讓你對ESP-IDF開發有一個總體的認識,比我們后續學習打下基礎!
第二篇:ESP32-IDF外設驅動介紹,主要會根據esp-idf現有的driver,提供各個外設的驅動,比如LED,OLED,SPI LCD,TOUCH,紅外,Codec ic等等,在這一篇中,我們不僅僅來做外設驅動,還會對常用的外設總線做一個介紹,讓大家知其然又知其所以然!
第三篇:目前比較火熱的GUI LVGL介紹,主要會設計LVGL7.1,LVGL8的移植介紹,并且也會介紹各個組件,知道原理后,最后,我們會推出一款組態軟件來構建我們的GUI,來提升我們的效率!
第四篇:ESP32-藍牙,熟悉我的,應該都知道,我即使從事藍牙協議棧的開發的,所以這個是我們獨有的優勢,在這一篇章,我們會提供不僅僅是藍牙應用方法的知識,也會應用結合藍牙底層協議棧的理論,讓你徹底從上到下打通藍牙任督二脈!
第五篇:Wi-Fi介紹,熟悉我的,應該也知道,我們也做過一款sdio wifi的驅動教程板子,所以在wifi這方面我們也是有獨有的優勢,在這一篇章,我們同樣不僅僅提供Wi-Fi應用方面的知識,也會結合底層理論,讓你對Wi-Fi有一個清晰的認知!
另外,我們的教程包括但是不局限于以上篇章,為了給你一個更好的導航,以下信息尤其重要,請詳細查看!!
------------------------------------------------------------------------------------------------------------------------------------------
購買開發板(點擊我)
ESP32系列文章目錄(點擊我)
Github代碼倉庫(點擊我)
藍牙交流扣扣群:539357317
微信公眾號↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
????
------------------------------------------------------------------------------------------------------------------------------------------
一. 整體介紹
此文章主要介紹基于ESP32 VHCI的架構實現BLE的搜索,也就是不使用默認的Host API,自己編寫一部分代碼來實現功能,具體ESP32的架構如下圖所示:
那我們做的事情是什么呢?我們相當于把默認的Host拿掉,也就是如圖所示的bluedroid部分,寫Host部分的廣播實現
此文章算是熟悉一個VHCI的開發模式,不管對于默認的Host(Bluedroid/nimble)甚至自己移植進來一個Host都有很大的幫助,我就算起到一個拋磚引玉的作用吧!
二. menuconfig實現
我們雖然不用ESP32的默認Host,但是我們要用他默認的Controller,所以還是要配置一下的,一共三個地方需要特別留意一下,其他保持默認,如下圖所示:
1.Bluetooth controller mode
2.HCI mode
3.bluetooth Host
三. 程序實現
1.初始化NVS
/* Initialize NVS — it is used to store PHY calibration data */ esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init(); } ESP_ERROR_CHECK( ret );這一段必須要加上,主要是用于存儲PHY的信息,否則無法正常使用controller
2.初始化controller,注冊Controller的callback函數實現
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); if (ret) {ESP_LOGI(TAG, "Bluetooth controller release classic bt memory failed: %s", esp_err_to_name(ret));return; }if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {ESP_LOGI(TAG, "Bluetooth controller initialize failed: %s", esp_err_to_name(ret));return; }if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {ESP_LOGI(TAG, "Bluetooth controller enable failed: %s", esp_err_to_name(ret));return; }esp_vhci_host_register_callback(&vhci_host_cb);此段我們在介紹esp32 controller api的時候已經詳細介紹,看API名字也知道主要在做什么事情,沒什么難度,我們來看下注冊給controller的callback的實現
static void controller_rcv_pkt_ready(void) {printf("controller rcv pkt ready\n"); }static int host_rcv_pkt(uint8_t *data, uint16_t len) {printf("host rcv pkt: ");for (uint16_t i = 0; i < len; i++) {printf("0x%02x ", data[i]);}printf("\n");return 0; }static esp_vhci_host_callback_t vhci_host_cb = {controller_rcv_pkt_ready,host_rcv_pkt };以上代碼,我們接受到數據,并沒有做什么事情,只是打印下數據的hex而已!
3.實現HCI 的廣播
while (1) {bool send_avail = false;vTaskDelay(1000 / portTICK_PERIOD_MS);send_avail = esp_vhci_host_check_send_available();if (send_avail) {switch (cmd_cnt) {case 0: hci_cmd_send_reset(); ++cmd_cnt; break;case 1: hci_cmd_send_ble_set_adv_param(); ++cmd_cnt; break;case 2: hci_cmd_send_ble_set_adv_data(); ++cmd_cnt; break;case 3: hci_cmd_send_ble_adv_start(); ++cmd_cnt; break;}}printf("BLE Advertise, flag_send_avail: %d, cmd_sent: %d\n", send_avail, cmd_cnt); }在代碼中,我們不去解析hci event的parse,只是一個個命令的發送hci command來實現廣播,此部分也是我們本文章的重點,下面我就來一一詳細介紹下
3.1 hci廣播的流程
如果不考慮整個藍牙Host的健壯性以及可用性,只考慮用ESP32 VHCI架構實現BLE廣播功能,那么其實4個步驟就能實現,分別如下:
1) 發送HCI reset command
2) 發送ble set adv param,也就是設置廣播的參數
3) 發送ble set adv data,也就是設置廣播的數據
4) 發送ble adv start command,也就是開啟廣播
在后面的小節我們再一一介紹下每個command的格式以及作用!
3.2 hci command的格式
我們知道了步驟,并且知道了發送哪些command,但是command的格式是什么呢?那么這個算是一個比較大的話題,牽扯到藍牙core spec hci章節的內容,我們本章本著應用文章,此部門我們簡單的一筆帶過,如果想徹底了解內部原理,那么我建議你看下這兩篇文章:
藍牙HCI command/event/acl/sco格式介紹_Wireless_Link的博客-CSDN博客
藍牙傳輸介質Transport UART H4(RS232)介紹_Wireless_Link的博客-CSDN博客_藍牙通過什么介質傳輸
以上兩篇文章,分別介紹H4以及HCI的格式,好了,我們回歸主題來介紹下HCI command的格式。
Opcode:每個命令被分配一個2字節的操作碼(opcode),用來唯一地識別不同類型的命令,操作碼(opcode)參數分為兩個字段,稱為操作碼組字段(Opcode Group Field, OGF)和操作碼命令字段(Opcode Command Field, OCF)。其中OGF占用高6bit字節,OCF占用低10bit字節。
一共有以下幾組OGF:
1)Link Control commands, the OGF is defined as 0x01.鏈路控制OGF,也就是控制藍牙芯片跟remote溝通的命令
2)Link Policy commands, the OGF is defined as 0x02,鏈路策略OGF,也就是寫一些Policy,比如轉換角色等
3)HCI Control and Baseband commands, the OGF is defined as 0x03,控制本地芯片跟基帶的OGF。比如reset本地芯片等。
4)Informational Parameters commands, the OGF is defined as 0x04。讀取信息的OGF,比如讀取本地芯片的LMP版本呢,支持的command,藍牙地址等,
5)status parameters commands, the OGF is defined as 0x05,狀態參數OGF,比如讀取RSSI等。
6)Testing commands, the OGF is defined as 0x06,測試命令的OGF,比如讓芯片進入測試模式(DUT,device under test)
7)LE Controller commands, the OGF code is defined as 0x08,BLE 的comand
8)vendor-specific debug commands,the OGF code is defined as 0x3F,此部分是vendor定義的,也就是芯片廠商為了擴展core文檔的HCI command定義
OCF眾多,在每個OGF下都有一堆的OCF定義
Parameter Total Length:后續參數的長度
Parameter:每個command的para不同
3.3 hci reset命令
hci command的命令如下:
OGF是0x03,OCF是0x03
hci reset作用如下:
The HCI_Reset command willreset the Controller and the Link Manager on the BR/EDR Controller or the Link Layer on an LE Controller. If the Controller supports both BR/EDR and LEthen the HCI_Reset command shall reset the Link Manager, Baseband and Link Layer. The HCI_Reset command shall not affect the used HCI transport layer since the HCI transport layers may have reset mechanisms of their own. After the reset is completed, the current operational state will be lost, the Controller will enter standby mode and the Controller will automatically revert to the default values for the parameters for which default values are defined in the specification.
Note: The HCI_Reset command will not necessarily perform a hardware reset. This is implementation defined.
The Host shall not send additional HCI commands before the HCI_Command_Complete event related to the HCI_Reset command has been received。
函數實現如下:
/* HCI Command Opcode group Field (OGF) */ #define HCI_GRP_HOST_CONT_BASEBAND_CMDS (0x03 << 10) /* 0x0C00 */ #define HCI_GRP_BLE_CMDS (0x08 << 10)/* HCI Command Opcode Command Field (OCF) */ #define HCI_RESET (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS)uint16_t make_cmd_reset(uint8_t *buf) {UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);UINT16_TO_STREAM (buf, HCI_RESET);UINT8_TO_STREAM (buf, 0);return HCI_H4_CMD_PREAMBLE_SIZE; } static void hci_cmd_send_reset(void) {uint16_t sz = make_cmd_reset (hci_cmd_buf);esp_vhci_host_send_packet(hci_cmd_buf, sz); }組成的數據格式是:0x01 ,0x03 0x0c 0x00
3.4 hci set adv param命令
hci command的格式如下:
OGF=0x08,OCF=0x06
我們來看下這個command的格式:
這個命令是設置廣播的參數,其中參數如下:
Advertising_Interval_Min:廣播的最小間隔
Advertising_Interval_Max:廣播的最大間隔
設備每次廣播時,會在3個廣播信道上(根據設置的channel map而決定哪個信道)發送相同的報文。這些報文被稱為一個廣播事件。除了定向報文以外,其他廣播事件均可以選擇“20ms ~ 10.24s”不等的間隔。兩個相鄰廣播事件之間的時間稱為廣播間隔。
但是,設備周期性的發送廣播會有一個問題:由于設備間的時鐘會不同程度的漂移,兩個設備可能在很長一段時間同時廣播而造成干擾。為防止這一情況的發生,除定向廣播之外的其他廣播類型,發送時間均會被擾動。實現該擾動的方式為,在上一次廣播事件后加入“0 ~ 10ms”的隨機延時。這意味著,即使兩個設備廣播間隔相同,并在相同信道及時間點上發送造成了沖突,但它們發送下一個廣播事件時也會有很大可能不再沖突。
所以,兩個相鄰的廣播事件的之間的時間間隔(T_advEvent)為:T_AdvEvent = advInterval + advDelay
其中,advInterval 必須是“0.625ms”的整數倍,范圍是“20ms ~ 10.24s”之間。對于可掃描非定向廣播和不可連接非定向廣播這兩種廣播類型,該值最好不小于100ms,即(160個0.625ms)。advDelay是Link Layer(鏈接層)分配的一個偽隨機數,它的范圍為“0 ~ 10ms”。
廣播包的截圖如下:
當然,實際設置過程中沒有廣播間隔參數,而是設置Advertising_Interval_Min(最小廣播間隔)和Advertising_Interval_Max(最大廣播間隔)這兩個參數來調整廣播間隔,它們都是以“0.625ms”為單位,如果要固定廣播間隔為某一個值,只需要將這兩個參數設置為同一個有效數值即可。
Advertising_Type:廣播類型
一共分為四種廣播類型:
1)可連接的非定向廣播(Connectable Undirected Event Type):這是一種用途最廣的廣播類型,包括廣播數據和掃描響應數據,它表示當前設備可以接受其他任何設備的連接請求。
鑒于此種廣播類型用的最多,下面我們來討論一下此類型下廣播事件中廣播包的發送情況,另外要注意在一個廣播事件中,前一個“ADV_IND PDUs”的開始到相鄰的下一個“ADV_IND PDUs”的開始處的時間要小于等于 10ms :
第一種情況:僅僅有廣播 PDUs 。截圖顯示如下:
第二種情況:在廣播事件的中間有“SCAN_REQ”和“SCAN_RSP PDUs”。截圖顯示如下:
第三種情況:在廣播事件的結尾有“SCAN_REQ”和“SCAN_RSP PDUs”。截圖顯示如下:
第四種情況:在廣播事件的中間接收到“CONNECT_REQ PDU”的情況。截圖顯示如下:
此情況下,廣播事件會關閉,不會繼續在下一個信道上廣播。
2)可連接的定向廣播(Connectable Directed Event Type):定向廣播類型是為了盡可能快的建立連接。這種報文包含兩個地址:廣播者的地址和發起者的地址。發起者收到發給自己的定向廣播報文之后,可以立即發送連接請求作為回應。
定向廣播類型有特殊的時序要求。完整的廣播事件必須每3.75ms重復一次。這一要求使得掃描設備只需掃描3.75ms便可以收到定向廣播設備的消息。
當然,如此快的發送會讓報文充斥著廣播信道,進而導致該區域內的其他設備無法進行廣播。因此,定向廣播不可以持續1.28s以上的時間。如果主機沒有主動要求停止,或者連接沒有建立,控制器都會自動停止廣播。一旦到了1.28s,主機便只能使用間隔長得多的可連接非定向廣播讓其他設備來連接。
當使用定向廣播時,設備不能被主動掃描。此外,定向廣播報文的凈荷中也不能帶有其他附加數據。該凈荷只能包含兩個必須的地址。
3)不可連接的非定向廣播(Non-connectable Undirected Event Type):僅僅發送廣播數據。
4)可掃描的非定向廣播(Scannable Undirected Event Type):這種廣播不能用于發起連接,但允許其他設備掃描該廣播設備。這意味著該設備可以被發現,既可以發送廣播數據,也可以響應掃描發送掃描回應數據,但不能建立連接。這是一種適用于廣播數據的廣播形式,動態數據可以包含于廣播數據之中,而靜態數據可以包含于掃描響應數據之中。
注意:所謂的定向和非定向針對的是廣播的對象,如果是針對特定的對象進行廣播(在廣播包PDU中會包含目標對象的MAC)就是定向廣播,反之就是非定向。可連接和不可連接是指是否接受連接請求,如果是不可連接的廣播類型,它將不回應連接請求。可掃描廣播類型是指回應掃描請求。
不同的廣播類型對掃描請求和連接請求的不同結果如下圖:
Own_Address_Type:本地地址類型
Peer_Address_Type:對端地址類型
Public Device Addrss:公有設備地址是設備所特有的并且是不可改變的。類似網絡設備的MAC地址,它的長度為48位。由兩部分組成:
Ramdom Device Address:隨機設備地址(私有設備地址),它也是48位。組成如下所示:
Peer_Address:對端藍牙地址
Advertising_Channel_Map:廣播信道表
決定在哪個信道上廣播,如果0x07就是在廣播全信道(37,38,39)
Advertising_Filter_Policy:過濾策略
對應上圖的內容解釋如下:
接受任何設備的掃描請求或連接請求。(Value:0x00)
僅僅接受白名單中的特定設備的掃描請求,但是接受任何設備的連接請求。(Value:0x01)
接受任何設備的掃描請求,但僅僅接受白名單中的特定設備的連接請求。(Value:0x02)
僅僅接受白名單中的特定設備的掃描請求和連接請求。(Value:0x03)
整個實現的代碼如下:
/* HCI Command Opcode group Field (OGF) */ #define HCI_GRP_HOST_CONT_BASEBAND_CMDS (0x03 << 10) /* 0x0C00 */ #define HCI_GRP_BLE_CMDS (0x08 << 10)/* HCI Command Opcode Command Field (OCF) */ #define HCI_RESET (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS) /* Advertising Commands. */ #define HCI_BLE_WRITE_ADV_ENABLE (0x000A | HCI_GRP_BLE_CMDS) #define HCI_BLE_WRITE_ADV_DATA (0x0008 | HCI_GRP_BLE_CMDS) #define HCI_BLE_WRITE_ADV_PARAMS (0x0006 | HCI_GRP_BLE_CMDS)/* HCI Command length. */ #define HCIC_PARAM_SIZE_WRITE_ADV_ENABLE 1 #define HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS 15 #define HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA 31uint16_t make_cmd_ble_set_adv_param (uint8_t *buf, uint16_t adv_int_min, uint16_t adv_int_max,uint8_t adv_type, uint8_t addr_type_own,uint8_t addr_type_dir, bd_addr_t direct_bda,uint8_t channel_map, uint8_t adv_filter_policy) {UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_PARAMS);UINT8_TO_STREAM (buf, HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS );UINT16_TO_STREAM (buf, adv_int_min);UINT16_TO_STREAM (buf, adv_int_max);UINT8_TO_STREAM (buf, adv_type);UINT8_TO_STREAM (buf, addr_type_own);UINT8_TO_STREAM (buf, addr_type_dir);BDADDR_TO_STREAM (buf, direct_bda);UINT8_TO_STREAM (buf, channel_map);UINT8_TO_STREAM (buf, adv_filter_policy);return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS; } static void hci_cmd_send_ble_set_adv_param(void) {uint16_t adv_intv_min = 256; // 160msuint16_t adv_intv_max = 256; // 160msuint8_t adv_type = 0; // connectable undirected advertising (ADV_IND)uint8_t own_addr_type = 0; // Public Device Addressuint8_t peer_addr_type = 0; // Public Device Addressuint8_t peer_addr[6] = {0};uint8_t adv_chn_map = 0x07; // 37, 38, 39uint8_t adv_filter_policy = 0; // Process All Conn and Scanuint16_t sz = make_cmd_ble_set_adv_param(hci_cmd_buf,adv_intv_min,adv_intv_max,adv_type,own_addr_type,peer_addr_type,peer_addr,adv_chn_map,adv_filter_policy);esp_vhci_host_send_packet(hci_cmd_buf, sz); }3.5 hci set adv data命令
hci command的格式如下:
OGF=0x08,OCF=0x08
我們來看下這個command的格式:
Advertising_Data_Length:廣播數據長度,最長是31個字節
Advertising_Data:廣播數據
廣播參數格式如下:
一個1byte的length,n byte的type,后面跟的是這個item的廣播數據,符合L T V格式
L:length T:Type V:value
其中Type跟HCI EIR基本一樣,在文檔CSS_V9中,想詳細看的可以看下
說不講原理,又忍不住給你們講解原理了,就此打住,想看廣播數據的,可以看下我這篇文章:
低功耗藍牙搜索廣播的實現流流程介紹 /BLE?scan flow ----- 藍牙低功耗協議棧_Wireless_Link的博客-CSDN博客
在上述文章步驟6)解析BLE廣播event的數據包 有廣播數據類型,格式等
我們看下代碼實現
/* HCI Command Opcode group Field (OGF) */ #define HCI_GRP_HOST_CONT_BASEBAND_CMDS (0x03 << 10) /* 0x0C00 */ #define HCI_GRP_BLE_CMDS (0x08 << 10)/* HCI Command Opcode Command Field (OCF) */ #define HCI_RESET (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS) /* Advertising Commands. */ #define HCI_BLE_WRITE_ADV_ENABLE (0x000A | HCI_GRP_BLE_CMDS) #define HCI_BLE_WRITE_ADV_DATA (0x0008 | HCI_GRP_BLE_CMDS) #define HCI_BLE_WRITE_ADV_PARAMS (0x0006 | HCI_GRP_BLE_CMDS)/* HCI Command length. */ #define HCIC_PARAM_SIZE_WRITE_ADV_ENABLE 1 #define HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS 15 #define HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA 31 uint16_t make_cmd_ble_set_adv_enable (uint8_t *buf, uint8_t adv_enable) {UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_ENABLE);UINT8_TO_STREAM (buf, HCIC_PARAM_SIZE_WRITE_ADV_ENABLE);UINT8_TO_STREAM (buf, adv_enable);return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_WRITE_ADV_ENABLE; } static void hci_cmd_send_ble_adv_start(void) {uint16_t sz = make_cmd_ble_set_adv_enable (hci_cmd_buf, 1);esp_vhci_host_send_packet(hci_cmd_buf, sz); }3.6 hci send ble adv start命令
hci command的格式如下:
OGF=0x08,OCF=0x0a
我們來看下這個command的格式:
Advertising_Enable:停止或者關閉BLE廣播
代碼如下:
/* HCI Command Opcode group Field (OGF) */ #define HCI_GRP_HOST_CONT_BASEBAND_CMDS (0x03 << 10) /* 0x0C00 */ #define HCI_GRP_BLE_CMDS (0x08 << 10)/* HCI Command Opcode Command Field (OCF) */ #define HCI_RESET (0x0003 | HCI_GRP_HOST_CONT_BASEBAND_CMDS) /* Advertising Commands. */ #define HCI_BLE_WRITE_ADV_ENABLE (0x000A | HCI_GRP_BLE_CMDS) #define HCI_BLE_WRITE_ADV_DATA (0x0008 | HCI_GRP_BLE_CMDS) #define HCI_BLE_WRITE_ADV_PARAMS (0x0006 | HCI_GRP_BLE_CMDS)/* HCI Command length. */ #define HCIC_PARAM_SIZE_WRITE_ADV_ENABLE 1 #define HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS 15 #define HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA 31 uint16_t make_cmd_ble_set_adv_enable (uint8_t *buf, uint8_t adv_enable) {UINT8_TO_STREAM (buf, H4_TYPE_COMMAND);UINT16_TO_STREAM (buf, HCI_BLE_WRITE_ADV_ENABLE);UINT8_TO_STREAM (buf, HCIC_PARAM_SIZE_WRITE_ADV_ENABLE);UINT8_TO_STREAM (buf, adv_enable);return HCI_H4_CMD_PREAMBLE_SIZE + HCIC_PARAM_SIZE_WRITE_ADV_ENABLE; } static void hci_cmd_send_ble_adv_start(void) {uint16_t sz = make_cmd_ble_set_adv_enable (hci_cmd_buf, 1);esp_vhci_host_send_packet(hci_cmd_buf, sz); }四.程序效果
整個代碼運行效果就是我們可以搜索到我們的設備,但是不能連接哈,因為我們自己寫的這部分代碼不是完整的Host代碼
總結
以上是生活随笔為你收集整理的ESP32 VHCI实现BLE广播,就是这么神奇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux设备驱动程序学习(十)——PC
- 下一篇: junit简单介绍