(三)藍牙低功耗(BLE)基礎教程--基於nRF5x系列SOC

一.基本理論
1.屬性協議與通用屬性規範
  我希望大家把教程中提供的應用程序當作一個跳板,將來開發程序的時候可以對其進行擴展和完善。我會儘可能少地提到理論,但是這裏要提到的屬性協議(ATT)和通用屬性規範(GATT)是BLE中非常基礎而且重要的知識點,所以這裏要詳細介紹一下。
1.1屬性協議Attribute Protocol (ATT)
  從下圖可以看到,BLE協議棧分爲多個層。應用層位於GATT之上,而GATT在ATT層上。屬性協議是根據客戶端<->服務器關係建立起來的。服務器上保存諸如傳感器數據,位置數據,電燈開關狀態之類的信息。這些信息以一種數據表的形式保存,這個表就稱爲屬性表。屬性協議定義瞭如何組織這個表以及如何存取表中的數據。表中的每條屬性都是一個具體的數值或者具有特定意義的信息。例如客戶端想要獲取傳感器數據,它可以訪問屬性表的第11行,想要獲取燈的開關狀態就可以訪問第16行。
這裏寫圖片描述
  以下內容摘自藍牙核心規範V4.2:
  Vol 3:, Part F, Ch. 2, “Protocol Overview”:
  屬性協議定義了兩種角色:服務器和客戶端。客戶端可以通過屬性協議訪問服務器上的屬性表數據。屬性表裏的每條屬性除了屬性值外還有其它3個部分:(1)屬性類型,通過UUID定義;(2)屬性句柄;(3)上層協議定義的一系列權限控制位,用以控制對相應屬性的操作,但是不能通過屬性協議訪問。
  屬性類型聲明瞭本條屬性代表什麼。爲了便於高層規範使用,藍牙技術聯盟定義了多種屬性類型。開發者也可以自主定義屬性類型。
  現在我們來看一個具體的應用示例。下表是一個心率Profile,每一行是一條屬性,每條屬性都有屬性句柄,屬性類型,權限控制,屬性值。
表1
這裏寫圖片描述
屬性句柄
  屬性句柄用來唯一標識服務器上的每條屬性,同時客戶端據此發送屬性讀寫請求。爲了方便理解,我們可以把屬性句柄看作屬性在屬性表中的行號。但是屬性句柄並不是連續增加的,允許跳過一些數值,但是整個大方向必需是增加的。屬性句柄用4個十六進制數表示,隨後我們會看到協議棧SoftDevice 利用這些句柄來操作屬性。從開發者的角度來看,利用這種方法在函數間傳遞數據是非常高效的。應用程序藉此可以很容易地追蹤特定屬性並獲取需要的信息。屬性句柄的數量根據應用需要的屬性個數而變化。

屬性類型(UUID)
  UUID是一組16位或者128位的數字,用以標識每個屬性的類型。在上表中有5種不同類型的屬性:1.服務聲明(0x2800)2.特性聲明(0x2803)3.心率測量特性值(0x2A37)4.體傳感器位置特性值(0x2A38)5.描述符聲明(0x2902)。除此之外,還有其它種類的屬性類型,在本教程中,我們將自定義屬性類型。

屬性權限
  屬性權限定義了與屬性交互的規則。它定義了該條屬性是否可讀可寫以及進行讀寫操作時需要進行哪種授權。這裏需要強調的是屬性權限只能用來控制對屬性值的操作,它不用於屬性句柄,屬性類型以及屬性權限本身。客戶端可以通過遍歷服務器上的屬性表,就可以獲悉服務器可以提供的服務,不需要讀寫屬性值。

