一,前言

 

    半导体生产商NORDIC SEMICONDUCTOR为低功耗蓝牙芯片NRF51提供了开发工具包NRF51-SDK,该SDK中包含了与型号为NRF51的蓝牙芯片提供了大量的例程代码,同时也包含了几种不同的蓝牙协议栈(SOFTDEVICE)。但这些蓝牙协议栈是以二进制形式提供的(以Intel HEX文本格式),没有源代码。在工作中遇到扫描低功耗蓝牙广播包的需求,要求只在BLE三个广播信道中的一个来实现扫描功能。而SOFTDEVICE在扫描BLE广播时不会固定在一个广播信道上扫描,且早期版本的SOFTDEVCE未提供单信道扫描的接口(以后可能会有),那么要实现这个功能,必须使用非常规的手段。因此在这里记录一下这里用到的“非常规”方法,以便日后遇到此类问题时可以参考——但在工作中不推荐使用此类方法。

 

二,实现方案

 

    根据NRF51芯片手册(nRF51_Series_Reference_manual v3.0)的信息,我们知道,芯片的射频模块有一个寄存器,用于设置当前接收或发送的蓝牙数据的频率:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_固件

 

 

 

    那么我们需要找到NORDIC的蓝牙协议栈中写该寄存器的二进制机器码,将其对应的SOFTDEVICE IHEX文件修改成我们需要设置的BLE蓝牙信道频率,然后重新烧写SOFTDEVICE即可。但也要注意其可操作性,我们要避免将扫描的蓝牙信道修改成固定的值,而是可以容易修改的,也可以动态地取消增加的强制修改信道的代码。

    我们使用的硬件设备是在https://ohtcom.taobao.com/购买的蓝牙开发板,其中搭载了一颗型号为NRF51822_QFAC的蓝牙芯片,有256KB的FLASH存储空间和32KB的RAM空间,使用的NRF51 SDK版本为nRF5_SDK_12.0.0_12f24da,官方下载地址为

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调试工具:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_02

    由上面的操作可知,地址为0x11da0的指今处会对寄存器进行操作,下面就需要进一步验证一下。

    2, 使用IDA反汇编工具加载SOFTDEVICE并对之反汇编,找到0x11da0处并确认此处的指今对蓝牙射频的频率寄存器执行写入操作:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_协议栈_03

    可以看到,是0x11DA2处的指令会写入射频频率寄存器。现在的问题来了:会不会有其它的地方也会对该寄存器操作呢?这样的想法不无道理,但经过多次继续执行,可以认为在SOFTDEVICE扫描BLE广播时,它只是在这里操作了频率寄存器。

    从上图也可以看到,在写入0x40001508的寄存器后面的三个指令,会操作另一个寄存器,这里我们暂时不要关心。之后回有一条BX LR的指令,该指令告诉我们这里是一个类似于C语言的函数调用返回。下面就找到调用该函数的返回地址:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_固件_04

 

    这样我们得到在地址为0x133b9的指令之前会调用这个函数:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_android实现搜索低功耗蓝牙_05

 

    这样,我们就进一步知道写入频率寄存器的函数地址了,为0x11d76,用IDA看一下此函数的大致执行流程:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_06

    从中基本上可以确定该函数只有一个参数。其中0x25/0x26/0x27分别为BLE广播信道的信道编号,该函数于其进行特殊处理后就开始写蓝牙射频频率寄存器了:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_固件_07

    到这一步,我们就需要考虑如何修改此SOFTDEVICE,让它能够配置频率寄存器以我们想要的值呢?修改这个函数吗?这似乎是一个方案,但我们要知道,这样的修改是比较死板的,没有灵活性和可扩展性。我们可以考虑修改其调用代码指令,指向我们新增的代码,修改一下函数参数后,我们自己再调用此函数。

    3, 我们知道SOFTDEVICE和自己编译的固件是独立的,而自己编译的固件需要烧写在NRF51芯片指定地址上,这样SOFTDEVICE才能正确地执行它。从下图可以看到,自己编译的固件nrf51822_xxac.hex的中断向量表确实在固定的地址:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_协议栈_08

 

     该地址为0x1b000。实际上,这个地址是在链接脚本中指定的。于是我们考虑在向量表的最后面加入一个函数地址,这个函数是在nrf51822_xxac固件中实现的,这就要求在SOFTDEVICE中注入一段代码,在调用SOFTDEVCE的操作频率寄存器函数之前调用我们自己的函数。

    4, 下图即为在SOFTDEVICE中注入的代码,可以看到该代码会读取NRF51822_XXAC固件的中断向量表0x30偏移处,调用之并调用之前反汇编得到的函数。

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_09

 

    之后需要对其编译、链接并写入到新的SOFTDEVICE IHEX文件中:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_10

 

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_11

 

    这样我们就得到了四行的IHEX形式的注入代码,代码中函数地址为0x1AFE0,编译之前的C代码需要注意,得到的机器码长度不能太长,否则就可能与我们自己编译的固件在FLASH中烧写的位置重合了。上图中我们只需中间的两行,复制到新的SOFTDEVICE中:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_协议栈_12

    5, 修改0x133b4处的四字节指令,使其调用我们注入的函数。这需要手动来计算,根据ARM官方文档DDI0419C_arm_architecture_v6m_reference_manual对BL指令编码的介绍:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_13

 

    经计算,我们要将0x133b4处的四个字节FE F7 DF FC修改为07 F0 14 FE,之后重新计算行尾的较验值,并保存。

    6, 之后我们在自己编译的固件中加入注入的函数:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_协议栈_14

    可以看到,我们在中断向量表之后加入了一个符号_injected_freq_code,其相应的C函数实现在右边。编译后重新烧写SOFTDEVICE和NRF51822_XXAC的固件,可以在串口上得到调试结果:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_协议栈_15

 

    可以看到,函数_injected_freq_code函数被调用了,由于我们在代码中没有调此函数,可以确定,它是由我们在SOFTDEVICE中增加的代码调用的,也就是说,是SOFTDEVICE调用的。

    7, 为进一步确认,我们修改蓝牙的广播通道频率对扫描过程没什么影响,只是改变扫描信道,可以将扫描的信道都改变一下,看是否仍能扫描到BLE的广播:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_固件_16

 

    由上图可以看到,确实仍然能够扫描到BLE的广播数据。

    8, 之后,修改函数_inject_freq_code()函数,固定在38通道上扫描,也可以扫描到BLE广播数据:

android实现搜索低功耗蓝牙 低功耗蓝牙扫描_寄存器_17

至此,我们就实现了基于NRF51 SDK的蓝牙协议栈的BLE广播单通道扫描的功能。

 

四,总结

    这是一个非常规的解决方案,没有得到具体的应用,但这仍有一定的意义,以后遇到类似的问题,在没有其他可选的方法时可以参考一下。