打开主菜单

谷雨文档中心 β

NRF52832DK-Mesh组网实验

Jinx讨论 | 贡献2020年3月31日 (二) 10:20的版本 Node Address

蓝牙Mesh和BLE是并行发展的一个独立分支,虽然底层共用Physical Layer和Link Layer,但是上层协议并不相同。因此,nRF52832的Mesh例程,并不在BLE协议栈sdk中,而是安装一个扩展包。

本文介绍蓝牙Mesh相关的环境搭建和实验介绍。

目录

1 Mesh开发环境

1.1 蓝牙Mesh SDK

SDK位置:归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip,注意文件名中的Mesh字样。

和BLE协议栈一样,将该压缩包直接解压到BLE协议栈同级目录即可使用,解压后的SDK路径如下:E:\project-nordic\nRF5_SDK_Mesh_v310

1.2 集成开发软件SES

SES时是Segger Embedded Studio编译器的缩写,用来编译蓝牙Mesh代码,SES功能与IAR或Keil类似。

备注:Nordic为何使用SES而不是IAR,原因不得而知。

1.2.1 SES安装

SES软件位置:归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe

我们双击打开安装界面,按照如下动图所示方式进行安装(可以全部默认安装)。

1.2.2 SES许可证激活

我们双击打开安装好的SES,选择编译自带测试例程,此时弹出需要激活的界面。

由于我们准备使用SES编译nordic的NRF52832的SDK,所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权,所以是免费的许可证。

我们按照要求填写好信息,主要是邮箱地址(用于接收SEGGER公司发给我们的许可证),填写完成之后点击Request License。下面是我们接收的邮件信息,我们将红色框内的内容复制下来。

打开SES,选择Tools\License Manager...,我们选择Activate Embedded Studio,打开如下界面,并将我们的许可证粘贴进去,然后点击Install License。

安装好许可证之后,跳转到如下的界面。

此时我们的许可证激活完毕(有时候需要等待一段时间,才会显示激活成功)。

2 Light_switch

2.1 实验简介

此示例演示了包含充当两个角色的设备的网状生态系统:配置角色(Provisioner role)和节点角色(Node role、provisionee role)。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。

该示例由三个较小的示例组成:

Light switch server:一个实现了[Generic OnOff server model]的简约服务器,用于接收状态数据并控制板上LED 1的状态。

Light switch client:一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时,OnOff Set消息将发送到配置的目标地址。

Mesh Provisioner:一个简单的静态配置设备应用,用于建立演示网络,该配置设备在一个网状网络中配置所有节点。 此外,配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置,以使它们能够相互通信。

