nrf51822-提高nordic ble数据发送速率
講解2點(diǎn):
?????? 為什么?nordic的4.0協(xié)議棧中ble只能發(fā)送20字節(jié)的應(yīng)用負(fù)載數(shù)據(jù)。
?????? 大量數(shù)據(jù)發(fā)送時(shí)如何提高發(fā)送速率
?
1:為何上層應(yīng)用負(fù)載每次最多20字節(jié)
?
首先了解 4.0中鏈路層的包格式如下:
?????? PDU即協(xié)議數(shù)據(jù)單元,即鏈路層的負(fù)載數(shù)據(jù)。應(yīng)用層用戶發(fā)送的數(shù)據(jù)就是在這里面,但是并不全是用戶數(shù)據(jù)。
Ble有分廣播態(tài)和連接態(tài)。 所以上面的這個(gè)鏈路層幀可能是廣播數(shù)據(jù)也可能是連接后的數(shù)據(jù)。 所以就有兩種情況,一種為 廣播通道中的PDU,另一種為數(shù)據(jù)通道中的PDU。我們主要討論的是連接態(tài)下的數(shù)據(jù)通道中的數(shù)據(jù)幀,這里廣播通道下簡單介紹下。
廣播態(tài)下,廣播幀中的PDU如下圖所示,包含2字節(jié)的頭,其后的payload即為廣播數(shù)據(jù),比如通常我們設(shè)置的 設(shè)備名,廠商自定義數(shù)據(jù)等都在這里面,廣播數(shù)據(jù)肯定包含設(shè)備的地址,所以payload中的前6字節(jié)為設(shè)備地址
再看下 連接態(tài)下 數(shù)據(jù)通道中 鏈路層幀中的PDU組成,與廣播通道幀中的PDU類似,也是有2字節(jié)頭,隨后為payload即鏈路層的真實(shí)負(fù)載數(shù)據(jù)。
MIC為4字節(jié),只有在鏈路加密的情況下才會存在,為 消息完整性校驗(yàn),防止消息被篡改。
PS:加密鏈路中的空包不會存在MIC
協(xié)議都是分層的,ble也一樣,那么鏈路層的負(fù)載數(shù)據(jù)payload即為上層協(xié)議的數(shù)據(jù)幀,鏈路層的上一層協(xié)議為L2CAP,而L2CAP的幀格式如下如所示前4字節(jié)分別為長度和信道值。
PS:如果上圖Header中的LLID為3,則其后的負(fù)載為鏈路層控制報(bào)文而不是L2CAP層幀,這里不介紹。
同樣,L2CAP層的負(fù)載數(shù)據(jù)information payload為上層協(xié)議的數(shù)據(jù)幀,對于傳輸用戶數(shù)據(jù)而言,設(shè)備作為主機(jī)時(shí)用write寫數(shù)據(jù)到從機(jī),設(shè)備作為從機(jī)是用notify或indication 發(fā)送數(shù)據(jù)給主機(jī),這時(shí)候l2CAP層的負(fù)載中包含的就是 上層ATT的協(xié)議幀。
這里討論的是用戶發(fā)送數(shù)據(jù)為什么是限制為最大20字節(jié),所以了解下ATT協(xié)議中的write,notify,indication的命令格式就可以了。
如上圖所示,包含1字節(jié)opcode用來指示 write,notify,indication。2字節(jié)handle為句柄用來標(biāo)識是操作哪個(gè)特性值的。 之后就是真正用戶發(fā)送的數(shù)據(jù)了。
所以最終限制能一次發(fā)送多少數(shù)據(jù)就是這個(gè) ATT_MTU 為多少了。
規(guī)范中默認(rèn)這個(gè)MTU最小為23字節(jié),這個(gè)值其實(shí)是可以通過命令來協(xié)商的,而nordic的4.0協(xié)議棧中默認(rèn)只支持默認(rèn)值即23,所以也就限制了最終上層一次發(fā)送的數(shù)據(jù)限制在 20字節(jié)。
nrf52832使用的最新的s132協(xié)議棧中已經(jīng)開始支持MTU的協(xié)商了,這樣就可以一次傳輸更多數(shù)據(jù)了。
?
綜上,鏈路層的PDU中的數(shù)據(jù)如下圖所示:
PS:回顧最開始的鏈路層 幀結(jié)構(gòu)可以看到 PDU中允許的長度為2-39,即最少有2字節(jié)頭,有效負(fù)載數(shù)據(jù)最多37字節(jié)。
但是從ATT協(xié)議往鏈路層看,ATT 最多20字節(jié)用戶數(shù)據(jù),加上3字節(jié)頭,加上L2CAP的4字節(jié)頭,也就27字節(jié),為什么會有差額10字節(jié)?
原因在于 PDU因?yàn)榉智闆r有廣播通道的PDU,和數(shù)據(jù)通道的PDU,PDU除了2字節(jié)頭,有效負(fù)載為37字節(jié),在廣播數(shù)據(jù)中PDU需要包含6字節(jié)的廣播地址,其他廣播數(shù)據(jù)也就只有31字節(jié)了。但是數(shù)據(jù)通道中并不需要,但是為了簡單起見,也就限制了數(shù)據(jù)通道中有效負(fù)載數(shù)據(jù)最多31字節(jié)。 另一方面 如果鏈路加密了,數(shù)據(jù)通道中的PDU,最后會包含4字節(jié)的MIC,那么加密的有效負(fù)載數(shù)據(jù)就變成27字節(jié)了,這里又為了方便起見,也就讓即使不加密的鏈路發(fā)送的有效負(fù)載數(shù)據(jù)也為27。這就是差額的原因。
?
第二個(gè)問題:既然每次發(fā)送數(shù)據(jù)最多才20字節(jié),如果發(fā)送較多數(shù)據(jù)時(shí)如何提高發(fā)送速率?
以 ble_app_uart例子來說明,該例子中設(shè)備作為從機(jī),已經(jīng)實(shí)現(xiàn)了一個(gè)以notify方式向手機(jī)發(fā)送數(shù)據(jù)的函數(shù)。這里就直接利用這個(gè)發(fā)送函數(shù)。
一些簡單的應(yīng)用中通常可能很久才發(fā)送一次數(shù)據(jù),數(shù)據(jù)的發(fā)送量也沒有達(dá)到20字節(jié),這種情況下 直接調(diào)用該函數(shù)發(fā)送數(shù)據(jù)就可以了。
?
另一種情況,發(fā)送的數(shù)據(jù)比較多,但是對發(fā)送的速率并沒有要求。這種情況最簡單的可以直接用一個(gè)循環(huán)發(fā)送就可以了
While(沒發(fā)送完){
?????? ble_nus_string_send(數(shù)據(jù));
?????? delay_ms(n);
}
通常發(fā)送的數(shù)據(jù)越多delay_ms延遲的時(shí)間要越久一點(diǎn),這個(gè)要自己試驗(yàn)。通常只能用在一些少量數(shù)據(jù)比如一兩百字節(jié)。
?
更規(guī)范的做法應(yīng)該利用協(xié)議棧中的 發(fā)送完成事件?BLE_EVT_TX_COMPLETE,這個(gè)事件是在底層發(fā)送數(shù)據(jù)完成后由協(xié)議棧發(fā)上拋給應(yīng)用層的。
那么就可以利用這個(gè)事件,首先發(fā)送20字節(jié),當(dāng)?shù)讓影l(fā)送完成后上層收到這個(gè) 發(fā)送完成事件后再發(fā)送后續(xù)數(shù)據(jù)。
?
?
這里做一個(gè)簡單的實(shí)現(xiàn)
點(diǎn)擊(此處)折疊或打開
?
修改一下Main函數(shù),定義一個(gè)全局的buff用來放數(shù)據(jù)并初始化,將for循環(huán)中的power_manager去掉改成通過一個(gè)按鍵來啟動發(fā)送 buff中的數(shù)據(jù)。
燒寫程序,手機(jī)連接上后使能 特性值的notify功能,然后按鍵便會受到設(shè)備發(fā)給手機(jī)的100字節(jié)數(shù)據(jù)
?
?
?
啟動發(fā)送后只會發(fā)送前20字節(jié),當(dāng)這20字節(jié)發(fā)送完成后會收到BLE_EVT_TX_COMPLETE事件,在該事件處理中添加剩余數(shù)據(jù)的發(fā)送
直接在on_ble_evt事件處理函數(shù)中添加一下這個(gè)事件的處理
上面的實(shí)現(xiàn)只是針對 對發(fā)送速率沒要求的情況,這里抓包看一下實(shí)際的交互過程。
部分截圖如下
?
因?yàn)槊總€(gè)連接事件到來時(shí)都會切換到另一個(gè)通道(頻率)上進(jìn)行數(shù)據(jù)傳輸,而在這個(gè)連接事件持續(xù)時(shí)間中的數(shù)據(jù)交互都是在同一個(gè)通道上。
即每個(gè)連接事件到來時(shí)都會切換通道,但是一個(gè)連接事件內(nèi)部的通信都始終在那個(gè)通道上
所以由通道號可以區(qū)分出來這里基本上是兩個(gè)連接事件才會發(fā)送一次數(shù)據(jù),這樣效率就很低,因?yàn)閷?shí)際的底層基帶發(fā)送是很快的1Mbit/s, 也就是1us發(fā)送1bit。理論上簡單算一下,這里就直接以鏈路層最長包來算,1+4+39+3 也就只有47字節(jié),
47*8也就是發(fā)送一包的實(shí)際時(shí)間不足1ms,算上基帶啟動發(fā)送以及協(xié)議棧的一些處理也應(yīng)該是幾ms的事,那么一個(gè)連接間隔除了最前面的幾毫秒發(fā)送了一下數(shù)據(jù),之后這次連接間隔就關(guān)了。等之后的連接間隔到來才會繼續(xù)發(fā)送后續(xù)數(shù)據(jù)。那么發(fā)送效率就很低。
如果提高每個(gè)連接間隔中發(fā)送的數(shù)據(jù)包的數(shù)量,那么就可以提高發(fā)送速率。
前面的方法是調(diào)用每次發(fā)送函數(shù)后等待 完成事件,實(shí)際上,這個(gè)協(xié)議棧的底層應(yīng)該有一個(gè)自己的發(fā)送buff,能存放一定數(shù)據(jù),我們調(diào)用發(fā)送數(shù)據(jù)后協(xié)議棧會將數(shù)據(jù)放到這個(gè)buff中,最終再發(fā)送這個(gè)buff中的數(shù)據(jù)。
如果能在下個(gè)連接事件到來前竟可能的將多的數(shù)據(jù)放入這個(gè)協(xié)議棧中的buff里,那么他下次連接間隔發(fā)送的數(shù)據(jù)就變多了。
?
Sdk其實(shí)提供了這種方法,只不過比較隱晦。
我們利用的發(fā)送函數(shù)ble_nus_string_send,實(shí)際是調(diào)用了sd_ble_gatts_hvx 這個(gè)協(xié)議棧api函數(shù),這個(gè)函數(shù)有一個(gè)返回值NRF_ERROR_BUSY?表示忙,正在處理。
這應(yīng)該是表示開始發(fā)送了。
那么就可以直接重復(fù)調(diào)用這個(gè)ble_nus_string_send 函數(shù)直到其返回NRF_ERROR_BUSY 錯(cuò)誤,表示已經(jīng)開始發(fā)送了,不能再處理你提交的數(shù)據(jù)。
?
另外,協(xié)議棧中的buff肯定是有限的,如果我們調(diào)用這個(gè)發(fā)送函數(shù)的時(shí)候,即將到來下一個(gè)連接事件,那么buff肯定填不滿,最終出現(xiàn)的錯(cuò)誤是NRF_ERROR_BUSY,表示已經(jīng)開始發(fā)送了,你不能再填了。
?
但是如果調(diào)用的時(shí)候恰好離下一次連接事件到來還比較久,那么就會出現(xiàn)將協(xié)議棧中的buff填滿了,從而出現(xiàn)BLE_ERROR_NO_TX_BUFFERS?這個(gè)錯(cuò)誤。
?
這里只是介紹這兩種錯(cuò)誤,實(shí)際實(shí)現(xiàn)中可以不需要去判斷是不是這些錯(cuò)誤,因?yàn)榘l(fā)送是分包一點(diǎn)一點(diǎn)發(fā)送的,我們可以直接就判斷?ble_nus_string_send函數(shù)調(diào)用是不是返回NRF_SUCCESS,如果是才 更新 發(fā)送偏移,并且繼續(xù)循環(huán)調(diào)用該函數(shù)以填更多數(shù)據(jù)到協(xié)議棧buff中,如果返回值不正確,那么直接跳出,不更新發(fā)送偏移就可以了,而并不用去區(qū)分是BUSY錯(cuò)誤還是NO BUFF錯(cuò)誤。
?
點(diǎn)擊(此處)折疊或打開
最后再修改一下main函數(shù),發(fā)送500個(gè)字節(jié)
?
?
?
燒寫程序后運(yùn)行代碼,我們再次抓一下空中包看看是否每個(gè)連接間隔中發(fā)送了多個(gè)數(shù)據(jù)包
由通道號可以看到現(xiàn)在一個(gè)連接事件中發(fā)送了多個(gè)包(最多6個(gè))
總結(jié)
以上是生活随笔為你收集整理的nrf51822-提高nordic ble数据发送速率的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BLE 包结构及传输速率
- 下一篇: BLE】CC2541之通过RSSI测距