日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Nordic系列芯片讲解九 (BLE事件回调机制解析)

發(fā)布時間:2025/4/5 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Nordic系列芯片讲解九 (BLE事件回调机制解析) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

BLE事件回調(diào)機制解析

nRF5 SDK從版本14開始,對事件回調(diào)機制做了更新,引入了觀察者模式,以解耦不同BLE Layer對BLE事件的回調(diào)函數(shù)。

實現(xiàn)這套機制用到了Flash的段(Section),將RAM中的函數(shù)調(diào)用與Flash中的段操作結合到一起,這個想法很新穎。

本文嘗試理解和追蹤整個回調(diào)過程,并寫一段代碼驗證我們的思路。

一、觀察者模式簡介

面向對象編程世界里有許多著名的設計模式,其中一種叫觀察者模式,它解決的問題是:在某場景下對象之間存在一對多的依賴關系,當中心對象的狀態(tài)發(fā)生改變,其他所有依賴于它的對象都能得到通知并自動更新。

觀察者模式中有幾種角色:觀察者(Observer),主題(Subject)和發(fā)布者(Publisher)。

多個觀察者可以獨立的訂閱(Subscribe)一個主題,當該主題收到發(fā)布者推送的數(shù)據(jù),將數(shù)據(jù)通知(Notify)給各個觀察者進行后續(xù)處理。

實現(xiàn)觀察者模式,觀察者端需要實現(xiàn)一個訂閱功能,將自己的句柄和回調(diào)函數(shù)傳遞給主題。主機端應該有一個列表,所有訂閱它的觀察者句柄和回調(diào)函數(shù)都保存在該列表中,當需要通知時,則遍歷列表中的各個句柄,分別執(zhí)行各自的回調(diào)函數(shù)。發(fā)布者給主題發(fā)數(shù)據(jù),則簡單的暴露一個接口即可。

更進一步的,在代碼中要將句柄和回調(diào)函數(shù)封裝成一個結構體,這樣就可以方便傳參。觀察者準備一個函數(shù),將該結構體保存到一個列表中,這個函數(shù)稱為訂閱。主題準備一個函數(shù),讀取該列表,遍歷獲得各個結構體,并執(zhí)行回調(diào),這個函數(shù)稱為通知。

設計這個列表是關鍵。


最簡單的辦法是準備一個內(nèi)存數(shù)組,訂閱函數(shù)即寫數(shù)組,通知函數(shù)即讀數(shù)組。

nRF5 SDK選擇了另一種方式,使用Flash的段。

二、Flash段簡介

本文使用SEGGER Embedded Studio開發(fā)工具進行介紹,它后端使用arm gcc編譯器,需要用到它的鏈接文件(.ld)和map文件(.map)。

Flash的段是指在Flash中指定一塊空間,包含首地址和空間長度,并設定一個段名。段名以點(.)開頭。

C語言開發(fā)中經(jīng)常提及的段有代碼段.text,常量段.rodata等。

利用__attribute__關鍵字,可以為變量指定段名,以下代碼將變量my_var存放在段.my_section中:

static my_type_t my_var __attribute__ ((section(".my_section"))) __attribute__((used)) = {.handler = handler,.p_context = NULL };

?

還需要在內(nèi)存布局文件中,設定好該段的起始地址和長度。編譯SES工程,將生成鏈接文件(.ld),可以看到諸如以下代碼:

.sdh_ble_observers ALIGN(__pwr_mgmt_data_end__ , 4) : AT(ALIGN(__pwr_mgmt_data_end__ , 4)) { __sdh_ble_observers_start__ = .; __start_sdh_ble_observers = __sdh_ble_observers_start__; KEEP(*(SORT(.sdh_ble_observers*))) } __sdh_ble_observers_end__ = __sdh_ble_observers_start__ + SIZEOF(.sdh_ble_observers); __sdh_ble_observers_size__ = SIZEOF(.sdh_ble_observers);

其中xx_start__表示起始地址, xx_size__表示長度,xx_end__表示結束地址。

注意到一個關鍵行:KEEP((SORT(.sdh_ble_observers)))。