屬性值
  屬性值可以是任何東西。它可以是每分鐘心率數值,可以是燈的開關狀態,或者是像“hello world”之類的字符串。有時候它還可以包含某些信息,通過這些信息可以找到其它屬性及其性質。例如在上表中的服務聲明屬性(屬性句柄0x000C)中,屬性值是0x180D,這是藍牙技術聯盟定義的服務UUID,用以標識這是哪種服務。特性聲明屬性(屬性句柄0x000D)的屬性值包含了下一條屬性即特性值聲明屬性的信息,包括屬性句柄,屬性類型,屬性權限。最後,心率測量特性值屬性(屬性句柄0x000E)的屬性值中包含了每分鐘的心率數值。
1.2通用屬性規範(GATT)
  通用屬性規範的概念是指將屬性表中的屬性以一種特定的邏輯順序組合在一起。上表中的心率profile就是這種分組的一個實例。
1.2.1服務聲明屬性
  在每一組屬性的最前面是一條服務聲明屬性,它的屬性類型總是0x2800,即標準服務聲明UUID。屬性句柄取決於屬性表中有多少屬性。屬性權限是隻讀,不需要任何認證或授權。屬性值是一個標識其爲何種服務的UUID,在上表中,這個UUID爲0x180D,它是藍牙技術聯盟定義的心率服務UUID。本文後面部分我們會自己製作屬性表,創建自己的服務聲明UUID。
1.2.2特性聲明
  緊隨服務聲明屬性之後的一般是特性聲明屬性(也可能有例外情況,本文對此不做詳解)。特性聲明與服務聲明類似。特性聲明屬性的屬性類型是0x2803,即標準特性聲明UUID。特性聲明屬性的屬性權限總是隻讀類型的,不需要任何認證或授權。其屬性值包含了下一條特性值聲明屬性的信息,包括屬性句柄,屬性類型以及屬性性質,其中屬性句柄描述了下一條特性值聲明屬性在屬性表中的位置,屬性類型UUID描述了下一條特性值聲明屬性中包含那種信息或數據,例如溫度值,開關狀態等等,屬性性質描述瞭如何與特性值進行交互。下表展示了屬性性質控制位信息,隨後我們將詳細介紹這個表。
表2
這裏寫圖片描述
  說到這裏你可能會疑問了,爲什麼每條屬性已經有屬性權限來控制屬性值的讀寫了,還需要其他的屬性性質來控制特性值的讀寫呢?它們不應該是一樣的嗎?這是一個值得一提的問題。事實上,控制特性值的屬性性質只用在GATT和應用層,僅僅爲客戶端提供一些線索,客戶端可以據此知道從特性值聲明屬性中能夠獲取哪些內容。控制屬性的屬性權限(ATT層)凌駕於特性值性質(GATT層)之上,最終決定對屬性值的訪問。你可能會問,爲什麼需要屬性權限和性質兩個部分呢?答案很簡單,因爲藍牙核心規範是這樣規定的。可能這個答案很令人困惑和失望,但是藍牙核心規範決定我們如何設置這些特性,所以這裏要提一下。
1.2.3特性值聲明
  特性聲明之後是特性值聲明。這條屬性包含最終的數據值。這個數據值可能是溫度值,開關狀態等等。特性值聲明的屬性類型與特性聲明的屬性值中定義的類型相同。特性值聲明的屬性權限由上層應用規定。
1.2.4描述符聲明
  特性值聲明之後的下一條屬性可能是:
  1)新的特性聲明(一種服務中可以包含多種特性)
  2)新的服務聲明(一個屬性表中可以包含多種服務)
  3)描述符聲明
  表1中,特性值聲明之後是描述符聲明。這條屬性包含關於本條特性的其它信息。描述符有許多種,本文我們只涉及客戶端特性配置描述符(CCCD),之後我們會對其做詳細介紹。

二.MCP中的屬性表(譯者注:MCP軟件已更新爲nRF Connect軟件)
  我們已經學了許多相關理論了,現在讓我們看看在實際中這些理論是如何體現的。我們以SDK中提供的心率服務爲例。下載程序,連接nRF51 DK板,在MCP上運行服務發現程序,我們就可以看到整個屬性表。每一行是一條屬性,並按服務進行分組。如下圖1所示。
