ESP32_IDF學習6【經典藍牙與BLE】


在這里着重記述低功耗藍牙BLE相關內容,庫函數部分翻譯自樂鑫官網文檔

低功耗藍牙(BLE)協議棧

低功耗藍牙協議是藍牙通信協議的一種,BLE協議棧就是實現低功耗藍牙協議的代碼

層次協議

藍牙協議規定了兩個層次的協議,分別為藍牙核心協議(Bluetooth Core)和藍牙應用層協議(Bluetooth Application)

藍牙核心協議就是對藍牙技術本身的規范,不涉及其應用方式

藍牙應用層協議是在藍牙核心協議的基礎上,根據具體的應用需求定義出的特定策略

藍牙協議棧框圖如下所示:

藍牙核心協議(Bluetooth Core)

藍牙核心協議包含BLE Controller和BLE Host兩部分

Controller負責定義RF、Baseband等偏硬件的規范

Host負責在邏輯鏈路的基礎上,進行更為友好的封裝,這樣就可以屏蔽掉藍牙技術的細節,讓Bluetooth Application更為方便的使用

Controller是工作在物理層、數據鏈路層、網絡層、傳輸層的協議,Host則是工作在傳輸層 、會話層、表示層、應用層的協議,Host將Controller封裝成可被配置為函數的形式供程序使用

包含的層次簡介

  1. 物理層(PHY)

用於指定BLE所用的無線頻段、調制解調方式和方法等

BLE工作在1Mbps自適應跳頻的GFSK射頻,免於許可證的2.4GHz ISM(工業、課學、醫療)頻段

可以直觀理解為規定了BLE的天線部分

  1. 鏈路層(LL——Link Layer)

BLE協議棧的核心

相當於TCP/IP協議中的數據鏈路層(負責選擇哪個射頻通道,管理當前鏈路)+網絡層(負責識別和發送空中數據包)+傳輸層(負責保證數據包安全、完整的發送、接收、重傳等)

  1. 主機控制接口層(HCI——Host Controller Interface)

這一層是可選的,HCI主要用於2顆IC實現BLE協議棧的場合,用於貴方兩者的通信協議和通信命令等

藍牙應用協議(Bluetooth Application)

包含的層次簡介

  1. 通用訪問配置文件層(GAP——Generic access profile)

實際配置中常接觸到的一層

GAP是對LL層有效數據包(payload)進行解析的兩種方式中最簡單的一種,主要用於廣播、掃描、發起連接這些具體行為

  1. 邏輯鏈路控制及自適應協議層(L2CAP——Logical Link Control and Adaptation Protocol)

這一層是對LL的簡單封裝,在L2CAP中區分出是使用加密通道還是普通通道,同時負責連接間隔的管理

  1. 安全管理層(SM——Security Manager)

負責管理BLE連接的加密、安全,需要兼顧安全性和用戶使用的便利性

  1. 屬性協議層(ATT——Attribute protocol)

實際配置中最常接觸到的一層

負責定義用戶命令和命令操作的數據(如讀寫數據等)

詳細內容見后文【BLE的兩種模式】和【ATT簡述】

  1. 通用屬性配置文件層(GATT——Generic Attribute profile)

實際配置中常接觸到的一層

用於規范attribute中的數據內容(attribute見后文【BLE的兩種模式】),並使用分組(group)來對attribute進行分類管理

一般地,BLE在沒有GATT的情況下也能跑,只不過互聯互通會出現問題

BLE需要在GATT和各種應用profile的支持下才能實現最便捷高效穩定的通信

BT與BLE的區別

當前的藍牙協議分為基礎率/增強數據率(BR/EDR)和低耗能(LE)兩種技術類型

經典藍牙統稱BT,低功耗藍牙稱為BLE

經典藍牙模塊(BT)

泛指支持藍牙協議在4.0以下的模塊,一般用於數據量比較大的傳輸。

經典藍牙模塊可再細分為:傳統藍牙模塊和高速藍牙模塊

傳統藍牙模塊在2004年推出,主要代表是支持藍牙2.1協議的模塊,在智能手機爆發的

時期得到廣泛支持。

高速藍牙模塊在2009年推出,速率提高到約24Mbps,是傳統藍牙模塊的八倍。

低功耗藍牙模塊(BLE)

指支持藍牙協議4.0或更高的模塊,也稱為BLE模塊(Bluetooth Low Energy Module),最大的特點是成功和功耗的降低。

藍牙低功耗技術采用可變連接時間間隔,這個間隔根據具體應用可以設置為幾毫秒到幾秒不等。

