nrf51822杂乱笔记
一、nrf51822芯片和協(xié)議棧特性
51822芯片特色:
協(xié)議棧特性:
協(xié)議棧向APP發(fā)送信息是通過事件發(fā)動的!
APP向協(xié)議棧發(fā)送信息是通過API接口,而API接口通過SVC機制實現(xiàn)的。
協(xié)議棧如何發(fā)送數(shù)據(jù)給應(yīng)用程序:
????????當(dāng)有事件來臨,比如連接請求或者對端設(shè)備發(fā)來了數(shù)據(jù),協(xié)議棧內(nèi)部會進行處理,處理完就會置位SWI2中斷,中斷內(nèi)部就會調(diào)用事件分發(fā),發(fā)送一個事件,給了事件分發(fā)任務(wù)(dispatch),這個任務(wù)會發(fā)給每個服務(wù),每個服務(wù)會判斷這個事件是什么,如果是自己感興趣的就進行處理,比如連接請求是每個服務(wù)都感興趣,服務(wù)可能就會保存連接句柄,因為連接句柄是后續(xù)處理都要用的。
????????(if部分一般是不通的,沒有使能調(diào)度部分。)
????????這個中斷的觸發(fā)在協(xié)議棧內(nèi)部,我們看不到,比如協(xié)議棧收到發(fā)給自己的數(shù)據(jù)要進行處理,就會包裝一個事件,然后觸發(fā)這個SWI2中斷函數(shù)!
????????intern_softdevice_events_execute函數(shù)中就會間接調(diào)用ble_evt_dispatch函數(shù),把這個事件交給后者處理 ,后者就把事件發(fā)給各個服務(wù)。
????????(最開始的ble_stack_init協(xié)議棧初始化函數(shù)中通過函數(shù)softdevice_ble_evt_handler_set(ble_evt_dispatch),賦值給全局變量m_ble_evt_handler = ble_evt_dispatch;)
static void ble_evt_dispatch(ble_evt_t * p_ble_evt) {dm_ble_evt_handler(p_ble_evt);ble_conn_params_on_ble_evt(p_ble_evt);bsp_btn_ble_on_ble_evt(p_ble_evt);on_ble_evt(p_ble_evt);ble_advertising_on_ble_evt(p_ble_evt);/*YOUR_JOB add calls to _on_ble_evt functions from each service your application is usingble_xxs_on_ble_evt(&m_xxs, p_ble_evt);ble_yys_on_ble_evt(&m_yys, p_ble_evt);*/ }應(yīng)用程序如何發(fā)數(shù)據(jù)給協(xié)議棧:
在服務(wù)中通過調(diào)用API即可。
比如調(diào)用數(shù)據(jù)發(fā)送函數(shù),然后就是協(xié)議棧的事情了,我們也看不到代碼實現(xiàn)了。大致過程如下:
?????????number就相當(dāng)于ID號一樣。加入我們調(diào)用的數(shù)據(jù)發(fā)送功能函數(shù)的ID是3,就相當(dāng)于調(diào)用了_SVC(3),然后就會“陷入”協(xié)議棧中的SVC中斷函數(shù)。
??????? 上圖中,下半部是協(xié)議棧的代碼,上半部是我們自己寫的程序代碼,分別有一個中斷向量表。但是實際上硬件只認一個,所以應(yīng)用程序要緊挨著協(xié)議棧存儲,協(xié)議棧即可找到應(yīng)用程序中設(shè)定的“中斷向量表”,需要的時候跳轉(zhuǎn)就可。(協(xié)議棧中會有一個指針指向燒寫協(xié)議棧后的最末端的地址。)
????????實際上,當(dāng)真正的外部一切中斷到來時,都是到達協(xié)議棧內(nèi)部的中斷向量表中,找到對應(yīng)的中斷處理函數(shù),此函數(shù)會找到APP中斷向量表的偏移,最終定位到用戶寫的handler中斷處理函數(shù)。
二、nrf51 sdk框架
必備的部分如下:
int main(void) {ble_stack_init();gap_params_init();advertising_init();services_init();//跟自己創(chuàng)建的服務(wù)相關(guān),不同的服務(wù)細節(jié)不同但大致建立過程一致,通常在官方的例子上修改一些參數(shù)即可。ble_advertising_start(BLE_ADV_MODE_FAST);// Enter main loop.for (;;){power_manage();//進入睡眠。 /*內(nèi)部僅僅調(diào)用了sd_app_evt_wait(),內(nèi)部其實就是調(diào)用一個arm m0的睡眠指令。*/} }????????由上可知,初始化之后就進入睡眠。那是怎么通訊的呢,可以推測,肯定是通過中斷、事件這種機制。有事件產(chǎn)生時就在事件處理函數(shù)中處理的。
2.1 協(xié)議棧如何運作?
????????“100%基于事件”!協(xié)議棧向app發(fā)送的任何數(shù)據(jù)都是基于事件。
????????開機運行初始化完后就進入低功耗睡眠了,直到底層有事件發(fā)生,都是協(xié)議棧內(nèi)部先捕獲事件并處理的!!
???????? 而 BLE_GATTS_EVT_WRITE 代表寫事件,那么結(jié)構(gòu)體中的數(shù)據(jù)就是對端設(shè)備(比如手機)寫給板子的數(shù)據(jù)。
ble_evt_dispatch函數(shù)是初始化過程中“自己”注冊的。其代碼是我們可以修改的。
協(xié)議棧內(nèi)部處理完數(shù)據(jù)打包好就會交給ble_evt_dispatch函數(shù)。
比如 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);//連接管理相關(guān)的處理函數(shù) ble_nus_on_ble_evt(&m_nus, p_ble_evt);//服務(wù)相關(guān)的,對手機讀寫數(shù)據(jù)感興趣 on_ble_evt(p_ble_evt); } ????????在任何與 BLE 相關(guān)的事件被協(xié)議棧上拋上來給 app 時,該函數(shù)就會被調(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 。????????這個函數(shù)把事件交給各個模塊(實際就是各個事件處理函數(shù),內(nèi)部就是各種case事件)后,處理完后,最終還是會到這個函數(shù)這里,再次進入睡眠。形成一個循環(huán)的流程。
2.2 哪里添加服務(wù)?
????????在 main 函數(shù)的初始化過程中有一個 services_init();這個函數(shù)的內(nèi)部就是添加服務(wù),添加特征 值等代碼。 ??????? 以串口實驗的demo為例,函數(shù)內(nèi)部其實就是注冊了一回調(diào)函數(shù) nus_data_handler(該函數(shù)會在手機發(fā)數(shù)據(jù)給板子時將數(shù)據(jù)從電腦串口打印出來) 然后再執(zhí)行真正的初始化函數(shù) ble_nus_init。(這些都不是固定的,都是自己隨便寫的。參考template的demo,結(jié)構(gòu)體什么的都是自己這個應(yīng)用想怎么寫就怎么寫。51822sdk要求的是下面這兩個函數(shù)。) ????????該函數(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, 修改一下讀/寫屬性,多添加一個特征值等。要修改的其實很少。 ????????難點在于自己改sd_ble_gatts_characteristic_add ()函數(shù)時里面有很多參數(shù)需要自己設(shè)置。(其實很多綁定安全連接等參數(shù)我們初學(xué)使用中不改也行,需要的時候?qū)φ帐謨愿?#xff09;。2.3 主機發(fā)送來的數(shù)據(jù)在哪里?如何發(fā)送數(shù)據(jù)給手機?
問:手機發(fā)給 51822 設(shè)備的數(shù)據(jù),用哪個API函數(shù)取出來?(這體現(xiàn)了另一種協(xié)議棧設(shè)計方式) 答:沒有函數(shù)。協(xié)議棧會拋上來一個事件結(jié)構(gòu)體,收到的數(shù)據(jù)在事件結(jié)構(gòu)體中。 依舊以SDK中自帶的ble_app_uart這個demo為例: ble_evt_dispatch(ble_evt_t * p_ble_evt)中有?ble_nus_on_ble_evt(&m_nus, p_ble_evt); 后者內(nèi)部“捕獲”三個事件:switch (p_ble_evt->header.evt_id) case BLE_GAP_EVT_CONNECTED 、BLE_GAP_EVT_DISCONNECTED、BLE_GATTS_EVT_WRITE(寫事件!)。 typedef struct {ble_evt_hdr_t header; /**< Event header. */union{ble_common_evt_t common_evt; /**< Common Event, evt_id in BLE_EVT_* series. */ble_gap_evt_t gap_evt; /**< GAP originated event, evt_id in BLE_GAP_EVT_* series. */ble_l2cap_evt_t l2cap_evt; /**< L2CAP originated event, evt_id in BLE_L2CAP_EVT* series. */ble_gattc_evt_t gattc_evt; /**< GATT client originated event, evt_id in BLE_GATTC_EVT* series. */ble_gatts_evt_t gatts_evt; /**< GATT server originated event, evt_id in BLE_GATTS_EVT* series. */} evt; } ble_evt_t;typedef struct {uint16_t conn_handle; /**< Connection Handle on which the event occurred. */union{ble_gatts_evt_write_t write; /**主機寫數(shù)據(jù)過來的結(jié)構(gòu)體!!*/ble_gatts_evt_rw_authorize_request_t authorize_request; /**< Read or Write Authorize Request Parameters. */ble_gatts_evt_sys_attr_missing_t sys_attr_missing; /**< System attributes missing. */ble_gatts_evt_hvc_t hvc; /**< Handle Value Confirmation Event Parameters. */ble_gatts_evt_timeout_t timeout; /**< Timeout Event. */} params; /**< Event Parameters. */ } ble_gatts_evt_t;typedef struct {uint16_t handle; /**< Attribute Handle.句柄!! */ble_uuid_t uuid; /**< Attribute UUID. */uint8_t op; /**< Type of write operation, see @ref BLE_GATTS_OPS. */uint8_t auth_required; /**< Writing operation deferred due to authorization requirement. Application may use @ref sd_ble_gatts_value_set to finalise the writing operation. */uint16_t offset; /**< Offset for the write operation. */uint16_t len; /**< Length of the received data. */uint8_t data[1]; /**< 數(shù)據(jù)!!Received data. @note This is a variable length array. The size of 1 indicated is only a placeholder for compilation.See @ref sd_ble_evt_get for more information on how to use event structures with variable length array members. */ } ble_gatts_evt_write_t; ????? 補充:? sd_ble_gatts_hvx()這個函數(shù)是藍牙的發(fā)送函數(shù)。可以通過參數(shù)來設(shè)置是以通知方式發(fā)送還是指示方式發(fā)送(通知不需要回復(fù)確認,指示需要)。 ??????? 為啥又發(fā)送的函數(shù),不弄個接受的函數(shù)呢???????? 因為發(fā)送是“同步的”,我們代碼知道什么時候發(fā)。可是不知道什么時候收數(shù)據(jù),如果調(diào)用函數(shù),豈不是一直等?這是異步的過程,底層實現(xiàn)只能通過異步事件通知的方式。
三、main函數(shù)中初始化詳解
https://blog.csdn.net/chen3522061/article/details/118265541
四、main函數(shù)進入休眠之后呢?
????????整個初始化完成后,設(shè)備便進入睡眠模式,每當(dāng)廣播間隔到期會發(fā)送一次廣播。即工作狀態(tài)為:睡眠+廣播。直到有設(shè)備發(fā)來連接請求,當(dāng)設(shè)備連接上手機后邊繼續(xù)處于睡眠狀態(tài)等待”事件”的發(fā)生。老版本的串口透傳的例子為例
電腦串口->51822->手機
????????main 函數(shù)的串口初始化程序 uart_init 的最后打開了串口的接收中斷。那么這個方向的數(shù)據(jù)流的起點就是在串口中斷中收到電腦上發(fā)來的數(shù)據(jù)為起點,通過無線發(fā)給手機。 (重點是API:sd_ble_gatts_hvx函數(shù)) void UART0_IRQHandler(void)//就在主文件的main函數(shù)的上面 {static uint8_t data_array[BLE_NUS_MAX_DATA_LEN];static uint8_t index = 0;uint32_t err_code; uint8_t temp; //取得電腦串口發(fā)過來的數(shù)據(jù)data_array[index] = simple_uart_get();index++; //判斷串口發(fā)送給來的數(shù)據(jù)是否達到 20 的字節(jié),或者是不是發(fā)送了字母’q’。如果滿足 //調(diào)用發(fā)送函數(shù)將收到的串口數(shù)據(jù)發(fā)送給手機。否則不發(fā)送等待知道滿足條件。// (這里通常新手說手機收不到數(shù)據(jù)的原因,因為沒輸入達到 20 個字節(jié))if ((data_array[index - 1] == 'q') || (index >= (BLE_NUS_MAX_DATA_LEN - 1))){err_code = ble_nus_send_string(&m_nus, data_array, index + 1); //這個模樣像API?然而是這個項目自己寫的,封裝了一下,其實內(nèi)部起作用的是sd_ble_gatts_hvx!!//發(fā)送了數(shù)據(jù)后清零數(shù)組下標。以繼續(xù)緩存后續(xù)的串口數(shù)據(jù)。index = 0;} }uint32_t ble_nus_send_string(ble_nus_t * p_nus, uint8_t * string, uint16_t length) {ble_gatts_hvx_params_t hvx_params;//這里是檢測參數(shù)是否正確。是否是已經(jīng)連接上了手機 (只有連接后,conn_handle 才會// 被賦值為有效值),檢查手機是否使能了開發(fā)板的通知,因為開發(fā)板作為服務(wù)端向手機 //發(fā)送數(shù)據(jù)時通過通知或指示兩種方式,這兩種方式都需要手機先使能開發(fā)板。if((p_nus->conn_handle==BLE_CONN_HANDLE_INVALID)||(!p_nus->is_notification_enabled)){return NRF_ERROR_INVALID_STATE;} //一次發(fā)送的長度不能超過限定值,通常為 20if (length > BLE_NUS_MAX_DATA_LEN){return NRF_ERROR_INVALID_PARAM;}memset(&hvx_params, 0, sizeof(hvx_params)); //以為是通過 Rx 這個參數(shù)來發(fā)送數(shù)據(jù)給手機的,所以句柄要填 rx 的句柄 //這個句柄是在上面的服務(wù)初始化函數(shù)中的添加特征值函數(shù)調(diào)用完畢后或得的(最后一 // 個參數(shù)為返回的句柄) //然后就是賦值要發(fā)送的數(shù)據(jù),并且設(shè)置為 notify 方式hvx_params.handle = p_nus->rx_handles.value_handle;hvx_params.p_data = string;hvx_params.p_len = &length;hvx_params.type = BLE_GATT_HVX_NOTIFICATION; //發(fā)送函數(shù)return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params); } ????????調(diào)用 sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);發(fā)送數(shù)據(jù)給手機的時候,第二 個參數(shù)是上面賦值的,那第一個參數(shù)這個連接句柄是怎么回事?在哪里設(shè)置過他? ????????連接句柄你可以看做是信道標志一樣(實際數(shù)據(jù)接入地址),每兩個連接的設(shè)備都會具有這個 連接句柄。他們后續(xù)的通信都是通過這個連接句柄來進行(可以理解是信道標志,兩個設(shè) 的通信標志必須一樣,這代表他們是在同樣的信道上通信才能正確進行通信) ????????上面說過,板子整個初始化流程走完后就是睡眠和廣播等待手機連接。那么這個conn_handle 就一定是手機發(fā)來連接,板子中協(xié)議棧處理完后上拋給 app 的連接事件中賦值的。從而記錄下后續(xù)板子和手機通信的”信道”。 在 程序框架剖析 那一講中介紹過,協(xié)議棧拋上來的事件結(jié)構(gòu)體最終是由 dispatch 這個派發(fā)程序發(fā)給再發(fā)給各個服務(wù)的事件處理函數(shù)和模塊的事件處理函數(shù)的。 static void ble_evt_dispatch(ble_evt_t * p_ble_evt) { //將事件交給連接管理模塊的事件處理函數(shù) ble_conn_params_on_ble_evt(p_ble_evt); //將事件交給 uart 服務(wù)的事件處理函數(shù) ble_nus_on_ble_evt(&m_nus, p_ble_evt); //處理一些一般的事件on_ble_evt(p_ble_evt); } 再進入 uart 服務(wù)的事件處理函數(shù)中看下發(fā)生連接時是如何記下 后續(xù)通信所用的連接句柄 的:疑問:那注冊服務(wù)時的&p_nus->service_handle是什么呢?????????????
手機->51822手機電腦串口->51822->
sdk11版本的串口透傳的例子為例
總結(jié)
以上是生活随笔為你收集整理的nrf51822杂乱笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NoSQL书籍大全
- 下一篇: Cpython源码分析03(*)_简要总