BLE-NRF51822教程3-sdk程序框架剖析

51822的官方SDK其實是沒有框架依耐性的。什麼叫框架,比如TI的BLE SDK中就有一個操作系統抽象層(OSAL)他是一個輪訓的調度。你需要按照他的方式去創建任務等等。

而51822的SDK本質上只是提供了各種調用接口,比如開啓初始化協議棧,初始化一些硬件功能模塊,開始廣播,發起鏈接等等。這些接口怎麼用完全取決於自己。不過一般固件開發都是一些類似的流程各種資源的初始化,51822也不例外。所以sdk中的作爲從機的例子main函數都是類似如下的步驟:

以官方的串口BLE 爲例:

int main(void)

{

    leds_init();                           //非必須,只是該例子中用到了

    timers_init();                      //非必須,只是該例子中用到了

    buttons_init();                    //非必須,只是該例子中用到了

 

    uart_init();                          //非必須,只是該例子中用到了串口

    ble_stack_init();               //必須

    gap_params_init();                   //必須

    services_init();                  //跟自己創建的服務相關,不同的服務細節不同但大體建立                                                         
                                                //過程基本一致,通常在直接使用官    方的例子修改一些參數即可

    advertising_init();            //廣播數據初始化,必須

    conn_params_init();         //是情況而定,如果連接後不需要連接參數的協商,該初始化也                                                //可不要

    sec_params_init();           //安全參數初始化,如果沒用到配對綁定相關這個也可以不初始化

 

    advertising_start();                  //開啓廣播,必須

 

        // Enter main loop

    for (;;)

    {

        power_manage();             //進入睡眠

    }

}

 

可以看到其實核心必要的只有這5個函數而已。你可以將其他代碼全都去掉,只要留下這5個函數設備一樣可以運行,手機也能搜到設備並與設備通信。

這種初始化的方式可以說是與我們一般的單片機開發沒有區別。

 

 

那麼初始化之後呢。以前的裸板單片機開發我們就是進入一個while循環執行一些周而復始的事,後面爲了降低功耗開始在while(1)循環中加個睡眠代碼讓沒有工作時芯片處於睡眠狀態,並依靠中斷來喚醒從而處理到來的事物。

 

 

而上面的51822的main函數最後也是一個for{}循環,power_manage();       內部代碼其實就是一個睡眠指令。Main函數到這裏就已經沒了,最後其實就是一個循環睡眠。這裏看不到任何任務(task),只有睡眠。那麼可想而知,51822的協議棧實現應該是基於”事件喚醒”的,也就是沒事的時候睡眠,有事的時候喚醒工作而後繼續睡眠。那麼那些處理事件的代碼都是在哪裏的?

 

 

那協議棧到底是怎麼運作的?

我希望創建一個服務在哪裏添加?

手機發送來的數據在哪裏?

我怎麼發送數據給手機?

下面一一解釋這些問題:

 

 

協議棧如何運作?

要明白協議棧怎麼運作,首先就要理解51822的協議棧是基於100%的事件驅動的。就是說協議棧向app發送的任何數據都是基於事件的。

比如設備收到手機發來的鏈接請求,或是手機發過來的數據等等。協議棧首先收到這些數據後做一些處理,然後將這些數據(比如鏈接請求,或是普通數據等)打包成一個結構體,並附上事件ID,比如BLE_GAP_EVT_CONNECTED或BLE_GATTS_EVT_WRITE來分別告訴上層app這個事件結構體代表的事件。

比如BLE_GAP_EVT_CONNECTED代表鏈接事件,那麼這個事件結構體中包含的數據就是連接參數等數據。

而BLE_GATTS_EVT_WRITE代表寫事件,那麼結構體中的數據就是對端設備(比如手機)寫給板子的數據。

 

比如uart的demo中dispatch派發函數

 

static void ble_evt_dispatch(ble_evt_t * p_ble_evt)

{

    ble_conn_params_on_ble_evt(p_ble_evt);

    ble_nus_on_ble_evt(&m_nus, p_ble_evt);

    on_ble_evt(p_ble_evt);

}

 

在任何與BLE相關的事件被協議棧上拋上來給app時,ble_evt_dispatch就會被調用。從而將事件拋給各個服務函數或處理模塊,這裏是將事件拋給了

連接參數管理處理函數ble_conn_params_on_ble_evt

Uart服務的事件處理函數ble_nus_on_ble_evt    (nus爲Nordicuart server)

通用的事件處理函數on_ble_evt

 

不同的事件在事件結構體ble_evt_t中通過id來區別。不同是事件處理函數通常也只是處理自己感情去的事件,我們來看看ble_nus_on_ble_evt事件處理函數的內部

voidble_nus_on_ble_evt(ble_nus_t * p_nus, ble_evt_t * p_ble_evt)