[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意,当服务器设置了发布地址(如本例所示)时,服务器会将其状态更改的任何操作披露到其发布地址。

下图给出了将由静态供应商设置的网状网络的整体视图, 括号中的数字表示预配置程序分配给这些节点的地址。

灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载(PB-ADV)和GATT承载(PB-GATT)进行配置,并且还支持网状代理服务器(Proxy Service),但是不支持代理客户端(Proxy Client)。

2.2 硬件说明

完成这个实验,我们最少需要两个开发板硬件用做我们的节点设备(Node):

  • 一个开发板用做client
  • 一个或者多个开发板用做server

此外,我们还需要以下之一作为我们的配置设备(Provisioner):

  • 如果您决定使用静态预配器示例,则多准备一个开发板
  • 如果您决定使用app程序进行设置,则需要安装手机app@link_nrf_mesh_app(@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android)。
默认我们使用3个开发板完成我们的实验,3个开发板分别对应client、server以及provisioner三个类型。

2.3 软件说明

1、我们使用NrfGo上位机,依次对三个开发板进行擦除及softdevice的烧写

2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`<InstallFolder>/examples/light_switch`路径下的3个实验,并完成编译

●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject

●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject

●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject

3、此时我们对3个开发板分别进行固件的烧写,分别烧写client、server、provisioner(建议大家给3个开发板贴上标签,方便后续区分)

2.4 实验现象

注意:保证我们的3个设备都是刚按照软件说明部分的步骤配置完成,并且没有进行过任何硬件控制(按键操作)。目的是保证所有设备均未被我们人为配置过,没有进行过网络配置,否则可能有任意异常(不同网络配置,导致的不同现象)

1、首先我们分别给client和provisioner供电,然后我们按下provisioner的按键S1:

●provisoner:LED灯点亮,代表正在配置查找设备,配置组网

●client:首先LED3和LED4闪烁,代表正在组网;LED1-LED4四个灯一起闪烁,代表组网完成

我们如果打开RTT检测log打印,可以看到如下的信息,这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息(上),client信息(下)。

2、使用provisioner配置好client之后,我们给server开发板上电,这个时候provisioner将会配置server入网:

●provisoner:LED1点亮代表正在配置网络,LED2点亮代表网络配置完成

●server:首先LED3和LED4闪烁,代表正在组网;LED1-LED4四个灯一起闪烁,代表组网完成

我们打开RTT检测log打印,可以看到server已经配置入网,并且被分配了地址Node Address为0x0104。provisioner信息(上),server信息(下)。

3、此时整个网络的配置已经完成,这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。

由于我们的server被provisioner分配的Node Address是0x0104(偶数),所以我们通过client的S3控制server的LED1点亮,通过client的S4控制server的LED1熄灭。

client设备对于server设备的控制,通过Node Address的奇偶位不同,分成了两个组。

client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。

client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。

我们打开RTT检测log打印,可以看到client分别按下button2(S3)以及button3(S4),分别会设置server的GPIO输出高低电平。client信息(上),server信息(下)。

4、至此我们的mesh组网的lightswitch实验测试完成。

2.5 源码详解

3 Beaconing example

3.1 实验简介

3.2 硬件说明

3.3 软件说明

3.4 实验现象

3.5 源码详解

4 Coexistence example

4.1 实验简介

4.2 硬件说明

4.3 软件说明

4.3.1 将Mesh集成到nRF5 SDK示例中

用于网格的nRF5 SDK与Nordic的nRF5 SDK兼容。 这使您可以将nRF5 SDK中的资源包括在现有的Mesh项目中,也可以将nRF5 SDK中的Mesh功能包括在nRF5 SDK示例中。

请参阅构建网格堆栈和示例,以获取有关如何下载和安装nRF5 SDK的信息。 查看共存示例,以了解如何将nRF5 SDK功能与用于网格的nRF5 SDK同时使用。

4.3.1.1 动态记忆

在将nRF5 SDK功能与nRF5 Mesh SDK功能一起使用时(如并存示例所示),您可能会遇到以下情况:应用程序注意事项可能需要更改动态内存的分配方式和可分配的动态内存量。 网格堆栈使用网格内存管理器界面进行动态内存分配。 默认的后端mesh_mem_stdlib.c使用标准库malloc(),该库需要定义足够大的堆大小。 可以通过用另一个内存管理器替换后端来更改此行为。

如果使用Segger Embedded Studio构建应用程序,请在“项目选项”>“代码”>“运行时内存区域”设置中将“堆大小”设置为8192字节。

4.3.1.2 并发的SoftDevice和Mesh活动

同时运行SoftDevice和Mesh时,最大的性能问题通常来自无线电时间争用。虽然SoftDevice通常在预定的短脉冲中运行,但Mesh会尝试尽可能多地使用无线电。只要SoftDevice没有无线电活动,Mesh就会连续扫描和通告。 SoftDevice活动将减少Mesh广播的时间,并且为了保持一致的Mesh性能,必须在不影响用户体验的情况下尽可能保守地设置SoftDevice无线电参数。

在使用SoftDevice进行广告时,请尝试使用您的使用情况可以容许的最大广告间隔。如果可能,请在不需要时关闭SoftDevice广告商,仅在您希望收到连接请求时才激活它。如果您只需要发送不可连接的,不可扫描的广告(例如,针对第三方信标协议),请使用Mesh Advertiser API,因为它与Mesh一起使用时可以进行最少的上下文切换。

当SoftDevice在连接中运行时:

尝试协商应用程序可以允许的最大连接间隔。如果需要通过连接的高吞吐量,则在每个连接事件中发送更多的数据要比减少连接间隔更好,因为大部分开销来自上下文切换。

如果网状设备在其SoftDevice连接中充当外围设备(从设备),则还可以增加“从设备等待时间”,这应使SoftDevice跳过连接事件,而不会增加通过链接的任何传出数据传输的等待时间。

与广告一样,建议仅在需要时才保持连接活动。空闲的软设备连接对网状网性能的影响几乎与具有大量流量的连接一样多,特别是在从属延迟较低的情况下。

基于SoftDevice的扫描对所有SoftDevice活动的Mesh性能影响最大。当SoftDevice正在扫描时,Mesh无法接收数据包,因此每个SoftDevice扫描窗口都会替换Mesh扫描。仅在尝试建立连接或需要活动扫描时才应使用SoftDevice扫描。如果需要常规的被动BLE扫描(用于侦听信标或其他第三方活动),请通过使用nrf_mesh_rx_cb_set函数设置RX回调来挂接到Mesh扫描器。如果您的应用程序需要主动扫描或需要启动连接,则应尽可能保守地设置扫描参数。较长的扫描间隔和较短的扫描窗口将使Mesh拥有更多的时间进行自身的无线电活动。类似地,建立上下文时在短时间内执行连续扫描可能比执行长时间运行的占空比扫描有利,因为上下文切换会导致很多不必要的开销。最后,强烈建议为SoftDevice连接启动调用设置超时,以避免长时间的空闲扫描。

由于Mesh无法主动阻止SoftDevice无线电活动,因此减少同时运行SoftDevice活动的设备上的Mesh活动不会直接影响SoftDevice的性能。但是,附近的其他Mesh设备会干扰广告渠道中的SoftDevice活动,这可能会使连接启动花费更长的时间。

通常,Mesh必须在SoftDevice活动之间执行其所有无线电操作,因此,如果设备在执行SoftDevice无线电操作时正在发送大量Mesh数据包,则它将在此上花费大部分Mesh时间,而不是接收传入的数据。为了解决这个问题,请根据可用的无线电时间调整输出数据包的数量,以减少Mesh数据包的发送。如果可能,请通过在CORE_TX_ROLE_RELAY角色上调用mesh_opt_core_adv_set来暂停耗时的SoftDevice操作期间的Mesh数据包中继。

4.3.1.3 在用于网格示例的nRF5 SDK中包括nRF5 SDK

根据您的工具链:

使用Segger Embedded Studio时,请添加代码文件并包括相应SES项目文件的路径。

使用CMake构建用于网格堆栈的nRF5 SDK时,添加代码文件并包括指向相应CMakeLists.txt文件的路径。 SDK_ROOT根符号用于引用nRF5 SDK安装文件夹(例如,请参阅Light Switch服务器示例中的CMakeLists.txt)。

网格示例项目已经在其include /目录中包含sdk_config.h文件。 这些文件是默认SDK配置文件的副本,并且网格示例所需的所有更改都包含在示例目录中的include / app_config.h文件中。

注意,

必须先在SDK配置文件中显式启用某些SDK功能,然后才能使用它们。 有关详细信息,请参见SDK文档页面SDK配置头文件。

4.3.1.4 在nRF5 SDK示例中包括用于网格功能的nRF5 SDK

在nRF5 SDK示例的项目文件中包括来自nRF5 SDK for Mesh的以下源文件:

  • All C files in mesh/core/src
  • All C files in mesh/bearer/src
  • All C files in mesh/prov/src except nrf_mesh_prov_bearer_gatt.c
  • All C files in mesh/access/src
  • All C files in mesh/dfu/src
  • All C files in mesh/stack/src
  • models/foundation/config/src/config_server.c
  • models/foundation/config/src/composition_data.c
  • models/foundation/config/src/packed_index_list.c
  • models/foundation/health/src/health_server.c
  • Any other mesh models that are used in your application
  • external/micro-ecc/uECC.c
  • examples/common/src/assertion_handler_weak.c
  • examples/common/src/mesh_provisionee.c

注意

如果不需要各种网格特征(例如DFU),则可以从项目文件中省略相应的文件。但是,在其位置添加examples / nrf_mesh_weak.c以提供缺少的API函数的存根。

将以下文件夹添加到nRF5 SDK示例的项目包含路径:

  • mesh/core/api
  • mesh/core/include
  • mesh/bearer/api
  • mesh/bearer/include
  • mesh/prov/api
  • mesh/prov/include
  • mesh/access/api
  • mesh/access/include
  • mesh/dfu/api
  • mesh/dfu/include
  • mesh/stack/api
  • models/foundation/config/include
  • models/foundation/health/include
  • Path to include folder of any other mesh models that are used in your application
  • external/micro-ecc
  • examples/common/include
  • Path to any other resources in the mesh examples that are used in your application

将以下预处理器符号添加到nRF5 SDK示例的项目文件中:

  • NRF52_SERIES
  • NRF_MESH_LOG_ENABLE=NRF_LOG_USES_RTT (because logging in the mesh stack relies on RTT)
  • CONFIG_APP_IN_CORE
4.3.1.5 可选变更

此外,您可能需要应用以下一项或多项更改:

如果与nRF5 SDK集成在一起,则可能需要更新在网格堆栈中使用simple_hal模块的示例以使用Nordic nRF5 SDK bsp模块。可以同时使用两者,但是在这种情况下,必须从其中之一删除GPIOTE_IRQHandler,并且只有一个模块可以注册回调函数。

如果原始的Nordic nRF5 SDK示例使用了SoftDevice,请确保在启用SoftDevice之后初始化并启用了网格堆栈。在这种情况下,必须将SoftDevice事件转发到网格堆栈。将以下代码添加到您的应用程序:

#include“ nrf_sdh_soc.h”

#定义MESH_SOC_OBSERVER_PRIO 0

静态void mesh_soc_evt_handler(uint32_t evt_id,void * p_context)

{

    nrf_mesh_on_sd_evt(evt_id);

}

NRF_SDH_SOC_OBSERVER(m_mesh_soc_observer,MESH_SOC_OBSERVER_PRIO,mesh_soc_evt_handler,NULL);

如果您有多个SOC观察者,请确保一次仅从其中一个观察者将SOC观察者事件转发到网格堆栈。

默认情况下,在网格堆栈以及某些Nordic nRF5 SDK应用程序中启用网络配置的闪存存储。用于此目的的闪光区域可能会重叠并导致错误。为了使闪存存储模块Flash管理器与网状堆栈中的闪存管理器以及Nordic nRF5 SDK中的闪存存储模块安全地共存,请在nrf_mesh_config_app.h中添加以下代码块:

#include“ fds.h”

#include“ fds_internal_defs.h”

#define FLASH_MANAGER_RECOVERY_PAGE_OFFSET_PAGES FDS_PHY_PAGES

如果要添加自己的网格功能,而不是使用现有的网格示例,则还需要添加文件nrf_mesh_config_app.h。将其从网格堆栈存储库中的examples / templates文件夹复制到您的项目文件夹中,并删除文件顶部的#error消息。对文件内容进行其他适当的更改,例如将ACCESS_ELEMENT_COUNT和ACCESS_MODEL_COUNT调整为所需数量的元素和模型。

4.3.1.6 Flash放置项目文件

Segger Embedded Studio项目旁边都有一个flash_placement.xml文件,该文件充当链接器的输入。在flash_placement.xml文件中,nRF5 SDK配置了一组ProgramSection列表,用于放置某些变量。除了nRF5 SDK组件所需的所有ProgramSections外,网格还需要另外两个部分,即nrf_mesh_flash和nrf_mesh_ram。

必须将其他与网格相关的部分添加到flash_placement.xml文件中:

将以下行添加到标记为<MemorySegment name =“ FLASH” ...>的内存段中:

<ProgramSection alignment =“ 4” keep =“是” load =“是” name =“。nrf_mesh_flash”输入节=“ *(SORT(.nrf_mesh_flash。*))”“ address_symbol =” __ start_nrf_mesh_flash“ end_symbol =” __ stop_nrf_mesh_flash“ />

将以下行添加到标记为<MemorySegment name =“ RAM” ...>的内存段中:

<ProgramSection alignment =“ 4” keep =“是” load =“否” name =“。nrf_mesh_ram” inputsections =“ *(SORT(.nrf_mesh_ram。*))”“ address_symbol =” __ start_nrf_mesh_ram“ end_symbol =” __ stop_nrf_mesh_ram“ />

注意

对于共存示例已经进行了此更改。在编辑任何现有nRF5 SDK示例的Flash放置文件时,可以在这些示例中使用flash_placement.xml文件作为参考。

4.4 实验现象

4.5 源码详解

5 Coexistence Uart example

5.1 实验简介

5.2 硬件说明

5.3 软件说明

5.3.1 SDK UART共存示例

本示例演示了如何同时使用适用于Mesh的nRF5 SDK和nRF5 SDK示例。它围绕两个示例构建,分为两个部分:

此示例的网格部分从nRF5 SDK for Mesh实现了灯开关客户端示例。

该示例的BLE部分实现了nRF5 SDK的ble_app_uart示例,但以下更改除外:

  • BSP事件SLEEP,DISCONNECT和WHITELIST_OFF被忽略。这使得板子按钮仅控制应用程序的网格部分
  • 增加广告间隔以为网格堆栈留出更多时间
  • 可以通过发送要模拟的灯光开关客户端按钮编号,通过BLE UART来控制灯光开关服务器

运行此示例的结果是,您将能够使用网状网络,该示例中的网状网络可以代替电灯开关客户端示例。

在开始测试此共存示例之前,请参阅以下页面:

将Mesh集成到nRF5 SDK示例中

灯光开关示例和灯光开关客户端详细信息以及Mesh API

5.3.1.1 测试示例

将ble_app_uart_coexist文件夹复制到nRF5 SDK安装路径下的examples / ble_peripheral文件夹中。

在ble_app_uart_coexist / pca10040 / s132 / ses / ble_app_uart_pca10040_s132.emProject中打开Segger Embedded Studio项目。

将MESH_ROOT添加到您的Segger Embedded Studio全局宏列表:

在SES菜单栏中,单击工具>选项...。

在左列中,单击建筑物。

在右列中,双击“全局宏”。

在新行中添加网格根目录:MESH_ROOT = <网格安装路径>。

如灯光开关示例中所述对灯光开关示例设备进行编程,但以下情况除外:

用examples / ble_peripheral / ble_app_uart_coexist / pca10040 / s132 / ses / Output / Release / Exe / ble_app_uart_pca10040_s132.hex替换电灯开关客户端。

现在,您可以并行或顺序运行两个并存的示例:

按照电灯开关示例中的说明运行电灯开关示例。

按照nRF5 SDK文档中的描述运行ble_app_uart示例。

编写与UART RX特性中的数字之一(1、2、3或4)等效的十六进制ASCII码,以模拟按钮按下。

5.4 实验现象

5.5 源码详解

6 mesh工程文件简介

分组 文件 说明
Access 访问层:

访问层定义高层应用程序如何使用上层传输层。 它定义了应用程序数据的格式; 它定义并控制在上层传输层执行的应用程序数据加密和解密; 并检查是否已在正确的网络和应用程序密钥的上下文中接收到传入的应用程序数据,然后再将其转发到更高层。

access
access_loopback
access_publish
access_publish_retransmissior
access_reliable
device_state_manager
Application
app_error_weak
app_onoff
assertion_handler_weak
ble_softdevice_support
main
mesh_adv
mesh_app_utils
mesh_provisionee
nrf_mesh_weak
rtt_input
sdk_config
simple_hal

7 ***************************************正式整理章节**************************************

8 mesh实验学习流程概论

1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”,这个是很重要的一个mesh基本概念的介绍,只有看完这个文档,我们才能明白mesh组网是什么,是怎么完成的,后续的我们实验内容,也会在这个文档中查到相应的对照。

2、按照Light_switch测试实验的实验说明,跑通测试流程(可以固件清零重新烧写多测试几遍,有助于增强我们的流程熟练度,利于后面的开发)。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。

3、根据我们下面拆分的组网流程,对照依次的实验说明,进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备,配置者设备功能我们使用手机app完成,目的是减少大家的学习难度,配置者设备我们会放在最后一个实验给大家做介绍。

①广播(通信TX)——了解节点设备的广播功能,分为三个重要部分:PB-ADV和Unprov Beacon,PB-GATT的广播,以及模型的Publish用于发送数据。

②扫描(通信RX)——了解节点设备的扫描功能,分为两个重要部分:普通的BLE SCAN功能,以及模块的Subscription用于接收数据。

③入网验证——了解节点设备接入网络的验证过程,实验中使用的是PB-GATT去配置的,包含了:交换公共密钥,完成验证(是否有带外OOB)。

④设备信息分配——启动配置数据分配,给节点分配如下数据:网络key(Netkey)、应用key(Appkey)以及节点设备地址(nodeAddr)。

⑤元素——了解节点设备的元素以及模型获取以及index的分配。

⑥模型——了解模型的功能和创建方法,模型的订阅subscription和发布publish功能。

9 Light_switch测试实验

9.1 实验简介

此示例演示了包含充当两个角色的设备的网状生态系统:配置角色(Provisioner role)和节点角色(Node role、provisionee role)。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。

该示例由三个较小的示例组成:

Light switch server:一个实现了[Generic OnOff server model]的简约服务器,用于接收状态数据并控制板上LED 1的状态。

Light switch client:一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时,OnOff Set消息将发送到配置的目标地址。

Mesh Provisioner:一个简单的静态配置设备应用,用于建立演示网络,该配置设备在一个网状网络中配置所有节点。 此外,配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置,以使它们能够相互通信。

[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意,当服务器设置了发布地址(如本例所示)时,服务器会将其状态更改的任何操作披露到其发布地址。

下图给出了将由静态供应商设置的网状网络的整体视图, 括号中的数字表示预配置程序分配给这些节点的地址。

灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载(PB-ADV)和GATT承载(PB-GATT)进行配置,并且还支持网状代理服务器(Proxy Service),但是不支持代理客户端(Proxy Client)。

9.2 硬件说明

完成这个实验,我们最少需要两个开发板硬件用做我们的节点设备(Node):

  • 一个开发板用做client
  • 一个或者多个开发板用做server

此外,我们还需要以下之一作为我们的配置设备(Provisioner):

  • 如果您决定使用静态预配器示例,则多准备一个开发板
  • 如果您决定使用app程序进行设置,则需要安装手机app@link_nrf_mesh_app(@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android)。
默认我们使用3个开发板完成我们的实验,3个开发板分别对应client、server以及provisioner三个类型。

9.3 软件说明

1、我们使用NrfGo上位机,依次对三个开发板进行擦除及softdevice的烧写

2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`<InstallFolder>/examples/light_switch`路径下的3个实验,并完成编译

●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject

●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject

●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject

3、此时我们对3个开发板分别进行固件的烧写,分别烧写client、server、provisioner(建议大家给3个开发板贴上标签,方便后续区分)

9.4 实验现象

注意:保证我们的3个设备都是刚按照软件说明部分的步骤配置完成,并且没有进行过任何硬件控制(按键操作)。目的是保证所有设备均未被我们人为配置过,没有进行过网络配置,否则可能有任意异常(不同网络配置,导致的不同现象)

1、首先我们分别给client和provisioner供电,然后我们按下provisioner的按键S1:

●provisoner:LED灯点亮,代表正在配置查找设备,配置组网

●client:首先LED3和LED4闪烁,代表正在组网;LED1-LED4四个灯一起闪烁,代表组网完成

我们如果打开RTT检测log打印,可以看到如下的信息,这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息(上),client信息(下)。

2、使用provisioner配置好client之后,我们给server开发板上电,这个时候provisioner将会配置server入网:

●provisoner:LED1点亮代表正在配置网络,LED2点亮代表网络配置完成

●server:首先LED3和LED4闪烁,代表正在组网;LED1-LED4四个灯一起闪烁,代表组网完成

我们打开RTT检测log打印,可以看到server已经配置入网,并且被分配了地址Node Address为0x0104。provisioner信息(上),server信息(下)。

3、此时整个网络的配置已经完成,这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。

由于我们的server被provisioner分配的Node Address是0x0104(偶数),所以我们通过client的S3控制server的LED1点亮,通过client的S4控制server的LED1熄灭。

client设备对于server设备的控制,通过Node Address的奇偶位不同,分成了两个组。

client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。

client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。

我们打开RTT检测log打印,可以看到client分别按下button2(S3)以及button3(S4),分别会设置server的GPIO输出高低电平。client信息(上),server信息(下)。

4、至此我们的mesh组网的lightswitch实验测试完成。

10 ①广播(通信TX)

有关广播部分,我们需要给大家讲解三个方面,分别如下:

1、PB-ADV,这个是mesh中的广播承载层,用于我们启动配置过程(暂时的例程和手机app都是用的PB-GATT来配置的,但是源码中PB-ADV的流程是包含的)。

2、Unprov Beacon,这个是广播一个未被配置的beacon,就是一个特殊形式的广播数据包,用于声明自己是未经配置的设备。

3、PB-GATT,这个是mesh中的GATT承载层,用于我们启动配置过程(当前手机app就是使用PB-GATT,也就是连接之后,通过GATT服务去发送配置信息)。

4、Publish,这个是我们用于模型发布数据的部分,我们将需要发送的数据通过advData广播出去。

10.1 PB-ADV

PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。

我们来看下PB-ADV整个的流程,首先在mian文件中,我们的start()函数下,我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。

我们继续追踪mesh_provisionee_prov_start(),可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下,可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。

在nrf_mesh_prov_bearer_adv_interface_get()函数中,我们可以看到prov_bearer_interface_t结构体,这个结构体下面提供了我们配置承载层需要的API函数。

10.2 Unprov Beacon

Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。

上面我们有说到PB-ADV是承载层用于配置节点的功能,而Unprov Beacon则是我们的节点设备用于声明自己未经配置,所以这两个其实是一脉相承的。

紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数,其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数,在这个结构体当中有一个.listen_start = prov_bearer_adv_listen,是用于开始监听传入的配置链接的功能,在这个函数中我们可以看到send_unprov_beacon()函数。

send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数,在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包,然后调用advertiser_interval_set函数去设置广播的间隔,最后调用advertiser_packet_send函数去将我们的数据包发送出去。

默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。

最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。

这里我们再最后看一下prov_beacon_unprov_build()函数,这个函数是生成我们的Unprov Beacon的数据包。

其中标识位是0x2B,这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV(未配置的节点设备),接下来的16个字节是device uuid(这个是寄存器FICR中的device id生成的),接下来2字节是oob info(这里是00 00,代表没有使能OOB),最后的4个字节是URI通过AES-CMAC算法计算出来的值。

10.3 PB-GATT

PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。

和PB-ADV一样的,我们从main文件中的start()函数,追踪到mesh_provisionee_prov_start(),可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下,可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。

首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数,这个是初始化并设置PB-GATT服务(通过Mesh GATT接口)的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务,可以看到其中的PB-GATT的服务以及特征值。

再往下的mesh_gatt_init()函数,这个就不给大家分析了,是和我们普通的BLE一样的方式去注册一个服务。

在nrf_mesh_prov_bearer_gatt_interface_get()函数中,我们可以看到prov_bearer_interface_t结构体,这个结构体下面提供了我们配置承载层需要的API函数(这个是和上方的PB-ADV是一样的)。

和Unprov Beacon类似的,当我们开始监听配置链接的时候,会触发.listen_start = listen_start_cb的回调,在这个回调中我们触发FSM(有限状态机)的事件E_LISTEN_START,然后调用link_evt_send()去post顺序承载事件。

然后我们来到状态机列表m_pb_gatt_fsm_transition_table[],可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作,执行的是a_listen_start函数功能(这其中涉及的代码逻辑,暂时先不说明,大家可以自己查看源码的引用)。

然后我们继续追踪a_listen_start(),可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS,调用mesh_adv_data_set函数设置了我们的广播数据内容,最后调用mesh_adv_start函数启动广播。

默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。

我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据,可以看到广播数据的flag是0x06,服务的UUID就是我们上面提到的PB-GATT服务的UUID,接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值,最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。

最后是我们的mesh_adv_start()函数去启动广播,可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。

10.4 Publish(模型发布)

这一章节我们只给大家讲解模块publish的数据发布流程,暂时不说明模型的创建方法,以及如何使用,这个放在下面单独的模型章节进行说明。

以generic_onoff_server模型为例,我们找到access_model_publish以及access_model_reply函数,这两个函数的功能类似的,都是用于我们用户的模型发送数据的。

其中access_model_publish是用于模型主动发送,而access_model_reply是用于模型给对应的Opcode回应数据。

这里我们以access_model_publish函数为主,来看下发布数据的代码流程。

查看一下access_model_publish()函数,我们可以看到packet_alloc_and_tx()函数,这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容,是我们定义的重传机制(mesh数据为了保证传输的成功率,默认都是会重传数据包的,默认重传次数是4次)。

然后我们继续看一下packet_alloc_and_tx()函数,在这个函数中,我们最终会调用packet_tx()函数将数据发送出去。

继续追踪packet_tx()函数,这个函数的内容比较多,我们只截取了其中数据发送的部分,也就是我们的nrf_mesh_packet_send()函数,我们在他的tx_params中赋值好我们需要发送出去的数据内容。

nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数,最终是调用upper_transport_tx()函数进行的数据发送。

我们再继续追踪upper_transport_tx()函数,可以看到我们会根据p_metadata->segmented来判断数据是否要分包发送。

接下里我们以不分包发送函数unsegmented_packet_tx()为例,来继续说明数据传输流程。

我们可以看到我们的unsegmented_packet_tx()函数中,最终是调用的network_packet_send()函数。(分包的segmented_packet_tx()函数最终也是调用的network_packet_send(),大家可以自己追踪代码看一下)

继续追踪我们的network_packet_send()函数,可以看到我们的net_packet_encrypt()加密函数,这个是将我们的明文数据,做好加密工作。 然后调用core_tx_packet_send()函数发送出去。

最后我们追踪到core_tx_packet_send()函数,可以看到调用的是p_bearer->p_interface->packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。

可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。

我们追踪core_tx_bearer_add()函数的引用路径,可以看到是在core_tx_adv_init()当中。(除了此处,还有另一处地方也有引用,功能是类似的,大家可以自行了解)。 我们看下参数m_interface,可以看到m_interface->packet_send功能,就是对应我们在core_tx_packet_send()函数调用的p_bearer->p_interface->packet_send()。

所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能,可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了,和上面的几个广播功能,最后调用的都是相同的广播包发送函数。

11 ②扫描(通信RX)

11.1 标准BLE SCAN

看过了我们的广播功能之后,我们来看下扫描的部分。在ble mesh中,我们的扫描功能scan,就是用于获取数据的,类似于数据通信的RX。

我们从main文件中的initialize()函数开始,首先是我们mesh_init()函数,最终我们会调用mesh_stack_init()函数。

在mesh_satck_init函数中,我们可以看到调用了nrf_mesh_init函数去初始化core的参数。

这一段是nrf_mesh_init函数部分,由于这个初始化函数太长,所以我们只截取了scan相关的部分。

首先是第一点scanner_init(scanner_packet_process_cb)函数,在这个函数中,我们回去初始化扫描的参数配置,并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据(获得所有的BLE广播数据)。

然后是第二点ad_listener_subscribe(&m_nrf_mesh_listener),这个是用来侦听订阅的消息的(从获得的所有BLE广播数据中,筛选出的订阅的消息)。

然后我们继续看一下scanner_init的扫描初始化函数,在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了:

1、扫描的通道SCANNER_CHANNELS_DEFAULT,也就是BLE的{37,38,39}三个channels都去扫描。

2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。

3、配置物理层协议是1Mbit模式。

4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS,扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS

5、射频的发射功率RADIO_POWER_NRF_0DBM。

6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。

最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描,这个大家可以自己查看一下代码,比较容易理解。

最后我们来看一下scanner_packet_process_cb()回调函数,其中我们可以直接拿到的扫描的数据包是rx_data,包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的,但是我们还需要再看下下面一部分的ad_listener的流程。

11.2 Subscription(模型订阅)

我们需要注意下ad_listener_process()函数,这个函数是将我们扫描到的订阅数据(不符合标准的BLE广播数据,则认为是侦听到的订阅数据)传递到订阅者进行处理(订阅者指的是带订阅功能的模型)。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。

然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅,一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系,大家有兴趣的可以继续查阅他的底层代码逻辑处理,一个是向list中添加订阅者信息,一个是将数据通过p_listener->handler()将数据传递给订阅者处理。

最终当我们扫描到订阅消息,并调用ad_listener_process()函数传递时,会触发我们注册的回调nrf_mesh_listen(调用ad_listener_subscribe(&m_nrf_mesh_listener)时,在m_nrf_mesh_listener中)。我们来看下nrf_mesh_listen函数中的处理,其中我们先从数据包中解析了数据的类型type,然后根据不同类型的数据进行相应的处理。

AD_TYPE_MESH 蓝牙网格物体的AD类型
AD_TYPE_BEACON 蓝牙网状信标的AD类型
AD_TYPE_PB_ADV PB-ADV消息的AD类型
AD_TYPE_DFU nRF OpenMesh消息的AD类型

12 ③入网验证

经过前两章的学习,我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解,也就是已经大概的明白了数据的发送和接收的流程。

那么从这一章节开始,我们将会给大家展示,如何配置一个节点设备入网。配置者设备,我们使用的是手机APP:nRF Mesh。

我们从main文件中的start()函数开始,来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能,我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的,有携带的是设备的信息的、有携带认证密码的,有回调返回的,所以我们会挨个介绍一下。

首先携带的是static_auth_data,这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候,选择使用Static OOB,那么当我们在进行PB-GATT的连接过程中,会弹出一个新的窗口,要求我们去输入这个静态的128bit的OOB密钥。

接下来携带的是provisioning_complete_cb()回调函数,这个函数处理的是节点设备成功的被配置入网,在这里我们可以获取到我们的节点设备被分配的node_Address。

然后携带是device_identification_start_cb()回调函数,这个回调函数是用于通知开发者,我们的节点设备当前已经开始配置。

再然后携带的是URI【统一资源标识符(Uniform Resource Identifier,URI)】,这个是用于标识我们的设备类型的,这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。

这里我们是light switch的server设备,所以我们定义URI是EX_URI_LS_SERVER。

分析完携带的参数之后,我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中,我们首先是定义了一下prov_caps,也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。

再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数,里面有结构体本身m_pro_ctx,需要配置的参数公钥m_public、私钥m_private,以及我们刚刚定义的oob功能prov_caps,最后还携带了一个事件回调函数prov_ect_handler。

接下来是PB-ADV以及PB-GATT的处理过程,这个前面已经讲解过,这里不再赘述。

最后我们调用provisionee_start()函数去开启配置。

我们继续来追踪一下provisionee_start()函数,在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。

然后在nrf_mesh_prov_listen()函数中,我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体,以及p_bearer、URI、oob_info_source等参数信息,继续向下传递。

最终我们来查看下prov_provisionee_listen()函数,在这个函数中我们传递了p_bearer->p_callbacks给m_prov_callbacks回调,然后调用p_bearer->p_interface->listen_start()函数去启动PB-ADV以及PB-GATT的侦听。

当我们在侦听之后,接收到任何配置相关的消息,都将通过m_prov_callbacks()函数回调传递给我们处理,大家可以自行看下其中4个不同的功能函数,具体处理的都是什么消息。

在prov_provisionee_listen()函数中,我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer->p_callbacks,我们一路向上查找,最终可以找到这个p_bearer->p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回,而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。

在这个回调函数中,我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。

那么除了上方已经存在的几个回调,我们可以看到mian文件中的start()函数下的prov_start_params参数,还缺少一个配置完成的回调prov_complete_cb()。

这个回调的返回方式比较特殊,和上方其他的都不同,是由SD(softdevice)直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER,返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED,这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的,这个大家可以自行查看一下,不太重要,因为都是nordic底层处理好的消息。

以上整个配置流程的处理,不太好分步展示相应的现象,这个大家可以自己使用SES在线仿真的方式去调试一下流程,这里我们直接给大家展示整个流程打印出的相应的信息,以及nRF Mesh中暂时出来的现象。

13 ④设备信息分配

当我们成功的配置节点设备入网之后,我们需要给节点设备主要分配如下几个信息,有关这几个要点的介绍,可以查看“BLE-Mesh技术揭秘”第3章节:

1、节点在网络中的地址Node Address

2、网络密钥Netkey

3、应用密钥Appkey

4、元素的地址Element Address

5、绑定model的Appkey,分配Publish Address,Subscription Address

13.1 Node Address

13.2 Netkey

13.3 Appkey

13.4 Element

13.5 model

13.5.1 model bind appkey

13.5.2 model publish address

13.5.3 model subscription address