從零開始的nrf52832藍(lán)牙開發(fā)——藍(lán)牙串口實(shí)現(xiàn)
來源:https://blog.csdn.net/qwe5959798/article/details/120152350
藍(lán)牙串口是一個(gè)很實(shí)用的例子,里面涉及到數(shù)據(jù)發(fā)送和接收,這是應(yīng)該是作為藍(lán)牙設(shè)備很基礎(chǔ)的功能。如果你沒有看上一篇,建議從上一篇看起,因?yàn)檫@一篇會省去上一篇的重復(fù)內(nèi)容。
打開工程:
nRF5_SDK_15.3.0_59ac345\examples\ble_peripheral\experimental\bluetoothds_template\pca10040\s132\ses
實(shí)驗(yàn)現(xiàn)象:
下載代碼后打開串口可以接收到手機(jī)發(fā)送的數(shù)據(jù):


手機(jī)也可以接收到串口發(fā)送的數(shù)據(jù),需要啟用接收功能:

至于按鍵和LED和上一篇模板差不多不再贅述。
代碼差異分析:
首先和上一章模板有區(qū)別的地方:
main函數(shù)里少了配對管理初始化,多了串口初始化:

串口初始化很簡單,關(guān)鍵是傳入了一個(gè)事件回調(diào):

在藍(lán)牙協(xié)議棧初始化中,創(chuàng)建的觀察者的回調(diào)函數(shù)中新增了對兩個(gè)事件的處理:

在gatt初始化時(shí),傳入了GATT事件處理回調(diào)函數(shù)并設(shè)置最大傳輸單元,注意這個(gè)值是從機(jī)建議主機(jī)的值,至于主機(jī)采不采用完全由主機(jī)說了算:

在回調(diào)函數(shù)里處理了交換MTU的事件,設(shè)置串口最長傳輸數(shù)據(jù) = MTU - OPCODE - HANDLE,這個(gè)m_ble_nus_max_data_len值才是實(shí)際通信過程中發(fā)送的最大數(shù)據(jù)長度:

在服務(wù)初始化中初始化藍(lán)牙串口服務(wù):

細(xì)節(jié)展開放在后面詳細(xì)講。然后是廣播初始化中,廣播模式為有限可發(fā)現(xiàn)模式:

兩種模式有什么區(qū)別可以自行查找相關(guān)資料。
串口服務(wù)分析
串口服務(wù)對象實(shí)例化
由上面可以看出,其實(shí)相比上一章的藍(lán)牙模板來說,改動并不大,主要是新增了藍(lán)牙串口服務(wù)。
按照Nordic的SDK編程風(fēng)格,首先對于一個(gè)新的服務(wù)對象,需要有一個(gè)服務(wù)實(shí)例,所以第一步是創(chuàng)建對象,然后實(shí)例化,在main.c里有:

這個(gè)宏就是Nordic寫的實(shí)例化藍(lán)牙串口這個(gè)對象的宏,初始化后,m_nus就是我們后面要操作的藍(lán)牙串口服務(wù)對象,NRF_SDH_BLE_TOTAL_LINK_COUNT 代表連接數(shù)最大為1,宏原型:

初步展開:
最后展開:

可以看到,這個(gè)宏首先定義了一個(gè)連接上下文存儲的結(jié)構(gòu)體變量,這個(gè)結(jié)構(gòu)體里面保存了所有連接占用的內(nèi)存池、最大支持多少個(gè)連接、單個(gè)連接內(nèi)存這三個(gè)參數(shù),在這里單個(gè)連接占用多少內(nèi)存如下圖:

可以看到單個(gè)連接占用內(nèi)存為 ble_nus_client_context_t 結(jié)構(gòu)體的大?。?/p>

也就是一個(gè)bool類型的變量,具體多大,由編譯器決定,不同編譯器可能最終結(jié)果不同。
然后實(shí)例化m_nus,并把上面那個(gè)結(jié)構(gòu)體變量存入m_nus 這個(gè)實(shí)例中。
最后是把 ble_nus_on_ble_evt 這個(gè)回調(diào)函數(shù)注冊為觀察者,然后當(dāng)調(diào)用這個(gè)函數(shù)時(shí),傳入的上下文參數(shù)為 m_nus 這個(gè)實(shí)例的指針。
然后看一下這個(gè)藍(lán)牙串口服務(wù)結(jié)構(gòu)體:

它包含:
1.服務(wù)的uuid,相當(dāng)于這個(gè)服務(wù)的唯一編號
2.服務(wù)句柄,服務(wù)創(chuàng)建成功后,協(xié)議棧自動分配給我們的操作標(biāo)識
3.特征句柄,特征創(chuàng)建成功后,協(xié)議棧自動分配給我們的操作標(biāo)識
4.連接上下文內(nèi)存池指針,指向用來保存連接上下文的內(nèi)存池,該內(nèi)存池需要我們根據(jù)自己的需求去自己創(chuàng)建(其實(shí)就是全局?jǐn)?shù)組),在實(shí)例化時(shí)初始化
5.數(shù)據(jù)處理回調(diào)函數(shù)指針,可有可無,主要作為函數(shù)指針,在數(shù)據(jù)到來時(shí)執(zhí)行。
串口服務(wù)初始化
比上一篇模板就多了一個(gè)藍(lán)牙串口初始化函數(shù),這個(gè)函數(shù)兩個(gè)參數(shù)分別為服務(wù)對象和初始化參數(shù):

