NRF51-SDK的低功耗藍芽廣播包的單通道掃描實現
一,前言
半導體生產商NORDIC SEMICONDUCTOR為低功耗藍芽晶片NRF51提供了開發工具包NRF51-SDK,該SDK中包含了與型號為NRF51的藍芽晶片提供了大量的例程程式碼,同時也包含了幾種不同的藍芽協議棧(SOFTDEVICE)。但這些藍芽協議棧是以二進位制形式提供的(以Intel HEX文字格式),沒有原始碼。在工作中遇到掃描低功耗藍芽廣播包的需求,要求只在BLE三個廣播通道中的一個來實現掃描功能。而SOFTDEVICE在掃描BLE廣播時不會固定在一個廣播通道上掃描,且早期版本的SOFTDEVCE未提供單通道掃描的介面(以後可能會有),那麼要實現這個功能,必須使用非常規的手段。但在實際工作中,該功能實現後,卻被拋棄了,最終沒有被採納,可能是同事們考慮這樣的方案過於“暴力”。因此在這裡記錄一下這裡用到的“非常規”方法,以便日後遇到此類問題時可以參考——但在工作中不推薦使用此類方法。
二,實現方案
根據NRF51晶片手冊(nRF51_Series_Reference_manual v3.0)的資訊,我們知道,晶片的射頻模組有一個暫存器,用於設定當前接收或傳送的藍芽資料的頻率:
那麼我們需要找到NORDIC的藍芽協議棧中寫該暫存器的二進位制機器碼,將其對應的SOFTDEVICE IHEX檔案修改成我們需要設定的BLE藍芽通道頻率,然後重新燒寫SOFTDEVICE即可。但也要注意其可操作性,我們要避免將掃描的藍芽通道修改成固定的值,而是可以容易修改的,也可以動態地取消增加的強制修改通道的程式碼。
我們使用的硬體裝置是在https://ohtcom.taobao.com/
https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v12.x.x/nRF5_SDK_12.0.0_12f24da.zip
三,實現步驟
1, 找到寫入NRF51射頻模組的FREQUENCY暫存器的程式碼地址。上圖中暫存器地址為0x40001508。將SOFTDEVICE(s130_nrf51_2.0.1_softdevice.hex)和掃描的程式碼燒寫到NRF51晶片中,開啟SEGGER提供的GDB除錯工具,載入gdb除錯工具:
由上面的操作可知,地址為0x11da0的指今處會對暫存器進行操作,下面就需要進一步驗證一下。
2, 使用IDA反彙編工具載入SOFTDEVICE並對之反彙編,找到0x11da0處並確認此處的指今對藍芽射頻的頻率暫存器執行寫入操作:
可以看到,是0x11DA2處的指令會寫入射頻頻率暫存器。現在的問題來了:會不會有其它的地方也會對該暫存器操作呢?這樣的想法不無道理,但經過多次繼續執行,可以認為在SOFTDEVICE掃描BLE廣播時,它只是在這裡操作了頻率暫存器。
從上圖也可以看到,在寫入0x40001508的暫存器後面的三個指令,會操作另一個暫存器,這裡我們暫時不要關心。之後回有一條BX LR的指令,該指令告訴我們這裡是一個類似於C語言的函式呼叫返回。下面就找到呼叫該函式的返回地址:
這樣我們得到在地址為0x133b9的指令之前會呼叫這個函式:
這樣,我們就進一步知道寫入頻率暫存器的函式地址了,為0x11d76,用IDA看一下此函式的大致執行流程:
從中基本上可以確定該函式只有一個引數。其中0x25/0x26/0x27分別為BLE廣播通道的通道編號,該函式於其進行特殊處理後就開始寫藍芽射頻頻率暫存器了:
到這一步,我們就需要考慮如何修改此SOFTDEVICE,讓它能夠配置頻率暫存器以我們想要的值呢?修改這個函式嗎?這似乎是一個方案,但我們要知道,這樣的修改是比較死板的,沒有靈活性和可擴充套件性。我們可以考慮修改其呼叫程式碼指令,指向我們新增的程式碼,修改一下函式引數後,我們自己再呼叫此函式。
3, 我們知道SOFTDEVICE和自己編譯的韌體是獨立的,而自己編譯的韌體需要燒寫在NRF51晶片指定地址上,這樣SOFTDEVICE才能正確地執行它。從下圖可以看到,自己編譯的韌體nrf51822_xxac.hex的中斷向量表確實在固定的地址:
該地址為0x1b000。實際上,這個地址是在連結指令碼中指定的。於是我們考慮在向量表的最後面加入一個函式地址,這個函式是在nrf51822_xxac韌體中實現的,這就要求在SOFTDEVICE中注入一段程式碼,在呼叫SOFTDEVCE的操作頻率暫存器函式之前呼叫我們自己的函式。
4, 下圖即為在SOFTDEVICE中注入的程式碼,可以看到該程式碼會讀取NRF51822_XXAC韌體的中斷向量表0x30偏移處,呼叫之並呼叫之前反彙編得到的函式。
之後需要對其編譯、連結並寫入到新的SOFTDEVICE IHEX檔案中:
這樣我們就得到了四行的IHEX形式的注入程式碼,程式碼中函式地址為0x1AFE0,編譯之前的C程式碼需要注意,得到的機器碼長度不能太長,否則就可能與我們自己編譯的韌體在FLASH中燒寫的位置重合了。上圖中我們只需中間的兩行,複製到新的SOFTDEVICE中:
5, 修改0x133b4處的四位元組指令,使其呼叫我們注入的函式。這需要手動來計算,根據ARM官方文件DDI0419C_arm_architecture_v6m_reference_manual對BL指令編碼的介紹:
經計算,我們要將0x133b4處的四個位元組FE F7 DF FC修改為07 F0 14 FE,之後重新計算行尾的較驗值,並儲存。
6, 之後我們在自己編譯的韌體中加入注入的函式:可以看到,我們在中斷向量表之後加入了一個符號_injected_freq_code,其相應的C函式實現在右邊。編譯後重新燒寫SOFTDEVICE和NRF51822_XXAC的韌體,可以在串列埠上得到除錯結果:
可以看到,函式_injected_freq_code函式被呼叫了,由於我們在程式碼中沒有調此函式,可以確定,它是由我們在SOFTDEVICE中增加的程式碼呼叫的,也就是說,是SOFTDEVICE呼叫的。
7, 為進一步確認,我們修改藍芽的廣播通道頻率對掃描過程沒什麼影響,只是改變掃描通道,可以將掃描的通道都改變一下,看是否仍能掃描到BLE的廣播:由上圖可以看到,確實仍然能夠掃描到BLE的廣播資料。
8, 之後,修改函式_inject_freq_code()函式,固定在38通道上掃描,也可以掃描到BLE廣播資料:
至此,我們就實現了基於NRF51 SDK的藍芽協議棧的BLE廣播單通道掃描的功能。
四,總結
這是一個非常規的解決方案,雖然工作中該功能實現後很快就被放棄了,沒有得到具體的應用,而是採用了其他的替代性方案,但這仍有一定的意義,以後遇到類似的問題,在沒有其他可選的方法時可以參考一下。