(原創)使用nRF51822/nRF51422創建一個簡單的BLE應用 ---入門實例手冊(中文)之四


4  LED Button 應用實例

LED Button 應用示例是為了讓你學習如何在nRF51822上開發BLE應用,它是一個簡單的演示通過BLE的指示功能進行通信的BLE應用。當它運行時,你可以通過集中器觸發nRF51822上LED的輸出,並且當在nRF51822上的按鍵被按下時集中器將會收到一個通知。

這個應用通過一個服務被建立,這個服務包括2個特性:LED特性和按鍵特性。LED特性:通過沒有回應的寫遠程操作LED的亮滅。按鍵特性:當按鍵被按下或釋放時,將會發送一個通知。

4.1 代碼簡介

下面的章節將對這個應用是如何工作的進行一個簡單的介紹,以幫助你理解和使用這些代碼。

4.1.1 代碼結構

這個示例代碼分為3個文件:

l  main.c

l  ble_lbs.c

l  ble_lbs.h

這個結構和SDK中其他的示例是一樣的,在main.c中實現了應用的行為,分離的服務文件(ble_lbs.c,ble_lbs.h)實現了服務本身和它的行為。所有的輸入輸出的處理都是在應用層中進行的。

一個運行在nRF51822上的應用能夠與以下幾個部分進行交互:

l  硬件寄存器

寄存器在nrf51.h中定義,用到的常量在nrf51_bitfields.h中定義。在LED BUTTON應用中沒有關於使用它們的示例。

l  SDK模塊

SDK模塊包含了所有BLE開發的所有文件,從封裝了硬件寄存器的基本頭文件如nrf_gpio.h,到提供給應用層的復雜的功能函數如app_timer或者ble_conn_params。

l  協議棧Softdevice 函數

用來配置或觸發Softdevice中的行為,所有Softdevice函數的前綴都是以“sd_”開頭。

4.1.2 代碼流程

在nRF51822上運行一個標准的BLE應用的基本流程是:初始化所有需要的部分,開始廣播,進入省電模式,等待BLE事件發生。當接收到一個事件發生時,它將會被發送到BLE的服務和模塊中,這個事件是但不僅限於以下一種事件:

l  當一個對等設備連接到nRF51822時

l  當一個對等設備寫入到一個特性時

l  廣播超時事件通知

這種流程讓應用變得模塊化,一個服務通常可以通過初始化加入一個應用之中,並且保證當事件發生時事件的句柄被調用。

4.1.3  在KEIL工程中進行導航和檢測

建議在你開始查看示例代碼之前先對它進行編譯。編譯之后,你可以在任何函數、變量、類型、定義上點擊鼠標右鍵,通過選擇Go To Definition Of(跳轉到定義體)或者Go To Reference To(跳轉到引用體)選項跳轉到目標定義所在的地方。

Go To Definition Of跳轉到真正的實現方法(即源代碼文件),Go To Reference To跳轉到它的頭文件內的聲明中,這意味着你不能跳轉到SoftDevice 中函數的定義體中,因為這些函數沒有源碼。但是,你可以跳轉到他們的引用體,那里可以告訴你有關如何使用的方法說明。你可以使用這個有用的功能熟悉API函數和示例工程。

 

4.2 代碼獲取

這個應用筆記介紹了如何建立一個LED Button應用的步驟,另外,完整的示例代碼在GitHub上的Git資源庫中提供,你可以看到代碼的歷史版本以及它是如何被開發的。每一部分都有它自己的標簽,在本文的末尾將告訴你如何查閱這些代碼。

在GitHub上可以找到本工程的代碼:https://github.com/NordicSemiconductor/nrf51-ble-app-lbs

4.3 創建

本應用例程需要使用nRF51822 Evaluation Kit開發板,當然,可以經過適當的修改也可以在Development Kit.開發板上運行。

4.3.1 設置Evaluation開發板

因為nRF51822 Evaluation Kit開發板上已經有板載SEGGER芯片,因此你可以使用USB線就能直接開始工作。