這裏寫圖片描述
  在圖1中,我標記了服務聲明屬性。你可以看到在紅框中,這條屬性的UUID是0x2800,屬性句柄是0x000C,屬性值是0x180D(低位在前,LSB)。這與我們在表1中看到的一致。UUID顯示了該條屬性是一條服務聲明屬性,屬性值顯示了它是心率服務。

這裏寫圖片描述
  在圖2中我標記了第一條特性聲明屬性。其UUID是0x2803,顯示了它確實是一條特性聲明屬性。這裏的屬性值就比較奇怪了,下面我們解釋一下它代表的含義。屬性值裏的數據是以LSB形式組織的,爲了方便閱讀,我們將其改爲MSB形式:2A37-000E-10。你可能已經注意到,2A37是心率測量特性的UUID,000E是特性值聲明屬性的屬性句柄,表徵其在屬性表中的位置。0x10定義了屬性性質,與表2進行對比可以發現它代表了“通知”。上面提到的這些都與我們在表1中的描述一致。
這裏寫圖片描述
  在圖3中,你可以看到特性值聲明的UUID確實是0x2A37,而且屬性句柄就是0x000E,正如圖2中特徵聲明屬性所指定的那樣。

這裏寫圖片描述
  最後,在圖4中,你可以看到CCCD屬性的值。你可以看到描述符UUID(0x2902),而屬性值是0x0000,這意味着通知現在被關閉了。隨後我們會再詳細介紹相關內容。

三.操作實例
  現在是時候實際操作一下了。我認爲作爲一個例子,我們應該首先創建一個可讀可寫的特性,然後我們就可以手動讀寫數據了。爲了防止你們不知道芯片內部有溫度傳感器,我們可以將nRF5x芯片CPU溫度值放入特性中。它可能不是很準確,但是這是一種非常簡單的方法,可以幫助我們熟悉如何使用動態傳感器值。最後,我們會啓用通知來更新溫度值。
  步驟:1.添加服務。本系列教程的上一篇文章介紹瞭如何創建服務;
  2.聲明與配置特性;
  3.添加CCCD,使得特性在溫度改變時,或者週期性地發送溫度值。
表3是我們要包含的屬性表。
這裏寫圖片描述
第一步:添加服務
  上面我們已經提到,這一步是在上一篇教程中完成的,現在我們快速瞭解一下最終結果在MCP中是什麼樣子的。如下圖所示:
這裏寫圖片描述
  標亮的一行是服務聲明,聲明我們提供何種服務。你可以看到屬性類型是0x2800,屬性句柄是0x000C,屬性值是128位自定義UUID。

第二步:添加特性
  調用SoftDevice函數sd_ble_gatts_characteristic_add()。該函數會向屬性表中添加特性聲明屬性和特性值聲明屬性。但是爲了達到這個目的,我們必須先做一些事情。sd_ble_gatts_characteristic_add()接受四個參數:
@param[in] uint16_t service_handle.
@param[in] ble_gatts_char_md_t const * p_char_md
@param[in] ble_gatts_attr_t const * p_attr_char_value
@param[out] ble_gatts_char_handles_t * p_handles
  你可以看到,該函數有三個輸入參數和一個輸出參數。我們需要填入三個輸入參數來定義特性屬性。這些參數定義了屬性性質、讀/寫權限、描述符、特徵值等。實際上,在定義特性時,可以使用70多種性質。這聽起來令人生畏,但不要放棄,更不要扔掉你的DK板。大多數參數都是可選的,如果使memset(&parameter, 0, sizeof(parameter)),將它們初始化爲0,則幾乎所有的參數都可以正常工作。
  我們會先填充我們需要的基本參數。事實上,我們所要做的只是選擇在哪裏存儲特性屬性,以及使用我們自定義的UUID定義一個特性值類型。在函數our_char_add()中,我已經聲明瞭所有必需的變量,並將它們初始化爲0。我聲明它們的順序可能有些混亂,但是由於參數嵌套到結構中,這些結構可能還會嵌套到其他結構中,所以我不得不這樣做。同時爲了與SDK中的命名慣例一致,我選擇了一些看起來有點不直觀的名字。
  三個最重要的變量是:
1. ble_gatts_attr_md_t attr_md,即屬性Metadata:這個結構體包含特性值屬性需要的屬性權限和授權等級,以及特性值是否爲可變長度,還有屬性值的存儲位置。
2. ble_gatts_char_md_t char_md,即特性Metadata:這個結構體包含特性的性質,如讀寫控制,以及與CCCD或者其它描述符相關的信息。
3. ble_gatts_attr_t attr_char_value, 即特性值屬性:這個結構體包含實際的特性值(如溫度值)。它還包含屬性值的最大長度(例如4字節長度)和屬性UUID。

  接下來我們開始填充這些參數。爲了便於理解,我會分步進行,並做詳細解釋。
  A. 使用定製UUID定義特性值類型
  特性和服務一樣都需要添加UUID,添加方法相同。在our_char_add()函數中添加下面幾行:
uint32_t err_code;
ble_uuid_t char_uuid;
ble_uuid128_t base_uuid = BLE_UUID_OUR_BASE_UUID;
char_uuid.uuid = BLE_UUID_OUR_CHARACTERISTC_UUID;
err_code = sd_ble_uuid_vs_add(&base_uuid, &char_uuid.type);
APP_ERROR_CHECK(err_code);
  特性和服務使用相同的基礎UUID,但是使用不同的16位UUID。我們在our_service.h文件中將這條特性的UUID定義爲0xBEEF。在前面的教程中我們已經提到如何將基礎UUID添加到特定廠商表中,這樣對相同基礎UUID的調用就會參照相同的特定廠商表,這樣就不需要保存全部的128位UUID,只要使用16位UUID再加上特定廠商表中的基礎UUID即可,從而節省儲存空間,
  B.配置屬性Metadata
  我們只需要下面3行代碼就可以描述特性屬性。這幾行代碼決定了在哪保存屬性。我們打算在SoftDevice(也稱爲協議棧)中保存屬性,所以我們使用了BLE_GATTS_VLOC_STACK。也可以使用BLE_GATTS_VLOC_USER來讓用戶定義存儲位置。
ble_gatts_attr_md_t attr_md;
memset(&attr_md, 0, sizeof(attr_md));
attr_md.vloc = BLE_GATTS_VLOC_STACK;
  在屬性metadata結構體ble_gatts_attr_md_t 中,你可以定義屬性權限,例如你可以設置中間人防護(MITM)或者訪問屬性需要密碼。
  C.配置特性值屬性
  將前面創建的UUID以及屬性metadata帶入對於結構。
ble_gatts_attr_t attr_char_value;
memset(&attr_char_value, 0, sizeof(attr_char_value));
attr_char_value.p_uuid = &char_uuid;
attr_char_value.p_attr_md = &attr_md;
  D.向結構體中加入特性的屬性句柄
  我們需要向服務結構體中加入一個變量,用以保存特性的屬性句柄。所以在our_service.h中進行如下操作。
typedef struct
{
uint16_t conn_handle;
uint16_t service_handle;
// OUR_JOB: Step 2.D, Add handles for our characteristic
ble_gatts_char_handles_t char_handles;
}ble_os_t;
  我們可以看到,ble_os_t結構體中已經有了一個用於服務聲明的屬性句柄。其中的連接句柄conn_handle用於保存當前連接,它不對屬性表做任何操作。繼續看ble_gatts_char_handles_t的定義,可以發現這個結構體中包含特性值屬性、用戶描述符、CCCD以及服務器特性配置描述符(SCCD)共四種變量的16位屬性句柄,其中SCCD相關內容本教程不涉及。

  E.向服務中加入新特性
  現在我們要向屬性表中加入新特性,如下所示:
err_code =sd_ble_gatts_characteristic_add(p_our_service->service_handle,
&char_md,
&attr_char_value,
&p_our_service->char_handles);
APP_ERROR_CHECK(err_code);
  在上面的代碼中,我們向SoftDevice中填入相關的信息,包括新加的特性屬於哪個服務(服務句柄),特性Metadata和特性值屬性。然後協議棧處理這些參數並初始化該特性。最後將特性的屬性句柄保存到p_our_serice結構中。
  現在將例程編譯並下載到DK板中,結果如下圖所示:
這裏寫圖片描述
  我們可以看到,服務中多了一個新特性,但是它現在還沒什麼用處,因爲它沒有值,也不能對其進行讀寫操作。你可以嘗試讀寫一下,MCP會發送讀寫請求,但是由於我們還沒有定義任何讀寫權限,讀/寫屬性權限默認初始化爲0,所以DK板會拒絕任何讀寫請求。MCP工作log如下圖所示:
這裏寫圖片描述
  F.向特性值中加入讀/寫性質
  首先在our_char_add()函數中加入下面幾行:
ble_gatts_char_md_t char_md;
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.read = 1;
char_md.char_props.write = 1;
  這樣就會在特性的metadata中加入讀/寫性質。編譯並下載程序後可以看到操作已經生效:
這裏寫圖片描述
  現在再嘗試一下讀寫操作。結果我們會發現還是不能正常讀寫。爲什麼會出現這種情況呢?如果你是從本文開頭閱讀到這裏的,你就會發現,我們僅僅設置了特性聲明屬性的屬性值中的性質位,它僅僅是一種指導說明,真正的屬性權限還沒有設置。因此協議棧不知道該如何操作,所以拒絕了對該特性的讀寫訪問。
  G.設置特性的讀/寫權限
  現在我們設置特性的讀寫權限,爲了方便,我們將其設置爲沒有加密,不需要密碼。我們在 our_char_add() 函數中加入下面兩行:
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&attr_md.write_perm);
  程序運行結果如下:
這裏寫圖片描述
  現在你就可以進行讀寫操作了。結果如下圖所示。我們讀取了0字節,因爲還沒有賦值以及設置值長度。
這裏寫圖片描述
  嘗試寫入0x12,結果如下:
這裏寫圖片描述

  H.設置特性的值長度
  如何解決上面的問題呢?添加如下代碼即可:
attr_char_value.max_len = 4;
attr_char_value.init_len = 4;
uint8_t value[4] = {0x12,0x34,0x56,0x78};
attr_char_value.p_value = value;
  設置初始長度以及最大程度爲4,特性值就會有4字節長度。同時設置初始值。現在就可以進行讀寫操作了,寫入的值會保存在DK板上,下次連接時還會保留。
  下面的操作可以自行嘗試一下:
1. 向特性中寫入1字節,如‘12’
2. 向特性中寫入4字節,如‘12-34-56-78’
3. 向特性中寫入5個或者更多字節,如‘12-34-56-78-90’
  爲什麼不允許寫入超過4字節?如何修改?
  使用下面幾個宏定義修改屬性權限,然後進行讀寫操作,看看會有什麼結果。
1.BLE_GAP_CONN_SEC_MODE_SET_NO_ACCESS(),2.BLE_GAP_CONN_SEC_MODE_SET_OPEN()
3.BLE_GAP_CONN_SEC_MODE_SET_ENC_NO_MITM()
  這裏提示一下,對於第3種情況,當點擊MCP上的綁定操作後,會自動加密藍牙連接,但是不使用中間人防護(MITM)。

第三步:客戶端特性配置描述符
  我們可以添加溫度數據,然後讓DK板週期性地向客戶端發送溫度數據。爲此我們需要:
1. 添加數據發送函數;
2. 管理藍牙連接和服務;
3. 設置定時器週期性更新數據。
  藍牙核心規範定義了兩種發送數據的方法:
  Vol 3: Part G, Ch. 4.10 & 4.11:
  指示-服務器將特性值指示給客戶端,客戶端收到指示後發送應答。
  通知-服務器將特性值通知給客戶端,不需要應答。
  本着方便的原則我們這裏使用通知,不需要應答。爲了添加通知功能,我們需要向屬性表中加入一個描述符,即客戶端特性配置描述符(CCCD),藍牙核心規範對其定義如下:
  Vol 3: Part G, Ch 3.3.3.3:
  客戶端特性配置描述符是一種可選擇的描述符,它定義了某個特性可以被特定客戶端如何配置…
  上面比較費解的兩行是說CCCD是一種可寫的描述符,客戶端如MCP或者手機可以通過寫入CCCD來啓用或停止指示或通知。

  A.配置CCCD metadata
  將CCCD添加到特性中:
ble_gatts_attr_md_t cccd_md;
memset(&cccd_md, 0, sizeof(cccd_md));
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.read_perm);
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&cccd_md.write_perm);
cccd_md.vloc = BLE_GATTS_VLOC_STACK;
char_md.p_cccd_md = &cccd_md;
char_md.char_props.notify = 1;
1. 定義CCCD metadata結構體變量來保存配置;
2. 設置CCCD屬性權限;
3. 設置描述符保存位置爲協議棧;
4. 將CCCD metadata結構體保存到特性metadata結構體中;
5. 啓用通知功能。
  編譯並下載程序後可得:
這裏寫圖片描述
  我們可以看到“通知notify”已經加入到特性聲明的性質中。CCCD的UUID是0x2902,屬性值是0x0000,意思是通知和指示均關閉。如果你想啓用通知,可以寫入相應的值即可,但是我們沒有更新溫度數據,所以並不會發送通知。

  B.連接管理1:設置服務連接句柄爲默認值
  在簡單的例子中這一步可以不必做,但是當我們開發高級一點的應用時,這就很關鍵了。另外由於每個BLE例程中都涉及到了這一點,所以這裏還是要提一下。
  服務結構體ble_os_t中有一個變量conn_handle,它用來保存當前連接句柄,該句柄由BLE協議棧提供。系統啓動時需要初始化conn_handle,由於系統啓動時還沒有連接,所以在SDK中我們將其初始化爲BLE_CONN_HANDLE_INVALID(值爲0xFFFF)。我們只需要到our_service.c文件中找到our_service_init()函數,加入下面一行即可:
p_our_service->conn_handle = BLE_CONN_HANDLE_INVALID;

  C.連接管理2:對連接和斷開事件做響應
  在main.c文件中有一個函數ble_evt_dispatch(),當BLE協議棧中有事件發生時,會調用此函數。爲了讓我們添加的服務與最新的連接事件同步,可以在ble_evt_dispatch()函數中調用如下函數:
ble_our_service_on_ble_evt(&m_our_service, p_ble_evt);

  D.連接管理3:處理與我們新加入的服務相關的BLE事件
  在上面的ble_our_service_on_ble_evt()函數中,加入如下代碼。在連接成功後保存連接句柄,斷開連接時設置連接句柄爲初始值。
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
p_our_service->conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
break;
case BLE_GAP_EVT_DISCONNECTED:
p_our_service->conn_handle = BLE_CONN_HANDLE_INVALID;
break;
default:
// No implementation needed.
break;
}

  E.更新特性值
  在our_termperature_characteristic_update()函數中加入如下代碼就可以實現通知功能。在程序中,首先檢查是否存在有效連接,如果在沒有有效連接的情況下發送通知,協議棧會返回錯誤。
