BLE-NRF51822教程3-sdk程序框架剖析
【IT168 技術(shù)】本講為框架介紹,不會牽涉到太多代碼細節(jié)。51822的官方SDK其實是沒有框架依耐性的。什么叫框架,比如TI的BLE SDK中就有一個操作系統(tǒng)抽象層(OSAL)他是一個輪訓的調(diào)度。你需要按照他的方式去創(chuàng)建任務(wù)等等。
而51822的SDK本質(zhì)上只是提供了各種調(diào)用接口,比如開啟初始化協(xié)議棧,初始化一些硬件功能模塊,開始廣播,發(fā)起鏈接等等。這些接口怎么用完全取決于自己。不過一般固件開發(fā)都是一些類似的流程各種資源的初始化,51822也不例外。所以sdk中的作為從機的例子main函數(shù)都是類似如下的步驟:
以官方的串口BLE 為例:
int main(void)
{
leds_init(); //非必須,只是該例子中用到了
timers_init(); //非必須,只是該例子中用到了
buttons_init(); //非必須,只是該例子中用到了
uart_init(); //非必須,只是該例子中用到了串口
ble_stack_init(); //必須
gap_params_init(); //必須
services_init(); //跟自己創(chuàng)建的服務(wù)相關(guān),不同的服務(wù)細節(jié)不同但大體建立
//過程基本一致,通常在直接使用官 方的例子修改一些參數(shù)即可
advertising_init(); //廣播數(shù)據(jù)初始化,必須
conn_params_init(); //是情況而定,如果連接后不需要連接參數(shù)的協(xié)商,該初始化也 //可不要
sec_params_init(); //安全參數(shù)初始化,如果沒用到配對綁定相關(guān)這個也可以不初始化
advertising_start(); //開啟廣播,必須
// Enter main loop
for (;;)
{
power_manage(); //進入睡眠
}
}
可以看到其實核心必要的只有這5個函數(shù)而已。你可以將其他代碼全都去掉,只要留下這5個函數(shù)設(shè)備一樣可以運行,手機也能搜到設(shè)備并與設(shè)備通信。
這種初始化的方式可以說是與我們一般的單片機開發(fā)沒有區(qū)別。
那么初始化之后呢。以前的裸板單片機開發(fā)我們就是進入一個while循環(huán)執(zhí)行一些周而復(fù)始的事,后面為了降低功耗開始在while(1)循環(huán)中加個睡眠代碼讓沒有工作時芯片處于睡眠狀態(tài),并依靠中斷來喚醒從而處理到來的事物。
而上面的51822的main函數(shù)最后也是一個for{}循環(huán),power_manage(); 內(nèi)部代碼其實就是一個睡眠指令。Main函數(shù)到這里就已經(jīng)沒了,最后其實就是一個循環(huán)睡眠。這里看不到任何任務(wù)(task),只有睡眠。那么可想而知,51822的協(xié)議棧實現(xiàn)應(yīng)該是基于”事件喚醒”的,也就是沒事的時候睡眠,有事的時候喚醒工作而后繼續(xù)睡眠。那么那些處理事件的代碼都是在哪里的?
那協(xié)議棧到底是怎么運作的?我希望創(chuàng)建一個服務(wù)在哪里添加?手機發(fā)送來的數(shù)據(jù)在哪里?我怎么發(fā)送數(shù)據(jù)給手機?
下面一一解釋這些問題:
協(xié)議棧如何運作?
要明白協(xié)議棧怎么運作,首先就要理解51822的協(xié)議棧是基于100%的事件驅(qū)動的。就是說協(xié)議棧向app發(fā)送的任何數(shù)據(jù)都是基于事件的。
比如設(shè)備收到手機發(fā)來的鏈接請求,或是手機發(fā)過來的數(shù)據(jù)等等。協(xié)議棧首先收到這些數(shù)據(jù)后做一些處理,然后將這些數(shù)據(jù)(比如鏈接請求,或是普通數(shù)據(jù)等)打包成一個結(jié)構(gòu)體,并附上事件ID,比如BLE_GAP_EVT_CONNECTED或BLE_GATTS_EVT_WRITE來分別告訴上層app這個事件結(jié)構(gòu)體代表的事件。
比如BLE_GAP_EVT_CONNECTED代表鏈接事件,那么這個事件結(jié)構(gòu)體中包含的數(shù)據(jù)就是連接參數(shù)等數(shù)據(jù)。而BLE_GATTS_EVT_WRITE代表寫事件,那么結(jié)構(gòu)體中的數(shù)據(jù)就是對端設(shè)備(比如手機)寫給板子的數(shù)據(jù)。
比如uart的demo中dispatch派發(fā)函數(shù)
?static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
??? ble_conn_params_on_ble_evt(p_ble_evt);
??? ble_nus_on_ble_evt(&m_nus, p_ble_evt);
??? on_ble_evt(p_ble_evt);
}
在任何與BLE相關(guān)的事件被協(xié)議棧上拋上來給app時,ble_evt_dispatch就會被調(diào)用。從而將事件拋給各個服務(wù)函數(shù)或處理模塊,這里是將事件拋給了
連接參數(shù)管理處理函數(shù)ble_conn_params_on_ble_evt
Uart服務(wù)的事件處理函數(shù)ble_nus_on_ble_evt (nus為Nordicuart server)
通用的事件處理函數(shù)on_ble_evt
不同的事件在事件結(jié)構(gòu)體ble_evt_t中通過id來區(qū)別。不同是事件處理函數(shù)通常也只是處理自己感情去的事件,我們來看看ble_nus_on_ble_evt事件處理函數(shù)的內(nèi)部
voidble_nus_on_ble_evt(ble_nus_t * p_nus, ble_evt_t * p_ble_evt)
{
if ((p_nus == NULL) || (p_ble_evt == NULL))
{
return;
}
switch (p_ble_evt->header.evt_id)
{
caseBLE_GAP_EVT_CONNECTED:
on_connect(p_nus, p_ble_evt);
break;
caseBLE_GAP_EVT_DISCONNECTED:
on_disconnect(p_nus, p_ble_evt);
break;
caseBLE_GATTS_EVT_WRITE:
on_write(p_nus, p_ble_evt);
break;
default:
// No implementation needed.
break;
}
}
可以看到,uart服務(wù)事件處理函數(shù)只關(guān)心三個事件,鏈接事件,斷開鏈接事件以及寫事件(對端設(shè)備發(fā)數(shù)據(jù)過來),不同的事件再針對做不同的,這個就由開發(fā)人員自己來實現(xiàn)了。比如對于連接事件通常應(yīng)該記錄下事件結(jié)構(gòu)體中的連接句柄,因為后續(xù)的BLE操作基本都要基于連接句柄(可以看做是兩個設(shè)備通信的信道ID,實際為鏈路層中的數(shù)據(jù)接入地址概念)。
PS: 事件是交給dispatch來派發(fā)給各個服務(wù)以及模塊的,對于更底層的事件又是如何交給dispatch函數(shù)的過程請參考群公告中的 51822教程-協(xié)議棧概述教程。
解決了所謂的事件驅(qū)動再來解決:如果希望創(chuàng)建一個服務(wù)在哪里添加?
在main函數(shù)的初始化過程中有一個services_init();這個函數(shù)的內(nèi)部就是添加服務(wù),添加特征值等代碼。
函數(shù)內(nèi)部其實就是注冊了一會回調(diào)函數(shù)nus_data_handler(該函數(shù)會在手機發(fā)數(shù)據(jù)給板子時將數(shù)據(jù)從電腦串口打印出來) 然后再執(zhí)行真正的初始化函數(shù)ble_nus_init。
該函數(shù)的內(nèi)部又會調(diào)用sd_ble_gatts_service_add這個協(xié)議棧的api接口來添加服務(wù)。
后面也會調(diào)用sd_ble_gatts_characteristic_add這個協(xié)議棧的api接口來添加特征值。
層次關(guān)系如下:
也就是說完成一個完整的服務(wù)建立函數(shù)其實只要sd_ble_gatts_service_add()和sd_ble_gatts_characteristic_add ()這兩個核心函數(shù)。
通常建立服務(wù)并不需要自己去從頭寫過。而是直接賦值官方的這個services_init()函數(shù),然后做一些小改動就可以。比如修改一下uuid, 修改一下讀/寫屬性,多添加一個特征值等。要修改的其實很少。
下面解決最后兩個問題:手機發(fā)送來的數(shù)據(jù)在哪里?我怎么發(fā)送數(shù)據(jù)給手機?
要搞清楚這兩個問題,先來看一下群里常問的幾個與上面相關(guān)的問題:
問:手機發(fā)給51822設(shè)備的數(shù)據(jù)在哪個函數(shù)里出來的?
答:
沒有函數(shù)
協(xié)議棧會拋上來一個事件結(jié)構(gòu)體
收到的數(shù)據(jù)在結(jié)構(gòu)體中
問:藍牙上傳函數(shù),與下發(fā)函數(shù)都是一樣的嗎?都是服務(wù)API函數(shù)?
答:
只有上傳函數(shù) 是服務(wù)器用來將數(shù)據(jù)傳給客戶端的。
下發(fā)數(shù)據(jù) 是藍牙芯片收到數(shù)據(jù)后,協(xié)議棧會拋上來一個有數(shù)據(jù)的事件結(jié) 構(gòu)體。具體參看示例代碼中的 dispatch派發(fā)程序中各個事件處理函數(shù)對各 種事件的數(shù)據(jù)。
問:sd_ble_gatts_hvx()這個函數(shù)是 藍牙的發(fā)送函數(shù),有知道藍牙的接收函數(shù) ?
答:
藍牙沒有接收函數(shù),藍牙的數(shù)據(jù)接收在底層,接收完后會返回事件給上層的 ble_evt_dispatch 分發(fā)函數(shù),它將事件分發(fā)給各個服務(wù)或者事件處理函數(shù)。 服 務(wù)或處理函數(shù)會捕獲是否存在寫事件case BLE_GATTS_EVT_WRITE: 存在就做 相應(yīng)的處理。收到的數(shù)據(jù)都在返回的事件結(jié)構(gòu)體里
其實看完這三個問題基本上上面的問題其實已經(jīng)解決差不多了。作為從設(shè)備,BLE的發(fā)送數(shù)據(jù)給手機是有API接口的,就是上面問到的sd_ble_gatts_hvx(),可以通過參數(shù)來設(shè)置是以通知方式發(fā)送還是指示方式發(fā)送(通知不需要回復(fù)確認,指示需要)。但是手機發(fā)過來數(shù)據(jù)卻是沒有接收函數(shù),為什么?因為協(xié)議棧是基于事件驅(qū)動的!所以收到數(shù)據(jù)后協(xié)議棧會給上層app一個寫事件(指示對端設(shè)備寫數(shù)據(jù)過來了),而寫過來的數(shù)據(jù)時在這個事件結(jié)構(gòu)體中。我們只要提取出來就行了。所以沒有接收函數(shù)API。
從另一方面也可以解釋為什么沒有接收數(shù)據(jù)函數(shù)。因為發(fā)送數(shù)據(jù)時”同步的”,是主動調(diào)用的,在往想發(fā)送數(shù)據(jù)的時候。但是接收數(shù)據(jù)時”異步的”,數(shù)據(jù)可能隨時到來,總不來一直調(diào)用一個函數(shù)然后原地等待數(shù)據(jù)到來吧,如果數(shù)據(jù)不來豈不是什么事都干不了了。所以接收是基于事件驅(qū)動的。有數(shù)據(jù)來再轉(zhuǎn)過去處理。
用個圖來解釋下:
如果還是覺得有點抽象,回到前面看看協(xié)議棧運作講解部分。應(yīng)該更能體會所謂的事件驅(qū)動。
原文地址:http://blog.chinaunix.net/uid-28852942-id-5335038.html
總結(jié)
以上是生活随笔為你收集整理的BLE-NRF51822教程3-sdk程序框架剖析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对于C++中多态的理解
- 下一篇: 51822模拟ble广播-实践