4.3.2 設置應用

有很多模式樣板代碼需要用於開始創建一個應用和服務,所以第一步就是從SDK中復制代碼:

1. 進入Board\nrf6310\s110\ble_app_template文件夾

2. 把這個文件夾復制到Board\pca10001\s110\,並重新命名為:ble_app_lbs

3. 進入文件夾ble_app_lbs中的arm文件夾,把工程文件名從ble_app_template 改為 ble_app_lbs。

4.3.3 設置服務

SDK沒有一個服務的模板,但是有一個現成的電池服務,這個服務是一個簡單的首要服務,非常適合開始創建一個服務的模板。因此需要進行如下步驟:

1. 從Source/ble/ble_services復制ble_bas.c到Board/pca10001/ble/ble_app_lbs/

2. 從Include/ble/ble_services復制ble_bas.h到Board/pca10001/ble/ble_app_lbs/

3. 把ble_bas.c重命名為ble_lbs.c,把ble_bas.h重命名為ble_lbs.h

4. 在Keil左邊的工程窗口中雙擊Services文件夾,選擇添加ble_lbs.c文件。這樣就把ble_lbs.c文件添加到了你的keil工程中。

因為這個是一個定制的服務,最好把它放在應用層文件夾中,以代替放在SDK 的服務文件夾中。

4.4 實現服務

這個服務采用通用的方式實現,因此可以很容易重用於其他應用。它能夠使應用程序通過初始化就能使用這個服務、處理事件、提供輸入輸出的實現。實現其他預定義服務的方法也是類似的。

4.4.1 API設計

ble_lbs.h頭文件實現了各種數據結構、應用需要實現的事件句柄和以下3個API函數:

uint32_t ble_bas_init(ble_bas_t * p_bas, const ble_bas_init_t * p_bas_init);

void ble_bas_on_ble_evt(ble_bas_t * p_bas, ble_evt_t * p_ble_evt);

uint32_t ble_bas_battery_level_update(ble_bas_t * p_bas, uint8_t battery_level);

注意:本文檔中代碼片段的注釋已經被去掉

在上面的代碼中,ble_bas_t用於引用這個服務實例,在后面還會用到。而ble_bas_init_t用於初始化參數,后面不會再用到。所有的API函數使用一個指向服務實例的指針作為第一個輸入參數。

LED Button服務的API 函數也是同樣的設計,步驟如下:

  1. 在對應的頭文件和源文件中,執行Find and Replace All功能,把所有出現ble_bas的地方替換成ble_lbs;把所有出現BLE_BAS的地方替換成BLE_LBS;把所有出現p_bas的地方替換成p_lbs。
  2. 在對應的頭文件和源文件中,把ble_lbs_battery_level_update()函數都移除。
  3. 在ble_lbs.c文件中,使用#if 0 在第一個函數之前,使用#endif 在初始化函數ble_lbs_init()之上注釋掉所有的函數,不要完全刪除它們,因為后面建立函數還需要用到它們。
  4. 在對應的頭文件和源文件中,把battery_level_update函數都移除。
  5. 在初始化函數ble_lbs_init()的末尾,刪除battery_level_char_add()函數,用return NRF_SUCCESS進行代替。
  6. 考慮到服務要符合應用的需求,LED Button服務需要知道什么時候按鍵狀態改變了,才能把狀態發送到集中器設備,因此,你需要增加一個函數當按鍵狀態改變的時候應用程序能夠調用它。

uint32_t ble_lbs_init(ble_lbs_t * p_lbs, const ble_lbs_init_t * p_lbs_init);

void ble_lbs_on_ble_evt(ble_lbs_t * p_lbs, ble_evt_t * p_ble_evt);

uint32_t ble_lbs_on_button_change(ble_lbs_t * p_lbs, uint8_t button_state);

這里有2個數據結構需要實現:ble_lbs_t 和 ble_lbs_init_t。

4.4.2 實現數據結構體