該行使用了通配符,.sdh_ble_observers*末尾的星號表示任意字符,所以我們可能在代碼中看到形如.sdh_ble_observers1這種段名。SORT表示將這些通配符所匹配的段按名稱增序排列。

查看map文件,可以看到如下記錄:

.sdh_ble_observers0x0000000000030ae4 0x300x0000000000030ae4 __sdh_ble_observers_start__ = .0x0000000000030ae4 __start_sdh_ble_observers = __sdh_ble_observers_start__*(SORT_BY_NAME(.sdh_ble_observers*)).sdh_ble_observers00x0000000000030ae4 0x8 Output/ble_app_blinky_pca10040_s132 Debug/Obj/ble_conn_state.o.sdh_ble_observers10x0000000000030aec 0x8 Output/ble_app_blinky_pca10040_s132 Debug/Obj/main.o.sdh_ble_observers10x0000000000030af4 0x8 Output/ble_app_blinky_pca10040_s132 Debug/Obj/ble_conn_params.o.sdh_ble_observers20x0000000000030afc 0x10 Output/ble_app_blinky_pca10040_s132 Debug/Obj/main.o

在map文件中,我們看到了多個名字相似的段.sdh_ble_observers[0, 1, 2],它們擺列在一起,Flash地址前后相接,并且它們長度之和等于.sdh_ble_observers段長度。

可以認為.sdh_ble_observers*是 .sdh_ble_observers的子段。

如何獲取段內(nèi)的數(shù)據(jù)呢?

一個是直接調(diào)用變量名,比如上面的my_var,另一種是通過段名,索引出其中的子段內(nèi)容。SDK中提供了段操作的函數(shù)庫nrf_section_iter, 如果已知一個段名,可以利用以下代碼獲取其中的子段內(nèi)容:

nrf_section_iter_t iter; for (nrf_section_iter_init(&iter, &my_section);nrf_section_iter_get(&iter) != NULL;nrf_section_iter_next(&iter)) {my_type_t * p_section;p_section = (my_type_t *)nrf_section_iter_get(&iter); }

三、BLE事件回調(diào)

以SDK15.1/ble_app_blinky工程為例, 追蹤它的BLE回調(diào)事件的調(diào)用邏輯。

在main.c –> ble_stack_init()中,調(diào)用了:

NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);

其中ble_evt_handler是我們設定的BLE事件回調(diào)函數(shù)。

NRF_SDH_BLE_OBSERVER是一個異常復雜嵌套宏,經(jīng)過層層解剖,該代碼變成如下形式:

static nrf_sdh_ble_evt_observer_t m_ble_observer __attribute__ ((section(".sdh_ble_observers3"))) __attribute__((used)) = {.handler =ble_evt_handler,.p_context = NULL };

這個代碼在.sdh_ble_observers3段中定義一個結構體變量,并且將回調(diào)函數(shù)設定為參數(shù)。

那ble_evt_handler()是在什么地方調(diào)用的呢?

找到nrf_sdh_ble.c -> nrf_sdh_ble_evts_poll(),看見關鍵代碼:

nrf_section_iter_t iter; for (nrf_section_iter_init(&iter, &sdh_ble_observers);nrf_section_iter_get(&iter) != NULL;nrf_section_iter_next(&iter)) {nrf_sdh_ble_evt_observer_t * p_observer;nrf_sdh_ble_evt_handler_t handler;p_observer = (nrf_sdh_ble_evt_observer_t *)nrf_section_iter_get(&iter);handler = p_observer->handler;handler(p_ble_evt, p_observer->p_context); }

這正是我們上面分析的,通過段名來獲取所有的子段內(nèi)容,然后執(zhí)行其回調(diào)函數(shù)。

仍然在該文件中,進一步找到關鍵代碼:

NRF_SDH_STACK_OBSERVER(m_nrf_sdh_ble_evts_poll, NRF_SDH_BLE_STACK_OBSERVER_PRIO) = {.handler = nrf_sdh_ble_evts_poll,.p_context = NULL, };

與上面類似,這是個嵌套宏,經(jīng)過層層解剖,得到如下代碼:

static nrf_sdh_stack_observer_t m_nrf_sdh_ble_evts_poll __attribute__ ((section(".sdh_stack_observers2"))) __attribute__((used)) = {.handler =nrf_sdh_ble_evts_poll,.p_context = NULL };

