從零開始的nrf52832藍(lán)牙開發(fā)--藍(lán)牙模板解析
來源:https://blog.csdn.net/qwe5959798/article/details/120152350
本篇文章使用SDK版本為?15.3.0,開發(fā)板使用官方開發(fā)板PCA10040。

我們打開nRF5_SDK_15.3.0\examples\ble_peripheral\experimental\bluetoothds_template\pca10040\s132\ses
由SDK說明文檔可知,這個例子是用Bluetooth Developer studio生成的一個模板工程,這個開發(fā)工具是由SIG推出的,不過感覺國內(nèi)用的人很少。
代碼功能
LED1指示連接狀態(tài),如果藍(lán)牙未被連接,LED1閃爍,如果藍(lán)牙被連接,則LED1常亮。按鍵1連接狀態(tài)下長按2S可以斷開連接并重新開啟一個無白名單廣播,未連接狀態(tài)下按下會進(jìn)入休眠狀態(tài),再按下喚醒。按鍵2也可以喚醒設(shè)備但會刪除所有綁定信息,正常運行中按下會關(guān)閉白名單。廣播超過3分鐘未連接設(shè)備則會自動休眠。使用nrf connect這個APP連接開發(fā)板如下:

main

main函數(shù)里面包含所有功能的初始化。我們詳細(xì)看一下和藍(lán)牙有關(guān)的初始化。
timers_init

因為藍(lán)牙協(xié)議棧使用到了app定時器,所以app定時器初始化是必須的,即使你沒有使用到,使用的方法官方已經(jīng)給出,如上圖紅框。
buttons_leds_init

這個函數(shù)第一初始化了LED和按鍵,第二把藍(lán)牙事件與LED和按鍵綁定起來。
LED和按鍵初始化中,按鍵初始化如下:
按鍵的設(shè)計思路是:通過定時器去實現(xiàn)按鍵按下、按鍵釋放、按鍵長按三種情況判斷,每種動作情況可以配置相同或者不同的事件類型,根據(jù)事件類型去處理不同情況。
在bsp_init里還有LED的初始化:
可以看到在這里創(chuàng)建了一個有關(guān)LED的定時器,這個定時器里通過判斷當(dāng)前BSP的事件狀態(tài)去改變LED狀態(tài):
bsp_led_indication這個函數(shù)因為處理的狀態(tài)比較多所以很長,這里我們只看用到的:

按鍵初始狀態(tài)事件綁定在函數(shù)bsp_btn_ble_init里:
因為有兩種喚醒方式(刪除綁定和不刪除),所以需要在程序喚醒后判斷是用哪一個按鍵喚醒的,從而決定要不要刪除所有綁定信息。
而函數(shù)?advertising_buttons_configure里則更改了按鍵動作觸發(fā)的事件:
函數(shù)bsp_event_to_button_action_assign主要用來把事件對應(yīng)到哪個按鍵完成什么動作觸發(fā)這個綁定:
這個函數(shù)上面還有一個差不多的連接狀態(tài)下的按鍵配置函數(shù)connection_buttons_configure:
這個函數(shù)在何處被調(diào)用?首先在bsp_btn_ble.c中注冊了一個觀察者:
它的回調(diào)函數(shù)中處理了連接和斷開兩種事件:
也就是連接后用connection_buttons_configure配置按鍵,斷開后用advertising_buttons_configure配置按鍵。關(guān)于觀察者詳細(xì)查看下一節(jié)。
ble_stack_init