另外,因為BLE技術采用非常快速的連接方式,因此可以處於“非連接”狀態(節省能源),此時鏈路兩端相互間僅能知曉對方,必要時可以才開啟鏈路,然后在盡可能短的時間內關閉鏈路。

其他分類

按用途來分:藍牙模塊有數據藍牙模塊,語音藍牙模塊,串口藍牙模塊和車載藍牙模塊

按芯片設計來分:藍牙模塊有flash版本和ROM版本。前者一般是BGA封裝(球柵陣列封裝),外置flash;后者一般是LCC封裝(表面貼裝型封裝),外接EEPROM。

BLE的兩種模式

  1. 客戶端 Client

請求數據服務

客戶端可以主動搜索並連接附近的服務端

客戶端類似蹭網的

  1. 服務端Server

提供數據服務

服務端不需要進行主動設置,只要開啟廣播就可以讓附近的客戶端搜索到,並提供連接

服務端類似被蹭網的wifi

如果想要讓ESP處於別人隨時可以搜索連接的情況要配置為服務端;如果想讓ESP通過掃描連接周圍可連接的藍牙設備,需要把它設置成客戶端,正好和WiFi模式的設定相反

Server通過characteristic對數據進行封裝,多個characteristic組成一個Service——Server是一個基本的BLE應用,如果某個Service是一個藍牙聯盟定義的標准服務,也可以稱其為profile

要具體了解這些內容需要先了解屬性協議層ATT

ATT簡述

屬性協議層ATT(Attribute Protocol)是GATT和GAP的基礎,它定義了BLE協議棧上層的數據結構和組織方式;在層內,它定義了屬性(Attribute)的內容,規定了訪問屬性的方法和權限

屬性是一個數據結構,它包括了數據類型和數據值,可以像C語言的結構體那樣構造

屬性包括三種類型:服務項特征值描述符,三者呈包含關系:服務項包含一個或多個特征值,特征值包含一個或多個描述符,多個服務項組織在一起,構成屬性規范(Attribute Profile)

屬性的種類和分組

屬性大致可以分為三種類型:服務項Profile特征值Characteristic描述符Descriptor

最頂級為Profile, 下面是多個服務項(Service), 服務項下面是多個特征值(Characteristic), 特征值下面是多個描述符(Descriptor)

每個設備都包含以下必要的特征值和服務項:

PROFILE

  • Generic Access Service(Primary Service)
    • Device Name(Characteristic)
    • Appearance(Characteristic)
  • Generic Attribute Service(Primary Service)
    • Service Changed(Characteristic)
      • CCCD(Descriptor)

服務項Service

服務項這種類型本身並不包含數據,僅僅相當於是一個容器,用來容納特征值

特征值characteristic

特征值用於保存用戶數據,但它也有自己的UUID——可以把它看作一個變量,變量里存着數據(用戶數據),也有自己的地址信息(UUID)

使用特征值時,也要遵循“先聲明再賦值”的步驟——先聲明特征值自身,再聲明它的項

一個characteristic包含三種條目:

  1. characteristic聲明:每個characteristic的分界符,解析時一旦遇到一個聲明,就可以認為接下來又是一個新的characteristic;聲明還包含了接下來characteristic值的讀寫屬性等
  2. characteristic值:數據的值
  3. characteristic描述符:數據的額外信息

一般BLE的屬性體系在系統中以GattDB表示,即屬性數據庫,gattDB是BLE協議棧在內存中開辟的一段專有區域,會在特定的時候寫入Flash進行保存,並在啟動時讀取出來回寫到內存中去,但並非所有的BLE數據通信是操作gattDB

characteristic用Attribute數據結構來實現

屬性Attribute的數據結構