在第25頁4.4.1節“API設計”中,有一些用到的數據結構還沒有定義:ble_lbs_t 和 ble_lbs_init_t.,我們可以基於電池服務的數據結構體實現類似的結構體,電池服務的數據結構體如下:

typedef struct

{

ble_bas_evt_handler_t        evt_handler;

bool                         support_notification;

ble_report_ref_t              * p_report_ref;

uint8_t                       initial_batt_level;

ble_cccd_security_mode_t     battery_level_char_attr_md;

ble_gap_conn_sec_mode_t    battery_level_report_read_perm;

} ble_bas_init_t;

 

typedef struct ble_bas_s

{

ble_bas_evt_handler_t          evt_handler;

uint16_t                       service_handle;

ble_gatts_char_handles_t       battery_level_handles;

uint16_t                       report_ref_handle;

uint8_t                        battery_level_last;

uint16_t                       conn_handle;

bool                          is_notification_supported;

} ble_bas_t;

以上的代碼中的初始化結構體包含服務的事件句柄,一些選項參數,初始化值,安全模式,服務結構體包含服務的聲明,如句柄,當前電量值,通知功能是否打開等。

電池服務使用一個通用的事件句柄讓應用程序知道什么時候開始和停止讀取電池電量。LED Button 服務不依賴於任何啟動或停止,所以只使用一個函數作為回調函數,當LED特性被寫入時被調用。

這個句柄是初始化中唯一有效的參數,也是初始化結構體中唯一的成員。

typedef struct

{

ble_lbs_led_write_handler_t   led_write_handler;

} ble_lbs_init_t;

在這個結構體中,函數類型的定義如下(在頭文件中必須在ble_lbs_init_t定義之前添加,代替之前已經存在的事件句柄定義):

typedef void (*ble_lbs_led_write_handler_t) (ble_lbs_t * p_lbs, uint8_t new_state);

然而,下面的參數參數還需要定義:

l  服務的句柄

l  特性的句柄

l  連接的句柄

l  UUID類型

l  LED寫的回調函數

服務結構體定義如下:

typedef struct ble_lbs_s

{

uint16_t                     service_handle;

ble_gatts_char_handles_t     led_char_handles;

ble_gatts_char_handles_t     button_char_handles;

uint8_t                      uuid_type;

uint16_t                     conn_handle;

} ble_lbs_t;

可以刪除ble_lbs.h文件中有關電池服務事件的定義。

4.4.3 服務初始化

進入服務初始化函數services_init(),函數ble_lbs_init()被調用。參數你不需要改變。

  1. 刪除evt_handler, is_notification_supported和 battery_level_last.
  2. 在服務初始化結構體和服務結構體中,都要把evt_handler重命名為led_write_handler。

p_lbs->led_write_handler = p_lbs_init->led_write_handler;

UUID需要重新設置,因為本服務中將要使用一個定制(私有)的UUID,以代替藍牙技術聯盟所定義的UUID。

首先,先定義一個基本UUID,一種方式是采用nRFgo Studio來生成:

  1. 打開nRFgo Studio
  2. 在nRF8001 Setup菜單中,選擇Edit 128-bit UUIDs選項,點擊Add new。

這就產生了一個隨機的UUID,可以用於你的定制服務中。

新產生的基本UUID必須以數組的形式包含在源代碼中,但是只需要在一個地方用到:

  1. 為了可讀性,在頭文件ble_lbs.h中以宏定義的方式添加,連同用於服務和特性的16位UUID也一起定義:

#define LBS_UUID_BASE { 0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF,0x12, 0x12, 0x00, 0x00, 0x00, 0x00 }

#define LBS_UUID_SERVICE        0x1523

#define LBS_UUID_LED_CHAR      0x1525

#define LBS_UUID_BUTTON_CHAR  0x1524

在服務初始化中:

  1. 添加基本UUID到協議棧列表中,就設置了服務使用這個基本UUID。在ble_lbs_init()中只添加一次:

ble_uuid128_t base_uuid = LBS_UUID_BASE;

err_code = sd_ble_uuid_vs_add(&base_uuid, &p_lbs->uuid_type);

if (err_code != NRF_SUCCESS)

{

return err_code;

}

以上代碼段為加入一個定制的基本UUID到協議棧中,並且保存了返回的UUID類型。

  1. 當為LED Button服務設置UUID時,使用這個UUID類型,它在ble_lbs:init()中:

ble_uuid.type = p_lbs->uuid_type;

ble_uuid.uuid = LBS_UUID_SERVICE;

err_code = sd_ble_gatts_service_add( BLE_GATTS_SRVC_TYPE_PRIMARY, &ble_uuid,

&p_lbs->service_handle);

if (err_code != NRF_SUCCESS)

{

return err_code;

}

以上代碼只是添加了一個空的服務,所以還必須添加特性到服務中,下面的章節將介紹如何添加特性。

4.4.3.1  實現按鍵特性

本服務將要實現2個特性,一個是控制LED的狀態,一個是反饋按鍵的狀態。這兩個功能需要創建並增加2個特性到ble_lbs.c中實現,我們先從按鍵特性說起。

  按鍵特性在有按鍵狀態改變的時候有通知事件,同時也允許對等設備讀取這個按鍵狀態。這非常類似於在電池服務中電量特性的特征,因此你可以采用以下方式實現:

  1. 找到battery_level_char_add()函數並重命名為button_char_add()。
    button_char_add()期望找到一個參數說明是否支持通知功能,在這個示例中我們希望只支持通知功能。
  2. 刪除檢測參數is_notification_supported的if語句,但是保留if語句中的內容。
  3. 確保char_md中對應字段設置為支持通知功能。
  4. 刪除所有與報告參考相關的代碼和參數p_report_ref(每次執行到if語句都檢查p_report_ref是否設置)。

在電池服務的初始化中有一個標志設置了CCCD的安全模式,它存儲在ble_gap_conn_sec_mode_t結構體中,這個結構體使用在頭文件ble_gap.h中定義的宏BLE_GAP_CONN_SEC_MODE來設置,根據不同的安全等級定義了不同的宏,你可以根據屬性的需要進行選擇。

1. 使用宏BLE_GAP_CONN_SEC_MODE_SET_OPEN把CCCD設置成對任何連接和加密都是可讀可寫的模式。

  1. 如果對於按鍵狀態特性我們想讓每一個連接都可讀但不能寫,可以使用BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS 代替BLE_GAP_CONN_SEC_MODE_SET_OPEN。

BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);

BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);

...

memset(&attr_md, 0, sizeof(attr_md));

BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);

BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(&attr_md.write_perm);

  1. 確保你不要刪除字段vloc的設置,它決定了變量是使用協議棧的內存還是用戶空間的內存。
  2. 設置UUID的類型和UUID的值:

ble_uuid.type = p_lbs->uuid_type;

ble_uuid.uuid = LBS_UUID_BUTTON_CHAR;

  1. 初始化值不重要,你可以把p_initial_value設置為NULL。
  2. 確保把返回的特性句柄button_char_handles保存在正確的地方,最后調用的函數如下:

return ble_gatts_characteristic_add( p_lbs->service_handle,

&char_md,

&attr_char_value,

&p_lbs->button_char_handles );

刪除#endif以上的內容后,你就可以編譯工程了,根據編譯的警告刪除沒有用到的變量(initial_battery_level, encoded_report_ref, init_len, err_code)。

4.4.3.2  實現LED特性

LED狀態特性需要能夠可讀可寫,但沒有任何通知功能:

  1. 復制按鍵特性函數,重命名為led_char_add。
  2. 刪除與cccd_md有關的代碼。
  3. 增加寫的性質代替通知性質(給這個特性使能寫性質)。

char_md.char_props.write = 1;

  1. 更改使用的16位UUID為LBS_UUID_LED_CHAR:

ble_uuid.type = p_lbs->uuid_type;