那 nrf_sdh_ble_evts_poll()是在什么地方調(diào)用的呢?

找到nrf_sdh.c -> nrf_sdh_evts_poll(),看見關鍵代碼:

for (nrf_section_iter_init(&iter, &sdh_stack_observers);nrf_section_iter_get(&iter) != NULL;nrf_section_iter_next(&iter)) {nrf_sdh_stack_observer_t * p_observer;nrf_sdh_stack_evt_handler_t handler;p_observer = (nrf_sdh_stack_observer_t *) nrf_section_iter_get(&iter);handler = p_observer->handler;handler(p_observer->p_context); }

進一步,看到該函數(shù)的調(diào)用地點:

void SD_EVT_IRQHandler(void) {nrf_sdh_evts_poll(); }

SD_EVT_IRQHandler是BLE事件的中斷處理函數(shù),一旦芯片產(chǎn)生BLE事件,都會進入到這個中斷處理函數(shù)中。按照上面的追蹤思路反向推導,就能夠調(diào)用到最初的ble_evt_handler回調(diào)函數(shù)。

至此我們搞清楚了BLE事件回調(diào)的跳轉邏輯,整個調(diào)用過程如下:

SD_EVT_IRQHandler()? ? ? ? ? ? ? ? ?//SoftDevice Event IRQ handler. Used for both BLE events and SoC events. The default?interrupt priority for this handler is set to 6
?? ? nrf_sdh_evts_poll()? ? ? ? ? ? ? ? ?//Function for polling stack events(include BLE events and SoC events) from the SoftDevice.The events are passed to the?application using the registered event handlers.
?? ??? ?nrf_sdh_ble_evts_poll()? ? ? ?//Function for polling BLE events.? nrf_sdh_soc_evts_poll(),Function for polling SoC events.
?? ??? ??? ?ble_evt_handler()? ? ? ? ? ? //我們定義的BLE事件處理函數(shù)

四、幾處細節(jié)

(1)SD_EVT_IRQHandler 是什么

它是BLE事件中斷。

經(jīng)過多次重定義跳轉,我們找到它最初的名字: SWI2_EGU2_IRQHandler。

在ses_startup_nrf52.s文件中,看出它是一個中斷向量:

/* External Interrupts */.word POWER_CLOCK_IRQHandler.word RADIO_IRQHandler.word UARTE0_UART0_IRQHandler // .....word COMP_LPCOMP_IRQHandler.word SWI0_EGU0_IRQHandler.word SWI1_EGU1_IRQHandler.word SWI2_EGU2_IRQHandler.word SWI3_EGU3_IRQHandler

為什么它就代表了BLE的事件中斷呢?

在芯片手冊的Memory章節(jié),找到Instantiation小節(jié),列出了全部的中斷向量地址:
Instantiation Table

比較這個列表與上面的中斷向量定義,發(fā)現(xiàn)它們是一一對應,嚴格按順序排列的。所以排到SWI2_EGU2_IRQHandler所在的位置,就代表了SWI2和EGU2的中斷向量,無論它取什么名字。

注意,SWI2和EGU2使用了同樣的向量地址,所以它們共享一個中斷向量,于是向量名稱寫成SWI2_EGU2_IRQHandler。
(2)為什么要索引兩次

在nrf_sdh_evts_poll函數(shù)中,調(diào)用了 nrf_sdh_ble_evts_poll(),然后再調(diào)用我們的ble_evt_handler,為什么要索引兩次呢?

仔細看代碼發(fā)現(xiàn),nrf_sdh_evts_poll處理了BLE和SOC兩種事件。而ble_evt_handler只是BLE事件。

這是因為SWI2和EGU2這二者共享一個中斷向量,它們除了給出BLE事件中斷,還會給出SOC相關的中斷,比如時鐘Clock等。
(3)APP_BLE_OBSERVER_PRIO是什么

它代表了優(yōu)先級。