Attribute由四部分組成:

  1. 屬性句柄Attribute handle:可以視為指向屬性實體的指針,對端設備通過屬性句柄來訪問某個屬性,大小2字節,起始於0x0001,系統初始化時,各屬性的句柄逐步+1,但最大不超過0xFFFF

  2. 屬性類型Attribute type:用以區分當前屬性是服務項或是特征值等,用通用唯一識別碼(UUID)標識的16字節十六進制字符串(形如f6257d37-34e5-41dd-8f40-e308210498b4,從網上抄來的示例,如有雷同那就是雷同)表示。一個合法的UUID,一定是隨機的、全球唯一的,不應該出現兩個相同的UUID。屬性類型分類如下:

    • 首要服務項Primary Service
    • 次要服務項Secondary Service
    • 包含服務項Include
    • 特征值Characteristic

    他們與UUID的映射關系如下:

    • 0x1800 – 0x26FF :服務項類型
    • 0x2700 – 0x27FF :單位
    • 0x2800 – 0x28FF :屬性類型
    • 0x2900 – 0x29FF :描述符類型
    • 0x2A00 – 0x7FFF :特征值類型

    為了減少傳輸的數據量,BLE協議做了一個轉換約定,給定一個固定的16字節模板,只設置2個字節為變化量,其他為常量,2字節的UUID在系統內部會被替換,進而轉換成標准的16字節UUID;反之,如果一個特征值的UUID是16字節的,在系統內部它的屬性類型也可能寫成第3、4字節組成的雙字節

    示例如下:

    UUID模板為

    0000XXXX-0000-1000-8000-00805F9B34FB
    

    其中從左數第3、4個字節“XXXX”就是變化位,其他為固定位。如:UUID=0x2A00在系統內部會轉換成00002A00-0000-1000-8000-00805F9B34FB。

  3. 屬性值Attribute value:真正的數據值,大小為0-512字節。如果該屬性是服務項類型或者是特征值聲明類型,那么它的屬性值就是UUID等信息;如果是普通的特征值,則屬性值是用戶的數據,屬性值需要預留空間以保存用戶數據,可以將屬性值的預留空間看做I2C的數據空間,操作特征值里的用戶數據,就是對那塊內存空間進行讀寫所以啟用藍牙后會占用額外的內存

  4. 屬性權限Attribute permissions:Attribute的權限屬性,主要有四種:

    • 訪問權限(Access Permission):只讀或只寫或讀寫
    • 加密權限(Encryption Permission):加密或不加密
    • 認證權限(Authentication Permission):需要認證或無需認證。指相互確認對方身份,BLE中所說的“認證”過程就是設備配對
    • 授權權限(Authorization Permission):需要授權或無需授權。指對授信設備開放權利

    授權的管控等級要高於認證,認證的設備未必被授權,授權的設備一定是認證的——認證是授權的充分不必要條件。認證是設備配對,兩邊都符合協議規定就行,但是授權取決於Server設備對Client設備的主動許可。

    一個沒有經過認證的設備,被稱為未知設備(Unknown Device);經過了認證,該設備會在綁定信息中被標記為Untrusted,被稱為不可信設備(Untrusted Device);經過了認證,並且在綁定信息中被標記為Trusted的設備被稱為可信設備(Trusted Device)

    具體的權限示例如下所示:

    • Open(隨意讀寫)
    • No Access(禁止讀寫)
    • Authentication(需要配對才能讀寫,分成很多子類型用於適配配對的類型)
    • Authorization(允許應用在回調函數中讀寫)
    • Signed(簽名后才能隨意讀寫)

屬性協議ATT PDU

擁有一組屬性的設備稱為服務端(Server);讀寫該屬性值的設備稱為客戶端(Client)

Client和Server之間通過ATT PDU通信

屬性協議ATT PDU共有6種,如下表所示:

ATT PDU種類 發送方向 觸發響應 說明
Command Client -> Server 客戶端發送Command,服務器無需任何返回
Request Client -> Server Response 客戶端發送Request,服務器需要返回一個Response,表明服務器收到
Response Server -> Client
Notification Server -> Client 服務器發送Notification,戶端無需任何返回
Indication Server -> Client Confirmation 服務器發送Indication,客戶端需要返回一個Confirmation,表明客戶端收到
Confirmation Client -> Server

BLE下,所有命令都是“必達”的,每個命令發送完畢后,發送者會等待ACK信息(類似I2C),如果收到了ACK包,發起方認為命令完成;否則發起方會一直重傳該命令直到超時導致BLE連接斷開(類似CAN的出錯重發機制),可以說只要數據包放到了協議棧射頻FIFO中,藍牙協議棧就能保證該數據包“必達”對方,但是沒有回復相對有回復就是“不太可靠”,這時候就需要特殊的“有回復屬性”

Request后綴

特別地,如果一個命令需要response,那么可以在相應命令后面加上request后綴,這個response包在應用層有回調事件,可以用於觸發特殊的功能,這是默認的協議ACK恢復不具有的,采用request/response方式,應用層可以按順序地發送一些數據包;如果一個命令只需要ACK而不需要response,那么它的后面就不會帶request

然而Request/response會大大降低通信的吞吐率,因為request/response必須在不同的連接間隔中出現,這就導致兩個連接間隔最多只能發一個數據包,而不帶request后綴的ATT命令就沒有這個問題——一般情況下,在同一個連接間隔中可以同時發多個數據包,這樣將大大提高數據的吞吐率

常用的帶request命令:所有read命令,writerequest,indication等

常用的不帶request命令:write command,notification等

通用屬性協議GATT簡述

GATT(Generic Attribute Profile),描述了一種使用ATT的服務框架。該框架定義了服務(Server)和服務屬性(characteristic)的過程(Procedure)及格式,負責處理具體數據段通過藍牙連接的發送和接收