函數(shù)nrf_sdh_enable_request內(nèi)容如下:
注意這里用到的通知函數(shù)有兩種:
sdh_request_observer_notify
sdh_state_observer_notify
如何通知所有觀察者?如把類型為request的信息通知給所有觀察者:
通知?state?類型如下:
這兩個段被注冊在nrf_sdh.c內(nèi):
用來定義段的宏定義為?NRF_SECTION_SET_DEF:
關(guān)于宏和段的使用,可以參考我之前剖析RT-Thread那篇自動初始化部分:
剖析RT-Thread中console與finsh組件實現(xiàn)(1)
什么是觀察者?
詳細(xì)解析參考下面博文:
BLE事件回調(diào)機制解析
還有在這個函數(shù)里我們需要特別注意函數(shù)?nrf_sdh_ble_enable?函數(shù)內(nèi)容:
由上一章我們知道了其實所有數(shù)據(jù)在服務(wù)端都以attribute呈現(xiàn),就像古代的藥店,有一面墻都是抽屜,每個抽屜的標(biāo)簽寫了藥材的名字,里面則放的相應(yīng)的藥材,類比一下,每個attribute就是一個抽屜,attribute的handle就像藥材的名字,attribute的內(nèi)容就像藥材本身,而attribute的權(quán)限就像藥性??蛻舳司拖衽渌幍拇蠓?,需要什么藥得根據(jù)名字去找,然后根據(jù)藥材的藥性去決定取的量。所有的attribute組成一個表叫attribute table如下:
所以每當(dāng)你添加服務(wù)或者特征,都會增大attribute table的大小,而這個attribute table是直接分配在協(xié)議棧所用ram里的,這里需要提一下,協(xié)議棧其實就是一段代碼,也需要運行空間,nrf的做法就像用OS創(chuàng)建了兩個任務(wù),一個任務(wù)專門跑協(xié)議棧,一個任務(wù)跑你的應(yīng)用代碼,我們知道創(chuàng)建任務(wù)是需要分配任務(wù)空間的,attribute table就占用協(xié)議棧的任務(wù)空間大小。因為協(xié)議棧的ram是從芯片ram起始地址開始的,所以我們只需要設(shè)置應(yīng)用程序的ram從哪開始、有多大就可以了,這樣前面的就全部分配給協(xié)議棧。所以回到前面,當(dāng)attribute table變大了,就需要把應(yīng)用程序的起始ram往后推,應(yīng)用程序ram大小當(dāng)然也要減去增加的,因為芯片ram總大小是一定的。
所以如果當(dāng)你代碼沒有問題,但是程序跑不起來或者藍(lán)牙服務(wù)異常,有可能就是你的attribute table太大了導(dǎo)致數(shù)據(jù)溢出,如果只影響到協(xié)議棧,那你的藍(lán)牙服務(wù)就會崩潰,如果嚴(yán)重到影響到應(yīng)用代碼,你的整個程序都會崩潰。但是你并不能直接去修改應(yīng)用ram起始和大小,而是應(yīng)該先去增加attribute table的大?。?br style="box-sizing: border-box;">
然后上面函數(shù)里的打印會告訴你應(yīng)該把起始地址和大小調(diào)整為多少。調(diào)整方法:

修改格式IDE已經(jīng)給出,仿照上圖紅框,修改參數(shù)填入到下圖即可。
比如我的這段代碼LOG:
我只需要修改:
最后在此函數(shù)中還初始化了一個觀察者:
該宏NRF_SDH_BLE_OBSERVER如下:
sdh_ble_observers段定義在nrf_sdh_ble.c內(nèi):
如果我們后續(xù)添加自己的服務(wù)也應(yīng)該注冊在該段內(nèi)。該觀察者事件回調(diào)為:
可以看到連接成功后,會觸發(fā)一次LED狀態(tài)指示函數(shù),也就是常亮LED1。
peer_manager_init

有關(guān)配對與綁定,其實主要是為了安全,詳細(xì)解析推薦博文:
低功耗藍(lán)牙配對綁定解讀和實踐
除了注意配對的一些參數(shù),需要注意配對事件的回調(diào)函數(shù)。
在協(xié)議棧收到一些配對有關(guān)信息,它會處理好分為不同事件類型上報給應(yīng)用層,方法就是觸發(fā)事件回調(diào)。上圖回調(diào)函數(shù)里并沒有處理任何事件,實際我們寫為:
上圖只處理了一種情況?PM_EVT_PEERS_DELETE_SUCCEEDED?如果你需要處理其他情況,直接添加不同的case即可:
gap_params_init