ble_uuid.uuid = LBS_UUID_LED_CHAR;

保存返回的變量led_char_handles(LED特性的句柄),代替button_char_handles的位置,最后調用的函數如下:

return ble_gatts_characteristic_add( p_lbs->service_handle,

&char_md,

&attr_char_value,

&p_lbs->led_char_handles );

編譯之后,你會發現有一些參數沒有使用,刪除它們。

4.4.3.3 增加特性

創建了增加特性的函數之后,你可以在服務初始化的末尾調用它們,如下面的例子:

// Add characteristics

err_code = button_char_add(p_lbs, p_lbs_init);

if (err_code != NRF_SUCCESS)

{

return err_code;

}

err_code = led_char_add(p_lbs, p_lbs_init);

if (err_code != NRF_SUCCESS)

{

return err_code;

}

return  NRF_SUCCESS;

因為任何錯誤都會導致函數提前退出,因此當你到達函數的末尾時可以認為初始化成功了。

如果你想現在就進行測試,你可以跳到第37頁第4.5.1節“為evaluation kit開發板修改模塊”和第40頁第4.5.3節“包含服務”,完成之后,最后的測試在第47頁第5章“應用測試”中介紹。測試時你會發現,你可以連接到這個設備並且可以發現所有的服務,但是其他的功能不能工作,因此還需要在這個服務中實現處理協議棧事件和按鍵處理。

4.4.4 處理協議棧事件

當協議棧需要通知應用程序一些有關它的事情的時候,協議棧事件就發生了,例如當寫入特性或是描述符時。對應於本應用,你需要寫入LED特性,為了讓通知功能更好地工作,你需要保存連接句柄,通過這個句柄,你可以在連接事件和斷開事件中實現某些操作。

作為API的一部分,你可以定義一個函數ble_lbs_on_ble_evt用來處理協議棧事件,可以使用簡單的switch-case語句通過返回事件頭部的id號來區分不同的事件,並進行不同的處理。

 4.4.4.1 保存連接句柄

電池服務已經保存了連接句柄,但沒有改變之前采用Find and Replace來查找時需要注意一些。

4.4.4.2 參數CCCD寫的處理

現有的事件句柄監聽CCCD寫的操作並把它發送應用層的電池服務的事件句柄,但是,在本應用中,不需要這樣。

原有的代碼中實現了發送通知的方法,但是如果CCCD沒有使能,sd_ble_gatts_hvx()(通知或者指示回調函數)將不允許發送通知,因此你不需要在應用層中進行檢測,而是交給協議棧SoftDevice中去檢測。

在on_write()函數中,你可以刪除與通知功能相關的代碼。

4.4.4.3 處理LED特性寫

當LED特性被寫入的時候,你添加到數據結構的函數指針將會通知到應用層,你可以在on_write()函數中實現這樣的功能。

當接收到一個寫事件時,驗證這個寫事件是發生在對應的特性上是一個基本的任務,包括驗證數據的長度,回調函數是否已設置。如果所有這些都是正確的,則回調函數將會調用,並且把已經寫入的值作為輸入參數。因此,on_write()函數的內容將會是這樣:

ble_gatts_evt_write_t * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;

if ((p_evt_write->handle == p_lbs->led_char_handles.value_handle) &&

(p_evt_write->len == 1) &&

(p_lbs->led_write_handler != NULL))

{

p_lbs->led_write_handler(p_lbs, p_evt_write->data[0]);

}

       真正觸發LED的操作在於應用層,這樣的設計讓服務很容易重用,雖然這只是針對於LED和按鍵的服務。

4.4.5 處理按鍵事件

你已經添加了一個回調API函數讓服務知道按鍵何時被按下,但還沒有全部實現,因此你需要從頭文件中通過復制來添加,在處理按鍵按下的時候,你需要給對等設備發送一個通知以告知它新的按鍵狀態。協議棧SoftDevice API函數sd_ble_gatts_hvx來完成這個事情,它需要連接句柄和結構體ble_gatts_hvx_params_t作為輸入參數,它管理一個值被通知的整個過程。