現在的BLE大多建立在GATT協議之上,GATT建立在ATT和L2CAP之上,GATT需要使用通用訪問協議GAP來確定設備的連接

通用訪問協議GAP

GAP 使設備被其他設備可見,並決定了當前設備是否可以或者怎樣與合同設備進行交互

GAP中,設備被分為外圍設備Peripheral中心設備Central

外圍設備:性能相對較弱、功耗相對低的設備,他們通常被連接到更加強大的中心設備

中心設備:性能相對較強、功耗較高的設備

GAP廣播

GAP 中外圍設備不停向外廣播以讓中心設備知道它的存在。通過兩種方式向外廣播數據:

廣播數據(Advertising Data Payload):必須的,外設需要以此來和中心設備取得連接

掃描回復(Scan Response Data Payload):可選的,中心設備可以向外圍設備請求掃描回復,向其提供一些設備的額外信息

外圍設備會設定一個廣播建個,每個間隔中,它會重新發送自己的廣播數據,廣播間隔越長約省電,但同時更不容易被掃描到

基於GATT廣播的BLE連接只能是一個外圍設備連接一個中心設備,可以理解成一個藍牙耳機只能連接一台手機,不能同時連接兩台手機

GATT協議

GATT協議建立在ATT協議的基礎上。將ATT協議中的Service、Characteristic 及對應數據都保存在一個查找表中,查找表使用16位的ID作為索引。建立GATT連接前必須先經過GAP協議

GATT連接是獨占的,也就是說同一個BLE外設(外部設備)同時只能被一個中心設備連接,一旦外設被連接,它就會停止GAP廣播,對其它設備不可見;當設備斷開時它又開始廣播。

如果中心設備和外設需要雙向通信,唯一的方式就是建立GATT連接,GAP通信是單向的,只能讓中心設備向外設發送信息

GATT通信雙方是C/S關系,外設作為GATT的Server,維持ATT查找表、Service、Characteristic定義;中心設備作為GATT的Client,向Server發起請求,所有通信事件都由中心設備Client發起,從Server接收響應。一旦連接建立,外設將會給中心設備建議一個連接間隔,中心設備可以選擇在每個連接間隔嘗試重新連接,檢查是否有新數據,不過這個間隔只是建議,中心設備可以不嚴格按照這個間隔執行請求。

GATT結構

GATT結構建立在ATT的屬性Attribute數據結構之上(其實和ATT的那些東西一模一樣)

Attribute結構體組成種類不同的Characteristic,多個Characteristic被封裝在Servce容器中,Characteristic和Service容器都有着自己的UUID(有官方認證的16位UUID和自定義的128位UUID),各種常用的Service集合成Profile

BLE外設的通信主要通過Characteristic,通過在Characteristic中讀寫數據就實現了雙向通信,也可以通過實現類似串口的Service來配置TxCharacteristic和RxCharacteristic,這些都是具體項目的選擇了

BLE從初始化到建立連接的過程簡述

  1. 外圍設備開始廣播,發送完一個廣播包后T_IFS,開啟射頻Rx窗口接收來自中心設備的數據包
  2. 中心設備掃描到廣播,在收取此廣播T_IFS后如果開啟了中心設備的掃描回復,中心設備將向外設發送回復
  3. 外設收取到中心設備的回復,做好接收准備,並返回ACK包
  4. 如果ACK包未被中心設備接收到,中心設備將一直發送回復直到超時,此期間內只要外設返回過一次ACK包就算連接成功
  5. 開始建立通信,后續中心設備將以收取到外設廣播的時間為原點,以Connection Interval為周期向外設發送數據包,數據包將具有兩個作用:同步兩設備時鍾建立主從模式通信

外設每收到中心設備的一個包,就會把自己的時序原點重新設置,以和中心設備同步(Service向Client同步)

BLE通信在建立成功后變為主從模式,中心設備Central變為Master,外設Peripheral變為Slave,Slave只能在Master向它發送了一個包以后才能在規定的時間內把自己的數據回傳給Master

  1. 連接建立成功
  2. 外設自動停止廣播,其他設備無法再查找到該外設
  3. 按照以下時序進行通信,在中心設備發送包的間隔內,外設可以發送多個包

在這里插入圖片描述

  1. 需要連接斷開時只需要中心設備停止連接(停止發送包)即可
  2. 中心設備可以將外設的addr寫入Flash或SRAM等存儲器件,保持監聽此addr,當再次收到外設廣播時就可以建立通信。BLE Server設備為了省電,當一段時間內沒有數據要發送時,可以不再發送包,雙方就會因為連接超時(connection timeout)斷開,這時需要中心設備啟動監聽,這樣,當BLE Server設備需要發送數據時,就可以再次連接