這個函數(shù)里可以修改加密模式、設(shè)備名稱、還有一些GAP連接參數(shù)。中間注釋掉的那段代碼是添加設(shè)備外觀的,如果添加的是SIG所制定的一些標(biāo)準(zhǔn)profile是有設(shè)備外觀的,而自定義的則無。
gatt_init

可以看到gatt初始化很簡單,實際在自己的項目中基本會提供一個GATT的事件回調(diào)函數(shù),如:

可以看到就兩種事件,一種是ATT的MTU大小更新,另一種是數(shù)據(jù)長度更新。
advertising_init

這個函數(shù)是設(shè)置廣播相關(guān)內(nèi)容的,也就是在廣播階段你想要展示的內(nèi)容都在這里設(shè)置。最開始手機截圖可以看到?jīng)]有連接的時候也有顯示180A這個服務(wù)。
services_init
這個函數(shù)里就是我們要實現(xiàn)的功能,如果我們要添加自己的服務(wù),就在下圖紅框內(nèi)的服務(wù)初始化函數(shù)內(nèi)添加,如果是多個服務(wù),你可以添加多個服務(wù)初始化函數(shù)。

可以看到上圖就是一個服務(wù)的模板文件,實際開發(fā)時,我們最好把每個服務(wù)放在單獨的.c文件里,這里服務(wù)初始化是空的,也就是沒有我們自己的服務(wù)。
conn_params_init

這個函數(shù)主要設(shè)置了連接參數(shù)協(xié)商參數(shù),因為連接參數(shù)是由連接發(fā)起方設(shè)置的,我們作為被連接方,如果想要改變連接參數(shù),只能通過協(xié)商的方式,即告訴連接方我們想如何連接,但是連接方可以選擇采納也可以拒絕。
advertising_start

可以看到廣播一開始就以快速廣播模式開始廣播,這個函數(shù)里面比較復(fù)雜,我們不過多關(guān)注,只需要注意一個地方:
假如你前面沒有設(shè)置快速廣播的相關(guān)參數(shù),實際開啟的廣播可能會變成其他廣播模式。
這里case中沒有break,也就是返回值是最近能得到的廣播模式。
變量定義

還有一些需要使用到的全局變量都定義在main.c最開始的位置。分別是與隊列模塊相關(guān)的、GATT模塊相關(guān)的、與廣播模塊相關(guān)的、保存當(dāng)前連接句柄的全局變量。
這里看一下廣播的結(jié)構(gòu)體變量,因為它和廣播如果3分鐘沒有設(shè)備連接就自動休眠有關(guān)。
先看一下廣播結(jié)構(gòu)體變量初始化:
這里很重要的宏是?NRF_SDH_BLE_OBSERVER,它用來注冊觀察者,上面在藍(lán)牙協(xié)議棧初始化已經(jīng)講過。我們看一下廣播的事件回調(diào)ble_advertising_on_ble_evt:
連接成功和連接斷開不做展開,但是廣播超時后廣播就立馬停止了嗎?
可以看到無論哪種超時原因,最后的結(jié)果都是繼續(xù)廣播,函數(shù)adv_mode_next_get會返回下個廣播模式,但是因為我們只配置了快速廣播模式一種,所以ble_advertising_start這個啟動廣播的函數(shù)里面
在判斷所選廣播模式是否配置過會判斷不過,最終函數(shù)adv_mode_next_avail_get只能返回BLE_ADV_MODE_IDLE
最終這個狀態(tài)會在廣播事件回調(diào)on_adv_evt里被觸發(fā)
而on_adv_evt被調(diào)用的位置在函數(shù)ble_advertising_start結(jié)尾:
而在休眠函數(shù)sleep_mode_enter里設(shè)置如下:
???????????????? ?END ????????????????
關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。
點擊“閱讀原文”查看更多分享,歡迎點分享、收藏、點贊、在看。
