一、什么是藍牙?
藍牙是一種短距的無線通訊技術,可實現固定設備、移動設備之間的數據交換。一般將藍牙3.0之前的BR/EDR藍牙稱為傳統藍牙,而將藍牙4.0規范下的LE藍牙稱為低功耗藍牙。
很多人對藍牙的認識還很局限于手機領域,其實藍牙的應用已經遠遠不止于此。過去幾年里,藍牙的增長量就達到了80%,當然,低功耗藍牙的出現也起到關鍵的作用,相信未來藍牙會開創一個可交互的物聯世界。
藍牙4.0標準包括傳統藍牙模塊部分和低功耗藍牙模塊部分,是一個雙模標準。低功耗藍牙也是建立在傳統藍牙基礎之上發展起來的,并區別于傳統模塊,最大的特點就是成本和功耗降低,應用于實時性要求比較高。
BLE(Bluetooh Low Energy)藍牙低能耗技術是短距離、低成本、可互操作性的無線技術,它利用許多智能手段最大限度地降低功耗。 BLE技術的工作模式非常適合用于從微型無線傳感器(每半秒交換一次數據)或使用完全異步通信的遙控器等其它外設傳送數據。這些設備發送的數據量非常少(通常幾個字節),而且發送次數也很少(例如每秒幾次到每分鐘一次,甚至更少)。
二、BLE協議棧的結構和配置
1、協議有兩個部分組成:Controller和Host 2、Profiles和應用總是基于GAP和GATT之上 3、在單芯片方案中,Controller和Host,profiles,和應用層都在同一片芯片中 4、在網絡控制器模式中,Host和Controller是在一起運行的,但是應用和profiles在另外一個器件上,比如PC或者其他微控制器,可以通過UART,USB進行操作 5、在雙芯片模式中,Controller運行在一個控制器,而應用層,profiles和Host是運行在另外一個控制器上
BLE設備連接狀態流程圖
三、BLE協議棧各層功能機制
如上圖所述,要實現一個BLE應用,首先需要一個支持BLE射頻的芯片,然后還需要提供一個與此芯片配套的BLE協議棧,最后在協議棧上開發自己的應用。可以看出BLE協議棧是連接芯片和應用的橋梁,是實現整個BLE應用的關鍵。那BLE協議棧具體包含哪些功能呢?簡單來說,BLE協議棧主要用來對你的應用數據進行層層封包,以生成一個滿足BLE協議的空中數據包,也就是說,把應用數據包裹在一系列的幀頭(header)和幀尾(tail)中。具體來說,BLE協議棧主要由如下幾部分組成:
-
PHY層(Physical layer物理層)。 PHY層用來指定BLE所用的無線頻段,調制解調方式和方法等。PHY層做得好不好,直接決定整個BLE芯片的功耗,靈敏度以及selectivity等射頻指標。
-
LL層(Link Layer鏈路層)。 LL層是整個BLE協議棧的核心,也是BLE協議棧的難點和重點。像Nordic的BLE協議棧能同時支持20個link(連接),就是LL層的功勞。LL層要做的事情非常多,比如具體選擇哪個射頻通道進行通信,怎么識別空中數據包,具體在哪個時間點把數據包發送出去,怎么保證數據的完整性,ACK如何接收,如何進行重傳,以及如何對鏈路進行管理和控制等等。LL層只負責把數據發出去或者收回來,對數據進行怎樣的解析則交給上面的GAP或者ATT。
-
HCI(Host controller interface)。 HCI是可選的,HCI主要用于2顆芯片實現BLE協議棧的場合,用來規范兩者之間的通信協議和通信命令等。
-
GAP層(Generic access profile)。 GAP是對LL層payload(有效數據包)如何進行解析的兩種方式中的一種,而且是最簡單的那一種。GAP簡單的對LL payload進行一些規范和定義,因此GAP能實現的功能極其有限。GAP目前主要用來進行廣播,掃描和發起連接等。
-
L2CAP層(Logic link control and adaptation protocol)。 L2CAP對LL進行了一次簡單封裝,LL只關心傳輸的數據本身,L2CAP就要區分是加密通道還是普通通道,同時還要對連接間隔進行管理。
-
SMP(Secure manager protocol)。 SMP用來管理BLE連接的加密和安全的,如何保證連接的安全性,同時不影響用戶的體驗,這些都是SMP要考慮的工作。
-
ATT(Attribute protocol)。 簡單來說,ATT層用來定義用戶命令及命令操作的數據,比如讀取某個數據或者寫某個數據。BLE協議棧中,開發者接觸最多的就是ATT。BLE引入了attribute概念,用來描述一條一條的數據。Attribute除了定義數據,同時定義該數據可以使用的ATT命令,因此這一層被稱為ATT層。
-
GATT(Generic attribute profile )。 GATT用來規范attribute中的數據內容,并運用group(分組)的概念對attribute進行分類管理。沒有GATT,BLE協議棧也能跑,但互聯互通就會出問題,也正是因為有了GATT和各種各樣的應用profile,BLE擺脫了ZigBee等無線協議的兼容性困境,成了出貨量最大的2.4G無線通信產品。
四、BLE藍牙模塊主要應用領域
- 移動擴展設備
- 汽車電子設備
- 健康醫療用品:心跳帶、血壓計等
- 定位應用:室內定位、井下定位等
- 近距離數據采集:無線抄表、無線遙測等
- 數據傳輸:智能家居室內控制、藍牙調光、打印機等
五、 BLE協議棧詳解
協議概述
所謂協議,即將指定的字節按照一定的順序排列起來,以便他人使用自己的設備時,能通過該協議同其他設備進行通信。協議一特點,就是有固定的幀格式,通過該格式發送,接收者通過解讀幀格式,進而得到新息內容;
BLE連接過程
一般通信協議,一類通信是直接發生數據,當設備接送到數據時,直接對數據進行解析,當接受到的數據合法時,即為有效數據,該類型的通信協議,主要用在有線通信協議中,比如Modbus,Can通常采用的即為該類型的通信方式。
另一類通信協議,則需要新建立連接,當雙方連接建立成功了方可通信,例如TCP、BLE;BLE協議在需要進行通信時,即需要向外發送廣播信號,告訴接收者,即將和它進行通信,接受者接收到廣播內容后,確認是與自己通信,于是向廣播者發送一響應信息,這樣當廣播者和接受者都有了對方的身份信息時,即表示雙方連接成功。
因此,在連接過程中,必定有相應的廣播幀格式。在BLE通信過程中,假設設備A需要連其他設備假設為B,則A需要不斷地發送廣播信號(此過程一般有一個時間間隔,在沒發送廣播數據時間內,芯片處于低功耗狀態),每發送一次廣播包,稱之為一次廣播事件。
前導: 是一個8比特的交替序列 接入地址的第一個比特為0:01010101 接入地址的第一個比特為1:10101010 接入地址:廣播幀為固定地址:0x8E89BED6(低字節在前)
廣播報文的報頭: 包含4bit廣播報文類型、2bit保留位、1bit發送地址類型和1bit接收地址類型。
廣播報文類型:
發送地址類型: 0: 公共地址 1:隨機地址 長度:廣播報文的長度域包含8個比特,有效值的范圍是6~37 數據: 廣播者地址(6個字節)+廣播數據(31個字節) 校驗: 3個字節,為CRC校驗。 廣播數據: 分為有效數據和無效數據
有效數據部分: 包含N個AD Structure,每個AD Structure由Length,AD Type和AD Data組成。其中: Length: AD Type和AD Data的長度。 AD Type: 指示AD Data數據的含義。 詳見https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/
六、 BLE連接建立過程
1. BLE廣播與掃描
設備B不斷發送廣播信號給手機(Observer),如果手機不開啟掃描窗口,手機是收不到設備B的廣播的,如下圖所示,不僅手機要開啟射頻接收窗口,而且只有手機的射頻接收窗口跟廣播發送的發射窗口匹配成功,而且廣播射頻通道和手機掃描射頻通道是同一個通道,手機才能收到設備B的廣播信號。也就是說,如果設備B在37通道發送廣播包,而手機在掃描38通道,那么即使他們倆的射頻窗口匹配,兩者也是無法進行通信的。由于這種匹配成功是一個概率事件,因此手機掃到設備B也是一個概率事件,也就是說,手機有時會很快掃到設備B,比如只需要一個廣播事件,手機有時又會很慢才能掃到設備B,比如需要10個廣播事件甚至更多。
2. 建立連接(connection establishment)
根據藍牙spec規定,advertiser發送完一個廣播包之后150us(T_IFS),advertiser必須開啟一段時間的射頻Rx窗口,以接收來自observer的數據包。Observer就可以在這段時間里給advertiser發送連接請求。如下圖所示,手機在第三個廣播事件的時候掃到了設備B,并發出了連接請求CONN_REQ(CONN_REQ又稱為CONNECT_IND)。
注:圖中M代表手機,S代表設備B,M->S表示手機將數據包發給設備B,即手機開啟Tx窗口,設備B開啟Rx窗口;S->M正好相反,表示設備B將數據包發給手機,即設備B開啟Tx窗口,手機開啟Rx窗口。
如圖所示,手機在收到A1廣播包ADV_IND后,以此為初始錨點(這個錨點不是連接的錨點),T_IFS時間后給Advertiser發送一個connection request命令,即A2數據包,告訴advertiser我將要過來連你,請做好準備。Advertiser根據connect_req命令信息做好接收準備,connect_req包含如下關鍵信息:
- Transmit window offset,定義如上圖示
- Transmit window size,定義如上圖所示
- connect_req數據包完整定義如下所示
connect_req其實是在告訴advertiser,手機將在Transmit Window期間發送第一個同步包(P1)給你,請在這段時間里把你的射頻接收窗口打開。設備B收到P1后,T_IFS時間后將給手機回復數據包P2(ACK包)。
一旦手機收到數據包P2,連接即可認為建立成功。當然,實際情況會比較復雜,手機有可能收不到P2,這個時候手機將持續發送同步包直到超時時間(supervision timeout)到,在此期間只要設備B回過一次ACK包,連接即算成功。所以一旦P1包發出,主機(手機)即認為連接成功,而不管有沒有收到設備的ACK包。
這也是為什么在Android或者iOS系統中,應用經常收到連接成功的回調事件(該回調事件就是基于P1包有沒有發出,只要P1包發出,手機即認為連接成功,而不管有沒有收到設備的ACK包),但實際上手機和設備并沒有成功建立連接。
后續手機將以P1為錨點(原點),Connection Interval為周期,周期性地給設備B發送數據包(Packet),Packet除了充當數據傳送功能,它還有如下兩個非常重要的功能: 同步手機和設備的時鐘,也就是說,設備每收到手機發來的一個包,都會把自己的時序原點重新設置,以跟手機同步。
告訴設備你現在可以傳數據給我了。連接成功后,BLE通信將變成主從模式,因此把連接發起者(手機)稱為Master或者Central,把被連接者(之前的Advertiser)稱為Slave或者Peripheral。
BLE通信之所以為主從模式,是因為Slave不能“隨性”給Master發信息,它只有等到Master給它發了一個packet后,然后才能在規定的時間把自己的數據回傳給Master。
3. 連接失敗
有如下幾種典型的連接失敗情況:
-
如步驟2圖所示,如果slave在transmit window期間沒有收到master發過來的P1,那么連接將會失敗。此時應該排查master那邊的問題,看看master為什么沒有在約定的時間把P1發出來。
-
如果master在transmit window期間把P1發出來了,也就是說master按照connect_req約定的時序把P1發出來了,但slave沒有把P2回過去或者沒有在超時時間內把P2回過去,那么連接也會失敗。此時應該排查slave這邊的問題,看一看slave為什么沒有把P2回過去
-
如果master把P1發出來了,slave也把P2回過去了,此時主機或者從機還是報連接失敗,這種情況有可能是軟件有問題,需要仔細排查master或者slave的軟件。
-
還有一種比較常見的連接失敗情況:空中射頻干擾太大。此時應該找一個干凈的環境,比如屏蔽室,排除干擾后再去測試連接是否正常。
七、幀格式
1、數據鏈路層報文結構
報文的基礎是數據鏈路層的報文,其它報文都是從此展開的,BLE數據鏈路層數據格式如下:
在最新的core spec 5.2中,有1M PHY和2M的PHY,對應前導符變為1-2個字節。前導符用于頻率同步、時序評估和自動增益控制訓練。前導符第一bit應該與接入地址的LSB相同。
字段解析:
報文類型(低 4 個 bit):
ADV_IND(0000) ——通用廣播
ADV_DIRECT_IND(0001) ——定向連接廣播
ADV_NONCONN_IND(0010) ——不可連接廣播
ADV_SCAN_IND(0110) ——可掃描廣播
SCAN_REQ( 0011) ——主動掃描請求
SCAN_RSP( 0100) ——主動掃描應答
CONNECT_REQ( 0101) ——連接請求
發送地址( TXADD)和接收地址( RXADD): 當此位為“1”時表示 Random Add(隨機地址),當此位為“0”時表示 Public Add(公共地址)。 這個地址指的是數據凈荷中最初的幾個地址字節。
凈荷長度: 這個長度是指在 PDU 中的數據除去報頭和長度之外的有效凈荷數 據長度。
2、 廣播通道與數據通道 PDU 區別:
廣播通道的 PDU 格式:
字段解析:
報文類型(低 4 個 bit):
ADV_IND(0000) ——通用廣播
ADV_DIRECT_IND(0001) ——定向連接廣播
ADV_NONCONN_IND(0010) ——不可連接廣播
ADV_SCAN_IND(0110) ——可掃描廣播
SCAN_REQ( 0011) ——主動掃描請求
SCAN_RSP( 0100) ——主動掃描應答
CONNECT_REQ( 0101) ——連接請求
發送地址( TXADD)和接收地址( RXADD): 當此位為“1”時表示 Random Add (隨機地址),當此位為“0”時表示 Public Add(公共地址)。 這個地址指的是數據凈荷中最初的幾個地址字節。
凈荷長度: 這個長度是指在 PDU 中的數據除去報頭和長度之外的有效凈荷數據長度。
數據通道的 PDU 格式:
字段釋義: LLID: 表示此包數據是 LL Date PDU 還是 LL Control PDU
00b: Reserved
01b: LL Date PDU:Continuation fragment of L2CAP message, or an Empty PDU.
10b: LL Date PDU:Start of an L2CAP message or a complete L2CAP message with no fragmentation.
11b: LL Control PDU
MIC( Message Integrity Check): 信息完整性檢測。涉及到加密操作,上圖中是用虛線表示的,并不是一定要有此項。
MD: 這個標志位是用來通知對方設備自己還有其他數據準備發送。0 表示沒有更多數據發送, 1 表示有更多數據準備發送。這樣,只要還有數據需要發送,連接事件會自動擴展。一旦不再有數據發送,連接事件立即關閉。
Note:如何區分是確定包、新包還是重發包?
SN: 只有一個 bit 位,所以值是在 0 和 1 之間進行切換。如果序列號與之前的一樣,則為重傳報文,如果序列號和之間的不同,則為新報文。
NESN: 預期序列號,它是接收方希望接到的下一包的序列號,也就是數據包的確認標志。當設備接收到序列(SN)為 0 的報文后,在發送給對方的數據包中,應將 NESN 設為 1,這樣對方接收到這個包后,會發送一個新的數據包過來,否則就會重發上一次序列號為 0 的包。這個標志可以用來判斷數據包是否被正確接收還是需要重傳。
3、 BLE 報文格式
4、 AD Structure 解析
(1) AD type
(2) AD data 簡述
Flags:
SERVICE:
Local Name:
TX Power Level:
八、代碼示例
本例以OSAL下BLE代碼為例做講解。
什么是OSAL?
OSAL為:Operating System Abstraction Layer,即操作系統抽象層,支持多任務運行,它并不是一個傳統意義上的操作系統,但是實現了部分類似操作系統的功能。
OSAL概念是由TI公司在ZIGBEE協議棧引入,他的意思是”模擬操作系統”,此OS,并非一個真正的OS,而是模擬OS的一些方法為廣大編程者提供一種寫MCU程序的方法。當有一個事件發生的時候,OSAL負責將此事件分配給能夠處理此事件的任務,然后此任務判斷事件的類型,調用相應的事件處理程序進行處理。
實驗平臺
1、藍牙協議棧:1.3.2
2、軟件平臺:IAR For 8051 8.10.3
3、硬件平臺:Smart RF開發板(從機),Android_Lightblue(主機)
代碼解析
- int main(void)
int main(void)
{
/* Initialize hardware */
HAL_BOARD_INIT();//初始化時鐘和使能緩存預取模式
// Initialize board I/O
InitBoard( OB_COLD );//冷啟動,關閉了led燈與中斷,避免接下來的各種初始化受干擾
/* Initialze the HAL driver */
HalDriverInit();//各種驅動的初始化、如按鍵、lcd、adc、usb、uart等8
/* Initialize NV system */
osal_snv_init();//snv 內部用于保存配對數據或你的用戶自定義數據的一段flash,4kB空間
/* Initialize LL */
/* Initialize the operating system */
osal_init_system();//oasl 操作系統初始化, 包含內存分配、消息隊列、定時器、電源管理和任務等
/* Enable interrupts */
HAL_ENABLE_INTERRUPTS();// 開啟全局中斷
// Final board initialization
InitBoard( OB_READY ); //設置標志標示系統初始化完畢
}
- osal_init_system()
uint8 osal_init_system( void )
{
// Initialize the Memory Allocation System
osal_mem_init();//初始化內存分配系統
// Initialize the message queue
osal_qHead = NULL;//初始化消息隊列
// Initialize the timers
osalTimerInit();//初始化定時器
// Initialize the Power Management System
osal_pwrmgr_init();//初始化電源管理系統
// Initialize the system tasks.
osalInitTasks();//初始化系統任務, 這一個任務初始花非常關鍵
// Setup efficient search for the first free block of heap.
osal_mem_kick();
return ( SUCCESS );
}
- osalInitTasks()
void osalInitTasks( void )
{
/* L2CAP Task */
L2CAP_Init( taskID++ );
/* GAP Task */
GAP_Init( taskID++ );
/* GATT Task */
GATT_Init( taskID++ );
/* SM Task */
SM_Init( taskID++ );
/* Profiles */
GAPRole_Init( taskID++ ); //鏈路角色初始化
GAPBondMgr_Init( taskID++ ); //鏈路綁定初始化
GATTServApp_Init( taskID++ );
/* Application */
SimpleBLEPeripheral_Init( taskID );
}
- GAPRole_Init( taskID++ )
void GAPRole_Init( uint8 task_id )
{
gapRole_TaskID = task_id; //定義任務地址
gapRole_state = GAPROLE_INIT; //鏈路狀態設置為GAPROLE_INIT
gapRole_ConnectionHandle = INVALID_CONNHANDLE; //設置鏈路連接句柄為0xFFFF
GAP_RegisterForHCIMsgs( gapRole_TaskID );//注冊控制接口的任務ID
// Initialize the Profile Advertising and Connection Parameters
gapRole_profileRole = GAP_PROFILE_PERIPHERAL; //鏈路配置角色為從機
VOID osal_memset( gapRole_IRK, 0, KEYLEN ); //密鑰緩沖器清零
VOID osal_memset( gapRole_SRK, 0, KEYLEN );
gapRole_signCounter = 0; //密鑰計數標志位清零
gapRole_AdvEventType = GAP_ADTYPE_ADV_IND; //廣播類型為可連接無定向廣播
gapRole_AdvDirectType = ADDRTYPE_PUBLIC; //廣播方式為通過廣播(可被發現掃描連接)
gapRole_AdvChanMap = GAP_ADVCHAN_ALL ; //廣播所有通道37、38、39
gapRole_AdvFilterPolicy = GAP_FILTER_POLICY_ALL; //允許掃描,允許連接
// Restore Items from NV
VOID osal_snv_read( BLE_NVID_IRK, KEYLEN, gapRole_IRK ); //讀出存儲的密鑰和密鑰計數標志位
VOID osal_snv_read( BLE_NVID_CSRK, KEYLEN, gapRole_SRK );
VOID osal_snv_read( BLE_NVID_SIGNCOUNTER, sizeof( uint32 ), &gapRole_signCounter );
}
- SimpleBLEPeripheral_Init
//初始化完成后
void SimpleBLEPeripheral_Init( uint8 task_id )
{
osal_set_event( simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT ); //啟動設備開始事件
}
- SimpleBLEPeripheral_ProcessEvent
uint16 SimpleBLEPeripheral_ProcessEvent( uint8 task_id, uint16 events )
{
if ( events & SBP_START_DEVICE_EVT )// 初始化后就執行這個啦
{
// Start the Device
VOID GAPRole_StartDevice( &simpleBLEPeripheral_PeripheralCBs ); //配置鏈路事件通知回調函數
// Start Bond Manager
VOID GAPBondMgr_Register( &simpleBLEPeripheral_BondMgrCBs ); //配置配對消息回調函數
// Set timer for first periodic event
osal_start_timerEx( simpleBLEPeripheral_TaskID, POWER_DETECT_EVT, DetectPowerPeriod );
return ( events ^ SBP_START_DEVICE_EVT );
}
}
- GAPRole_StartDevice
Status_t GAPRole_StartDevice( gapRolesCBs_t *pAppCallbacks )
{
if ( gapRole_state == GAPROLE_INIT ) //如果鏈路狀態是初始化狀態
{
// Clear all of the Application callbacks
if ( pAppCallbacks )
{
pGapRoles_AppCGs = pAppCallbacks;//設置回調函數
}
// Start the GAP
gapRole_SetupGAP();//開始建立鏈路
return ( SUCCESS );
}
else //否則返回已經在請求模式狀態
{
return ( bleAlreadyInRequestedMode );
}
}
- gapRole_SetupGAP
static void gapRole_SetupGAP( void )
{
VOID GAP_DeviceInit( gapRole_TaskID,
gapRole_profileRole, 0,
gapRole_IRK, gapRole_SRK,
&gapRole_signCounter );
}
- GAP_DeviceInit
bStatus_t GAP_DeviceInit( uint8 taskID,
uint8 profileRole,
uint8 maxScanResponses,
uint8 *pIRK,
uint8 *pSRK,
uint32 *pSignCounter )
{
// Setup the device configuration parameters
stat = GAP_ParamsInit( taskID, profileRole ); //設置設備配置參數
#if ( HOST_CONFIG & ( CENTRAL_CFG | PERIPHERAL_CFG ) )
{
GAP_SecParamsInit( pIRK, pSRK, pSignCounter );
}
#endif
#if ( HOST_CONFIG & ( PERIPHERAL_CFG | BROADCASTER_CFG ) )
{
// Initialize GAP Peripheral Device Manager
VOID GAP_PeriDevMgrInit(); //初始化從機設備管理
#if ( HOST_CONFIG & PERIPHERAL_CFG )
{
// Initialize SM Responder
VOID SM_ResponderInit(); //回應者初始化
}
#endif
}
#endif
}
-
當GAP_DeviceInit初始化完成后,將產生GAP_DEVICE_INIT_DONE_EVENT事件; uint16 GAPRole_ProcessEvent( uint8 task_id, uint16 events ) //鏈路處理事件
-
static void gapRole_ProcessOSALMsg( osal_event_hdr_t *pMsg ) //鏈路系統消息事件
-
gapRole_ProcessGAPMsg
static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //鏈路處理連接消息
{
uint8 notify = FALSE; // State changed notify the app? (default no)
switch ( pMsg->opcode )
{
case GAP_DEVICE_INIT_DONE_EVENT: //當GAP_DeviceInit初始化完成后,將產生此事件
{
gapDeviceInitDoneEvent_t *pPkt = (gapDeviceInitDoneEvent_t *)pMsg;
bStatus_t stat = pPkt->hdr.status;
if ( stat == SUCCESS )
{
// Save off the generated keys
VOID osal_snv_write( BLE_NVID_IRK, KEYLEN, gapRole_IRK );//保存生成的密鑰
VOID osal_snv_write( BLE_NVID_CSRK, KEYLEN, gapRole_SRK );
// Save off the information
VOID osal_memcpy( gapRole_bdAddr, pPkt->devAddr, B_ADDR_LEN );//保存設備地址
gapRole_state = GAPROLE_STARTED; //鏈路開始
// Update the advertising data
stat = GAP_UpdateAdvertisingData( gapRole_TaskID,//更新廣播數據
TRUE, gapRole_AdvertDataLen, gapRole_AdvertData );
}
notify = TRUE; //通知回調函數鏈路的狀態
}
break;
if ( notify == TRUE )
{
// Notify the application with the new state change
if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) //判斷是否設置了回調函數
{
pGapRoles_AppCGs->pfnStateChange( gapRole_state );//調用設置的回調函數,通知gapRole_state當前狀態
}
}
}
-
stat=GAP_UpdateAdvertisingData( gapRole_TaskID,TRUE, gapRole_AdvertDataLen, gapRole_AdvertData );//更新廣播數據后,將產生GAP_ADV_DATA_UPDATE_DONE_EVENT事件;
-
gapRole_ProcessGAPMsg
static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //鏈路處理連接消息
{
uint8 notify = FALSE; // State changed notify the app? (default no)
switch ( pMsg->opcode )
{
case GAP_ADV_DATA_UPDATE_DONE_EVENT:
{
gapAdvDataUpdateEvent_t *pPkt = (gapAdvDataUpdateEvent_t *)pMsg;
if ( pPkt->hdr.status == SUCCESS )
{
if ( pPkt->adType )
{
// Setup the Response Data
pPkt->hdr.status = GAP_UpdateAdvertisingData( gapRole_TaskID,
FALSE, gapRole_ScanRspDataLen, gapRole_ScanRspData );//更新掃描回應數據
}
else
{
// Start advertising
VOID osal_set_event( gapRole_TaskID, START_ADVERTISING_EVT ); //啟動廣播事件
}
}
if ( pPkt->hdr.status != SUCCESS ) //如果不成功將通知回調函數,否則不通知
{
// Set into Error state
gapRole_state = GAPROLE_ERROR;
notify = TRUE;
}
}
break;
- gapRole_ProcessGAPMsg
static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //鏈路處理連接消息
{
uint8 notify = FALSE; // State changed notify the app? (default no)
switch ( pMsg->opcode )
{
case GAP_ADV_DATA_UPDATE_DONE_EVENT:
{
gapAdvDataUpdateEvent_t *pPkt = (gapAdvDataUpdateEvent_t *)pMsg;
if ( pPkt->hdr.status == SUCCESS )
{
if ( pPkt->adType )
{
// Setup the Response Data
pPkt->hdr.status = GAP_UpdateAdvertisingData( gapRole_TaskID,
FALSE, gapRole_ScanRspDataLen, gapRole_ScanRspData );//更新掃描回應數據
}
else
{
// Start advertising
VOID osal_set_event( gapRole_TaskID, START_ADVERTISING_EVT ); //啟動廣播事件
}
}
if ( pPkt->hdr.status != SUCCESS ) //如果不成功將通知回調函數,否則不通知
{
// Set into Error state
gapRole_state = GAPROLE_ERROR;
notify = TRUE;
}
}
break;
- 執行廣播事件
uint16 GAPRole_ProcessEvent( uint8 task_id, uint16 events )
{
VOID task_id; // OSAL required parameter that isn't used in this function
if ( events & START_ADVERTISING_EVT )
{
if ( gapRole_AdvEnabled )
{
gapAdvertisingParams_t params;
// Setup advertisement parameters
params.eventType = gapRole_AdvEventType; //GAP_ADTYPE_ADV_IND; 廣播類型為可連接無定向廣播
params.initiatorAddrType = gapRole_AdvDirectType; //ADDRTYPE_PUBLIC; 廣播方式為通用廣播
VOID osal_memcpy( params.initiatorAddr, gapRole_AdvDirectAddr, B_ADDR_LEN ); //發起者地址配置
params.channelMap = gapRole_AdvChanMap; //廣播通道配置:廣播所有通道37、38、39
params.filterPolicy = gapRole_AdvFilterPolicy;//過濾策略GAP_FILTER_POLICY_ALL;允許掃描,允許連接
if ( GAP_MakeDiscoverable( gapRole_TaskID, ¶ms ) != SUCCESS ) //配置廣播參數,并產生一個GAP_MakeDiscoverable 消息事件
{
gapRole_state = GAPROLE_ERROR;//如果不成功將通知回調函數-鏈路錯誤
// Notify the application with the new state change
if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange )
{
pGapRoles_AppCGs->pfnStateChange( gapRole_state );
}
}
}
return ( events ^ START_ADVERTISING_EVT );
}
- 處理GAP_MakeDiscoverable消息事件
static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //鏈路處理連接消息
{
uint8 notify = FALSE; // State changed notify the app? (default no)
switch ( pMsg->opcode )
{
case GAP_MAKE_DISCOVERABLE_DONE_EVENT: //使能可被發現完成事件即開始廣播了
case GAP_END_DISCOVERABLE_DONE_EVENT: //結束可被發現完成事件即停止廣播了
{
gapMakeDiscoverableRspEvent_t *pPkt = (gapMakeDiscoverableRspEvent_t *)pMsg;
if ( pPkt->hdr.status == SUCCESS )
{
if ( pMsg->opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT )
{
gapRole_state = GAPROLE_ADVERTISING; //設置當前鏈路狀態
}
else // GAP_END_DISCOVERABLE_DONE_EVENT//結束可被發現完成事件即停止廣播了
{
if ( gapRole_AdvertOffTime != 0 ) //如果gapRole_AdvertOffTime等于0,將不再廣播,否則啟動定時廣播件
{
if ( ( gapRole_AdvEnabled ) )//如果使能廣播
{
VOID osal_start_timerEx( gapRole_TaskID, START_ADVERTISING_EVT, gapRole_AdvertOffTime );//啟動周期廣播事件
}
}
else
{
// Since gapRole_AdvertOffTime is set to 0, the device should not
// automatically become discoverable again after a period of time.
// Set enabler to FALSE; device will become discoverable again when
// this value gets set to TRUE
gapRole_AdvEnabled = FALSE;
}
// In the Advertising Off period
gapRole_state = GAPROLE_WAITING;//如果GAP_END_DISCOVERABLE_DONE_EVENT,鏈路當前狀態為等待狀態
}
}
else
{
gapRole_state = GAPROLE_ERROR;
}
notify = TRUE;//通知回調函數
}
break;
if ( notify == TRUE )
{
// Notify the application with the new state change
if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) //判斷是否設置了回調函數
{
pGapRoles_AppCGs->pfnStateChange( gapRole_state );//調用設置的回調函數,通知gapRole_state當前狀態
}
}
-
這時候底層已經使能硬件在廣播了,要么廣播超時產生一個GAP_END_DISCOVERABLE_DONE_EVENT消息,要么被連接事件 GAP_LINK_ESTABLISHED_EVENT;
-
廣播超時產生一個GAP_END_DISCOVERABLE_DONE_EVENT消息
static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //鏈路處理連接消息
{
uint8 notify = FALSE; // State changed notify the app? (default no)
switch ( pMsg->opcode )
{
case GAP_MAKE_DISCOVERABLE_DONE_EVENT: //使能可被發現完成事件即開始廣播了
case GAP_END_DISCOVERABLE_DONE_EVENT: //結束可被發現完成事件即停止廣播了
{
gapMakeDiscoverableRspEvent_t *pPkt = (gapMakeDiscoverableRspEvent_t *)pMsg;
if ( pPkt->hdr.status == SUCCESS )
{
if ( pMsg->opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT )
{
gapRole_state = GAPROLE_ADVERTISING; //設置當前鏈路狀態
}
else // GAP_END_DISCOVERABLE_DONE_EVENT//結束可被發現完成事件即停止廣播了
{
if ( gapRole_AdvertOffTime != 0 ) //如果gapRole_AdvertOffTime不等于0,啟動定時廣播事件,否則將關閉廣播
{
if ( ( gapRole_AdvEnabled ) )//如果使能廣播
{
VOID osal_start_timerEx( gapRole_TaskID, START_ADVERTISING_EVT, gapRole_AdvertOffTime );//啟動周期廣播事件
}
}
else
{
// Since gapRole_AdvertOffTime is set to 0, the device should not
// automatically become discoverable again after a period of time.
// Set enabler to FALSE; device will become discoverable again when
// this value gets set to TRUE
gapRole_AdvEnabled = FALSE; //關閉廣播
}
// In the Advertising Off period
gapRole_state = GAPROLE_WAITING;//如果GAP_END_DISCOVERABLE_DONE_EVENT,鏈路當前狀態為等待狀態,或不再廣播或等待周期廣播
}
}
else
{
gapRole_state = GAPROLE_ERROR;
}
notify = TRUE;//通知回調函數
}
break;
if ( notify == TRUE )
{
// Notify the application with the new state change
if ( pGapRoles_AppCGs && pGapRoles_AppCGs->pfnStateChange ) //判斷是否設置了回調函數
{
pGapRoles_AppCGs->pfnStateChange( gapRole_state );//調用設置的回調函數,通知gapRole_state當前狀態
}
}
- 廣播時產生一個GAP_LINK_ESTABLISHED_EVENT消息
static void gapRole_ProcessGAPMsg( gapEventHdr_t *pMsg ) //鏈路處理連接消息
{
uint8 notify = FALSE; // State changed notify the app? (default no)
switch ( pMsg->opcode )
{
case GAP_LINK_ESTABLISHED_EVENT:
{
gapEstLinkReqEvent_t *pPkt = (gapEstLinkReqEvent_t *)pMsg;
if ( pPkt->hdr.status == SUCCESS )
{
VOID osal_memcpy( gapRole_ConnectedDevAddr, pPkt->devAddr, B_ADDR_LEN );//保存主機的地址
gapRole_ConnectionHandle = pPkt->connectionHandle; //保存主機連接句柄
gapRole_state = GAPROLE_CONNECTED; //通知鏈路狀態:連接成功
notify = TRUE;
}
}
}