ESP的藍牙外設配置

藍牙配置相關庫函數

相關頭文件及其作用

#include "bt.h"//藍牙控制器和VHCI設置頭文件
#include "esp_gap_ble_api.h"//GAP設置頭文件,廣播和連接相關參數配置
#include "esp_gatts_api.h"//GATT配置頭文件,創建Service和Characteristic
#include "esp_bt_main.h"//藍牙棧空間的初始化頭文件

藍牙控制器

使用esp_bt_controller_init()

esp_bt_controller_init(esp_bt_controller_config_t *cfg);//esp_bt_controller_config_t是藍牙控制器配置結構體

struct esp_bt_controller_config_t
{
    uint16_t controller_task_stack_size;//藍牙控制器棧大小
    uint8_t controller_task_prio;//藍牙控制器任務優先級
    uint8_t hci_uart_no;//使用哪個UART作為HCI的IO,僅能選擇UART1或UART2串口
	uint32_t hci_uart_baudrate;//HCI串口波特率
    uint8_t scan_duplicate_mode;//重復掃描模式
    uint8_t scan_duplicate_type;//重復掃描類型
    uint16_t normal_adv_size;//普通廣播報文大小
    uint16_t mesh_adv_size;//mesh廣播報文大小
    uint16_t send_adv_reserved_size;//藍牙控制器最小的內存大小(保留出發送報文所需的內存大小)
    uint32_t controller_debug_flag;//藍牙控制器debug log的屬性
    uint8_t mode;//BR/EDR/BLE/Dual模式選擇
	uint8_t ble_max_conn;//BLE模式最多連接個數
    uint8_t bt_max_acl_conn;//BR或EDR最大的ACL連接個數
    uint8_t bt_sco_datapath;//SCO數據路徑 用於HCI或PCM模塊
	bool auto_latency;//BLE自動延遲,用於降低傳統藍牙的功耗
    bool bt_legacy_auth_vs_evt;//BR/EDR傳統的授權完畢事件,用於防止BIAS攻擊
    uint8_t bt_max_sync_conn;//BR/EDR最多的ACL連接數目,也可以在menuconfig中配配置
    uint8_t ble_sca;//BLE晶振准確度指數
    uint8_t pcm_role;//PCM角色,選擇master或slave
    uint8_t pcm_polar;//PCM觸發極性,選擇下降沿或上升沿
    uint32_t magic;//神奇數字
}

初始化藍牙控制器,此函數只能被調用一次,且必須在其他藍牙功能被調用之前調用

使用esp_bt_controller_deinit()來取消初始化,用於關閉藍牙並清除其占用的內存,還會將藍牙任務刪除

下面是藍牙控制器的常用API

esp_bt_controller_enable(esp_bt_mode_t mode);//使能藍牙控制器,mode是藍牙模式,如果想要動態改變藍牙模式不能直接調用該函數,應該先用下面的disable關閉藍牙再使用該API來改變藍牙模式
esp_bt_controller_disable(void);//關閉藍牙控制器
sp_bt_controller_get_status(void);//獲取藍牙控制器狀態
esp_bt_get_mac(void);//獲取藍牙MAC地址

esp_bt_controller_mem_release(esp_bt_mode_t mode);//釋放藍牙控制器的所有內存,包括BSS、數據和其他藍牙使用的堆棧空間
//這個API僅僅應該再esp_bt_controller_init()或after esp_bt_controller_deinit()之前被調用
esp_bt_mem_release(esp_bt_mode_t mode);
//釋放藍牙控制器和藍牙數據的所有內存,比esp_bt_controller_mem_release()更徹底
esp_bt_sleep_enable(void);//讓藍牙進入睡眠模式,這個函數應該在esp_bt_controller_enable()被調用之后再調用

特別地,官方文檔中給出了一套在線升級藍牙設備軟件時的關閉流程

esp_bluedroid_disable();
esp_bluedroid_deinit();
esp_bt_controller_disable();
esp_bt_controller_deinit();
esp_bt_mem_release(ESP_BT_MODE_BTDM);

經典藍牙

用於藍牙運行的API如下所示

esp_bluedroid_get_status(void);//獲取藍牙當前狀態
//可能的狀態如下所示
ESP_BLUEDROID_STATUS_UNINITIALIZED==0//未初始化
ESP_BLUEDROID_STATUS_INITIALIZED//已被初始化但是未開啟
ESP_BLUEDROID_STATUS_ENABLED//初始化並開啟
    