在結構體ble_gatts_hvx_params_t中,你需要設置為通知模式還是指示模式,用哪一個屬性的句柄用來進行通知(本例中使用值的句柄),新的值以及值的長度。方法如下:

uint32_t ble_lbs_on_button_change(ble_lbs_t * p_lbs, uint8_t button_state)

{

ble_gatts_hvx_params_t params;

uint16_t len = sizeof(button_state);

memset(&params, 0, sizeof(params));

params.type = BLE_GATT_HVX_NOTIFICATION;

params.handle = p_lbs->button_char_handles.value_handle;

params.p_data = &button_state;

params.p_len = &len;

return  sd_ble_gatts_hvx(p_lbs->conn_handle, &params);

}

也可以使用sd_ble_gatts_value_set()函數一次性設置特性的值,當通知的時候調用sd_ble_gatts_hvx()不需要設置值和值的長度。但是,沒必要使用sd_ble_gatts_hvx()做任何事情,它不過類似於一個清潔工的角色。使用函數sd_ble_gatts_value_set()更新一個可讀(但不能通知)的值,因為這個函數不通過空中發送數據包。

按照本服務的實現方法,本例子可以應用在其他應用中。

4.5 應用層實現

4.5.1 修改模板適應於evaluation kit開發板

需要使用模板進行一些修改以適應於evaluation kit開發板,以代替Development Kit開發板。

 首先,你需要更改定義板子的宏定義:

1. 打開工程文件,進入到“Target options”標簽和“C/C++”標簽。

2. 把BOARD_NRF6310更改為BOARD_PCA10001,如下圖所示:

注意:如果你用development kit開發板代替evaluation kit開發板,跳到步驟4和步驟5,並且把ble_app template拷貝到Board\nrf6310\s110\ble_app_lbs下,你便建立了一個工程。

 

你需要在main.c中刪除一些引腳定義,因為在evaluation kit開發板上,沒有一個單獨的LED用於言斷。

  1. 在main.c中刪除所有有關ASSERT_LED_PIN_NO的代碼(宏定義,在app_error_handler()中使用,在leds_init()中設置為輸出引腳)。
  2. 你需要一個LED用於LED Button服務,因此你需要在main.c的頂部添加定義LED引腳的宏定義。

#define LEDBUTTON_LED_PIN_NO        LED_0

  1. 在In leds_init()中配置引腳為作為LED引腳:

nrf_gpio_cfg_output(LEDBUTTON_LED_PIN_NO);

  1. 在SDK 4.1.0以后,當錯誤發生時,默認的應用層錯誤處理是復位,但是對於開發,更有用的是使用提供的debug模塊,你需要確保取消注釋debug言斷的處理,注釋掉復位的處理:

void app_error_handler(uint32_t error_code, uint32_t line_num, const uint8_t *

p_file_name)

{

// [Comment removed from snippet for brevity]

ble_debug_assert_handler(error_code, line_num, p_file_name);

// On assert, the system can only recover with a reset.

//NVIC_SystemReset();

}

當你運行程序在調試模式下的時候,如果出現錯誤你可以停止cpu,並能夠發現文件中哪一行發生了錯誤。你可用SoftDevice中的APP_ERROR_CHECK()宏來檢測返回的錯誤代碼得到有關錯誤的信息。如圖5所示的例子.

在實際的產品中,通常使用復位來恢復錯誤,但在開發中,日記更重要。

建議在工程中更改藍牙設備的名字,可以通過改變main.c中的宏DEVICE_NAME來實現,例如可以改成“LedButtonDemo”。

4.5.2 使用調度

SDK提供了調度模塊,這個模塊提供了把事件處理和中斷處理轉移到main函數中進行的機制,它保證了所有的中斷處理都能快速被執行。

      在開始使用的模板中,默認使能了調度功能。如果你不想使用它,你可以刪除它的初始化和main循環中對它的調用(scheduler_init(), app_sched_execute()),設置SDK的模塊初始化函數中相關的標識使用調度的參數為假(softdevice_handler, app_timer,app_button)。

      關於調度的更多詳情,請查閱nRF51 SDK文檔。

