藍牙BLE詳解,有這一篇就夠了


什麼是藍牙?編程


    藍牙是一種短距的無線通信技術,可實現固定設備、移動設備之間的數據交換。通常將藍牙3.0以前的BR/EDR藍牙稱爲傳統藍牙,而將藍牙4.0規範下的LE藍牙稱爲低功耗藍牙。緩存

    不少人對藍牙的認識還很侷限於手機領域,其實藍牙的應用已經遠遠不止於此。過去幾年裏,藍牙的增加量就達到了80%,固然,低功耗藍牙的出現也起到關鍵的做用,相信將來藍牙會開創一個可交互的物聯世界。安全

    標準分類
微信

    藍牙4.0標準包括傳統藍牙模塊部分低功耗藍牙模塊部分,是一個雙模標準。低功耗藍牙也是創建在傳統藍牙基礎之上發展起來的,並區別於傳統模塊,最大的特色就是成本和功耗下降,應用於實時性要求比較高。網絡

    BLE(Bluetooh Low Energy)藍牙低能耗技術是短距離、低成本、可互操做性的無線技術,它利用許多智能手段最大限度地下降功耗。併發

    BLE技術的工做模式很是適合用於從微型無線傳感器(每半秒交換一次數據)或使用徹底異步通訊的遙控器等其它外設傳送數據。這些設備發送的數據量很是少(一般幾個字節),並且發送次數也不多(例如每秒幾回到每分鐘一次,甚至更少)。app


BLE協議棧的結構和配置異步

一、協議有兩個部分組成:Controller和Host編輯器

二、Profiles和應用老是基於GAP和GATT之上函數

三、在單芯片方案中,Controller和Host,profiles,和應用層都在同一片芯片中

四、在網絡控制器模式中,Host和Controller是在一塊兒運行的,可是應用和profiles在另一個器件上,好比PC或者其餘微控制器,能夠經過UART,USB進行操做

五、在雙芯片模式中,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除了充當數據傳送功能,它還有以下兩個很是重要的功能:
  1. 同步手機和設備的時鐘,也就是說,設備每收到手機發來的一個包,都會把本身的時序原點從新設置,以跟手機同步。

  2. 告訴設備你如今能夠傳數據給我了。鏈接成功後,BLE通訊將變成主從模式,所以把鏈接發起者(手機)稱爲Master或者Central,把被鏈接者(以前的Advertiser)稱爲Slave或者Peripheral。BLE通訊之因此爲主從模式,是由於Slave不能「隨性」給Master發信息,它只有等到Master給它發了一個packet後,而後才能在規定的時間把本身的數據回傳給Master。

3. 鏈接失敗

有以下幾種典型的鏈接失敗狀況:


  1. 如步驟2圖所示,若是slave在transmit window期間沒有收到master發過來的P1,那麼鏈接將會失敗。此時應該排查master那邊的問題,看看master爲何沒有在約定的時間把P1發出來。

  2. 若是master在transmit window期間把P1發出來了,也就是說master按照connect_req約定的時序把P1發出來了,但slave沒有把P2回過去或者沒有在超時時間內把P2回過去,那麼鏈接也會失敗。此時應該排查slave這邊的問題,看一看slave爲何沒有把P2回過去

  3. 若是master把P1發出來了,slave也把P2回過去了,此時主機或者從機仍是報鏈接失敗,這種狀況有多是軟件有問題,須要仔細排查master或者slave的軟件。

  4. 還有一種比較常見的鏈接失敗狀況:空中射頻干擾太大。此時應該找一個乾淨的環境,好比屏蔽室,排除干擾後再去測試鏈接是否正常。


數據幀格式


鏈接成功後,雙方將能夠互相發送數據,那麼將涉及到其數據幀格式:

字段釋義:
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 的包。這個標誌能夠用來判斷數據包是否被正確接收仍是須要重傳。


代碼示例

   

    本例以OSAL下BLE代碼爲例作講解。

什麼是OSAL? 

    OSAL爲:Operating System Abstraction Layer,即操做系統抽象層,支持多任務運行,它並非一個傳統意義上的操做系統,可是實現了部分相似操做系統的功能。

      OSAL概念是由TI公司在ZIGBEE協議棧引入,他的意思是」模擬操做系統」,此OS,並不是一個真正的OS,而是模擬OS的一些方法爲廣大編程者提供一種寫MCU程序的方法。當有一個事件發生的時候,OSAL負責將此事件分配給可以處理此事件的任務,而後此任務判斷事件的類型,調用相應的事件處理程序進行處理。

   

實驗平臺

    一、藍牙協議棧:1.3.2

    二、軟件平臺:IAR For 8051 8.10.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 ;  //廣播全部通道3七、3八、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 );

}

//初始化完成後

五、

void SimpleBLEPeripheral_Init( uint8 task_id )

{

osal_set_event( simpleBLEPeripheral_TaskID, SBP_START_DEVICE_EVT );              //啓動設備開始事件

}

六、

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 );

      }

}

七、

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 );

  }

}

八、

static void gapRole_SetupGAP( void )

{

  VOID GAP_DeviceInit( gapRole_TaskID,

          gapRole_profileRole, 0,

          gapRole_IRK, gapRole_SRK,

          &gapRole_signCounter );

}

九、

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 ) //鏈路系統消息事件

十二、

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當前狀態

          }

      }

}

1三、stat=GAP_UpdateAdvertisingData( gapRole_TaskID,TRUE, gapRole_AdvertDataLen, gapRole_AdvertData );//更新廣播數據後,將產生GAP_ADV_DATA_UPDATE_DONE_EVENT事件;

1四、

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;

1五、

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;

1六、執行廣播事件

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;  //廣播通道配置:廣播全部通道3七、3八、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 );

  }

  1七、處理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當前狀態

      }

  }

1八、這時候底層已經使能硬件在廣播了,要麼廣播超時產生一個GAP_END_DISCOVERABLE_DONE_EVENT消息,要麼被鏈接事件 GAP_LINK_ESTABLISHED_EVENT;

1九、廣播超時產生一個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當前狀態

      }

  }

20、廣播時產生一個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;

        }

      }

 }

本文分享自微信公衆號 - 嵌入式雲IOT技術圈(gh_d6ff851b4069)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索