{   
     if ((p_nus == NULL) || (p_ble_evt == NULL))

    {

        return;

    }

    switch (p_ble_evt->header.evt_id)

    {

        caseBLE_GAP_EVT_CONNECTED:

            on_connect(p_nus, p_ble_evt);

        break;

        caseBLE_GAP_EVT_DISCONNECTED:

            on_disconnect(p_nus, p_ble_evt);

        break;

        caseBLE_GATTS_EVT_WRITE:

            on_write(p_nus, p_ble_evt);

        break;

        default:

            // No implementation needed.

        break;

    }

}

 

可以看到,uart服務事件處理函數只關心三個事件,鏈接事件,斷開鏈接事件以及寫事件(對端設備發數據過來),不同的事件再針對做不同的,這個就由開發人員自己來實現了。比如對於連接事件通常應該記錄下事件結構體中的連接句柄,因爲後續的BLE操作基本都要基於連接句柄(可以看做是兩個設備通信的信道ID,實際爲鏈路層中的數據接入地址概念)。

 

PS: 事件是交給dispatch來派發給各個服務以及模塊的,對於更底層的事件又是如何交給dispatch函數的過程請參考羣公告中的  51822教程-協議棧概述教程。

 

 


解決了所謂的事件驅動再來解決:如果希望創建一個服務在哪裏添加?

在main函數的初始化過程中有一個services_init();這個函數的內部就是添加服務,添加特徵值等代碼。

 

函數內部其實就是註冊了一會回調函數nus_data_handler(該函數會在手機發數據給板子時將數據從電腦串口打印出來) 然後再執行真正的初始化函數ble_nus_init。

該函數的內部又會調用sd_ble_gatts_service_add這個協議棧的api接口來添加服務。

後面也會調用sd_ble_gatts_characteristic_add這個協議棧的api接口來添加特徵值。

層次關係如下:


也就是說完成一個完整的服務建立函數其實只要sd_ble_gatts_service_add()和sd_ble_gatts_characteristic_add ()這兩個核心函數。

通常建立服務並不需要自己去從頭寫過。而是直接賦值官方的這個services_init()函數,然後做一些小改動就可以。比如修改一下uuid, 修改一下讀/寫屬性,多添加一個特徵值等。要修改的其實很少。

 


 

 

下面解決最後兩個問題:手機發送來的數據在哪裏?我怎麼發送數據給手機?

 

要搞清楚這兩個問題,先來看一下羣裏常問的幾個與上面相關的問題:



問:

    手機發給51822設備的數據在哪個函數裏出來的呀, 
答:
    沒有函數
    協議棧會拋上來一個事件結構體
    收到的數據在結構體中


問:

    藍牙上傳函數,與下發函數都是一樣的嗎?都是服務API函數?
答:
    只有上傳函數 是服務器用來將數據傳給客戶端的。
    下發數據  是藍牙芯片收到數據後,協議棧會拋上來一個有數據的事件結 構體。具體參看示例代碼中的 dispatch派發程序中各個事件處理函數對各   種事件的數據。

 

問:

       sd_ble_gatts_hvx()這個函數是 藍牙的發送函數,有知道藍牙的接收函數 ?
答:
       藍牙沒有接收函數,藍牙的數據接收在底層,接收完後會返回事件給上層的 ble_evt_dispatch 分發函數,它將事件分發給各個服務或者事件處理函數。  服     務或處理函數會捕獲是否存在寫事件case BLE_GATTS_EVT_WRITE:  存在就做    相應的處理。收到的數據都在返回的事件結構體裏

 

 

其實看完這三個問題基本上上面的問題其實已經解決差不多了。作爲從設備,BLE的發送數據給手機是有API接口的,就是上面問到的sd_ble_gatts_hvx(),可以通過參數來設置是以通知方式發送還是指示方式發送(通知不需要回復確認,指示需要)。但是手機發過來數據卻是沒有接收函數,爲什麼?因爲協議棧是基於事件驅動的!所以收到數據後協議棧會給上層app一個寫事件(指示對端設備寫數據過來了),而寫過來的數據時在這個事件結構體中。我們只要提取出來就行了。所以沒有接收函數API。


從另一方面也可以解釋爲什麼沒有接收數據函數。因爲發送數據時”同步的”,是主動調用的,在往想發送數據的時候。但是接收數據時”異步的”,數據可能隨時到來,總不來一直調用一個函數然後原地等待數據到來吧,如果數據不來豈不是什麼事都幹不了了。所以接收是基於事件驅動的。有數據來再轉過去處理。

  

用個圖來解釋下:



如果還是覺得有點抽象,回到前面看看協議棧運作講解部分。應該更能體會所謂的事件驅動

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章