if (p_our_service->conn_handle != BLE_CONN_HANDLE_INVALID)
{
uint16_t len = 4;
ble_gatts_hvx_params_t hvx_params;
memset(&hvx_params, 0, sizeof(hvx_params));

hvx_params.handle = p_our_service->char_handles.value_handle;
hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
hvx_params.offset = 0;
hvx_params.p_len = &len;
hvx_params.p_data = (uint8_t*)temperature_value;

sd_ble_gatts_hvx(p_our_service->conn_handle, &hvx_params);
}
  在這段程序中你可能會奇怪hvx代表什麼,它不怎麼直觀,其實hvx代表Handle Value X,X表示通知或指示。這裏我們建立了一個ble_gatts_hvx_params_t類型的變量hvx_params來保存通知相關數據,然後將其提供給sd_ble_gatts_hvx()函數,這些數據包括:
  1. handle:協議棧需要知道我們操作的是哪個特性值,所以我們需要向其提供句柄,即p_our_service->char_handles.value_handle。
  2. type:協議棧需要知道我們想要使用的是通知還是指示,這裏我們使用通知BLE_GATT_HVX_NOTIFICATION。
  3. offset:需要傳輸的特性值可能有多個字節,如果你只想傳輸其中的幾個字節,可以設置偏移量offset。這裏我們需要傳輸全部四個字節,所以設置offset爲0.
  4. p_len:需要告知協議棧的傳輸字節數。如果我們只想傳輸四個字節,那就不需要每次傳輸20字節了。比如有四個字節: 0x01, 0x02, 0x03, 0x04, 0x05,我們想傳輸第三和第四字節,則設置offset爲2,len爲2即可。
  5. p_data:實際數據保存位置。
  最後我們將這個結構體傳入sd_ble_gatts_hvx()函數中,並輸入相關連接句柄。因爲在有些應用中可能同時存在多個連接,所以我們要向協議棧函數提供相應的連接句柄。
  F.將溫度數據更新到特性中
  現在我們需要測量溫度並更新溫度數據。在main.c文件中的timer_timeout_handler()函數中加入如下代碼:
int32_t temperature = 0;
sd_temp_get(&temperature);
our_termperature_characteristic_update(&m_our_service, &temperature);
nrf_gpio_pin_toggle(LED_4);
  1. temperature:臨時變量保存溫度數據。
  2. sd_temp_get():協議棧函數,獲取溫度值。
  3. our_termperature_characteristic_update():調用溫度更新函數並傳入數據和服務變量。
  4. nrf_gpio_pin_toggle():點亮DK板LED進行提醒。
  G.設置定時器ID和定時器間隔
  我們還需要設置測量觸發條件。例如按鈕按下或者從客戶端接收到特定命令。在我們的例子中,我們使用定時器來週期性地測量溫度。我們需要做的第一件事是設置定時器間隔,我們需要一個定時器ID來標識這個特定的計時器。因此在main.c中輸入下面幾行,定義定時器ID變量,並定義一個1000 ms的定時器間隔:
APP_TIMER_DEF(m_our_char_timer_id);
#define OUR_CHAR_TIMER_INTERVAL APP_TIMER_TICKS(1000, APP_TIMER_PRESCALER) // 1000 ms intervals
  H.初始化定時器
app_timer_create(&m_our_char_timer_id, APP_TIMER_MODE_REPEATED, timer_timeout_handler);
  APP_TIMER_MODE_REPEATED設置定時器觸發方式爲重複觸發。timer_timeout_handler爲定時器溢出處理函數。

  I.啓動定時器
  在Nordic函數庫中大多數情況下初始化不代表啓動,所以我們還要啓動定時器。第三個函數參數是一個通用指針,可以傳入timer_timeout_handler,這裏設爲NULL即可。
app_timer_start(m_our_char_timer_id, OUR_CHAR_TIMER_INTERVAL, NULL);
  編譯下載程序後,如果一切正常的話,我們會發現LED燈閃爍,MCP Log如下:
這裏寫圖片描述
  將手指放在芯片上可以發現溫度值變化。另外讀者可以嘗試1.修改 our_termperature_characteristic_update()函數,達到只有溫度變化時才發送數據的效果;2.修改定時器,連接建立時啓動定時器,斷開連接後停止定時器。

四.總結
  到這裏我們的工作就基本完成了,我們建立了一個屬性表並添加了一個基本的數據傳輸特性。它還有許多侷限,比如只能單向通信並且沒有進行安全加密。但是我希望我已經達到我的目的:交給你一些新的知識,爲你提供一個跳板,你可以就此展開深入研究。

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