前面提到.ld文件中使用了SORT對所有子段進行增序排列,優(yōu)先級數(shù)值小的排前面,大的排后面,在索引子段內(nèi)容時候,總是先執(zhí)行高優(yōu)先級(數(shù)值小)的回調(diào)函數(shù),后執(zhí)行低優(yōu)先級(數(shù)值大)的回調(diào)函數(shù),相同優(yōu)先級的回調(diào)則不能確定執(zhí)行順序。
(4)觀察者角色

在上面的分析中,NRF_SDH_BLE_OBSERVER意味著訂閱函數(shù),main.c中的BLE處理相當于一個觀察者。

SDK中將訂閱函數(shù)進一步封裝成BLE_XXX_DEF()的宏形式,比如GATT的訂閱函數(shù)宏:

NRF_BLE_GATT_DEF(_name)

許多BLE庫都提供了訂閱函數(shù)宏,使用時候只需在main.c中聲明它們。

BLE通用訂閱函數(shù)宏:

#define BLE_ADVERTISING_DEF(_name) #define BLE_DB_DISCOVERY_DEF(_name) #define BLE_LINK_CTX_MANAGER_DEF() #define NRF_BLE_SCAN_DEF(_name) #define NRF_BLE_GATT_DEF(_name) #define NRF_BLE_QWR_DEF(_name)

BLE Profile訂閱函數(shù)宏:

#define BLE_BAS_DEF(_name) #define BLE_BPS_DEF(_name) #define BLE_CSCS_DEF(_name) #define BLE_GLS_DEF(_name) #define BLE_HIDS_DEF() #define BLE_HRS_DEF(_name) #define BLE_HTS_DEF(_name) #define BLE_LBS_DEF(_name)

如果我們創(chuàng)建一個自定義的Profile,也應該提供一個這樣的訂閱函數(shù)宏。

nrf_sdh_ble_evts_poll和nrf_sdh_evts_poll相當于通知函數(shù),nrf_sdh.c和nrf_sdh_ble.c充當主題角色。

發(fā)布者是芯片, SD_EVT_IRQHandler中斷就是發(fā)布者向主題推送數(shù)據(jù)接口。

五、驗證

嘗試寫一段代碼,驗證這種段操作的觀察者模式。

先定義一個段:syq_sections

typedef void (*syq_handler_t)(uint8_t const evt_code, void * p_context);typedef struct {syq_handler_t handler; //!< BLE event handler.void * p_context; //!< A parameter to the event handler. } const syq_type_t;NRF_SECTION_SET_DEF(syq_sections, syq_type_t, NRF_SDH_BLE_OBSERVER_PRIO_LEVELS);

設定三個不同優(yōu)先級的段變量:

void syq_handler1(uint8_t const evt_code, void * p_context) {NRF_LOG_INFO("handler1 is triggered"); }static syq_type_t m_syq_1 __attribute__ ((section(".syq_sections1"))) __attribute__((used)) = {.handler = syq_handler1,.p_context = NULL };void syq_handler2(uint8_t const evt_code, void * p_context) {NRF_LOG_INFO("handler2 is triggered"); }static syq_type_t m_syq_2 __attribute__ ((section(".syq_sections2"))) __attribute__((used)) = {.handler = syq_handler2,.p_context = NULL };void syq_handler3(uint8_t const evt_code, void * p_context) {NRF_LOG_INFO("handler3 is triggered"); }static syq_type_t m_syq_3 __attribute__ ((section(".syq_sections3"))) __attribute__((used)) = {.handler = syq_handler3,.p_context = NULL };

在主函數(shù)中執(zhí)行索引:

nrf_section_iter_t iter; for (nrf_section_iter_init(&iter, &syq_sections);nrf_section_iter_get(&iter) != NULL;nrf_section_iter_next(&iter)) {syq_type_t * p_observer;syq_handler_t handler;p_observer = (syq_type_t *)nrf_section_iter_get(&iter);handler = p_observer->handler;handler(1, p_observer->p_context); }

這樣就可以依次執(zhí)行三個不同優(yōu)先級的回調(diào)函數(shù),打印結果如下:

利用這套做法,實現(xiàn)了一個簡單的觀察者模式。

?

https://blog.csdn.net/wulazula/article/details/88738921

總結

以上是生活随笔為你收集整理的Nordic系列芯片讲解九 (BLE事件回调机制解析)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。