esp_bluedroid_enable(void);//使能藍牙
esp_bluedroid_disable(void);//關閉藍牙
esp_bluedroid_init(void);//初始化藍牙並分配系統資源,它應該被第一個調用
esp_bluedroid_deinit(void);//取消初始化藍牙並將系統資源釋放,用於藍牙結束工作后的收尾

用於設備藍牙配置的API如下所示

esp_bt_dev_get_address(void);//獲取當前設備藍牙地址
esp_bt_dev_set_device_name(const char *name);//設置設備名

這些函數都應該在藍牙啟用后被調用

BLE-GAP相關庫函數

外圍設備庫函數

esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params);//開始廣播
esp_ble_gap_stop_advertising(void);//停止廣播

esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data);//廣播數據參數設置
//adv_data數據結構如下
bool set_scan_rsp//設置是否需要掃描response
bool include_name//廣播內容是否包括設備名
bool include_txpower//廣播數據是否包括發射功率
int min_interval//最小廣播時間間隔
//計算公式:connIntervalmin = Conn_Interval_Min * 1.25 ms
//Conn_Interval_Min在0x0006到0x0C80之間,0xFFFF就是沒有特定的最小值
int max_interval//最大廣播間隔
//計算公式:connIntervalmax = Conn_Interval_Max * 1.25 ms
//Conn_Interval_Max在0x0006到0x0C80之間,Conn_Interval_Max應大於等於Conn_Interval_Min
//0xFFFF代表沒有特定的最大值
int appearance//設備外形(External appearance)
uint16_t manufacturer_len//生產商數據長度
uint8_t *p_manufacturer_data//生產商數據指針
uint16_t service_data_len//Service數據長度
uint8_t *p_service_data//Service數據指針
uint16_t service_uuid_len//Service的UUID長度
uint8_t *p_service_uuid//Service的UUID數組指針
uint8_t flag//廣播屬性(flag)
    
esp_ble_gap_config_adv_data_raw(uint8_t *raw_data, uint32_t raw_data_len);//設置空的廣播數據包,用戶需要自行設置包的內容

中心設備庫函數

esp_ble_gap_start_scanning(uint32_t duration);//使用該函數讓設備掃描附近正在廣播的外設,duration為掃描間隔
esp_ble_gap_stop_scanning(void);//停止掃描

esp_ble_gap_set_scan_params(esp_ble_scan_params_t *scan_params);//設置掃描參數
esp_ble_gap_register_callback(esp_gap_ble_cb_t callback)//間隔回調函數
    
esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_data_length);//設置最大數據包大小

esp_ble_gap_set_prefer_conn_params(esp_bd_addr_t bd_addr, uint16_t min_conn_int, uint16_t max_conn_int, uint16_t slave_latency, uint16_t supervision_tout);//設置當默認連接參數無法使用時的優先連接參數,這個庫函數只能用在中心設備master上

esp_ble_gap_config_scan_rsp_data_raw(uint8_t *raw_data, uint32_t raw_data_len);//設置空的response數據包,用戶需要自行設置數據

esp_ble_gap_read_rssi(esp_bd_addr_t remote_addr);//讀取遠程設備的RSSI,結果會在間隔回調函數中隨ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT事件返回

連接配置庫函數

esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params);//在連接建立后更新連接參數
esp_ble_gap_clear_rand_addr(void);//清空應用的隨機地址

esp_ble_gap_update_whitelist(bool add_remove, esp_bd_addr_t remote_bda, esp_ble_wl_addr_type_t wl_addr_type);//新建或移除白名單中的設備
esp_ble_gap_get_whitelist_size(uint16_t *length);//獲取白名單的大小

esp_ble_gap_set_device_name(const char *name);//設置本機設備名
esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t *addr_type);//獲取本機設備地址

GATT Server的配置

Server-Master

基本設置

esp_err_t ret;//用於debug
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();//設置藍牙為默認參數

ret = nvs_flash_init();//初始化NVS
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);
ESP_LOGI(TAG, "%s init NVS finished\n", __func__);

ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));//釋放藍牙所需空間

ret = esp_bt_controller_init(&bt_cfg);//初始化藍牙控制器
if (ret)
{
	ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
	return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);//使能藍牙控制器