4.5.3 包含服務

為了使用你創建的服務,你需要在模板中添加一些代碼。

在mian.c文件中,必須出現調用services_init()函數來初始化LED Button 服務:

  1. 在main.c中包含ble_lbs.h頭文件:

#include  "ble_lbs.h"

2. 如果沒有添加源文件,則添加源文件到工程中:在工程窗口的左邊在Services文件上點擊右鍵,單擊Add file,選擇ble_lbs.c文件。

3. 在main.c中添加服務的數據結構作為全局靜態變量:

static ble_lbs_t     m_lbs;

注意事件通過在main.c中使用靜態變量的方式被保存,m_lbs作為指針指向的變量經常會出現,指向它的指針為p_lbs。

  1. 現在你可以初始化你的服務:

static void services_init(void)

{

uint32_t   err_code;

ble_lbs_   init_t init;

init.led_write_handler = led_write_handler;

err_code = ble_lbs_init(&m_lbs, &init);

APP_ERROR_CHECK(err_code);

}

       在第35頁4.4.4.3節“處理LED特性寫”中,我們在服務結構體中設置了led_write_handler,當LED特性被寫入的時候將會調用。這個回調函數通過上面的初始化結構體被設置,但這個回調函數還沒有實現。

  1. 在services_init()函數之上,添加回調函數led_write_handler(),以實現聲明。
  2. 設置LED輸出狀態值,它是函數的一個輸入參數:

Static void led_write_handler(ble_lbs_t * p_lbs, uint8_t led_state)

{

if (led_state)

{

nrf_gpio_pin_set(LEDBUTTON_LED_PIN_NO);

}

else

{

nrf_gpio_pin_clear(LEDBUTTON_LED_PIN_NO);

}

}

  1. 最后,添加服務事件的處理函數到應用層事件調度函數(回調函數)

static void ble_evt_dispatch(ble_evt_t * p_ble_evt)

{

on_ble_evt(p_ble_evt);

ble_conn_params_on_ble_evt(p_ble_evt);

ble_lbs_on_ble_evt(&m_lbs, p_ble_evt);

}

4.5.4 測試

現在你可以進行測試了,見第47頁第5章“應用測試”,使用Master Control Panel,你可以寫“1”到LED特性中來點亮LED。

4.5.5 按鍵處理

為了完成本應用,你需要定義如何處理按鍵按下,你可以使用SDK提供的app_button模塊,這個模塊將會提供當按鍵按下和釋放時的一個回調函數。

在按鍵初始化buttons_init()里,設置你需要的按鍵,在這個示例中使用了evaluation kit開發板上的button 1。

  1. 添加一個新的宏定義,只是為了讓代碼具有可讀性:

#define  LEDBUTTON_BUTTON_PIN_NO      BUTTON_1

  1. 在buttons_init()里配置按鍵的引腳,添加按鍵配置數組如下:

static app_button_cfg_t buttons[] =

{

{WAKEUP_BUTTON_PIN, false, NRF_GPIO_PIN_PULLUP, NULL},

{LEDBUTTON_BUTTON_PIN_NO, false, NRF_GPIO_PIN_PULLUP, button_event_handler},

};

APP_BUTTON_INIT( buttons, sizeof(buttons) / sizeof(buttons[0]),

BUTTON_DETECTION_DELAY, true);

       在evaluation kit開發板上的按鍵是低電平有效,所以第2個參數為false,但它沒有外部上拉電阻,因此你需要使用NRF_GPIO_PIN_PULLUP來使能內部上拉電阻。喚醒按鍵wakeup_button沒有聲明,所以它的回調函數設置為NULL。完成之后,你就已經完成按鍵模塊的初始化了。

  1. 取消對函數button_event_handler()的注釋,app_button模塊將通過參數傳遞當前的按鍵狀態,因此你可以通過服務API函數直接傳遞這個按鍵狀態:

static void button_event_handler(uint8_t pin_no, uint8_t button action)

{

uint32_t err_code;

switch(pin_no)

{

case LEDBUTTON_BUTTON_PIN_NO:

err_code = ble_lbs_on_button_change(&m_lbs, button_action);

if (err_code != NRF_SUCCESS &&

err_code != BLE_ERROR_INVALID_CONN_HANDLE &&

err_code != NRF_ERROR_INVALID_STATE)

{

APP_ERROR_CHECK(err_code);

}

break;

default:

APP_ERROR_HANDLER(pin_no);

break;

}

}

在上面的代碼中,我們忽略可能客戶端CCCD還沒有被寫入,或者我們沒有正確連接所帶來的錯誤。

    為了添加已經定義的函數,你需要確定按鍵模塊已經被使能。默認情況下,應用模板建議連接事件和斷開連接事件中來完成使能和禁止,這里我們也使用這種方式。取消對app_button_enable() 和 app_button_disable()的注釋:

switch (p_ble_evt->header.evt_id)

{

case BLE_GAP_EVT_CONNECTED:

/* … */

err_code = app_button_enable();

break;

case BLE_GAP_EVT_DISCONNECTED:

/* … */

err_code = app_button_disable();

if (err_code == NRF_SUCCESS)

{

advertising_start();

}

break;

現在你可以進行測試了,見第47頁第5章“應用測試”的描述,然而為了讓集中器在掃描的時候能夠更容易區分不同的設備,有必要在廣播數據包中加入本服務的UUID。

4.5.6 加入本服務的UUID到廣播數據包中

在廣播數據包中包含服務UUID,可以使集中器利用這個信息決定是否進行連接。在第8頁第2.1.2節“廣播”中所提到,一個廣播數據包最多可攜帶31字節,如果需要更多的數據需要傳輸,可以使用掃描回應發送。

你需要增加一個定制的16位的UUID到掃描回應數據包中,因為廣播數據包已經沒有可用的空間了。

廣播數據的設置在main.c的advertising_init()中,設置廣播數據結構體,並調用ble_advdata_set()來設置,它使用2個相同的數據類型的參數,一個是廣播數據包,一個是掃描回應數據包。你必須添加一個數據結構作為掃描回應的參數。

服務UUID設置為LBS_UUID_SERVICE,類型使用結構體ble_lbs_t中的uuid_type,廣播數據包的初始化如下:

static void advertising_init(void)

{

uint32_t          err_code;

ble_advdata_t     advdata;

ble_advdata_t     scanrsp;

uint8_t   flags = BLE_GAP_ADV_FLAGS_LE_ONLY_LIMITED_DISC_MODE;

 

// YOUR_JOB: Use UUIDs for service(s) used in your application.

ble_uuid_t adv_uuids[] = {{LBS_UUID_SERVICE, m_lbs.uuid_type}};

 

// Build and set advertising data

memset(&advdata, 0, sizeof(advdata));

 

advdata.name_type           = BLE_ADVDATA_FULL_NAME;

advdata.include_appearance   = true;

advdata.flags.size             = sizeof(flags);

advdata.flags.p_data          = &flags;

 

memset(&scanrsp, 0, sizeof(scanrsp));

scanrsp.uuids_complete.uuid_cnt  =  sizeof(adv_uuids) / sizeof(adv_uuids[0]);

scanrsp.uuids_complete.p_uuids  =  adv_uuids;

 

err_code = ble_advdata_set(&advdata, &scanrsp);

APP_ERROR_CHECK(err_code);

}

因為m_lbs結構體的uuid_type在這里被使用,所以確保它在services_init()中已經被設置,並保證它在advertising_init()之前被調用,在main中:

int main(void)

{

services_init();

advertising_init();

到此,這個應用已經建立完成。

 


免責聲明!

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



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