添加藍(lán)牙服務(wù):
1.添加UUID
2.添加服務(wù)
3.添加特征
首先因?yàn)閁UID是一個(gè)128bit的特征碼,為了方便使用,我們先把它添加進(jìn)協(xié)議棧里,然后協(xié)議棧返回一個(gè)type(類型),其實(shí)就是一個(gè)索引,我們操作這個(gè)索引即相當(dāng)于操作這個(gè)UUID的服務(wù)。因?yàn)樵谒{(lán)牙協(xié)議里,不同的服務(wù)和特征,使用UUID的第12、13位來區(qū)別的,所以我們把完整的128bit(16bytes)的UUID存進(jìn)協(xié)議棧里,然后只修改它的第12和第13byte就可以了,而修改它是通過索引來修改的:

sd_ble_uuid_vs_add 函數(shù)原型:

sd_ble_gatts_service_add 函數(shù)原型:

其中服務(wù)類型分為三種:

服務(wù)句柄由協(xié)議棧分配(其實(shí)也相當(dāng)于一個(gè)索引),我們保存在之前介紹的m_nus實(shí)例里。
如果在你的工程里需要添加多個(gè)不同的基礎(chǔ)UUID,記得修改sdk_config.h里的NRF_SDH_BLE_VS_UUID_COUNT,它表示添加了幾個(gè)UUID:

添加完成后,還需要修改用戶代碼RAM的起始地址(后移0x10)和大小(減小0x10),修改方法查看上一篇。
其實(shí)添加到這里如果下載代碼,已經(jīng)可以看到藍(lán)牙多了一個(gè)服務(wù)了,只不過服務(wù)下面什么也沒有。
特征要添加在服務(wù)下面:

而其中最主要的特征參數(shù)設(shè)置為:

uuid:特征的UUID(完整的16bytes下的第12、13byte)
uuid_type:完整UUID的索引,如果為0,則使用SIG的UUID
max_len:特征值的最大長度
init_len:特征值的初始長度
p_init_value:特征值的初始值
is_var_len:特征值長度是否可變
char_props:特征屬性,其中又包括:
broadcast:是否允許廣播
read:是否允許讀取特征值
write_wo_resp:是否允許通過命令(cmd)寫特征值
write:是否允許通過請求(request)寫特征值
notify:是否允許通知
indicate:是否允許指示
auth_signed_wr:是否允許帶符號的寫入命令
char_ext_props:特性的拓展屬性,其中又包括:
reliable_wr:寫數(shù)據(jù)時(shí)是否允許使用隊(duì)列方式
wr_aux :是否允許寫入用戶特征描述符
is_defered_read:是否支持延遲讀取操作
is_defered_write:是否支持延遲寫入操作
read_access:讀取特征值的安全要求
write_access:寫入特征值的安全要求
cccd_write_access:寫入特征CCCD的安全要求
is_value_user:特征值是保存在用戶代碼的RAM里還是保存在協(xié)議棧的RAM里
p_user_descr:需要時(shí),指向用戶描述符
p_presentation_format:需要時(shí),指向特征格式
藍(lán)牙串口數(shù)據(jù)處理
對數(shù)據(jù)的處理都集中在藍(lán)牙事件回調(diào)函數(shù)里,這個(gè)函數(shù)兩個(gè)參數(shù),第一個(gè)參數(shù)包含事件類型等,第二個(gè)參數(shù)在我們初始化時(shí),我們把它設(shè)置為了藍(lán)牙串口實(shí)例的指針:
連接事件:

可以看到連接時(shí)主要判斷了是否使能通知,然后把連接事件傳給nus_data_handler ,但其實(shí)函數(shù)中并沒有處理這一事件。
接收數(shù)據(jù)分為兩個(gè),一個(gè)是接收到是否啟用通知,另一個(gè)是接收到串口數(shù)據(jù),這兩種數(shù)據(jù)是由特征句柄來區(qū)分的:


而在回調(diào)函數(shù)里,只處理了這一種回調(diào)類型,處理方式是使用for循環(huán)把數(shù)據(jù)通過串口發(fā)送出去:

發(fā)送事件基本沒有什么作用,因?yàn)樵诨卣{(diào)里沒有對該類型處理:

發(fā)送函數(shù)在串口事件處理回調(diào)函數(shù)里:

具體的發(fā)送很簡單:

???????????????? END ???????????????
關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。
點(diǎn)擊“閱讀原文”查看更多分享,歡迎點(diǎn)分享、收藏、點(diǎn)贊、在看。