if (ret)
{
	ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
	return;
}
ret = esp_bluedroid_init();//初始化藍牙棧bluedroid stack
/*
藍牙棧bluedroid stack包括了BT和BLE使用的基本的define和API
初始化藍牙棧以后並不能直接使用藍牙功能,
還需要用FSM管理藍牙連接情況
*/
if (ret)
{
	ESP_LOGE(TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
	return;
}
ret = esp_bluedroid_enable();//使能藍牙棧
if (ret) 
{
	ESP_LOGE(TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
	return;
}
ESP_LOGI(TAG, "%s init bluetooth finished\n", __func__);
//建立藍牙的FSM
//這里使用回調函數來控制每個狀態下的響應,需要將其在GATT和GAP層的回調函數注冊
ret = esp_ble_gatts_register_callback(BLE_gatts_event_handler);
if (ret)
{
	ESP_LOGE(TAG, "gatts register error, error code = %x", ret);
	return;
}
ret = esp_ble_gap_register_callback(BLE_gap_event_handler);
if (ret)
{
	ESP_LOGE(TAG, "gap register error, error code = %x", ret);
	return;
}
/*BLE_gatts_event_handler和BLE_gap_event_handler處理藍牙棧可能發生的所有情況,達到FSM的效果*/

//下面創建了兩個BLE GATT profile,相當於兩個獨立的應用程序
ret = esp_ble_gatts_app_register(GATT_APP_A_ID);
if (ret)
{
	ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
	return;
}
ret = esp_ble_gatts_app_register(GATT_APP_B_ID);
if (ret)
{
	ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
	return;
}

有限狀態機FSM(finite state machine),或者說狀態機SM(state machine)是一種特殊的控制算法,能夠根據控制信號按照預先設定的狀態進行狀態轉移

若輸出只和狀態有關而與輸入無關,則稱為Moore狀態機;若輸出不僅和狀態有關而且和輸入有關系,則稱為Mealy狀態機

控制藍牙的狀態機一般為Moore狀態機,隨藍牙所處的狀態進行不同的操作(代碼中通過switch語句進行控制)

Server的profile利用一個結構體來定義,結構體成員取決於在這個profile中執行的service和characteristic,如下所示

struct gatts_profile_inst {
    esp_gatts_cb_t gatts_cb;//GATT回調函數
    uint16_t gatts_if;//GATT接口
    uint16_t app_id;//應用的ID
    uint16_t conn_id;//連接的ID
    uint16_t service_handle;//Service句柄
    esp_gatt_srvc_id_t service_id;//Service ID
    uint16_t char_handle;//Characteristic句柄
    esp_bt_uuid_t char_uuid;//Characteristic的UUID
    esp_gatt_perm_t perm;//屬性Attribute 授權
    esp_gatt_char_prop_t property;//Characteristic的優先級
    uint16_t descr_handle;//Client的Characteristic配置句柄
    esp_bt_uuid_t descr_uuid;//Client的Characteristic UUID
};

可以將這個結構體進一步組合為結構體數組

static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
    [PROFILE_A_APP_ID] = {
        .gatts_cb = gatts_profile_a_event_handler,
        .gatts_if = ESP_GATT_IF_NONE,
    [PROFILE_B_APP_ID] = {
        .gatts_cb = gatts_profile_b_event_handler,
        .gatts_if = ESP_GATT_IF_NONE,
    },
};

這樣使用類似gl_profile_tab[i].gatts_if的語句就可以訪問結構體的成員,i用於指示第(i+1)個profile

使用上面的結構體數組來定義每個profile對應的GATT回調函數(gatts_profile_a_event_handler()、gatts_profile_b_event_handler()),就使得每個不同的profile使用不同的接口;初始化時,將gatts_if = ESP_GATT_IF_NONE,在之后通過各自的處理函數將profile連接到接口

最后使用esp_ble_gatts_app_register()這個API將應用的ID注冊到GATT

ret = esp_ble_gatts_app_register(GATT_APP_A_ID);//run GATT app A register
if (ret)
{
	ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
	return;
}
ret = esp_ble_gatts_app_register(GATT_APP_B_ID);//run GATT app B register
if (ret)
{
	ESP_LOGE(TAG, "gatts app register error, error code = %x", ret);
	return;
}

GAP設置

使用esp_ble_adv_data_t結構體來配置GAP廣播情況,並使用esp_ble_gap_config_adv_data()函數進行廣播

typedef struct {
    bool set_scan_rsp;//是否作為掃描的回應信號廣播
    bool include_name;//是否包括設備名
    bool include_txpower;//是否包括信號的發射功率
    int min_interval;//廣播數據顯示slave設備的連接最小時間間隔
    int max_interval;//廣播數據顯示slave設備的連接最大時間間隔
    int appearance;//設備外觀(?)
    uint16_t manufacturer_len;//附加數據長度
    uint8_t *p_manufacturer_data;//附加數據指針
    uint16_t service_data_len;//Service數據長度
    uint8_t *p_service_data;//Service數據指針
    uint16_t service_uuid_len;//Servic UUID長度
    uint8_t *p_service_uuid;//Servic UUID指針
    uint8_t flag;//廣播的發現模式,可選BLE_ADV_DATA_FLAG枚舉值
} esp_ble_adv_data_t;

//設置示例
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = false,
    .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec=7.5ms
    .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec=20ms
    .appearance = 0x00,
    .manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN
    .p_manufacturer_data = NULL, //&test_manufacturer[0]
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = sizeof(adv_service_uuid128),
    .p_service_uuid = adv_service_uuid128,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

一個廣播的有效數據是31字節,如果超過會導致超出部分被截掉

使用esp_ble_gap_config_adv_data_raw()和esp_ble_gap_config_scan_rsp_data_raw()函數可以廣播自定義的空數據

廣播數據設置完畢后,會自動進入ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT()或ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT狀態,此時可以在gap_event_handler()中設置FSM控制程序

static void BLE_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
{
    switch (event)
    {
#ifdef CONFIG_SET_RAW_ADV_DATA
    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
        adv_config_done &= (~adv_config_flag);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
    case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
        adv_config_done &= (~scan_rsp_config_flag);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
#else
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        adv_config_done &= (~adv_config_flag);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
    case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
        adv_config_done &= (~scan_rsp_config_flag);
        if (adv_config_done == 0)
        {
            esp_ble_gap_start_advertising(&adv_params);
        }
        break;
#endif
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
        //advertising start complete event to indicate advertising start successfully or failed
        if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS)
        {
            ESP_LOGE(TAG, "Advertising start failed\n");
        }
        break;
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
        if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS)
        {
            ESP_LOGE(TAG, "Advertising stop failed\n");
        }
        else
        {
            ESP_LOGI(TAG, "Stop adv successfully\n");
        }
        break;
    case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
        ESP_LOGI(TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
            param->update_conn_params.status,
            param->update_conn_params.min_int,
            param->update_conn_params.max_int,
            param->update_conn_params.conn_int,
            param->update_conn_params.latency,
            param->update_conn_params.timeout);
        break;
    default:
        break;
    }
}

只要使用了esp_ble_gap_start_advertising()函數,GATT Server就會開始廣播,在此之前還需要用esp_ble_adv_params_t結構體配置相關的參數

//廣播參數
typedef struct {
    uint16_t adv_int_min;
	//非定向和循環定向廣播的最小時間間隔
    //間隔設置在0x0020到0x4000,默認0x0800(1.28s),實際時間=N * 0.625 ms,時間范圍在20ms到10.24s
    
    uint16_t adv_int_max;
    //非定向和循環定向廣播的最大時間間隔
    //間隔設置在0x0020到0x4000,默認0x0800(1.28s),實際時間=N * 0.625 ms,時間范圍在20ms到10.24s
    
    esp_ble_adv_type_t adv_type;//廣播類型
    esp_ble_addr_type_t own_addr_type;//擁有者的藍牙設備地址類型
    esp_bd_addr_t peer_addr;//附近的藍牙設備地址
    esp_ble_addr_type_t peer_addr_type;//附近的藍牙設備地址類型
    esp_ble_adv_channel_t channel_map;//廣播通道映射
    esp_ble_adv_filter_t adv_filter_policy;//廣播過濾器設置
}
esp_ble_adv_params_t;

//設置示例
static esp_ble_adv_params_t adv_params = {
    .adv_int_min        = 0x20,//最小時間間隔
    .adv_int_max        = 0x40,//最大時間間隔
    .adv_type           = ADV_TYPE_IND,
    .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,//公共地址
    //.peer_addr            =默認
    //.peer_addr_type       =默認
    .channel_map        = ADV_CHNL_ALL,//全通道
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,//掃描所有連接
};

設置完畢后,可以使用esp_ble_gap_start_advertising()進行廣播

注意:esp_ble_gap_config_adv_data()使用esp_ble_adv_data_t結構體進行設置,配置的是廣播出去的數據;而esp_ble_gap_start_advertising()使用esp_ble_adv_params_t結構體進行設置,配置的是該怎樣廣播

經典藍牙的子集SPP

藍牙串口協議Serial Port Profile簡寫為SPP,SPP就是一種能在藍牙設備之間創建串口進行數據傳輸的協議,最終目的是在兩個不同設備(通信的兩端)上的應用之間保證一條完整的通信路徑

SPP的協議棧示意圖如下

image-20210205162726034

連接流程

  1. 創建虛擬連接
  2. 接受虛擬串口連接
  3. 在本地SDP數據上注冊服務

SPP協議與GATT協議的對比

經典藍牙BT-SPP 低功耗藍牙BLE-GATT
速率高 靈活多變、集成很多profile
兼容性好 高速率傳輸時兼容性難以保障
對IOS不友好 對IOS很友好
APP編程不方便 開發資源豐富、接口多


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM