<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
	<id>http://doc.iotxx.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ghostyu</id>
	<title>谷雨文档中心 - 用户贡献 [zh-cn]</title>
	<link rel="self" type="application/atom+xml" href="http://doc.iotxx.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ghostyu"/>
	<link rel="alternate" type="text/html" href="http://doc.iotxx.com/%E7%89%B9%E6%AE%8A:%E7%94%A8%E6%88%B7%E8%B4%A1%E7%8C%AE/Ghostyu"/>
	<updated>2026-04-09T11:38:31Z</updated>
	<subtitle>用户贡献</subtitle>
	<generator>MediaWiki 1.31.1</generator>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NB101%E7%A1%AC%E4%BB%B6%E8%AE%BE%E8%AE%A1%E6%89%8B%E5%86%8C&amp;diff=3100</id>
		<title>NB101硬件设计手册</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NB101%E7%A1%AC%E4%BB%B6%E8%AE%BE%E8%AE%A1%E6%89%8B%E5%86%8C&amp;diff=3100"/>
		<updated>2021-03-09T08:33:19Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 支持频段 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:NB101首图.jpg|缩略图|400x400像素]]&lt;br /&gt;
NB101是基于移远NB-IOT模块：BC95/BC35的小系统板，NB101板载了IPEX射频天线座，5V转3.3V LDO稳压电源，MicroSIM卡座，ESD防护电路，即插即用。用户只需注重上层应用，以最快的速度开发出基于NB-IOT应用的产品。&lt;br /&gt;
&lt;br /&gt;
NB101小系统板采用2.54标准间距排针，引出BC95模块最常使用的信号，整板尺寸仅23mm*24mm，便于嵌入到客户产品中。&lt;br /&gt;
&lt;br /&gt;
NB101采用了省电技术，在省电模式（PSM）下，低至40uA，其中BC95功耗5uA，LDO稳压芯片静态35uA。&lt;br /&gt;
&lt;br /&gt;
有关NB101的软件操作，请阅读【NB101软件手册】&amp;lt;ref group=&amp;quot;手册&amp;quot;&amp;gt;[[NB101软件设计手册]] 模块指令的使用流程&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==支持频段==&lt;br /&gt;
BC95-B5，单频850MHz，电信网络版本。&lt;br /&gt;
&lt;br /&gt;
BC95-B8，单频900MHz，移动联通版本。&lt;br /&gt;
&lt;br /&gt;
BC35-G，国内全网通版本。&lt;br /&gt;
&lt;br /&gt;
BC95-G，海外全网通版本&lt;br /&gt;
&lt;br /&gt;
全频段的模块上电后，会根据SIM卡网络类型自动搜索网络，无需手动设置频段。&lt;br /&gt;
&lt;br /&gt;
可使用指令&amp;lt;code&amp;gt;AT+NBAND?&amp;lt;/code&amp;gt;来查询当前工作频段。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+BC95支持的频段&lt;br /&gt;
!#&lt;br /&gt;
!B1&lt;br /&gt;
!B3&lt;br /&gt;
!B8&lt;br /&gt;
!B5&lt;br /&gt;
!B20&lt;br /&gt;
!B28&lt;br /&gt;
|-&lt;br /&gt;
|'''频率'''&lt;br /&gt;
|H-FDD&lt;br /&gt;
2011MHz&lt;br /&gt;
|H-FDD&lt;br /&gt;
1800MHz&lt;br /&gt;
|H-FDD&lt;br /&gt;
900MHz&lt;br /&gt;
|H-FDD&lt;br /&gt;
850MHz&lt;br /&gt;
|H-FDD&lt;br /&gt;
800MHz&lt;br /&gt;
|H-FDD&lt;br /&gt;
750MHz&lt;br /&gt;
|-&lt;br /&gt;
|'''运营商'''&lt;br /&gt;
'''或地区'''&lt;br /&gt;
| -&lt;br /&gt;
|中国联通&lt;br /&gt;
|中国移动&lt;br /&gt;
中国联通&lt;br /&gt;
&lt;br /&gt;
中国大陆&lt;br /&gt;
&lt;br /&gt;
台湾地区&lt;br /&gt;
|中国电信&lt;br /&gt;
|欧洲地区&lt;br /&gt;
|南美地区&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==主要性能==&lt;br /&gt;
{| class=&amp;quot;wikitable table-condensed&amp;quot;&lt;br /&gt;
!&lt;br /&gt;
!说明&lt;br /&gt;
|- scope=&amp;quot;row&amp;quot;&lt;br /&gt;
|'''供电'''&lt;br /&gt;
|VDD供电范围：3.6V~6V&lt;br /&gt;
|- scope=&amp;quot;row&amp;quot;&lt;br /&gt;
|'''省电'''&lt;br /&gt;
|电源指示灯消耗电流：2.5mA，若需要低功耗，请移除。&lt;br /&gt;
PSM模式最大耗流：40uA（BC95模块本身5uA，板载ldo降压芯片静态功耗35uA）&lt;br /&gt;
|-&lt;br /&gt;
|'''发射功率'''&lt;br /&gt;
|23dBm±2dB&lt;br /&gt;
|-&lt;br /&gt;
|'''温度范围'''&lt;br /&gt;
| -40℃~+85℃&lt;br /&gt;
|-&lt;br /&gt;
|'''SIM卡类型'''&lt;br /&gt;
|Micro卡槽，使用Micro中型卡。&lt;br /&gt;
|-&lt;br /&gt;
|'''串口'''&lt;br /&gt;
|'''主串口(2.54排针引出)：'''&lt;br /&gt;
AT指令和数据传输，波特率为9600bps&lt;br /&gt;
&lt;br /&gt;
也可用于软件升级，波特率115200bps&lt;br /&gt;
&lt;br /&gt;
'''调试串口(触点焊盘)：'''&lt;br /&gt;
&lt;br /&gt;
用于软件调试，波特率921600bps&lt;br /&gt;
|-&lt;br /&gt;
|'''网络协议'''&lt;br /&gt;
|UDP/CoAP&lt;br /&gt;
|-&lt;br /&gt;
|'''数据传输特性'''&lt;br /&gt;
|下行25.2kbps，上行15.625kbps&lt;br /&gt;
|-&lt;br /&gt;
|'''AT命令'''&lt;br /&gt;
|3GPP TS 27.007 V14.3.0 (2017-03) 定义的命令以及移远通信新增的AT命令&lt;br /&gt;
|-&lt;br /&gt;
|'''固件升级'''&lt;br /&gt;
|通过主串口或 DFOTA空中升级&lt;br /&gt;
|-&lt;br /&gt;
|'''RoHS'''&lt;br /&gt;
|所有器件完全符合 EU RoHS 标准&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 功能框图 ==&lt;br /&gt;
[[文件:功能框图-NB101.png|边框|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
==快速开始==&lt;br /&gt;
使用谷雨NB-QuickStarter快速测试NB101，NB-QuickStarter包含NB101核心板、串口转USB底板、收发指示灯、天线等外设，更多详情，请参考《NB-QuickStarter使用说明书》&amp;lt;ref group=&amp;quot;手册&amp;quot;&amp;gt;[[NB-QuickStarter使用说明书]] 开箱即用的NB评估板&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NB-QuickStarter实物照如下图所示：&lt;br /&gt;
[[文件:NB-QuickStarter.png|链接=undefined|边框|居中|无框|750x750像素]]&lt;br /&gt;
==应用接口==&lt;br /&gt;
NB101的应用接口如下图所示：&lt;br /&gt;
[[文件:NB101硬件特点.png|边框|居中|无框|800x800像素]]&lt;br /&gt;
&lt;br /&gt;
===引脚信号===&lt;br /&gt;
下图是NB101的CAD尺寸图和引脚信号说明。&lt;br /&gt;
[[文件:CAD-NB101.jpg|边框|居中|无框|750x750像素]]&lt;br /&gt;
{| class=&amp;quot;wikitable table-condensed&amp;quot;&lt;br /&gt;
!引脚号        &lt;br /&gt;
!引脚名       &lt;br /&gt;
!说明&lt;br /&gt;
|- scope=&amp;quot;row&amp;quot;&lt;br /&gt;
|1&lt;br /&gt;
|GND&lt;br /&gt;
|PCB板丝印为G，电源地&lt;br /&gt;
|- scope=&amp;quot;row&amp;quot;&lt;br /&gt;
|2&lt;br /&gt;
|VDD&lt;br /&gt;
|PCB板丝印为V，电源正极，电源范围：3.6V~6V，建议输入5V，且必须能够提供500mA的电流&lt;br /&gt;
|-&lt;br /&gt;
|3&lt;br /&gt;
|TXD&lt;br /&gt;
|模块UART发送引脚，接外部MCU的RX引脚，3.0V信号电平，与3.3V的单片机连接的话建议串联1K电阻。&lt;br /&gt;
|-&lt;br /&gt;
|4&lt;br /&gt;
|RXD&lt;br /&gt;
|模块UART接收引脚，接外部MCU的TX引脚，3.0V信号电平，与3.3V的单片机连接的话建议串联1K电阻。&lt;br /&gt;
|-&lt;br /&gt;
|5&lt;br /&gt;
|EN&lt;br /&gt;
|LDO稳压芯片使能引脚，NB101内部已默认上拉使能，拉低可关闭电源，不使用请悬空。&lt;br /&gt;
|-&lt;br /&gt;
|6&lt;br /&gt;
|RESET&lt;br /&gt;
|NB模块复位引脚，低电平复位，不使用请悬空。注意：'''不能'''与MCU的复位引脚直连，建议加三极管驱动后与MCU的GPIO口相连&lt;br /&gt;
|-&lt;br /&gt;
|7&lt;br /&gt;
|NET&lt;br /&gt;
|网络指示灯状态输出，高电平有效，NB101已板载一颗LED指示灯，但BC95模块尚未实现网络指示灯功能，预留备用。&lt;br /&gt;
|-&lt;br /&gt;
|8&lt;br /&gt;
|RI&lt;br /&gt;
|异步消息通知引脚，当模块有异步串口消息输出时（例如接收到新的数据，或者网络状态发生变化），NB101会拉低RI信号120ms，可用来唤醒MCU，准备接收NB模块的串口数据。&lt;br /&gt;
|}&lt;br /&gt;
另外，NB101还引出了BC95的DBG串口和ADC模拟采集引脚，在NB101背面以测试点的形式引出。&lt;br /&gt;
{| class=&amp;quot;wikitable table-condensed&amp;quot;&lt;br /&gt;
!引脚名&lt;br /&gt;
!说明&lt;br /&gt;
|- scope=&amp;quot;row&amp;quot;&lt;br /&gt;
|'''GND'''&lt;br /&gt;
|PCB板丝印为G，电源地&lt;br /&gt;
|- scope=&amp;quot;row&amp;quot;&lt;br /&gt;
|'''DRX'''&lt;br /&gt;
|BC95模块DBG UART接收引脚，接外部MCU的TX引脚，3.0V信号电平&lt;br /&gt;
|-&lt;br /&gt;
|'''DTX'''&lt;br /&gt;
|BC95模块DBG UART发送引脚，接外部MCU的RX引脚，3.0V信号电平&lt;br /&gt;
|-&lt;br /&gt;
|'''AD'''&lt;br /&gt;
|BC95模块ADC引脚，模拟电压转数字接口，电压范围：0V~4V。&lt;br /&gt;
&lt;br /&gt;
当前此功能未实现，请忽略。&lt;br /&gt;
|}&lt;br /&gt;
===工作模式===&lt;br /&gt;
模块工作模式如下图所示：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!功能状态&lt;br /&gt;
!描述&lt;br /&gt;
|-&lt;br /&gt;
|Active状态&lt;br /&gt;
|模块处于唤醒状态；所有功能正常可用，可以进行数据发送和接收；模块在此模式下可切换到 Idle 模式或 PSM 模式。&lt;br /&gt;
|-&lt;br /&gt;
|Idle状态&lt;br /&gt;
|模块处于轻休眠状态，网络处于 DRX/eDRX 状态，可接收寻呼消息。模块在此模式下可切换至 Active 或 PSM 模式。&lt;br /&gt;
|-&lt;br /&gt;
|PSM状态&lt;br /&gt;
|模块处于深睡眠状态，内部只有 RTC 工作，网络处于非连接状态。模块在此模式下可切换至 Active 模式。&lt;br /&gt;
|}&lt;br /&gt;
===供电电源===&lt;br /&gt;
NB101使用稳定的5V电压供电，电气要求如下表格：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!引脚名&lt;br /&gt;
!引脚号&lt;br /&gt;
!描述&lt;br /&gt;
!最小值&lt;br /&gt;
!典型值&lt;br /&gt;
!最大值&lt;br /&gt;
!单位&lt;br /&gt;
|-&lt;br /&gt;
|VDD&lt;br /&gt;
|2&lt;br /&gt;
|稳定的5V电源输入，至少能提供500mA续流能力&lt;br /&gt;
|3.6&lt;br /&gt;
|5&lt;br /&gt;
|6&lt;br /&gt;
|V&lt;br /&gt;
|-&lt;br /&gt;
|GND&lt;br /&gt;
|1&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot; |电源地&lt;br /&gt;
|}&lt;br /&gt;
===指示灯===&lt;br /&gt;
NB101板载两颗指示灯，红色指示灯指示电源状态，绿色指示灯指示网络状态。&lt;br /&gt;
&lt;br /&gt;
===串口===&lt;br /&gt;
NB101的主串口可用于 AT 命令传送和数据传输，此时其波特率为 9600bps。&lt;br /&gt;
&lt;br /&gt;
它还可用于固件升级，用于固件升级时的波特率为 115200bps。主串口在 Active 模式，Idle 模式和 PSM 模式下均可工作。&lt;br /&gt;
&lt;br /&gt;
NB101的调试串口可配合调试工具 UEMonitor来查看底层日志信息，进行软件调试，其默认波特率为921600bps&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!接口&lt;br /&gt;
!引脚名&lt;br /&gt;
!引脚号&lt;br /&gt;
!描述&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |主串口&lt;br /&gt;
|TXD&lt;br /&gt;
|3&lt;br /&gt;
|主串口发送引脚&lt;br /&gt;
|-&lt;br /&gt;
|RXD&lt;br /&gt;
|4&lt;br /&gt;
|主串口接收引脚&lt;br /&gt;
|-&lt;br /&gt;
| rowspan=&amp;quot;2&amp;quot; |调试串口&lt;br /&gt;
|TXD_DBG&lt;br /&gt;
|背面焊点&lt;br /&gt;
|调试串口发送引脚&lt;br /&gt;
|-&lt;br /&gt;
|RXD_DBG&lt;br /&gt;
|背面焊点&lt;br /&gt;
|调试串口接收引脚&lt;br /&gt;
|-&lt;br /&gt;
|振铃信号&lt;br /&gt;
|RI&lt;br /&gt;
|8&lt;br /&gt;
|串口URC输出（例如接收到新的数据，或者网络状态发生变化）时，NB101会拉低RI信号120ms。&lt;br /&gt;
|}&lt;br /&gt;
[[文件:串口连接示意图.png|链接=undefined|边框|居中|无框|750x750像素]]&lt;br /&gt;
===SIM卡槽===&lt;br /&gt;
NB101采用自弹式MicroSIM卡槽，'''注意SIM卡插入方向为：卡缺口朝外。'''&lt;br /&gt;
&lt;br /&gt;
===天线接口===&lt;br /&gt;
NB101的天线接口采用常见的IPEX座（型号为：U.FL-R-SMT），需要50欧姆阻抗的外置天线，注意不同频段需要使用不同频段的天线。例如B5和B8可以使用传统GPRS模块的900MHz频率的天线。&lt;br /&gt;
== 总结 ==&lt;br /&gt;
NB101尺寸小巧，硬件结构简单，实用，但是由于板载的LDO降压芯片静态电流（轻负载下的自身电流消耗）的缘故，故增加了最低功耗PSM模式下的电流，由BC95的5uA增加到40uA。&lt;br /&gt;
&lt;br /&gt;
如果需要低功耗，需要将NB101的电源指示灯移除，电源指示灯本身会消耗2.5mA左右的电流。&lt;br /&gt;
&lt;br /&gt;
另外NB101预留了网络指示灯功能（早期版本的BC95模块网络指示灯功能并未实现），可直接观察网络状态，带外壳产品可使用第7脚NET信号外接一个指示灯。但需要使用三极管加一级驱动。NET信号高电平有效。&lt;br /&gt;
&lt;br /&gt;
==本文参考==&lt;br /&gt;
&amp;lt;references group=&amp;quot;手册&amp;quot; /&amp;gt;&lt;br /&gt;
[[分类:NB-IOT]]&lt;br /&gt;
[[分类:NB101]]&lt;br /&gt;
[[分类:硬件手册]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=3099</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=3099"/>
		<updated>2021-02-01T10:17:33Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨物联网文档中心&lt;br /&gt;
&lt;br /&gt;
本地文档陆续迁移至文档中心，方便大家在线阅读，实时查看。&lt;br /&gt;
&lt;br /&gt;
以下是在线文档的资料汇总。{{Note|text=好消息，谷雨NRF52832DK已上市，丰富的开发资料，快来学习使用吧！ 资料直达：http://doc.iotxx.com/NRF52832DK|type=tips}}&lt;br /&gt;
&lt;br /&gt;
== NB-IOT系列 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+NB-IOT相关产品资料&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|NB101&lt;br /&gt;
|BC95/BC35核心板&lt;br /&gt;
|http://doc.iotxx.com/NB101&lt;br /&gt;
|-&lt;br /&gt;
|NB200&lt;br /&gt;
|BC28核心板&lt;br /&gt;
|http://doc.iotxx.com/NB200&lt;br /&gt;
|-&lt;br /&gt;
|NB260&lt;br /&gt;
|BC26核心板（支持OpenCPU）&lt;br /&gt;
|http://doc.iotxx.com/NB260&lt;br /&gt;
|-&lt;br /&gt;
|NB-EK-L476&lt;br /&gt;
|NB-IOT开发板（基于STM32L476）&lt;br /&gt;
|http://doc.iotxx.com/NB-EK-L476&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== BLE系列-Nordic ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+nRF52832芯片&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|NRF52832DK&lt;br /&gt;
|谷雨 NRF52832开发板&lt;br /&gt;
|http://doc.iotxx.com/NRF52832DK&lt;br /&gt;
|}&lt;br /&gt;
== BLE系列-TI德州仪器 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+CC254X芯片&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|SmartRF-CC254X&lt;br /&gt;
|CC2541开发板，支持CC2540&lt;br /&gt;
|http://doc.iotxx.com/CC2541EK&lt;br /&gt;
|}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+CC26X0芯片&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|LaunchIOT-CC26XX&lt;br /&gt;
|CC2640开发板，支持CC2650、CC2640R2&lt;br /&gt;
|http://doc.iotxx.com/CC2640EK&lt;br /&gt;
|}&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=3098</id>
		<title>首页</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E9%A6%96%E9%A1%B5&amp;diff=3098"/>
		<updated>2021-02-01T10:17:19Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨物联网文档中心&lt;br /&gt;
&lt;br /&gt;
本地文档陆续迁移至文档中心，方便大家在线阅读，实时查看。&lt;br /&gt;
&lt;br /&gt;
以下是在线文档的资料汇总。{{Note|text=好消息，谷雨NRF52832DK已上市，丰富的开发资料，快来学习使用吧！ 资料直达：http://doc.iotxx.com/NRF52832DK|type=tips}}&lt;br /&gt;
&lt;br /&gt;
http://v2.iotxx.com/img/01.jpg&lt;br /&gt;
&lt;br /&gt;
== NB-IOT系列 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+NB-IOT相关产品资料&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|NB101&lt;br /&gt;
|BC95/BC35核心板&lt;br /&gt;
|http://doc.iotxx.com/NB101&lt;br /&gt;
|-&lt;br /&gt;
|NB200&lt;br /&gt;
|BC28核心板&lt;br /&gt;
|http://doc.iotxx.com/NB200&lt;br /&gt;
|-&lt;br /&gt;
|NB260&lt;br /&gt;
|BC26核心板（支持OpenCPU）&lt;br /&gt;
|http://doc.iotxx.com/NB260&lt;br /&gt;
|-&lt;br /&gt;
|NB-EK-L476&lt;br /&gt;
|NB-IOT开发板（基于STM32L476）&lt;br /&gt;
|http://doc.iotxx.com/NB-EK-L476&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== BLE系列-Nordic ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+nRF52832芯片&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|NRF52832DK&lt;br /&gt;
|谷雨 NRF52832开发板&lt;br /&gt;
|http://doc.iotxx.com/NRF52832DK&lt;br /&gt;
|}&lt;br /&gt;
== BLE系列-TI德州仪器 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+CC254X芯片&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|SmartRF-CC254X&lt;br /&gt;
|CC2541开发板，支持CC2540&lt;br /&gt;
|http://doc.iotxx.com/CC2541EK&lt;br /&gt;
|}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+CC26X0芯片&lt;br /&gt;
!产品型号&lt;br /&gt;
!名称&lt;br /&gt;
!相关资料&lt;br /&gt;
|-&lt;br /&gt;
|LaunchIOT-CC26XX&lt;br /&gt;
|CC2640开发板，支持CC2650、CC2640R2&lt;br /&gt;
|http://doc.iotxx.com/CC2640EK&lt;br /&gt;
|}&lt;br /&gt;
__强显目录__&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Licensed2.jpg&amp;diff=3096</id>
		<title>文件:Licensed2.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Licensed2.jpg&amp;diff=3096"/>
		<updated>2021-01-26T02:28:47Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：Ghostyu上传文件:Licensed2.jpg的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Licensed2&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=3095</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=3095"/>
		<updated>2021-01-26T02:24:53Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* SES许可证激活 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
{{Note|text=假如没有弹出的对话框中没有显示Activate Your Free License，可以直接访问https://license.segger.com/Nordic.cgi注册，后续过程一致。|type=tips}}&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和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()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪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函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh天猫精灵实验 ==&lt;br /&gt;
&lt;br /&gt;
=== 天猫精灵介绍 ===&lt;br /&gt;
天猫精灵（TmallGenie）是阿里巴巴人工智能实验室（Alibaba A.I.Labs）于2017年7月5日发布的AI智能产品品牌，当天同步发布了天猫精灵首款硬件产品——AI智能语音终端设备天猫精灵，未来还将推出更多AI智能产品。&lt;br /&gt;
&lt;br /&gt;
天猫精灵内置AliGenie操作系统，AliGenie生活在云端，它能够听懂中文普通话语音指令，目前可实现智能家居控制、语音购物、手机充值、叫外卖、音频音乐播放等功能，带来人机交互新体验。依靠阿里云的机器学习技术和计算能力，AliGenie能够不断进化成长，了解使用者的喜好和习惯，成为人类智能助手。&lt;br /&gt;
&lt;br /&gt;
=== AliGenie平台介绍 ===&lt;br /&gt;
'''阿里精灵AliGenie平台地址：[//E%3A/NRF52832/www.aligenie.com www.aligenie.com]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT平台接入规范手册：[/www.aligenie.com/doc/357554/tgllbp https://www.aligenie.com/doc/357554/tgllbp]'''&lt;br /&gt;
&lt;br /&gt;
天猫精灵IoT开放平台，是阿里巴巴人工智能实验室(Alibaba A.I.Labs)面向品牌商、方案商、模组商/芯片商以及个人开发者推出的，将IoT物联网技术（蓝牙Mesh协议、WiFi协议、天猫精灵IoT云服务）和AI（天猫精灵ASR语音识别、NLP自然语言处理、TTS语音合成）等对外输出的开放式平台。&lt;br /&gt;
&lt;br /&gt;
开发者可按直连接入（WiFi模组、蓝牙Mesh模组）、云云接入（OAuth2.0）2类方式，接入天猫精灵软硬件生态（天猫精灵音箱、天猫精灵App、AliGenie Inside智能设备）及阿里巴巴集团生态服务，实现语音和触屏交互，为用户提供天猫精灵IoT控制、查询、播报和主动服务。目前已支持40+个IoT类目，1000+型号。天猫精灵IoT开放平台会持续创新，不断为开发者带来新技术，同时降低平台开发者进驻门槛，让AI普惠大众！&lt;br /&gt;
&lt;br /&gt;
=== 硬件连接 ===&lt;br /&gt;
1、NRF52832DK连接Jlink-Lite仿真器，并连接到电脑USB&lt;br /&gt;
&lt;br /&gt;
2、天猫精灵（方糖R）接电源供电，根据天猫精灵使用手册，将其连接上家里的WIFI&lt;br /&gt;
&lt;br /&gt;
=== 平台设备创建 ===&lt;br /&gt;
1、  登录IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]，大家先点击右上角注册一下，因为是阿里巴巴旗下的产品，所以我们只需要用我们的淘宝账号就可以完成注册。&lt;br /&gt;
[[文件:Tmjl-03.png|边框|居中|无框|1189x1189像素]]&lt;br /&gt;
&lt;br /&gt;
2、  注册完成之后，我们登入平台，并且创建一个蓝牙mesh设备，如下图所示：&lt;br /&gt;
[[文件:Tmjl-04.gif|边框|居中|无框|1200x1200像素]]3、  记录刚刚平台分配给我们的三元组，任意一个就行，不需要所有的，举例如下：&lt;br /&gt;
{| class=&amp;quot;wikitable sortable mw-collapsible&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Product ID&lt;br /&gt;
!Device Secret&lt;br /&gt;
!Mac 地址&lt;br /&gt;
|-&lt;br /&gt;
|9244&lt;br /&gt;
|0855f693ce1246f3e09653603283dc02&lt;br /&gt;
|f8a7635b7683&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 软件配置 ===&lt;br /&gt;
将例程“ghostyu-tmjl”解压到mesh SDK的如下路径中，并且确认编译无误：\nrf5SDKforMeshv310src\examples\ghostyu-tmjl&lt;br /&gt;
[[文件:Tmjl-01.png|边框|居中|无框|450x450像素]]&lt;br /&gt;
[[文件:Tmjl-02.png|边框|居中|无框|650x650像素]]&lt;br /&gt;
&lt;br /&gt;
==== 三元组配置 ====&lt;br /&gt;
''三元组具体说明可以查看''：https://www.aligenie.com/doc/357554/gtgprq&lt;br /&gt;
&lt;br /&gt;
举例如平台设备创建第三步记录的三元组：&lt;br /&gt;
&lt;br /&gt;
'''Product ID'''：'''9244''' ，转成16进制就是'''0x0000241C''' ，所以最终数据是{'''1c 24 00 00'''}（4字节、低位在前）&lt;br /&gt;
&lt;br /&gt;
'''Mac地址'''：'''f8a7635b7683'''，所以最终数据是'''｛83 76 5b 63 a7 f8｝'''（6字节，低位在前）&lt;br /&gt;
&lt;br /&gt;
'''AUTH Data'''：利用'''三元组的数据'''以及'''SHA256计算器'''得到我们的加密数据，如下图所示，最终我们得到加密数据为：'''{ 0x73,0x76,0x09,0x93, 0x8d,0x6b,0x6a,0x52, 0x42,0x54,0xe5,0x41, 0x57,0xe4,0x75,0x15}'''&lt;br /&gt;
&lt;br /&gt;
''SHA256计算器''：https://www.jisuan.mobi/pmHbB1bmuum16Xxx.html&lt;br /&gt;
[[文件:Tmjl-05.png|边框|居中|无框|589x589像素]]&lt;br /&gt;
&lt;br /&gt;
==== 代码修改 ====&lt;br /&gt;
根据刚刚三元组配置产生的数据，修改mian文件中对应的值。&lt;br /&gt;
[[文件:Tmjl-06.png|边框|居中|无框|1292x1292像素]]&lt;br /&gt;
&lt;br /&gt;
==== 软件烧写 ====&lt;br /&gt;
修改好代码之后，我们依次进行如下步骤操作，注意'''不可跳跃任意步骤。'''&lt;br /&gt;
&lt;br /&gt;
1、利用nrfgo工具擦除芯片&lt;br /&gt;
&lt;br /&gt;
2、下载正确的softdevice&lt;br /&gt;
&lt;br /&gt;
3、编译天猫精灵例程（注意：已经完成“代码修改”步骤）&lt;br /&gt;
&lt;br /&gt;
4、将编译好的程序下载到开发板中&lt;br /&gt;
&lt;br /&gt;
=== 调试阶段 ===&lt;br /&gt;
&lt;br /&gt;
==== 确认设备状态 ====&lt;br /&gt;
在进入调试之前，我们确认一下设备状态，设备状态正确后，进入调试流程。&lt;br /&gt;
&lt;br /&gt;
1、  按下开发板复位按键，4个LED灯闪烁2次后熄灭&lt;br /&gt;
&lt;br /&gt;
2、  对天猫精灵说“天猫精灵，你联网了吗”，等待天猫精灵回复“我已联网”&lt;br /&gt;
&lt;br /&gt;
==== 调试流程 ====&lt;br /&gt;
1、  对天猫精灵说“天猫精灵，找队友”，天猫精灵回复“正在查找智能设备”&lt;br /&gt;
&lt;br /&gt;
2、  如果我们前面的软件配置是对的，过一会天猫精灵会回复“找到智能灯设备，是否连接”；如果配置有问题，天猫精灵找不到设备会回复“未找到智能设备”，这个时候我们需要检查软件配置流程中的product ID和Mac地址&lt;br /&gt;
&lt;br /&gt;
3、  若天猫精灵回复“找到智能灯设备，是否连接”，我们回答“连接”，天猫精灵会回复“正在连接智能设备，请稍等”&lt;br /&gt;
&lt;br /&gt;
4、  等待1段时间（1min以内），如果回复“智能设备连接成功”，则代表成功连接开发板；如果回复“连接失败”，则检查软件配置流程中的Data Auth密码&lt;br /&gt;
&lt;br /&gt;
5、  如果连接成功，我们按下开发板按键S2（'''注意：复位开发板后，都需要重新按下S2'''）&lt;br /&gt;
&lt;br /&gt;
6、  我们对天猫精灵说“天猫精灵，打开灯”，这个时候开发板上D1会点亮；如果如果说“天猫精灵，关闭灯”，这个时候开发板上D1会熄灭&lt;br /&gt;
&lt;br /&gt;
7、  到第6步结束，已经完成测试，接下来我们也可以到平台上调试一下。此时我们只能测试“打开灯”以及“关闭灯”功能。&lt;br /&gt;
[[文件:Tmjl-07.jpg|边框|居中|无框|1105x1105像素]]&lt;br /&gt;
[[文件:Tmjl-08.png|边框|居中|无框|1109x1109像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh串口透传实验 ==&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=3094</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=3094"/>
		<updated>2021-01-26T02:24:32Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* SES许可证激活 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
{{Note|text=加入没有弹出的对话框中没有显示Activate Your Free License，可以直接访问https://license.segger.com/Nordic.cgi注册，后续过程一致。|type=tips}}&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和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()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪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函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh天猫精灵实验 ==&lt;br /&gt;
&lt;br /&gt;
=== 天猫精灵介绍 ===&lt;br /&gt;
天猫精灵（TmallGenie）是阿里巴巴人工智能实验室（Alibaba A.I.Labs）于2017年7月5日发布的AI智能产品品牌，当天同步发布了天猫精灵首款硬件产品——AI智能语音终端设备天猫精灵，未来还将推出更多AI智能产品。&lt;br /&gt;
&lt;br /&gt;
天猫精灵内置AliGenie操作系统，AliGenie生活在云端，它能够听懂中文普通话语音指令，目前可实现智能家居控制、语音购物、手机充值、叫外卖、音频音乐播放等功能，带来人机交互新体验。依靠阿里云的机器学习技术和计算能力，AliGenie能够不断进化成长，了解使用者的喜好和习惯，成为人类智能助手。&lt;br /&gt;
&lt;br /&gt;
=== AliGenie平台介绍 ===&lt;br /&gt;
'''阿里精灵AliGenie平台地址：[//E%3A/NRF52832/www.aligenie.com www.aligenie.com]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT平台接入规范手册：[/www.aligenie.com/doc/357554/tgllbp https://www.aligenie.com/doc/357554/tgllbp]'''&lt;br /&gt;
&lt;br /&gt;
天猫精灵IoT开放平台，是阿里巴巴人工智能实验室(Alibaba A.I.Labs)面向品牌商、方案商、模组商/芯片商以及个人开发者推出的，将IoT物联网技术（蓝牙Mesh协议、WiFi协议、天猫精灵IoT云服务）和AI（天猫精灵ASR语音识别、NLP自然语言处理、TTS语音合成）等对外输出的开放式平台。&lt;br /&gt;
&lt;br /&gt;
开发者可按直连接入（WiFi模组、蓝牙Mesh模组）、云云接入（OAuth2.0）2类方式，接入天猫精灵软硬件生态（天猫精灵音箱、天猫精灵App、AliGenie Inside智能设备）及阿里巴巴集团生态服务，实现语音和触屏交互，为用户提供天猫精灵IoT控制、查询、播报和主动服务。目前已支持40+个IoT类目，1000+型号。天猫精灵IoT开放平台会持续创新，不断为开发者带来新技术，同时降低平台开发者进驻门槛，让AI普惠大众！&lt;br /&gt;
&lt;br /&gt;
=== 硬件连接 ===&lt;br /&gt;
1、NRF52832DK连接Jlink-Lite仿真器，并连接到电脑USB&lt;br /&gt;
&lt;br /&gt;
2、天猫精灵（方糖R）接电源供电，根据天猫精灵使用手册，将其连接上家里的WIFI&lt;br /&gt;
&lt;br /&gt;
=== 平台设备创建 ===&lt;br /&gt;
1、  登录IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]，大家先点击右上角注册一下，因为是阿里巴巴旗下的产品，所以我们只需要用我们的淘宝账号就可以完成注册。&lt;br /&gt;
[[文件:Tmjl-03.png|边框|居中|无框|1189x1189像素]]&lt;br /&gt;
&lt;br /&gt;
2、  注册完成之后，我们登入平台，并且创建一个蓝牙mesh设备，如下图所示：&lt;br /&gt;
[[文件:Tmjl-04.gif|边框|居中|无框|1200x1200像素]]3、  记录刚刚平台分配给我们的三元组，任意一个就行，不需要所有的，举例如下：&lt;br /&gt;
{| class=&amp;quot;wikitable sortable mw-collapsible&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Product ID&lt;br /&gt;
!Device Secret&lt;br /&gt;
!Mac 地址&lt;br /&gt;
|-&lt;br /&gt;
|9244&lt;br /&gt;
|0855f693ce1246f3e09653603283dc02&lt;br /&gt;
|f8a7635b7683&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 软件配置 ===&lt;br /&gt;
将例程“ghostyu-tmjl”解压到mesh SDK的如下路径中，并且确认编译无误：\nrf5SDKforMeshv310src\examples\ghostyu-tmjl&lt;br /&gt;
[[文件:Tmjl-01.png|边框|居中|无框|450x450像素]]&lt;br /&gt;
[[文件:Tmjl-02.png|边框|居中|无框|650x650像素]]&lt;br /&gt;
&lt;br /&gt;
==== 三元组配置 ====&lt;br /&gt;
''三元组具体说明可以查看''：https://www.aligenie.com/doc/357554/gtgprq&lt;br /&gt;
&lt;br /&gt;
举例如平台设备创建第三步记录的三元组：&lt;br /&gt;
&lt;br /&gt;
'''Product ID'''：'''9244''' ，转成16进制就是'''0x0000241C''' ，所以最终数据是{'''1c 24 00 00'''}（4字节、低位在前）&lt;br /&gt;
&lt;br /&gt;
'''Mac地址'''：'''f8a7635b7683'''，所以最终数据是'''｛83 76 5b 63 a7 f8｝'''（6字节，低位在前）&lt;br /&gt;
&lt;br /&gt;
'''AUTH Data'''：利用'''三元组的数据'''以及'''SHA256计算器'''得到我们的加密数据，如下图所示，最终我们得到加密数据为：'''{ 0x73,0x76,0x09,0x93, 0x8d,0x6b,0x6a,0x52, 0x42,0x54,0xe5,0x41, 0x57,0xe4,0x75,0x15}'''&lt;br /&gt;
&lt;br /&gt;
''SHA256计算器''：https://www.jisuan.mobi/pmHbB1bmuum16Xxx.html&lt;br /&gt;
[[文件:Tmjl-05.png|边框|居中|无框|589x589像素]]&lt;br /&gt;
&lt;br /&gt;
==== 代码修改 ====&lt;br /&gt;
根据刚刚三元组配置产生的数据，修改mian文件中对应的值。&lt;br /&gt;
[[文件:Tmjl-06.png|边框|居中|无框|1292x1292像素]]&lt;br /&gt;
&lt;br /&gt;
==== 软件烧写 ====&lt;br /&gt;
修改好代码之后，我们依次进行如下步骤操作，注意'''不可跳跃任意步骤。'''&lt;br /&gt;
&lt;br /&gt;
1、利用nrfgo工具擦除芯片&lt;br /&gt;
&lt;br /&gt;
2、下载正确的softdevice&lt;br /&gt;
&lt;br /&gt;
3、编译天猫精灵例程（注意：已经完成“代码修改”步骤）&lt;br /&gt;
&lt;br /&gt;
4、将编译好的程序下载到开发板中&lt;br /&gt;
&lt;br /&gt;
=== 调试阶段 ===&lt;br /&gt;
&lt;br /&gt;
==== 确认设备状态 ====&lt;br /&gt;
在进入调试之前，我们确认一下设备状态，设备状态正确后，进入调试流程。&lt;br /&gt;
&lt;br /&gt;
1、  按下开发板复位按键，4个LED灯闪烁2次后熄灭&lt;br /&gt;
&lt;br /&gt;
2、  对天猫精灵说“天猫精灵，你联网了吗”，等待天猫精灵回复“我已联网”&lt;br /&gt;
&lt;br /&gt;
==== 调试流程 ====&lt;br /&gt;
1、  对天猫精灵说“天猫精灵，找队友”，天猫精灵回复“正在查找智能设备”&lt;br /&gt;
&lt;br /&gt;
2、  如果我们前面的软件配置是对的，过一会天猫精灵会回复“找到智能灯设备，是否连接”；如果配置有问题，天猫精灵找不到设备会回复“未找到智能设备”，这个时候我们需要检查软件配置流程中的product ID和Mac地址&lt;br /&gt;
&lt;br /&gt;
3、  若天猫精灵回复“找到智能灯设备，是否连接”，我们回答“连接”，天猫精灵会回复“正在连接智能设备，请稍等”&lt;br /&gt;
&lt;br /&gt;
4、  等待1段时间（1min以内），如果回复“智能设备连接成功”，则代表成功连接开发板；如果回复“连接失败”，则检查软件配置流程中的Data Auth密码&lt;br /&gt;
&lt;br /&gt;
5、  如果连接成功，我们按下开发板按键S2（'''注意：复位开发板后，都需要重新按下S2'''）&lt;br /&gt;
&lt;br /&gt;
6、  我们对天猫精灵说“天猫精灵，打开灯”，这个时候开发板上D1会点亮；如果如果说“天猫精灵，关闭灯”，这个时候开发板上D1会熄灭&lt;br /&gt;
&lt;br /&gt;
7、  到第6步结束，已经完成测试，接下来我们也可以到平台上调试一下。此时我们只能测试“打开灯”以及“关闭灯”功能。&lt;br /&gt;
[[文件:Tmjl-07.jpg|边框|居中|无框|1105x1105像素]]&lt;br /&gt;
[[文件:Tmjl-08.png|边框|居中|无框|1109x1109像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh串口透传实验 ==&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3087</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3087"/>
		<updated>2021-01-11T07:40:52Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 修改工作参数 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：建议1米以内（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数。{{Note|text=MAC扫描器默认处于工作模式，需要通过向MAC扫描器发送串口数据：+++（即三个加号，不带回车换行等任何其他数据），进入指令模式。设置指令需要带回车换行符号，最后发送：AT+Q退出指令模式。重新进入工作模式。|type=tips}}&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
1、串口发送+++，进入指令模式，不带回车换行符号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+++&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2、设定参数，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3、退出指令模式，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+Q&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;br /&gt;
提示：设定参数时，若某个字段，参数未改动，则可以省略不写。&lt;br /&gt;
&lt;br /&gt;
例如，只设置信号阈值：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
或者只设置输出频率：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 问题排查 ===&lt;br /&gt;
&lt;br /&gt;
==== Q1、扫描到其他不相干的设备，如何降低干扰？ ====&lt;br /&gt;
答：RSSI阈值默认为-56dBm，一般可以扫描到周围1米左右的BLE从机，也就是说，周围1米的BLE设备，如果在广播，就都可能会被扫描到。所以，需要调高扫描的信号阈值，才能降低其他周边设备的干扰。可以参考下面的信号与距离的对应关系，来设定隔离的信号阈值。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!扫描范围&lt;br /&gt;
!信号值&lt;br /&gt;
|-&lt;br /&gt;
|1米&lt;br /&gt;
| -50dBm ~ -60dBm&lt;br /&gt;
|-&lt;br /&gt;
|0.5米&lt;br /&gt;
| -40dBm ~ -50dBm&lt;br /&gt;
|-&lt;br /&gt;
|20cm&lt;br /&gt;
| -30dBm左右&lt;br /&gt;
|-&lt;br /&gt;
|10cm以内&lt;br /&gt;
| -20dBm左右&lt;br /&gt;
|}&lt;br /&gt;
例如设定信号阈值为：-20dBm，可以发送指令：&amp;lt;code&amp;gt;AT+SCANEX=-20&amp;lt;/code&amp;gt;，具体的指令格式，请参考：修改工作参数一节。&lt;br /&gt;
&lt;br /&gt;
==== Q2、扫描打印太频繁，或者太慢 ====&lt;br /&gt;
答：默认1s输出一次，可以重新调整输出频率，最小100ms，最大5s&lt;br /&gt;
&lt;br /&gt;
例如设定输出频率为：1.5s，可以发送指令：&amp;lt;code&amp;gt;AT+SCANEX=,15&amp;lt;/code&amp;gt;，具体的指令格式，请参考：修改工作参数一节。&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3086</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3086"/>
		<updated>2021-01-11T07:30:54Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 问题排查 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：建议1米以内（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数。{{Note|text=MAC扫描器默认处于工作模式，需要通过向MAC扫描器发送串口数据：+++（即三个加号，不带回车换行等任何其他数据），进入指令模式。设置指令需要带回车换行符号，最后发送：AT+Q退出指令模式。重新进入工作模式。|type=tips}}&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
1、串口发送+++，进入指令模式，不带回车换行符号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+++&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2、设定参数，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3、退出指令模式，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+Q&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 问题排查 ===&lt;br /&gt;
&lt;br /&gt;
==== Q1、扫描到其他不相干的设备，如何降低干扰？ ====&lt;br /&gt;
答：RSSI阈值默认为-56dBm，一般可以扫描到周围1米左右的BLE从机，也就是说，周围1米的BLE设备，如果在广播，就都可能会被扫描到。所以，需要调高扫描的信号阈值，才能降低其他周边设备的干扰。可以参考下面的信号与距离的对应关系，来设定隔离的信号阈值。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!扫描范围&lt;br /&gt;
!信号值&lt;br /&gt;
|-&lt;br /&gt;
|1米&lt;br /&gt;
| -50dBm ~ -60dBm&lt;br /&gt;
|-&lt;br /&gt;
|0.5米&lt;br /&gt;
| -40dBm ~ -50dBm&lt;br /&gt;
|-&lt;br /&gt;
|20cm&lt;br /&gt;
| -30dBm左右&lt;br /&gt;
|-&lt;br /&gt;
|10cm以内&lt;br /&gt;
| -20dBm左右&lt;br /&gt;
|}&lt;br /&gt;
例如设定信号阈值为：-20dBm，可以发送指令：&amp;lt;code&amp;gt;AT+SCANEX=-20&amp;lt;/code&amp;gt;，具体的指令格式，请参考：修改工作参数一节。&lt;br /&gt;
&lt;br /&gt;
==== Q2、扫描打印太频繁，或者太慢 ====&lt;br /&gt;
答：默认1s输出一次，可以重新调整输出频率，最小100ms，最大5s&lt;br /&gt;
&lt;br /&gt;
例如设定输出频率为：1.5s，可以发送指令：&amp;lt;code&amp;gt;AT+SCANEX=,15&amp;lt;/code&amp;gt;，具体的指令格式，请参考：修改工作参数一节。&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3085</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3085"/>
		<updated>2021-01-11T07:30:34Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：建议1米以内（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数。{{Note|text=MAC扫描器默认处于工作模式，需要通过向MAC扫描器发送串口数据：+++（即三个加号，不带回车换行等任何其他数据），进入指令模式。设置指令需要带回车换行符号，最后发送：AT+Q退出指令模式。重新进入工作模式。|type=tips}}&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
1、串口发送+++，进入指令模式，不带回车换行符号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+++&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2、设定参数，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3、退出指令模式，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+Q&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 问题排查 ===&lt;br /&gt;
Q1、扫描到其他不相干的设备，如何降低干扰？&lt;br /&gt;
&lt;br /&gt;
答：RSSI阈值默认为-56dBm，一般可以扫描到周围1米左右的BLE从机，也就是说，周围1米的BLE设备，如果在广播，就都可能会被扫描到。所以，需要调高扫描的信号阈值，才能降低其他周边设备的干扰。可以参考下面的信号与距离的对应关系，来设定隔离的信号阈值。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!扫描范围&lt;br /&gt;
!信号值&lt;br /&gt;
|-&lt;br /&gt;
|1米&lt;br /&gt;
| -50dBm ~ -60dBm&lt;br /&gt;
|-&lt;br /&gt;
|0.5米&lt;br /&gt;
| -40dBm ~ -50dBm&lt;br /&gt;
|-&lt;br /&gt;
|20cm&lt;br /&gt;
| -30dBm左右&lt;br /&gt;
|-&lt;br /&gt;
|10cm以内&lt;br /&gt;
| -20dBm左右&lt;br /&gt;
|}&lt;br /&gt;
例如设定信号阈值为：-20dBm，可以发送指令：&amp;lt;code&amp;gt;AT+SCANEX=-20&amp;lt;/code&amp;gt;，具体的指令格式，请参考：修改工作参数一节。&lt;br /&gt;
&lt;br /&gt;
Q2、扫描打印太频繁，或者太慢&lt;br /&gt;
&lt;br /&gt;
答：默认1s输出一次，可以重新调整输出频率，最小100ms，最大5s&lt;br /&gt;
&lt;br /&gt;
例如设定输出频率为：1.5s，可以发送指令：&amp;lt;code&amp;gt;AT+SCANEX=,15&amp;lt;/code&amp;gt;，具体的指令格式，请参考：修改工作参数一节。&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3084</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3084"/>
		<updated>2021-01-11T06:27:27Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 基本参数 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：建议1米以内（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数。{{Note|text=MAC扫描器默认处于工作模式，需要通过向MAC扫描器发送串口数据：+++（即三个加号，不带回车换行等任何其他数据），进入指令模式。设置指令需要带回车换行符号，最后发送：AT+Q退出指令模式。重新进入工作模式。|type=tips}}&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
1、串口发送+++，进入指令模式，不带回车换行符号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+++&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2、设定参数，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3、退出指令模式，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+Q&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3083</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3083"/>
		<updated>2021-01-11T06:26:36Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 修改工作参数 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数。{{Note|text=MAC扫描器默认处于工作模式，需要通过向MAC扫描器发送串口数据：+++（即三个加号，不带回车换行等任何其他数据），进入指令模式。设置指令需要带回车换行符号，最后发送：AT+Q退出指令模式。重新进入工作模式。|type=tips}}&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
1、串口发送+++，进入指令模式，不带回车换行符号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+++&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2、设定参数，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3、退出指令模式，需带回车换行符。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+Q&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3082</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3082"/>
		<updated>2021-01-11T06:25:43Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 修改工作参数 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数。{{Note|text=MAC扫描器默认处于工作模式，需要通过向MAC扫描器发送串口数据：+++（即三个加号，不带回车换行等任何其他数据），进入指令模式。设置指令需要带回车换行符号，最后发送：AT+Q退出指令模式。重新进入工作模式。|type=tips}}&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
1、串口发送+++，进入指令模式&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+++&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2、设定参数&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3、退出指令模式。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+Q&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3081</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3081"/>
		<updated>2021-01-11T06:19:01Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 扫描从机 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}如下图所示，扫描的设备通过串口向串口调试助手输出。&lt;br /&gt;
[[文件:MAC扫描器-串口输出.jpg|居中|无框|571x571像素]]&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数，注意指令需要以回车换行符结尾。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:MAC%E6%89%AB%E6%8F%8F%E5%99%A8-%E4%B8%B2%E5%8F%A3%E8%BE%93%E5%87%BA.jpg&amp;diff=3080</id>
		<title>文件:MAC扫描器-串口输出.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:MAC%E6%89%AB%E6%8F%8F%E5%99%A8-%E4%B8%B2%E5%8F%A3%E8%BE%93%E5%87%BA.jpg&amp;diff=3080"/>
		<updated>2021-01-11T06:18:48Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;MAC扫描器-串口输出&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3079</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3079"/>
		<updated>2021-01-11T06:16:48Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|336x336px]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数，注意指令需要以回车换行符结尾。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3078</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3078"/>
		<updated>2021-01-11T06:16:37Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|490x490像素]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 硬件介绍 ===&lt;br /&gt;
[[文件:MAC扫描器-硬件介绍.png|居中|无框|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数，注意指令需要以回车换行符结尾。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:MAC%E6%89%AB%E6%8F%8F%E5%99%A8-%E7%A1%AC%E4%BB%B6%E4%BB%8B%E7%BB%8D.png&amp;diff=3077</id>
		<title>文件:MAC扫描器-硬件介绍.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:MAC%E6%89%AB%E6%8F%8F%E5%99%A8-%E7%A1%AC%E4%BB%B6%E4%BB%8B%E7%BB%8D.png&amp;diff=3077"/>
		<updated>2021-01-11T06:16:15Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;MAC扫描器-硬件介绍&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3076</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3076"/>
		<updated>2021-01-11T06:05:25Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|490x490像素]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
==== 驱动安装 ====&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
==== 扫描从机 ====&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 修改工作参数 ====&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数，注意指令需要以回车换行符结尾。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3075</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3075"/>
		<updated>2021-01-11T06:04:53Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|490x490像素]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
=== 驱动安装 ===&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 硬件连接 ===&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 扫描从机 ===&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 修改工作参数 ===&lt;br /&gt;
MAC扫描器，默认输出频率为1000ms每次，RSSI过滤条件为：-56dBm，可以通过AT指令接口修改上述参数，注意指令需要以回车换行符结尾。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=&amp;lt;rssi&amp;gt;,&amp;lt;freq&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AT+SCANEX=-30,5&amp;lt;/code&amp;gt;&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| AT+SCANEX&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|设定的信号检测阈值。例如：-30，表示信号大于-30dBm的从机才会输出。&lt;br /&gt;
|-&lt;br /&gt;
|freq&lt;br /&gt;
|串口输出频率，单位100ms，例如：5，表示500ms输出一次&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3074</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3074"/>
		<updated>2021-01-11T05:59:02Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|490x490像素]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;br /&gt;
&lt;br /&gt;
=== 基本参数 ===&lt;br /&gt;
操作系统：windows&lt;br /&gt;
&lt;br /&gt;
工作电压：5V，USB供电&lt;br /&gt;
&lt;br /&gt;
串口波特率：115200&lt;br /&gt;
&lt;br /&gt;
扫描半径：0~1米（一般从机距离1米的信号值为-56dBm）&lt;br /&gt;
&lt;br /&gt;
信号阈值：-56dBm（默认值，范围：-1dBm~-90dBm，支持修改）&lt;br /&gt;
&lt;br /&gt;
扫描频率：1000ms（默认值，范围：100ms~5000ms，支持修改）&lt;br /&gt;
&lt;br /&gt;
=== 使用说明 ===&lt;br /&gt;
MAC扫描器的使用非常简单，只需要安装串口驱动程序即可按照默认参数工作。&lt;br /&gt;
&lt;br /&gt;
=== 驱动安装 ===&lt;br /&gt;
下载[http://www.wch.cn/downloads/file/5.html CH340串口驱动程序]，解压后，双击exe安装程序，安装驱动程序，如下图所示。&lt;br /&gt;
[[文件:CH340驱动安装.png|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 硬件连接 ===&lt;br /&gt;
使用MiniUSB将MAC扫描器与电脑连接，若第一次使用，可能会弹出加载驱动程序的窗口。驱动加载完成后，在设备管理器的端口（COM和LPT）分类下，将出现CH340的串口设备。如下图所示。&lt;br /&gt;
[[文件:设备管理器COM.jpg|居中|缩略图|700x700像素]]&lt;br /&gt;
&lt;br /&gt;
=== 扫描从机 ===&lt;br /&gt;
此时设备已经准备就绪，可以扫描从机设备，读取设备的MAC地址。读取到的MAC地址将通过串口输出给PC，输出格式如下&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=&amp;lt;addr_type&amp;gt;,&amp;lt;mac&amp;gt;,&amp;lt;rssi&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;+SCAN=2,63:3C:F5:E9:B7:0D,-40&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
参数解释：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!参数字段&lt;br /&gt;
!参数解释&lt;br /&gt;
|-&lt;br /&gt;
| +SCAN&lt;br /&gt;
|命令头&lt;br /&gt;
|-&lt;br /&gt;
|addr_type&lt;br /&gt;
|表示从此的地址类型，一般取值为2，可以忽略该字段&lt;br /&gt;
|-&lt;br /&gt;
|mac&lt;br /&gt;
|扫描到的从机MAC地址，例如：63:3C:F5:E9:B7:0D&lt;br /&gt;
|-&lt;br /&gt;
|rssi&lt;br /&gt;
|扫描的从机信号值，例如：-40，表示：-40dBm&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;CR&amp;gt;&amp;lt;LF&amp;gt;&lt;br /&gt;
|命令结束符号\r\n，对应的ASCII为0x0D 0x0A，可通过此符号判定指令输出已完成。&lt;br /&gt;
|}&lt;br /&gt;
{{Note|text=注意：若从机信号满足设定的条件，则会重复扫描到该设备，请上层软件做好过滤工作。|type=warning}}&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%AE%BE%E5%A4%87%E7%AE%A1%E7%90%86%E5%99%A8COM.jpg&amp;diff=3073</id>
		<title>文件:设备管理器COM.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%AE%BE%E5%A4%87%E7%AE%A1%E7%90%86%E5%99%A8COM.jpg&amp;diff=3073"/>
		<updated>2021-01-11T05:41:26Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;设备管理器COM&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3072</id>
		<title>MAC扫描器</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=MAC%E6%89%AB%E6%8F%8F%E5%99%A8&amp;diff=3072"/>
		<updated>2021-01-11T05:29:24Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：创建页面，内容为“490x490像素 MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可…”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[文件:MAC扫描器.png|右|无框|490x490像素]]&lt;br /&gt;
MAC扫描器是一个连续扫描BLE从机的设备，格式化输出从机的MAC地址，信号RSSI值等。可用于产品的出货记录、检测等领域。&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:MAC%E6%89%AB%E6%8F%8F%E5%99%A8.png&amp;diff=3071</id>
		<title>文件:MAC扫描器.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:MAC%E6%89%AB%E6%8F%8F%E5%99%A8.png&amp;diff=3071"/>
		<updated>2021-01-11T05:28:06Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;MAC扫描器&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E5%B0%84%E9%A2%91%E6%8E%A5%E5%8F%A3%E5%8F%82%E6%95%B0-V3.png&amp;diff=3051</id>
		<title>文件:射频接口参数-V3.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E5%B0%84%E9%A2%91%E6%8E%A5%E5%8F%A3%E5%8F%82%E6%95%B0-V3.png&amp;diff=3051"/>
		<updated>2020-09-01T03:02:59Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;射频接口参数-V3&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E8%BD%AF%E4%BB%B6%E5%8A%9F%E8%83%BD%E7%A4%BA%E6%84%8F%E5%9B%BE.png&amp;diff=3050</id>
		<title>文件:BLE181-软件功能示意图.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E8%BD%AF%E4%BB%B6%E5%8A%9F%E8%83%BD%E7%A4%BA%E6%84%8F%E5%9B%BE.png&amp;diff=3050"/>
		<updated>2020-06-28T03:17:19Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：Ghostyu上传文件:BLE181-软件功能示意图.png的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181 软件功能示意图&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181%E4%B8%B2%E5%8F%A3%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE%E6%97%B6%E5%BA%8F.png&amp;diff=3035</id>
		<title>文件:BLE181串口发送数据时序.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181%E4%B8%B2%E5%8F%A3%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE%E6%97%B6%E5%BA%8F.png&amp;diff=3035"/>
		<updated>2020-06-27T13:08:18Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181串口发送数据时序&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181%E4%B8%B2%E5%8F%A3%E6%8E%A5%E6%94%B6%E6%95%B0%E6%8D%AE%E6%97%B6%E5%BA%8F.png&amp;diff=3034</id>
		<title>文件:BLE181串口接收数据时序.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181%E4%B8%B2%E5%8F%A3%E6%8E%A5%E6%94%B6%E6%95%B0%E6%8D%AE%E6%97%B6%E5%BA%8F.png&amp;diff=3034"/>
		<updated>2020-06-27T13:07:33Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：Ghostyu上传文件:BLE181串口接收数据时序.png的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181串口接收数据时序&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181%E4%B8%B2%E5%8F%A3%E6%8E%A5%E6%94%B6%E6%95%B0%E6%8D%AE%E6%97%B6%E5%BA%8F.png&amp;diff=3032</id>
		<title>文件:BLE181串口接收数据时序.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181%E4%B8%B2%E5%8F%A3%E6%8E%A5%E6%94%B6%E6%95%B0%E6%8D%AE%E6%97%B6%E5%BA%8F.png&amp;diff=3032"/>
		<updated>2020-06-27T11:19:19Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181串口接收数据时序&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E6%97%A0%E7%BA%BF%E7%AB%AF%E6%8C%87%E4%BB%A4%E6%94%B6%E5%8F%91.png&amp;diff=3026</id>
		<title>文件:谷雨BLE调试工具-无线端指令收发.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E6%97%A0%E7%BA%BF%E7%AB%AF%E6%8C%87%E4%BB%A4%E6%94%B6%E5%8F%91.png&amp;diff=3026"/>
		<updated>2020-06-26T01:13:28Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨BLE调试工具 无线端指令收发&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:IPEX%E4%B8%80%E4%BB%A3%E5%BA%A7%E5%B0%BA%E5%AF%B8.png&amp;diff=3015</id>
		<title>文件:IPEX一代座尺寸.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:IPEX%E4%B8%80%E4%BB%A3%E5%BA%A7%E5%B0%BA%E5%AF%B8.png&amp;diff=3015"/>
		<updated>2020-06-25T13:52:53Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E6%A8%A1%E5%9D%97Reload%E7%94%B5%E8%B7%AF.png&amp;diff=3009</id>
		<title>文件:硬件模块Reload电路.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E6%A8%A1%E5%9D%97Reload%E7%94%B5%E8%B7%AF.png&amp;diff=3009"/>
		<updated>2020-06-25T13:11:43Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;硬件模块Reload电路&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E6%A8%A1%E5%9D%97LED%E6%8C%87%E7%A4%BA%E7%81%AF%E7%94%B5%E8%B7%AF.png&amp;diff=3008</id>
		<title>文件:硬件模块LED指示灯电路.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E6%A8%A1%E5%9D%97LED%E6%8C%87%E7%A4%BA%E7%81%AF%E7%94%B5%E8%B7%AF.png&amp;diff=3008"/>
		<updated>2020-06-25T13:03:13Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;硬件模块LED指示灯电路&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E7%94%B5%E8%B7%AF-%E4%B8%B2%E5%8F%A3%E7%94%B5%E5%B9%B3%E8%BD%AC%E6%8D%A2%E7%94%B5%E8%B7%AF-%E8%8A%AF%E7%89%87%E6%96%B9%E6%A1%88.png&amp;diff=3002</id>
		<title>文件:硬件电路-串口电平转换电路-芯片方案.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E7%94%B5%E8%B7%AF-%E4%B8%B2%E5%8F%A3%E7%94%B5%E5%B9%B3%E8%BD%AC%E6%8D%A2%E7%94%B5%E8%B7%AF-%E8%8A%AF%E7%89%87%E6%96%B9%E6%A1%88.png&amp;diff=3002"/>
		<updated>2020-06-25T03:40:52Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;硬件电路 串口电平转换电路 芯片方案&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E4%B8%B2%E5%8F%A3%E7%94%B5%E5%B9%B3%E8%BD%AC%E6%8D%A2%E7%94%B5%E8%B7%AF.png&amp;diff=3001</id>
		<title>文件:硬件串口电平转换电路.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E7%A1%AC%E4%BB%B6%E4%B8%B2%E5%8F%A3%E7%94%B5%E5%B9%B3%E8%BD%AC%E6%8D%A2%E7%94%B5%E8%B7%AF.png&amp;diff=3001"/>
		<updated>2020-06-25T03:15:19Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;硬件串口电平转换电路&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E6%89%AB%E6%8F%8F%E8%AE%BE%E5%A4%87.png&amp;diff=2979</id>
		<title>文件:谷雨BLE调试工具-扫描设备.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E6%89%AB%E6%8F%8F%E8%AE%BE%E5%A4%87.png&amp;diff=2979"/>
		<updated>2020-06-24T07:24:13Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：Ghostyu上传文件:谷雨BLE调试工具-扫描设备.png的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨BLE调试工具-扫描设备&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E5%AF%86%E7%A0%81%E5%86%99%E5%85%A5%E9%AA%8C%E8%AF%81.png&amp;diff=2956</id>
		<title>文件:BLE181-密码写入验证.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E5%AF%86%E7%A0%81%E5%86%99%E5%85%A5%E9%AA%8C%E8%AF%81.png&amp;diff=2956"/>
		<updated>2020-06-23T14:54:33Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：Ghostyu上传文件:BLE181-密码写入验证.png的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181密码写入验证&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E5%AF%86%E7%A0%81%E5%86%99%E5%85%A5%E9%AA%8C%E8%AF%81.png&amp;diff=2950</id>
		<title>文件:BLE181-密码写入验证.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E5%AF%86%E7%A0%81%E5%86%99%E5%85%A5%E9%AA%8C%E8%AF%81.png&amp;diff=2950"/>
		<updated>2020-06-23T12:48:52Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181密码写入验证&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE%E5%B9%BF%E6%92%AD%E5%8C%85%E7%BB%93%E6%9E%84.png&amp;diff=2940</id>
		<title>文件:BLE广播包结构.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE%E5%B9%BF%E6%92%AD%E5%8C%85%E7%BB%93%E6%9E%84.png&amp;diff=2940"/>
		<updated>2020-06-23T06:39:00Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE广播包结构&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:AT%E6%8C%87%E4%BB%A4%E8%BF%9B%E5%85%A5%E6%8C%87%E4%BB%A4%E6%A8%A1%E5%BC%8F%E8%AF%B4%E6%98%8E.png&amp;diff=2895</id>
		<title>文件:AT指令进入指令模式说明.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:AT%E6%8C%87%E4%BB%A4%E8%BF%9B%E5%85%A5%E6%8C%87%E4%BB%A4%E6%A8%A1%E5%BC%8F%E8%AF%B4%E6%98%8E.png&amp;diff=2895"/>
		<updated>2020-06-19T01:33:07Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;AT指令进入指令模式说明&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE%E6%9C%8D%E5%8A%A1FFC0.png&amp;diff=2889</id>
		<title>文件:BLE181-参数设置服务FFC0.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE%E6%9C%8D%E5%8A%A1FFC0.png&amp;diff=2889"/>
		<updated>2020-06-18T11:47:10Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181 参数设置服务FFC0&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E4%B8%B2%E5%8F%A3%E9%80%8F%E4%BC%A0%E6%9C%8D%E5%8A%A1FFE0.png&amp;diff=2888</id>
		<title>文件:BLE181-串口透传服务FFE0.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E4%B8%B2%E5%8F%A3%E9%80%8F%E4%BC%A0%E6%9C%8D%E5%8A%A1FFE0.png&amp;diff=2888"/>
		<updated>2020-06-18T11:46:01Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181 串口透传服务FFE0&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=CC2640EK&amp;diff=2880</id>
		<title>CC2640EK</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=CC2640EK&amp;diff=2880"/>
		<updated>2020-06-09T05:35:02Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{二维码|text={{fullurl:{{FULLPAGENAMEE}}}}|showText=否|tips=手机扫码阅读|style=right}}&lt;br /&gt;
尊敬的开发者您好，LaunchIOT-CC26XX系列开发套件资料目前均在百度云盘中，暂无在线文档。&lt;br /&gt;
&lt;br /&gt;
百度云盘资料链接如下：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!支持芯片型号&lt;br /&gt;
!相关资料链接&lt;br /&gt;
|-&lt;br /&gt;
|CC2640&lt;br /&gt;
CC2650&lt;br /&gt;
|套件主体资料：&amp;lt;nowiki&amp;gt;链接：https://pan.baidu.com/s/1gIGPp4axC43Olbhs_0OTYQ  提取码：buf3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
软件IAR-ARM-7.8独立链接：&amp;lt;nowiki&amp;gt;http://pan.baidu.com/s/1hsJ0E2K&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|CC2640R2&lt;br /&gt;
|套件主体资料：链接：&amp;lt;nowiki&amp;gt;https://pan.baidu.com/s/1mzUmBsb_8IKUsVT_CTpugw&amp;lt;/nowiki&amp;gt;  提取码：8kvi&lt;br /&gt;
软件IAR-ARM-8.11.3独立链接：&amp;lt;nowiki&amp;gt;https://pan.baidu.com/s/1jI7nYMM&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
下图是百度云盘资料截图：&lt;br /&gt;
&lt;br /&gt;
[[文件:CC2640DK 资料链接.png|无框|732x732像素]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=CC2640EK&amp;diff=2879</id>
		<title>CC2640EK</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=CC2640EK&amp;diff=2879"/>
		<updated>2020-06-09T05:34:06Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{二维码|text={{fullurl:{{FULLPAGENAMEE}}}}|showText=否|tips=手机扫码阅读|style=right}}&lt;br /&gt;
尊敬的开发者您好，LaunchIOT-CC26XX系列开发套件资料目前均在百度云盘中，暂无在线文档。&lt;br /&gt;
&lt;br /&gt;
百度云盘资料链接如下：&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!支持芯片型号&lt;br /&gt;
!相关资料链接&lt;br /&gt;
|-&lt;br /&gt;
|CC2640&lt;br /&gt;
CC2650&lt;br /&gt;
|套件主体资料：&amp;lt;nowiki&amp;gt;链接：https://pan.baidu.com/s/1gIGPp4axC43Olbhs_0OTYQ  提取码：buf3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
软件IAR-ARM-7.8独立链接：&amp;lt;nowiki&amp;gt;http://pan.baidu.com/s/1hsJ0E2K&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|CC2640R2&lt;br /&gt;
|套件主体资料：&amp;lt;nowiki&amp;gt;http://pan.baidu.com/s/1cbaNDk&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
软件IAR-ARM-8.11.3独立链接：&amp;lt;nowiki&amp;gt;https://pan.baidu.com/s/1jI7nYMM&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
下图是百度云盘资料截图：&lt;br /&gt;
&lt;br /&gt;
[[文件:CC2640DK 资料链接.png|无框|732x732像素]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NB260-OpenCPU%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C&amp;diff=2878</id>
		<title>NB260-OpenCPU软件开发手册</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NB260-OpenCPU%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C&amp;diff=2878"/>
		<updated>2020-06-09T04:07:54Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 擦除程序 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;NB260板载OpenCPU方案的BC26模块，除了可以作为标准AT指令模块外，还可作为OpenCPU用。&lt;br /&gt;
&lt;br /&gt;
OpenCPU是基于移远模块的二次开发方案，用户可以直接在模块里开发集成应用，从而省掉外部主控MCU。OpenCPU已被广泛的用于M2M领域，例如智能家居、智能城市、资产追踪，汽车能源等领域。&lt;br /&gt;
&lt;br /&gt;
== 方案对比 ==&lt;br /&gt;
OpenCPU与传统方案对比如下图。&lt;br /&gt;
在传统方案中，需要一个MCU作为主控制器，控制硬件外设，例如控制路灯的亮或灭，然后使用MCU的UART接口与标准模块进行AT指令通信，上报当前路灯状态，或接收来自远程服务器的开关灯命令。&lt;br /&gt;
&lt;br /&gt;
而OpenCPU方案，可以直接在模块中编写程序控制路灯，节约硬件成本，加速应用开发。&lt;br /&gt;
&lt;br /&gt;
[[文件:OpenCPU方案.png|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
== 功能框图 ==&lt;br /&gt;
BC26-OpenCPU功能框图如下。&lt;br /&gt;
&lt;br /&gt;
[[文件:BC26-OpenCPU框图.png|无框|1000x1000像素]]&lt;br /&gt;
&lt;br /&gt;
== 硬件资源 ==&lt;br /&gt;
OpenCPU硬件资源如下表格：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&lt;br /&gt;
!描述&lt;br /&gt;
|-&lt;br /&gt;
|'''CPU'''&lt;br /&gt;
|ARM Cortex-M4处理器，32位，带FPU（浮点运算）和MPU（内存管理），主频78MHz。&lt;br /&gt;
|-&lt;br /&gt;
|'''Flash'''&lt;br /&gt;
|总共4MB，OpenCPU可用空间200KB（App代码空间）。&lt;br /&gt;
|-&lt;br /&gt;
|'''RAM'''&lt;br /&gt;
|总共4MB，OpenCPU可用的静态内存空间100KB，动态内存：300KB。&lt;br /&gt;
|-&lt;br /&gt;
|'''GPIO'''&lt;br /&gt;
|总共有18个通用GPIO，支持多种复用模式。&lt;br /&gt;
完整的引脚说明，请阅读《NB260硬件设计手册》&amp;lt;ref group=&amp;quot;手册&amp;quot;&amp;gt;[[NB260硬件设计手册]]&amp;lt;/ref&amp;gt;中的引脚复用一节。&lt;br /&gt;
* GPIO接口&lt;br /&gt;
* EINT外部中断接口&lt;br /&gt;
* I2C接口&lt;br /&gt;
* SPI接口&lt;br /&gt;
* UART串口&lt;br /&gt;
* 网络指示灯接口&lt;br /&gt;
* PWM接口&lt;br /&gt;
* ADC模拟采集接口。&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 软件资源 ==&lt;br /&gt;
BC26的软件开发相关资源如表格：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&lt;br /&gt;
!描述&lt;br /&gt;
|-&lt;br /&gt;
|'''开发环境'''&lt;br /&gt;
|SourceInsight（语法高亮、变量函数关联，也可使用其他代码编辑工具）及命令行（代码编译）&lt;br /&gt;
|-&lt;br /&gt;
|'''编译器'''&lt;br /&gt;
|GCC（gcc-arm-none-eabi V4.8），谷雨提供的SDK中已包含，无需单独下载。&lt;br /&gt;
|-&lt;br /&gt;
|'''烧写软件'''&lt;br /&gt;
|MTK IOT_Flash_Tool，谷雨提供的SDK中已包含，无需单独下载。&lt;br /&gt;
|-&lt;br /&gt;
|'''SDK开发包'''&lt;br /&gt;
|BC26-OpenCPU SDK&lt;br /&gt;
|-&lt;br /&gt;
|'''开发语言'''&lt;br /&gt;
|标准C&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== 开始开发 ==&lt;br /&gt;
本节开始，介绍基于NB260的OpenSDK开发的整个流程，旨在让用户熟悉OpenSDK的开发流程。&lt;br /&gt;
&lt;br /&gt;
=== 获取SDK ===&lt;br /&gt;
SDK是Software Development Kit软件工具开发包的简称，是我们开发BC26的必要资料，里面包含api库文件，示例代码，编译器，烧写软件等。&lt;br /&gt;
&lt;br /&gt;
SDK位于NB260归档资料中（百度网盘中）。&lt;br /&gt;
&lt;br /&gt;
=== SDK文件结构 ===&lt;br /&gt;
注意：由于BC26模块硬件版本需要与SDK固件匹配。&lt;br /&gt;
* 硬件版本为BC26NB-04-STD的模块，需要使用BC26NBR01A03的模块固件和SDK固件。&lt;br /&gt;
* 硬件版本为BC26NC-04-STD的模块（包括BC26NE-04-B8模块），需要使用BC26NBR01A07的模块固件和SDK固件。&lt;br /&gt;
BC26-OpenCPU SDK根目录的文件结构如下图所示：&lt;br /&gt;
&lt;br /&gt;
[[文件:BC26OpenCPU SDK目录结构.png|无框|614x614像素]]&lt;br /&gt;
&lt;br /&gt;
详细解释如下表格：&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!目录或文件&lt;br /&gt;
!描述&lt;br /&gt;
|-&lt;br /&gt;
|BC26NBR01Axx_xxx&lt;br /&gt;
|BC26核心固件，使用的SDK版本必须与此固件匹配，需要先烧写一次该固件。&lt;br /&gt;
该固件同样使用IOT_Flash_Tool来烧写。&lt;br /&gt;
|-&lt;br /&gt;
|build&lt;br /&gt;
|代码编译后的中间文件以及生成的app固件，另外编译器输出的错误信息也在该目录下：build\gcc\build.log&lt;br /&gt;
|-&lt;br /&gt;
|custom&lt;br /&gt;
|用户app代码所在目录，今后都要在这个目录写编写代码&lt;br /&gt;
|-&lt;br /&gt;
|docs&lt;br /&gt;
|BC26 OpenSDK开发相关文档。&lt;br /&gt;
|-&lt;br /&gt;
|example&lt;br /&gt;
|SDK示例代码，例如GPIO，ADC，IIC等接口的使用示例，几乎涵盖了所有。&lt;br /&gt;
|-&lt;br /&gt;
|include&lt;br /&gt;
|SDK API头文件&lt;br /&gt;
|-&lt;br /&gt;
|libs&lt;br /&gt;
|SDK API库文件，APP中所有的api接口均来自这里。&lt;br /&gt;
|-&lt;br /&gt;
|make&lt;br /&gt;
|编译配置文件，例如Makefile。&lt;br /&gt;
|-&lt;br /&gt;
|ril&lt;br /&gt;
|ril源码目录&lt;br /&gt;
|-&lt;br /&gt;
|tools&lt;br /&gt;
|GCC编译器以及MTK的IOT_Flash_Tool烧写工具&lt;br /&gt;
|-&lt;br /&gt;
|BETA0801V03版本更新说明&lt;br /&gt;
|当前SDK版本说明&lt;br /&gt;
|-&lt;br /&gt;
|Make.bat&lt;br /&gt;
|辅助命令行，用于命令行的错误提示。&lt;br /&gt;
|-&lt;br /&gt;
|MS-DOS&lt;br /&gt;
|windows命令行的快捷方式，作用是方便的在当前目录下打开命令工具。&lt;br /&gt;
作用等同于在当前目录下按住Shift键同时鼠标右击打开：在此处打开命令窗口(W)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== main函数在哪 ===&lt;br /&gt;
嵌入式开发，大家一定要弄清楚main函数在哪，也就是整个程序的入口函数在哪，这是一切代码开发的开始。&lt;br /&gt;
&lt;br /&gt;
BC26OpenCPU的main函数位于custom目录下的main.c文件中，函数名为：void proc_main_task(s32 taskId)，大概位于68行。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;68&amp;quot;&amp;gt;&lt;br /&gt;
void proc_main_task(s32 taskId)&lt;br /&gt;
{ &lt;br /&gt;
    s32 ret;&lt;br /&gt;
    ST_MSG msg;&lt;br /&gt;
&lt;br /&gt;
    // Register &amp;amp; open UART port&lt;br /&gt;
    ret = Ql_UART_Register(m_myUartPort, CallBack_UART_Hdlr, NULL);//初始化一个串口&lt;br /&gt;
    if (ret &amp;lt; QL_RET_OK)&lt;br /&gt;
    {&lt;br /&gt;
        Ql_Debug_Trace(&amp;quot;Fail to register serial port[%d], ret=%d\r\n&amp;quot;, m_myUartPort, ret);&lt;br /&gt;
    }&lt;br /&gt;
    ret = Ql_UART_Open(m_myUartPort, 115200, FC_NONE);//以115200的波特率打开串口&lt;br /&gt;
    if (ret &amp;lt; QL_RET_OK)&lt;br /&gt;
    {&lt;br /&gt;
        Ql_Debug_Trace(&amp;quot;Fail to open serial port[%d], ret=%d\r\n&amp;quot;, m_myUartPort, ret);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    APP_DEBUG(&amp;quot;OpenCPU: Customer Application\r\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    // 开始程序大循环&lt;br /&gt;
    while(TRUE)&lt;br /&gt;
    {&lt;br /&gt;
        Ql_OS_GetMessage(&amp;amp;msg);//获取系统消息&lt;br /&gt;
        switch(msg.message)    //处理消息&lt;br /&gt;
        {&lt;br /&gt;
        case MSG_ID_RIL_READY:&lt;br /&gt;
            APP_DEBUG(&amp;quot;&amp;lt;-- RIL is ready --&amp;gt;\r\n&amp;quot;);&lt;br /&gt;
            Ql_RIL_Initialize();&lt;br /&gt;
            &lt;br /&gt;
            break;&lt;br /&gt;
        case MSG_ID_URC_INDICATION:&lt;br /&gt;
            //APP_DEBUG(&amp;quot;&amp;lt;-- Received URC: type: %d, --&amp;gt;\r\n&amp;quot;, msg.param1);&lt;br /&gt;
            switch (msg.param1)&lt;br /&gt;
            {&lt;br /&gt;
            case URC_SYS_INIT_STATE_IND:&lt;br /&gt;
                APP_DEBUG(&amp;quot;&amp;lt;-- Sys Init Status %d --&amp;gt;\r\n&amp;quot;, msg.param2);&lt;br /&gt;
                break;&lt;br /&gt;
            case URC_SIM_CARD_STATE_IND:&lt;br /&gt;
                APP_DEBUG(&amp;quot;&amp;lt;-- SIM Card Status:%d --&amp;gt;\r\n&amp;quot;, msg.param2);&lt;br /&gt;
                break;            &lt;br /&gt;
            case URC_EGPRS_NW_STATE_IND:&lt;br /&gt;
                APP_DEBUG(&amp;quot;&amp;lt;-- EGPRS Network Status:%d --&amp;gt;\r\n&amp;quot;, msg.param2);&lt;br /&gt;
                break;&lt;br /&gt;
            case URC_CFUN_STATE_IND:&lt;br /&gt;
                APP_DEBUG(&amp;quot;&amp;lt;-- CFUN Status:%d --&amp;gt;\r\n&amp;quot;, msg.param2);&lt;br /&gt;
                break;  &lt;br /&gt;
            default:&lt;br /&gt;
                APP_DEBUG(&amp;quot;&amp;lt;-- Other URC: type=%d\r\n&amp;quot;, msg.param1);&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
            break;&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;开始不必研究代码的细节，先要熟悉结构，了解大方向，最后再深入代码。&lt;br /&gt;
&lt;br /&gt;
=== 编译代码 ===&lt;br /&gt;
用惯了IAR或者KEIL的开发者来说，非常不习惯GCC的命令行的编译方式。&lt;br /&gt;
&lt;br /&gt;
其实不要太过于担心，GCC很好用，开发时只需要两个命令，make 和make clean。并且稍微麻烦一点的Makefile文件，SDK也已经提供了。所以我们只需要在命令行中输入make即可。&lt;br /&gt;
&lt;br /&gt;
进入SDK的根目录，双击运行MS-DOS，运行Windows的命令行工具。&lt;br /&gt;
&lt;br /&gt;
然后输入命令：&amp;lt;code&amp;gt;make new&amp;lt;/code&amp;gt;，为什么是make new，我们会在后面的Makefile一节讲解。&lt;br /&gt;
&lt;br /&gt;
如下图所示，开发编译代码，打印编译细节。&lt;br /&gt;
&lt;br /&gt;
[[文件:BC26OpenSDK编译.png|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
如果编译顺利，会看到编程成功的提示，如下图所示。&lt;br /&gt;
&lt;br /&gt;
[[文件:BC26编译成功截图.png|无框|677x677像素]]&lt;br /&gt;
&lt;br /&gt;
编译成功后，可以在build/gcc目录下看到app的bin文件了。如下图所示：&lt;br /&gt;
&lt;br /&gt;
app_image_bin.cfg是烧录app的配置文件。&lt;br /&gt;
&lt;br /&gt;
APPGS3MDM32A01.bin是最终得到的APP二进制文件。&lt;br /&gt;
&lt;br /&gt;
build.log是编译过程中输出的信息。编译过程中的警告、错误等消息，都会保存在这个文件中。&lt;br /&gt;
&lt;br /&gt;
[[文件:BC26生成APP固件目录结构.png|无框|620x620像素]]&lt;br /&gt;
&lt;br /&gt;
如果再使用make clean命令，会将这些生成的文件清理掉，以准备重新编译。&lt;br /&gt;
&lt;br /&gt;
=== 下载程序 ===&lt;br /&gt;
注意：由于BC26模块硬件版本需要与SDK固件匹配。&lt;br /&gt;
* 硬件版本为BC26NB-04-STD的模块，需要使用BC26NBR01A03的模块固件和SDK固件。&lt;br /&gt;
* 硬件版本为BC26NC-04-STD的模块，需要使用BC26NBR01A07的模块固件和SDK固件。&lt;br /&gt;
使用MDK的IOT_Flash_Tool来烧写程序。IOT_Flash_Tool烧写软件位于SDK的tools目录下。进入目录：tools\IOT_Flash_Tool\win，双击打开FlashTool.exe。&lt;br /&gt;
&lt;br /&gt;
由于SDK的版本需要与模块的固件匹配，因此第一次使用时，需要先烧写一次（一个模块仅需要烧写一次，后续只需要单独更新app即可）BC26核心固件：BC26NBR01Axx_xxx，注意需要与硬件匹配。&lt;br /&gt;
&lt;br /&gt;
FlashTool通过BC26的主串口（AT指令用的串口）来烧写程序，烧写程序时，需要BC26硬件上做一些配合，详细的烧写流程如下。&lt;br /&gt;
&lt;br /&gt;
首先我们来烧写BC26核心固件，固件版本为：BC26NBR01Axx_xxx。注意需要与硬件匹配。&lt;br /&gt;
&lt;br /&gt;
'''步骤一： 在DownLoad页面下选择对应串口号，并选择固件烧录配置文件.'''&lt;br /&gt;
&lt;br /&gt;
如下图所示。&lt;br /&gt;
&lt;br /&gt;
[[文件:FlashTool烧写BC26固件.png|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
'''步骤二：将NB260的PEN保持高电平不动（即持续拉低BC26的PWRKEY信号，让其开机状态，且保持）。'''&lt;br /&gt;
&lt;br /&gt;
'''步骤三：点击FlashTool的Start按钮，准备准备烧写'''。&lt;br /&gt;
&lt;br /&gt;
'''步骤四：使NB260复位。'''&lt;br /&gt;
&lt;br /&gt;
这是因为FlashTool使用BC26的主串口烧写固件，需要与BC26进行一些命令上的交互，且该交互过程只能在模块重启的瞬间。&lt;br /&gt;
&lt;br /&gt;
因此需要BC26保持开机状态，然后按复位。&lt;br /&gt;
&lt;br /&gt;
此时，就可以看到FlashTool在烧写固件了，等待一会烧写成功，如下图所示：&lt;br /&gt;
&lt;br /&gt;
[[文件:FlashTool烧写BC26固件成功.png|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
'''步骤五：烧写App固件'''&lt;br /&gt;
&lt;br /&gt;
使用相同方法烧写app固件，app的固件烧录配置文件位于：build\gcc中。文件名为：app_image_bin.cfg。&lt;br /&gt;
&lt;br /&gt;
假如模块的固件版本与SDK匹配（比如已经烧写过一次），只需要烧写app固件即可，无需重复烧写模块核心固件。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=如果模块中已烧写了APP固件，再次烧写的步骤如下：1、模块复位或重新上电；2、点击IoT Flash Tool的Start按钮等待烧写；3、此时，使模块开机。4、此刻会开始烧写App固件，若失败，重复步骤1~3|type=info}}&lt;br /&gt;
&lt;br /&gt;
=== 擦除程序 ===&lt;br /&gt;
如果要擦除已烧写的App固件，请按照本节步骤操作。 &lt;br /&gt;
&lt;br /&gt;
{{Note|text=注意：务必确认擦除的地址和长度，否则会导致擦掉模块的IMEI码等信息，一旦擦除，无法恢复！|type=danger}}&lt;br /&gt;
&lt;br /&gt;
'''步骤一：记录App固件的BeginAddress、EndAddress以及AddressType''' &lt;br /&gt;
&lt;br /&gt;
可以在烧写App固件的界面下找到。如下图所示。&lt;br /&gt;
[[文件:FlashTool烧写BC26的App固件.jpg|边框|居中|无框|811x811像素]]&lt;br /&gt;
'''步骤二：进入Format页面下，选择对应的串口号。设置Manual Format参数。'''&lt;br /&gt;
&lt;br /&gt;
根据步骤一中的参数设置BeginAddress，Length等于EndAddress减去BeginAddress。&lt;br /&gt;
[[文件:FlashTool擦除BC26的App固件.jpg|边框|居中|无框|811x811像素]]&lt;br /&gt;
'''步骤三：点击Start准备擦除。'''&lt;br /&gt;
&lt;br /&gt;
'''步骤四：模块重新上电（复位无效），然后使模块开机（拉高PEN引脚500ms以上）。'''&lt;br /&gt;
&lt;br /&gt;
'''步骤五：开始擦除，等到擦除成功。若失败，请重复步骤1~4。'''&lt;br /&gt;
&lt;br /&gt;
=== 功能验证 ===&lt;br /&gt;
APP固件烧写结束后，我们来验证APP功能是否正常。&lt;br /&gt;
&lt;br /&gt;
首先，恢复NB260硬件PEN信号的低电平状态，然后复位NB260。&lt;br /&gt;
&lt;br /&gt;
任意串口调试助手，设置波特率115200，其他参数默认，打开与NB260对应的串口。此时，将PEN拉高使模块开机。&lt;br /&gt;
&lt;br /&gt;
此时，可以看到BC26中APP固件输出的调试信息，可以看到，除了原先模块正常发送的信息外，还打印了额外的调试信息，说明APP已经成功运行。&lt;br /&gt;
&lt;br /&gt;
测试用的app固件代码仍然支持串口AT指令功能，大家可以根据自己的需要增删app源码。&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
F1: 0000 0000&lt;br /&gt;
V0: 0000 0000 [0001]&lt;br /&gt;
00: 0006 000C&lt;br /&gt;
01: 0000 0000&lt;br /&gt;
U0: 0000 0001 [0000]&lt;br /&gt;
T0: 0000 00B4&lt;br /&gt;
Leaving the BROM&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
RDY&lt;br /&gt;
&lt;br /&gt;
+CFUN: 1&lt;br /&gt;
OpenCPU: Customer Application&lt;br /&gt;
&amp;lt;-- RIL is ready --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
+CPIN: READY&lt;br /&gt;
&amp;lt;-- SIM Card Status:1 --&amp;gt;&lt;br /&gt;
&amp;lt;-- EGPRS Network Status:2 --&amp;gt;&lt;br /&gt;
AT+CEREG?&lt;br /&gt;
[ATResponse_Handler] &lt;br /&gt;
+CEREG: 1&lt;br /&gt;
&lt;br /&gt;
[ATResponse_Handler] &lt;br /&gt;
+CEREG: 1,1&lt;br /&gt;
&lt;br /&gt;
[ATResponse_Handler] &lt;br /&gt;
OK&lt;br /&gt;
&lt;br /&gt;
&amp;lt;-- EGPRS Network Status:1 --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
+IP: 10.45.226.144&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 调试程序 ===&lt;br /&gt;
由于BC26的是通过串口烧写固件，因此无法像其他单片机那样，使用仿真器来单步调试，那如何调试程序呢？&lt;br /&gt;
&lt;br /&gt;
建议是使用串口打印+硬件状态（例如指示灯状态）&lt;br /&gt;
&lt;br /&gt;
例如上面的烧写的app代码，使用&amp;lt;code&amp;gt;APP_DEBUG(&amp;quot;&amp;lt;-- RIL is ready --&amp;gt;\r\n&amp;quot;);&amp;lt;/code&amp;gt;来输出调试信息。&lt;br /&gt;
&lt;br /&gt;
=== Makefile简介 ===&lt;br /&gt;
熟悉linux开发的同学一定很了解Makefile。&lt;br /&gt;
&lt;br /&gt;
Makefile的作用是告诉编译器，如何将源码编译成obj中间文件（*.c 变成 *.o），然后如何链接中间文件编程二进制文件（*.o 编程 *.bin），并且设置哪些源码要编译，使用的头文件件在哪等等。&lt;br /&gt;
&lt;br /&gt;
在Windows下的代码开发，一般IDE都会把这些工作做了，而在linux中或者使用gcc，都需要编写makefile来控制。&lt;br /&gt;
&lt;br /&gt;
BC26-OpenCPU的makefile文件位于：\make\gcc 目录下，内容如下：&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
# Configure GCC installation path, and GCC version.&lt;br /&gt;
# To execute &amp;quot;arm-none-eabi-gcc -v&amp;quot; in command line can get the current gcc version &lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
GCC_INSTALL_PATH=tools\gcc\win\gcc-arm-none-eabi  #设置gcc编译器路径&lt;br /&gt;
GCC_VERSION=4.8.3&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
C_PREDEF=-D __CUSTOMER_CODE__&lt;br /&gt;
&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
# Configure version and out target&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
PLATFORM = APPGS3MD&lt;br /&gt;
MEMORY   = M32&lt;br /&gt;
VERSION  = A01&lt;br /&gt;
TARGET   = $(strip $(PLATFORM))$(strip $(MEMORY))$(strip $(VERSION))  #设置目标文件名，也就是APPGS3MDM3201.bin&lt;br /&gt;
&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
# Configure the include directories&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
INCS =  -I $(ENV_INC)                #设置系统头文件路径&lt;br /&gt;
INCS += -I ./           \            #设置额外的sdk库和用于代码的头文件目录。&lt;br /&gt;
        -I include      \&lt;br /&gt;
        -I ril/inc      \&lt;br /&gt;
        -I custom/config   \&lt;br /&gt;
		&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
# Configure source code dirctories&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
SRC_DIRS=example    \               #设置需要编译的源码目录&lt;br /&gt;
		 custom     \&lt;br /&gt;
		 custom\config     \&lt;br /&gt;
		 ril\src    \&lt;br /&gt;
		 &lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
# Configure source code files to compile in the source code directories&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
SRC_SYS=$(wildcard custom/config/*.c)    #设置具体要编译的源码文件，这里匹配目录下的所有c文件&lt;br /&gt;
SRC_SYS_RIL=$(wildcard ril/src/*.c)&lt;br /&gt;
SRC_EXAMPLE=$(wildcard example/*.c)&lt;br /&gt;
SRC_CUS=$(wildcard custom/*.c)&lt;br /&gt;
&lt;br /&gt;
OBJS=\                     &lt;br /&gt;
	 $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRC_SYS))        \   #设置要生成的中间文件目录和文件名&lt;br /&gt;
	 $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRC_SYS_RIL))    \&lt;br /&gt;
	 $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRC_EXAMPLE))    \&lt;br /&gt;
	 $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRC_CUS))        \&lt;br /&gt;
&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
# Configure user reference library&lt;br /&gt;
#-------------------------------------------------------------------------------&lt;br /&gt;
USERLIB=libs/gcc/app_start.lib&lt;br /&gt;
&lt;br /&gt;
.PHONY: all&lt;br /&gt;
all:&lt;br /&gt;
#	$(warning &amp;lt;-- make all, C_PREDEF=$(C_PREDEF) --&amp;gt;)&lt;br /&gt;
	@$(MAKE) new -f make/gcc/gcc_makefile                  #这里就是make new的由来，开始调用gcc 和makefile开始编译&lt;br /&gt;
&lt;br /&gt;
include make\gcc\gcc_makefiledef&lt;br /&gt;
&lt;br /&gt;
export GCC_INSTALL_PATH C_PREDEF OBJS USERLIB SRC_DIRS&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;总结，makefile并不需要高深的知识，只需要掌握上面几个常用的语法即可。&lt;br /&gt;
&lt;br /&gt;
== 继续开发 ==&lt;br /&gt;
SDK函数接口说明请阅读移远官方文档：《BC26-OpenCPU_User_Guide_V1.0.pdf》。&lt;br /&gt;
&lt;br /&gt;
另外，SDK根目录的example中的代码是官方的示例代码，涵盖所有外设，每个例子建议都做一下实验，掌握主要api的用法，然后再开始自己的项目开发，祝好运！&lt;br /&gt;
&lt;br /&gt;
== 本文视频教程 ==&lt;br /&gt;
测试视频，先了解一下5G。  {{优酷|id=XMzk4NTA0OTU5Mg}}&lt;br /&gt;
&lt;br /&gt;
== 本文参考 ==&lt;br /&gt;
&amp;lt;references group=&amp;quot;手册&amp;quot; /&amp;gt;&lt;br /&gt;
[[分类:NB-IOT]]&lt;br /&gt;
[[分类:NB260]]&lt;br /&gt;
[[分类:软件手册]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK%E5%8D%8F%E8%AE%AE%E6%A0%88%E5%AE%9E%E9%AA%8C&amp;diff=2877</id>
		<title>NRF52832DK协议栈实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK%E5%8D%8F%E8%AE%AE%E6%A0%88%E5%AE%9E%E9%AA%8C&amp;diff=2877"/>
		<updated>2020-05-20T06:14:10Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 实验现象 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;我们的蓝牙协议实验部分，将会给大家带来最直观的蓝牙协议部分的学习，我们通过拆分的方式，带领大家深入了解蓝牙协议的主要功能部分。&lt;br /&gt;
&lt;br /&gt;
在进行蓝牙协议实验学习之前，我们有一些基础但十分重要的蓝牙协议介绍分享给大家，这个部分的蓝牙协议不像蓝牙协议手册那样，对每个参数进行说明，洋洋洒洒会有好几千页的说明文档，这样大家也很难去看完并记忆。&lt;br /&gt;
&lt;br /&gt;
我们针对的仅仅是大家在利用协议栈开发个人需求的时候，绕不开的蓝牙协议部分的内容进行介绍。首先我们先分析一下蓝牙主从机通信，都有哪些重要点。&lt;br /&gt;
&lt;br /&gt;
主机部分：扫描、发起连接、发现服务、通信。&lt;br /&gt;
&lt;br /&gt;
从机部分：广播、注册服务、被连接、通信。&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+蓝牙主从机通信协议要点&lt;br /&gt;
! colspan=&amp;quot;4&amp;quot; |主机&lt;br /&gt;
! colspan=&amp;quot;4&amp;quot; |从机&lt;br /&gt;
|-&lt;br /&gt;
!扫描&lt;br /&gt;
!连接&lt;br /&gt;
!服务&lt;br /&gt;
!通信&lt;br /&gt;
!广播&lt;br /&gt;
!服务&lt;br /&gt;
!连接&lt;br /&gt;
!通信&lt;br /&gt;
|-&lt;br /&gt;
|扫描参数①&lt;br /&gt;
|连接参数③&lt;br /&gt;
|服务句柄⑦&lt;br /&gt;
|属性Write⑨&lt;br /&gt;
|广播数据②&lt;br /&gt;
|服务⑥&lt;br /&gt;
|连接参数③&lt;br /&gt;
|属性Notify⑨&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|连接句柄④&lt;br /&gt;
|使能通知⑧&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|连接句柄④&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|MTU大小⑤&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|MTU大小⑤&lt;br /&gt;
|&lt;br /&gt;
|}{{Note|text=实验源码位于百度云盘归档资料中：归档资料/1-协议栈SDK/谷雨实验源码包/，代码的使用请参考《NRF52832DK入门手册》的蓝牙协议栈SDK一节|type=tips}}&lt;br /&gt;
&lt;br /&gt;
==== 扫描参数① ====&lt;br /&gt;
&lt;br /&gt;
===== 主机扫描核心参数 =====&lt;br /&gt;
主机扫描核心参数主要是4个，也就是说这4个参数是不可获取的，必须要配置的。分别是扫描间隔interval、扫描窗口window、扫描持续时长duration、扫描模式active。&lt;br /&gt;
&lt;br /&gt;
扫描间隔interval：两个连续的扫描窗口的起始时间的时间差，这个很好理解，就是第一次扫描起始时间和第二次扫描起始时间的时间差。&lt;br /&gt;
&lt;br /&gt;
扫描窗口window：就是指的单次扫描的时间。&lt;br /&gt;
&lt;br /&gt;
扫描持续时长duration：我们发起一次扫描持续的时间，发起一次扫描包含了N个扫描窗口。&lt;br /&gt;
&lt;br /&gt;
所以我们的扫描参数需要满足如下条件：&lt;br /&gt;
&lt;br /&gt;
1、duration ≥ interval ≥ window&lt;br /&gt;
&lt;br /&gt;
2、协议规定window和interval不能超过10.24s&lt;br /&gt;
&lt;br /&gt;
协议栈中一些特殊参数情况的处理：&lt;br /&gt;
&lt;br /&gt;
1、duration设置为0，持续扫描。也就是说设置为0代表无穷大&lt;br /&gt;
&lt;br /&gt;
2、duration小于interval，则至少广播一次&lt;br /&gt;
&lt;br /&gt;
扫描模式：&lt;br /&gt;
&lt;br /&gt;
1、active配置1，主动扫描模式，可获取从设备的广播数据以及扫描回调数据&lt;br /&gt;
&lt;br /&gt;
2、active配置0，被动扫描模式，只可以获取从设备的广播数据&lt;br /&gt;
[[文件:BLE技术 扫描窗口和扫描间隔.jpg|居中|无框|520x520像素]]&lt;br /&gt;
===== 主机扫描特殊应用参数 =====&lt;br /&gt;
extended：这个是用于BLE5.0协议中新增的大广播包数据，定义为1，才可以获取到外部大广播包&lt;br /&gt;
&lt;br /&gt;
filter_policy：过滤扫描的参数，这个是用于我们扫描的时候，过滤出我们想要的设备&lt;br /&gt;
&lt;br /&gt;
scan_phys：扫描的PHYs，这个同样是BLE5.0协议中新增的物理层协议，分别BLE_GAP_PHY_1MBPS、BLE_GAP_PHY_2MBPS等&lt;br /&gt;
&lt;br /&gt;
channel_mask：扫描的信道（暂时不清楚此参数如何使用）&lt;br /&gt;
&lt;br /&gt;
==== 广播数据② ====&lt;br /&gt;
&lt;br /&gt;
===== BLE4.x广播数据 =====&lt;br /&gt;
BLE4.x的蓝牙广播数据包，最大是31byte，遵循的方式如下，首先是数据的长度、紧接着是数据类型，最后才是数据内容。&lt;br /&gt;
&lt;br /&gt;
数据长度：某一个类型的数据长度，注意长度包含数据类型及数据内容。&lt;br /&gt;
&lt;br /&gt;
数据类型：BLE协议规定的数据类型，例如可发现标志flag是0x01，local name是0x09。&lt;br /&gt;
&lt;br /&gt;
数据内容：用户自定义数据。&lt;br /&gt;
{{Note|text=包含广播数据和扫描回调数据：&lt;br /&gt;
所有的广播数据内容，用户都是可以自定义，但一定要符合上面所说的数据结构，不然BLE设备无法正确识别。|type=tips}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+广播包的数据格式&lt;br /&gt;
!数据长度&lt;br /&gt;
!数据类型&lt;br /&gt;
!数据内容&lt;br /&gt;
|-&lt;br /&gt;
|0x02&lt;br /&gt;
|0x01（flag 可发现标志）&lt;br /&gt;
|0x06&lt;br /&gt;
|-&lt;br /&gt;
|0x06&lt;br /&gt;
|0x09（name 本地名称）&lt;br /&gt;
|0x4759303031（“GY001”）&lt;br /&gt;
|-&lt;br /&gt;
|...&lt;br /&gt;
|...&lt;br /&gt;
|...&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===== BLE5.x新增大广播包数据 =====&lt;br /&gt;
&lt;br /&gt;
==== 连接参数③ ====&lt;br /&gt;
参数如下：&lt;br /&gt;
&lt;br /&gt;
'''Connection Interval连接间隔'''：在BLE连接中，使用跳频方案。两个设备在特定时间仅在特定频道上彼此发送和接收数据。这些设备稍后在新的通道（协议栈的链路层处理通道切换）上通过这个约定的时间相遇。这次用于收发数据的相遇称为连接事件。如果没有要发送或接收的应用数据，则两台设备交换链路层数据来维护连接。两个连接事件之间的时间跨度，是以1.25 ms为单位，连接间隔的范围从最小值6（7.5 ms）到最大值3200（4.0 s）。&lt;br /&gt;
&lt;br /&gt;
不同的应用可能需要不同的连接间隔。&lt;br /&gt;
[[文件:连接参数1.jpg|边框|居中|无框|603x603像素]]&lt;br /&gt;
&lt;br /&gt;
'''Slave Latency从机延迟'''，此参数为从机（外设设备）提供跳过多个连接事件的能力。这种能力给外设设备更多的灵活性。如果外设没有要发送的数据，则可以跳过连接事件，保持睡眠并节省电量。外设设备选择是否在每个连接事件时间点上唤醒。虽然外设可以跳过连接事件，但不能超出从延迟参数允许的最大值。&lt;br /&gt;
[[文件:连接参数2.jpg|边框|居中|无框|525x525像素]]&lt;br /&gt;
&lt;br /&gt;
'''Supervision Time-out监控超时'''，是两次成功连接事件之间的最长时间。如果在此时间内没有成功的连接事件，设备将终止连接并返回到未连接状态。该参数值以10 ms为单位，监控超时值可以从最小值10（100 ms）到3200（32.0 s）。超时必须大于有效的连接间隔。&lt;br /&gt;
&lt;br /&gt;
===== Effective Connection Interval有效连接间隔 =====&lt;br /&gt;
有效连接间隔等于两个连接事件之间的时间跨度，假设从机跳过最大数量的连接事件，且允许从机延迟（如果从机延迟设置为0，则有效连接间隔等于实际连接间隔，）。&lt;br /&gt;
&lt;br /&gt;
从机延迟表示可以跳过的最大事件数。该数字的范围可以从最小值0（意味着不能跳过连接事件）到最大值499。最大值不能使有效连接间隔（见下列公式）大于16秒。间隔可以使用以下公式计算：&lt;br /&gt;
&lt;br /&gt;
Effective Connection Interval '''=''' '''('''Connection Interval''')''' × '''('''1 '''+''' '''['''Slave Latency'''])'''&lt;br /&gt;
&lt;br /&gt;
Consider the following example''':'''&lt;br /&gt;
* Connection Interval''':''' 80 '''('''100 ms''')'''&lt;br /&gt;
* Slave Latency''':''' 4&lt;br /&gt;
* Effective Connection Interval''':''' '''('''100 ms''')''' × '''('''1 '''+''' 4''')''' '''=''' 500 ms&lt;br /&gt;
当没有数据从从机发送到主机时，从机每500ms一个连接事件交互一次。&lt;br /&gt;
&lt;br /&gt;
===== 连接参数的注意事项 =====&lt;br /&gt;
在许多应用中，从机跳过最大连接事件数。选择正确的连接参数组在低功耗蓝牙设备的功率优化中起重要作用。以下列表给出了连接参数设置中权衡的总体概述。&lt;br /&gt;
&lt;br /&gt;
减少连接间隔如下：&lt;br /&gt;
* 增加两个设备的功耗&lt;br /&gt;
* 增加双向吞吐量&lt;br /&gt;
* 减少任一方向发送数据的时间&lt;br /&gt;
增加连接间隔如下：&lt;br /&gt;
* 降低两个设备的功耗&lt;br /&gt;
* 降低双向吞吐量&lt;br /&gt;
* 增加任一方向发送数据的时间&lt;br /&gt;
减少从机延迟（或将其设置为零）如下：&lt;br /&gt;
* 增加外围设备的功耗&lt;br /&gt;
* 减少外围设备接收从中央设备发送的数据的时间&lt;br /&gt;
增加从机延迟如下：&lt;br /&gt;
* 在周边没有数据发送期间，可以降低外设的功耗到主机设备&lt;br /&gt;
* 增加外设设备接收从主机设备发送的数据的时间&lt;br /&gt;
&lt;br /&gt;
==== 连接句柄④ ====&lt;br /&gt;
设备的连接句柄范围是从0x0000-0xFFFD，当然我们实际使用，也连接不了这么多的设备。&lt;br /&gt;
&lt;br /&gt;
连接的句柄是按照连接的先后顺序分配，开发者无法自己指定，也就是说第一个与之连接的设备将会被分配连接句柄（connHandle）0x0000，第二个是0x0001，以此类推。&lt;br /&gt;
&lt;br /&gt;
为什么句柄的最终范围是0xFFFD，因为后面的0xFFFE与0xFFFF是有特殊用于含义的。&lt;br /&gt;
&lt;br /&gt;
0xFFFE：代表的是正在连接的设备的句柄。当我们发起对一个设备的连接，但是迟迟没有连接上，我们可以调用断开连接的函数，利用这个0xFFFE句柄断开这个连接。&lt;br /&gt;
&lt;br /&gt;
0xFFFF：断开的句柄。当设备与之断开连接之后，句柄就会返回为0xFFFF。&lt;br /&gt;
&lt;br /&gt;
==== MTU大小⑤ ====&lt;br /&gt;
MTU的大小，在BLE4.0的是时候，最大是只有27byte，当更新到BLE4.1向后，我们支持的MTU最大是251字节。&lt;br /&gt;
&lt;br /&gt;
低功耗蓝牙协议栈支持链路层L2CAP PDU的分片和重组。这种分段支持允许构建在L2CAP之上的L2CAP和更高级协议（如属性协议（ATT））使用较大的有效负载大小，并减少与较大数据事务相关的开销。当使用分片时，较大的分组被分割成多个链路层分组，并由对等体设备的链路层重新组合。&lt;br /&gt;
[[文件:MTU1.jpg|边框|居中|无框|745x745像素]]&lt;br /&gt;
L2CAP PDU的大小还定义了属性协议最大传输单元（ATT_MTU）的大小。默认情况下，LE设备假设L2CAP PDU的大小为27字节，这对应于可以在单个连接事件数据包中传输的LE数据包的最大大小。在这种情况下，L2CAP协议头为4字节，导致ATT_MTU的默认大小为23。&lt;br /&gt;
&lt;br /&gt;
注意： 使用LE数据长度分机功能时，LE包的长度最多可达251字节。&lt;br /&gt;
&lt;br /&gt;
==== 服务⑥ ====&lt;br /&gt;
&lt;br /&gt;
==== 服务句柄⑦ ====&lt;br /&gt;
&lt;br /&gt;
==== 使能通知⑧ ====&lt;br /&gt;
&lt;br /&gt;
==== 属性⑨ ====&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙协议实验目录 ===&lt;br /&gt;
蓝牙协议实验部分，我们借由串口透传实验，一步一步拆分，给大家介绍蓝牙的协议方面。&lt;br /&gt;
&lt;br /&gt;
下面是我们的实验列表，里面的实验是循序渐进的进行深入讲解蓝牙协议的，建议大家不要跳跃学习。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+实验列表&lt;br /&gt;
!主机实验编号&lt;br /&gt;
!主机实验内容&lt;br /&gt;
|-&lt;br /&gt;
|1.0_ble_central_pm&lt;br /&gt;
|主机低功耗实验&lt;br /&gt;
|-&lt;br /&gt;
|1.1_ble_central_log&lt;br /&gt;
|主机LOG打印实验&lt;br /&gt;
|-&lt;br /&gt;
|1.2_ble_central_scan_all&lt;br /&gt;
|主机通用扫描实验&lt;br /&gt;
|-&lt;br /&gt;
|1.3_ble_central_scan_filter&lt;br /&gt;
|主机过滤扫描实验&lt;br /&gt;
|-&lt;br /&gt;
|1.4_ble_central_scan_whitelist&lt;br /&gt;
|主机白名单扫描实验&lt;br /&gt;
|-&lt;br /&gt;
|1.5_ble_central_conn_all&lt;br /&gt;
|主机通用连接实验&lt;br /&gt;
|-&lt;br /&gt;
|1.6_ble_central_conn_filter&lt;br /&gt;
|主机过滤连接实验&lt;br /&gt;
|-&lt;br /&gt;
|1.7_ble_central_update_connParam&lt;br /&gt;
|主机连接参数更新实验&lt;br /&gt;
|-&lt;br /&gt;
|1.8_ble_central_update_mtu&lt;br /&gt;
|主机MTU大小配置实验&lt;br /&gt;
|-&lt;br /&gt;
|1.9_ble_central_profile_led&lt;br /&gt;
|主机服务Client实验（Write/Read属性）&lt;br /&gt;
|-&lt;br /&gt;
|1.0_ble_central_profile_btn&lt;br /&gt;
|主机服务Client实验（Notify属性）&lt;br /&gt;
|-&lt;br /&gt;
|1.11_ble_central_profile_nus&lt;br /&gt;
|主机获取NUS服务实验&lt;br /&gt;
|-&lt;br /&gt;
|1.12_ble_central_nus_communication&lt;br /&gt;
|主机利用NUS服务收发通信实验&lt;br /&gt;
|-&lt;br /&gt;
|2.0_ble_peripheral_pm&lt;br /&gt;
|从机低功耗实验&lt;br /&gt;
|-&lt;br /&gt;
|2.1_ble_peripheral_log&lt;br /&gt;
|从机LOG打印实验&lt;br /&gt;
|-&lt;br /&gt;
|2.2_ble_peripheral_adv_all&lt;br /&gt;
|从机通用广播实验&lt;br /&gt;
|-&lt;br /&gt;
|2.3_ble_peripheral_adv_filter&lt;br /&gt;
|从机过滤广播实验&lt;br /&gt;
|-&lt;br /&gt;
|2.4_ble_peripheral_adv_whitelist&lt;br /&gt;
|从机白名单广播实验&lt;br /&gt;
|-&lt;br /&gt;
|2.5_ble_peripheral_conn_all&lt;br /&gt;
|从机通用连接实验&lt;br /&gt;
|-&lt;br /&gt;
|2.6_ble_peripheral_conn_filter&lt;br /&gt;
|从机过滤连接实验&lt;br /&gt;
|-&lt;br /&gt;
|2.7_ble_peripheral_update_connParam&lt;br /&gt;
|从机连接参数请求更新实验&lt;br /&gt;
|-&lt;br /&gt;
|2.8_ble_peripheral_update_mtu&lt;br /&gt;
|从机MTU大小配置实验&lt;br /&gt;
|-&lt;br /&gt;
|2.9_ble_peripheral_profile_led&lt;br /&gt;
|从机服务Server实验（Write属性）&lt;br /&gt;
|-&lt;br /&gt;
|2.10_ble_peripheral_profile_btn&lt;br /&gt;
|从机服务Server实验（Notify属性）&lt;br /&gt;
|-&lt;br /&gt;
|2.11_ble_peripheral_profile_nus&lt;br /&gt;
|从机注册NUS服务实验&lt;br /&gt;
|-&lt;br /&gt;
|2.12_ble_peripheral_nus_communication&lt;br /&gt;
|从机利用NUS服务收发通信实验&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 样例工程结构简介 ===&lt;br /&gt;
我们首先借由协议栈自带的串口透传例程，给大家说明一下蓝牙工程的结构，我们打开工程，查看workspace部分（每一个Group都是同等地位，以下说明不分先后）。&lt;br /&gt;
[[文件:Workspace group.png|边框|居中|无框|436x436像素]]&lt;br /&gt;
Application：主要就两个文件，一个是主函数 main.c 文件，这个文件是大家后续编程主要修改的文件。另一个 sdk_config.h 配置文件，NRF52相关的蓝牙参数、外设参数等等，我们在使用之前都需要在这个文件中配置或者是使能。&lt;br /&gt;
[[文件:Workspace application.png|边框|居中|无框|351x351像素]]&lt;br /&gt;
Board Definition：nordic给我们用户实现好的有关按键和LED灯的控制的文件，这个文件主要是nordic方便自己例程的展示实现的，在后续的编程中大家可以选择使用，或者自己根据个人需要重新实现。&lt;br /&gt;
[[文件:Workspace boarddefinition.png|边框|居中|无框|345x345像素]]&lt;br /&gt;
Board Support：这个分组和Board Definition是一起作用的，就是利用按键和LED灯进行一些功能的展示，比如按键的外部中断唤醒，比如用LED来指示当前蓝牙的状态等。也是一样的，后续编程中大家可以根据个人需要选择保留还是自己实现。&lt;br /&gt;
[[文件:Workspace boardsupport.png|边框|居中|无框|344x344像素]]&lt;br /&gt;
None：这个分组也是包含了两个文件，这两个文件都是和芯片相关的。arm_startup_nrf52.s 是芯片的启动文件，这个文件配置了芯片启动时的堆栈空间，中断向量等等参数。system_nrf52.c 文件是芯片的系统文件，配置了芯片的RAM、时钟、射频以及引脚端口等等。这两个文件都是芯片的必要文件，所以每一个工程中都是需要包含的。&lt;br /&gt;
[[文件:Workspace none.png|边框|居中|无框|332x332像素]]&lt;br /&gt;
nRF_BLE：这个分组包含的是蓝牙协议相关的配置文件，也就是我们协议栈实验部分主要要讲解的内容。主要有如下几个部分，广播、连接、扫描等等，由于我们展示的例程是串口透传从机，所以这边看不到扫描相关的文件。&lt;br /&gt;
[[文件:Workspace nrfble.png|边框|居中|无框|335x335像素]]&lt;br /&gt;
nRF_BLE_Services：这个分组是用于存放我们的蓝牙profile服务文件，像我们串口透传例程，就包含了NUS（nordic uart service）的通用配置文件。&lt;br /&gt;
[[文件:Workspace nrfbleservices.png|边框|居中|无框|325x325像素]]&lt;br /&gt;
nRF_Drivers：这个分组包含的是外设驱动文件，其中前缀是nrf开头的代表的是老的驱动文件，nrfx代表的新的驱动文件，我们当前使用的SDK15.2兼容新旧两种外设驱动文件。&lt;br /&gt;
[[文件:Workspace nrfdrivers.png|边框|居中|无框|330x330像素]]&lt;br /&gt;
nRF_Libraries：库函数文件的分组，里面包含了两个大类。一个是以app为前缀的文件，这部分是留给我们用户在应用层调用的库文件，基本上是按键、时钟等等一些外设的库。另一部分是以nrf为前缀的库文件，这个是和芯片相关的库，包括内存分配、打印以及电源管理等等。&lt;br /&gt;
[[文件:Workspace nrflibraries.png|边框|居中|无框|452x452像素]]&lt;br /&gt;
nRF_Log：这个是nordic做好的一个打印调试信息的功能分组，主要分为两个，一个是利用Jlink仿真器实现的RTT，另一个则是利用串口打印。这个部分的功能在下面的实验中有讲解。&lt;br /&gt;
[[文件:Workspace nrflog.png|边框|居中|无框|333x333像素]]&lt;br /&gt;
nRF_Segger_RTT：这个部分是Segger公司实现好的RTT的驱动文件，我们使用的时候只需要将文件添加到我们的工程中，然后调用API接口就行，这样我们就能将调试信息通过Jlink打印到RTT Viewer显示。这个部分的功能是配置上面的Log打印使用的，方便我们开发者调试程序。&lt;br /&gt;
[[文件:Workspace nrfseggerrtt.png|边框|居中|无框|324x324像素]]&lt;br /&gt;
nRF_SoftDevice：这里包含的文件主要是配置协议栈初始化的时候协议栈的参数设定，由于协议栈实际上是不开源的，而是留下了配置接口，这些配置接口通过客户配置相关协议栈的参数来设置协议栈运行状态。也就是在我们的stack初始化部分去初始化softdevice。&lt;br /&gt;
[[文件:Workspace softdevice.png|边框|居中|无框|327x327像素]]UTF8/UTF16 convert：utf8与utf16之间的转换。[[文件:Workspace utf8utf16.png|边框|居中|无框|359x359像素]]Output：文件输出分组，里面存放了两个文件，一个是图像数据文件.map，一个是可执行的.out文件。[[文件:Workspace output.png|边框|居中|无框|361x361像素]]&lt;br /&gt;
&lt;br /&gt;
=== 主从机最小工程（低功耗实验） ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
低功耗实验1.0_ble_central_pm与2.0_ble_peripheral_pm，这两个实验给大家带来的是最精简的主机以及从机例程，精简到什么程度呢，只保留了协议栈初始化以及电源管理部分。利用此实验，大家可以测试一下我们的BLE工程进入低功耗模式下的功耗情况。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
我们将万用表串联到电路中，并且打到电流档，此时我们可以看到功耗如下。&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
首先我们查看一下main.c文件，在此文件的mian()函数中，我们首先初始化了电源管理模块，然后初始化了BLE栈堆，最后在while大循环中我们调用空闲状态处理的函数。&lt;br /&gt;
&lt;br /&gt;
接下来我们分别针对这三个部分进行介绍。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;170&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== ble_stack_init()函数及ble_evt_handler()回调函数 ======&lt;br /&gt;
我们查看一下BLE协议栈初始化，这个部分是一个格式化的东西。&lt;br /&gt;
&lt;br /&gt;
首先调用nrf_sdh_enable_request()函数请求使能softdevice，原因在于ble协议、时钟、错误的回调以及中断的配置等，都需要这个sd（softdevice）支持。&lt;br /&gt;
&lt;br /&gt;
接下来我们要配置默认的ble协议，主要包含了RAM起始地址、本工程的角色（根据设备支持连接的角色来判断）、MTU的大小及UUID和属性表大小。&lt;br /&gt;
&lt;br /&gt;
RAM的起始地址在下面的位置可以看到，我们打开'''\ble_ghostyu\1.0_ble_central_pm\LaunchIOT\s132\iar'''下的'''ble_app_ghostyu_iar_nRF5x.icf'''文件（将此文件拖到IAR中就可以打开，可以看到__ICFEDIT_region_RAM_start__   = 0x200029e0）。&lt;br /&gt;
&lt;br /&gt;
然后我们携带RAM起始地址，使能BLE协议栈。&lt;br /&gt;
&lt;br /&gt;
在最后，我们注册了一个ble_evt_handler回调，在这个回调中我们处理BLE的事件返回。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;102&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_stack_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化BLE协议栈&lt;br /&gt;
// details : 初始化SoftDevice、BLE事件中断&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_stack_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    // SD使能请求，配置时钟，配置错误回调，中断（中断优先级栈堆默认设置）&lt;br /&gt;
    err_code = nrf_sdh_enable_request();&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // SD默认配置（如下），SD RAM起始地址配置（0x200029e0）&lt;br /&gt;
    // 作为从机时的最大连接数量0&lt;br /&gt;
    // 作为主机时的最大连接数据1（本工程是主机）&lt;br /&gt;
    // 初始化MTU大小23&lt;br /&gt;
    // 供应商特定的UUID数量1&lt;br /&gt;
    // 属性表大小248（必须是4的倍数，以字节为单位）&lt;br /&gt;
    // 配置服务更改特性数量0&lt;br /&gt;
    uint32_t ram_start = 0;&lt;br /&gt;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能BLE栈堆&lt;br /&gt;
    err_code = nrf_sdh_ble_enable(&amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 注册BLE事件的处理程序，所有BLE的事件都将分派ble_evt_handler回调&lt;br /&gt;
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;ble_evt_handler回调函数，用于处理BLE的事件回调，包含了COMMON、GAP、GATT Client、GATT Server、L2CAP等多种事件类型。在这个例程中，我们只给大家保留了最基础的两个GAP状态，分别为连接状态和断开连接状态。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;68&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_handle: 0x%x&amp;quot;,p_gap_evt-&amp;gt;conn_handle);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== power_management_init()及idle_state_handle()函数 ======&lt;br /&gt;
power_management_init()函数调用底层的nrf_pwr_mgmt_init()函数去初始化电源管理的部分。&lt;br /&gt;
&lt;br /&gt;
idle_state_handle()函数调用底层的nrf_pwr_mgmt_run()函数，用于处理空闲状态的功能（处理完所有的挂起事件，然后进入休眠，直到下一个事件发生）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;138&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : power_management_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化电源管理&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void power_management_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
    err_code = nrf_pwr_mgmt_init();&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : idle_state_handle&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理空闲状态的功能（用于主循环）&lt;br /&gt;
// details : 处理任何挂起的日志操作，然后休眠直到下一个事件发生&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void idle_state_handle(void)&lt;br /&gt;
{&lt;br /&gt;
    if (NRF_LOG_PROCESS() == false)&lt;br /&gt;
    {&lt;br /&gt;
        nrf_pwr_mgmt_run();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== ble_stack_init()函数 ======&lt;br /&gt;
从机部分大体上是和主机一样的，在nordic的协议栈例程中，（如果大家对BLE协议有一定的了解或者使用过其他厂家的BLE芯片）我们可以发现，nordic为了简化BLE的开发难度，可谓是不择手段，他减掉了很多的ble协议相关的内容（这里的减掉指的是放到底层处理，不需要开发者去配置），这其中就包含了GAP Role，也就是蓝牙的角色。&lt;br /&gt;
&lt;br /&gt;
ble_satck_init()函数在主机与从机中，唯一不同的点在初始化参数配置。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;99&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_stack_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化BLE协议栈&lt;br /&gt;
// details : 初始化SoftDevice、BLE事件中断&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_stack_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    // SD使能请求，配置时钟，配置错误回调，中断（中断优先级栈堆默认设置）&lt;br /&gt;
    err_code = nrf_sdh_enable_request();&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // SD默认配置（如下），SD RAM起始地址配置（0x20002a98）&lt;br /&gt;
    // 作为从机时的最大连接数量1（本工程为从机）&lt;br /&gt;
    // 作为主机时的最大连接数据0&lt;br /&gt;
    // 初始化MTU大小23&lt;br /&gt;
    // 供应商特定的UUID数量1&lt;br /&gt;
    // 属性表大小248（必须是4的倍数，以字节为单位）&lt;br /&gt;
    // 配置服务更改特性数量0&lt;br /&gt;
    uint32_t ram_start = 0;&lt;br /&gt;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能BLE栈堆&lt;br /&gt;
    err_code = nrf_sdh_ble_enable(&amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 注册BLE事件的处理程序，所有BLE的事件都将分派ble_evt_handler回调&lt;br /&gt;
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;主机初始化时设置的是作为主机时最大连接数量1，从机初始化时设置的是作为从机时最大连接数量1。这个配置的宏定义是在sdk_config.h文件中。我们go to define，进入nrf_sdh_ble_default_cfg_set()默认参数配置函数中查看，可以找到如下部分。&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_BLE_PERIPHERAL_LINK_COUNT与NRF_SDH_BLE_CENTRAL_LINK_COUNT，在定义最大连接设备数量的同时，也决定了它本身的角色属性。&lt;br /&gt;
&lt;br /&gt;
BLE中我们称Central中心设备为主机（发起连接的设备）、Peripheral外部设备为从机（广播等待被连接的设备）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;103&amp;quot;&amp;gt;&lt;br /&gt;
ret_code_t nrf_sdh_ble_default_cfg_set(uint8_t conn_cfg_tag, uint32_t * p_ram_start)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t ret_code;&lt;br /&gt;
&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    // Configure the connection roles.&lt;br /&gt;
    memset(&amp;amp;ble_cfg, 0, sizeof(ble_cfg));&lt;br /&gt;
    ble_cfg.gap_cfg.role_count_cfg.periph_role_count  = NRF_SDH_BLE_PERIPHERAL_LINK_COUNT;&lt;br /&gt;
#ifndef S112&lt;br /&gt;
    ble_cfg.gap_cfg.role_count_cfg.central_role_count = NRF_SDH_BLE_CENTRAL_LINK_COUNT;&lt;br /&gt;
    ble_cfg.gap_cfg.role_count_cfg.central_sec_count  = MIN(NRF_SDH_BLE_CENTRAL_LINK_COUNT,&lt;br /&gt;
                                                            BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
    return NRF_SUCCESS;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
在低功耗主从机的学习中，我们可以了解到最精简的主从机工程。也就是只包含了softdevice与ble协议栈初始化、以及电源管理初始化。&lt;br /&gt;
&lt;br /&gt;
重点问题：&lt;br /&gt;
&lt;br /&gt;
1.了解如何定义一个BLE工程是主机还是从机，或者其他的属性（主从一体等等）。&lt;br /&gt;
&lt;br /&gt;
2.了解如何进行低功耗处理（main()函数中while循环的idle_state_handle空闲任务处理函数）。&lt;br /&gt;
&lt;br /&gt;
=== LOG打印实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
LOG打印实验1.1_ble_central_log与2.1_ble_peripheral_log，LOG打印实验是在低功耗实验的基础上，新增了LOG打印部分。&lt;br /&gt;
&lt;br /&gt;
那么为什么我们需要添加log功能，主要是因为在开发的过程中，我们几乎很难一次调通我们需求的功能，这个时候我们就需要一种好的方式去帮助我们调通，我们出现问题点在哪（查找bug），以及我们的流程进行到哪边了（流程监视）。说到这里，大家会说为什么不使用在线调试的方式解决问题呢，下面谈一谈我个人的使用感觉（仅针对BLE）。&lt;br /&gt;
&lt;br /&gt;
何时使用在线调试：&lt;br /&gt;
&lt;br /&gt;
1.针对细小的问题点，例如某个参数的数值是否正确&lt;br /&gt;
&lt;br /&gt;
2.针对不影响程序整机蓝牙功能运行的问题点，例如外设功能异常&lt;br /&gt;
&lt;br /&gt;
何时使用log：&lt;br /&gt;
&lt;br /&gt;
1.不能使用在线调试功能的时候（例如蓝牙连接状态下的通信调试，由于连接参数的限制，如果在线打断点调试，会导致异常断开）&lt;br /&gt;
&lt;br /&gt;
2.监测关键节点信息（有些问题会出现在一些特殊情况下，可能需要监测N多次，才会出现一次异常）&lt;br /&gt;
{{Note|text=注意：我们的LOG工程，仅针对RTT部分，没有介绍UART，UART的使用大家可以查看基础实验部分|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
我们打开J-Link RTT Viewer，选择我们的Jlink仿真器，可以看到log打印如下。&lt;br /&gt;
[[文件:Nrf rtt 11.png|边框|居中|无框|656x656像素]]{{Note|text=注意：如果设置了输出，但是RTT中并没有打印，可以尝试关闭RTT Viewer软件重连，另外需要检查sdk_config.h中的NRF_LOG_ENABLED 已时能，即 #define NRF_LOG_ENABLED 1|type=info}}&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
我们在工程中，新增加了nRF_Log与nRF_Segger_RTT分组，这两个分组中分别包含了log打印的相关函数，以及RTT的相关函数。&lt;br /&gt;
{{Note|text=使用RTT，可以从目标微控制器输出信息，并以非常高的速度向应用程序发送输入，而不会影响目标的实时行为。&lt;br /&gt;
&lt;br /&gt;
SEGGER RTT可与任何J-Link模型和任何支持目标处理器一起使用，后者允许后台存储器访问，即Cortex-M和RX目标。&lt;br /&gt;
&lt;br /&gt;
RTT在两个方向上支持多个通道，直到主机和目标，可以用于不同的目的，并为用户提供最大的自由。&lt;br /&gt;
&lt;br /&gt;
默认实现每个方向使用一个通道，用于可打印的终端输入和输出。使用J-Link RTT Viewer，该通道可用于多个“虚拟”终端，允许使用一个目标缓冲区打印到多个窗口（例如一个用于标准输出，一个用于错误输出，一个用于调试输出）。例如，可以使用附加的up（到主机）通道来发送分析或事件跟踪数据。|type=info}}&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
log打印实验相对于前一章节的低功耗实验，新增的功能并不多，我们仅仅添加了LOG的RTT打印功能，这边我们首先还是看下mian函数。&lt;br /&gt;
&lt;br /&gt;
可以看到，我们新增了log_init()的LOG初始化函数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;188&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
&lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;1.1_ble_central_log&amp;quot;);&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== log_init()函数以及底层调用 ======&lt;br /&gt;
我们查看到log_init()函数，先调用NRF_LOG_INIT()函数初始化LOG，然后我们调用NRF_LOG_DEFAULT_BACKENDS_INIT()函数来决定LOG的底层调用。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;140&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : log_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化log打印&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void log_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code = NRF_LOG_INIT(NULL);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    NRF_LOG_DEFAULT_BACKENDS_INIT();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来我们追踪NRF_LOG_DEFAULT_BACKENDS_INIT()函数，在这个函数中，我们会引用到RTT功能。&lt;br /&gt;
&lt;br /&gt;
首先我们go to define，找到NRF_LOG_DEFAULT_BACKENDS_INIT()函数的定义，sdk_config.h中我们定义了NRF_LOG_ENABLED为1，也就是使能LOG打印。&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;61&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @def NRF_LOG_DEFAULT_BACKENDS_INIT&lt;br /&gt;
 * @brief Macro for initializing default backends.&lt;br /&gt;
 *&lt;br /&gt;
 * Each backend enabled in configuration is initialized and added as a backend to the logger.&lt;br /&gt;
 */&lt;br /&gt;
#if NRF_LOG_ENABLED&lt;br /&gt;
#define NRF_LOG_DEFAULT_BACKENDS_INIT() nrf_log_default_backends_init()&lt;br /&gt;
#else&lt;br /&gt;
#define NRF_LOG_DEFAULT_BACKENDS_INIT()&lt;br /&gt;
#endif&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来我们追踪nrf_log_default_backends_init()函数，在sdk_config.h中，定义了NRF_LOG_BACKEND_RTT_ENABLED为1，也就是使能了RTT打印。&lt;br /&gt;
&lt;br /&gt;
在这个地方，我们会调用nrf_log_backend_rtt_init()函数初始化RTT，然后调用nrf_log_backend_add()添加新的后端接口，并调用nrf_log_backend_enable()将这个新的后端接口使能。&lt;br /&gt;
&lt;br /&gt;
这样处理之后，我们便可以使用面向RTT的log功能了。&lt;br /&gt;
{{Note|text=RTT部分的源码，这个大家有兴趣的可以自行了解，我们使用RTT功能的时候，只需要将RTT分组下的SEGGER_RTT_XX.c的三个文件添加到工程即可，不一定要了解这个源码|type=warning}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;58&amp;quot;&amp;gt;&lt;br /&gt;
void nrf_log_default_backends_init(void)&lt;br /&gt;
{&lt;br /&gt;
    int32_t backend_id = -1;&lt;br /&gt;
    (void)backend_id;&lt;br /&gt;
#if defined(NRF_LOG_BACKEND_RTT_ENABLED) &amp;amp;&amp;amp; NRF_LOG_BACKEND_RTT_ENABLED&lt;br /&gt;
    nrf_log_backend_rtt_init();&lt;br /&gt;
    backend_id = nrf_log_backend_add(&amp;amp;rtt_log_backend, NRF_LOG_SEVERITY_DEBUG);&lt;br /&gt;
    ASSERT(backend_id &amp;gt;= 0);&lt;br /&gt;
    nrf_log_backend_enable(&amp;amp;rtt_log_backend);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if defined(NRF_LOG_BACKEND_UART_ENABLED) &amp;amp;&amp;amp; NRF_LOG_BACKEND_UART_ENABLED&lt;br /&gt;
    nrf_log_backend_uart_init();&lt;br /&gt;
    backend_id = nrf_log_backend_add(&amp;amp;uart_log_backend, NRF_LOG_SEVERITY_DEBUG);&lt;br /&gt;
    ASSERT(backend_id &amp;gt;= 0);&lt;br /&gt;
    nrf_log_backend_enable(&amp;amp;uart_log_backend);&lt;br /&gt;
#endif&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== NRF_LOG_XX()函数说明 ======&lt;br /&gt;
上面说明了如何去初始化LOG向RTT打印的功能，下面我们将给大家介绍一下LOG打印的函数，毕竟这个才是我们真正要用到的部分。&lt;br /&gt;
&lt;br /&gt;
LOG打印的函数一共有4个，分别为打印ERROR、WARNING、INFO、DEBUG。他们的功能看字面意思就可以明白，分别是打印错误、警告、用户信息、调试信息。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;111&amp;quot;&amp;gt;&lt;br /&gt;
#define NRF_LOG_ERROR(...)                     NRF_LOG_INTERNAL_ERROR(__VA_ARGS__)&lt;br /&gt;
#define NRF_LOG_WARNING(...)                   NRF_LOG_INTERNAL_WARNING( __VA_ARGS__)&lt;br /&gt;
#define NRF_LOG_INFO(...)                      NRF_LOG_INTERNAL_INFO( __VA_ARGS__)&lt;br /&gt;
#define NRF_LOG_DEBUG(...)                     NRF_LOG_INTERNAL_DEBUG( __VA_ARGS__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;底层的打印函数大家自己go to define查看，我这边给大家各举一个例子，方便大家使用。&lt;br /&gt;
&lt;br /&gt;
其实他们的使用方式，都是和printf一样的，只是打印消息的级别不同而已。printf函数的使用大家不清楚的，可以百度查看一下。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
NRF_LOG_ERROR(&amp;quot;sd_ble_cfg_set() returned %s when attempting to set BLE_CONN_CFG_GAP.&amp;quot;,&lt;br /&gt;
                      nrf_strerror_get(ret_code));&lt;br /&gt;
                      &lt;br /&gt;
NRF_LOG_WARNING(&amp;quot;Change the RAM start location from 0x%x to 0x%x.&amp;quot;,&lt;br /&gt;
                      app_ram_start_link, *p_app_ram_start);&lt;br /&gt;
                      &lt;br /&gt;
NRF_LOG_INFO(&amp;quot;Shutdown started. Type %d&amp;quot;, m_pwr_mgmt_evt);&lt;br /&gt;
&lt;br /&gt;
NRF_LOG_DEBUG(&amp;quot;BLE event: 0x%x.&amp;quot;, p_ble_evt-&amp;gt;header.evt_id);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;上面一段话和大家说了nordic协议栈中4个标准的LOG打印函数，他们其实拥有相同的功能，但是打印的级别不同（针对不同的调试阶段和目的），我们我们如何控制这个打印的级别呢，也是在我们的sdk_config.h中，大家可以看到NRF_LOG_DEFAULT_LEVEL。默认的只能打印到info，如果大家需要使用debug打印，需要修改此处的值为4。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;7569&amp;quot;&amp;gt;&lt;br /&gt;
// &amp;lt;o&amp;gt; NRF_LOG_DEFAULT_LEVEL  - Default Severity level&lt;br /&gt;
 &lt;br /&gt;
// &amp;lt;0=&amp;gt; Off &lt;br /&gt;
// &amp;lt;1=&amp;gt; Error &lt;br /&gt;
// &amp;lt;2=&amp;gt; Warning &lt;br /&gt;
// &amp;lt;3=&amp;gt; Info &lt;br /&gt;
// &amp;lt;4=&amp;gt; Debug &lt;br /&gt;
&lt;br /&gt;
#ifndef NRF_LOG_DEFAULT_LEVEL&lt;br /&gt;
#define NRF_LOG_DEFAULT_LEVEL 3&lt;br /&gt;
#endif&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分与主机部分新增log功能相同，这边不再赘述。&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
LOG打印实验，大家需要掌握的三个要点如下：&lt;br /&gt;
&lt;br /&gt;
1.了解什么是RTT。&lt;br /&gt;
&lt;br /&gt;
2.了解nordic协议栈中LOG面向RTT功能的初始化、及打印函数的使用。&lt;br /&gt;
&lt;br /&gt;
3.要善于使用log功能，这样有利于我们程序的流程开发及异常问题的查找和处理。&lt;br /&gt;
&lt;br /&gt;
=== 通用广播与扫描实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
通用扫描实验1.2_ble_central_scan_all与2.2_ble_peripheral_adv_all，给大家带来的是主机的扫描功能展示，以及从机的广播功能展示。&lt;br /&gt;
&lt;br /&gt;
也就是从这一实验开始，我们才是真正进入到BLE协议的学习实验，我们将按照扫描、连接、获取服务、通信的流程，在接下来的实验中给大家介绍BLE协议。&lt;br /&gt;
&lt;br /&gt;
大家都清楚，低功耗蓝牙主从机间交互数据的方式一般来说是有两种，一种是连接之后通信（这个是蓝牙的主要功能），另一种就是本实验带来的主机扫描获取从机的广播数据。而上述的两种方式，不管是哪一种，都是需要扫描功能的，毕竟连接通信之前，我们的主机也是需要扫描到从机设备才可以发起连接。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机log打印如下，先打印当前例程的名称'''1.2_ble_central_scan_all'''，然后将会打印扫描到的从机设备的信息，格式如下:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
Device MAC： 0x010203040506         // 从机设备MAC地址&lt;br /&gt;
&lt;br /&gt;
adv data: 0x0102...xx...xx          // 从机设备广播数据&lt;br /&gt;
&lt;br /&gt;
scan data: 0x0102...xx...xx         // 从机设备扫描回调数据&lt;br /&gt;
&lt;br /&gt;
rssi: -xxdBm                        // 从机设备相当于当前主机的信号强度&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
[[文件:Nrf rtt 12.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
主机log打印如下，先打印当前例程的名称'''2.2_ble_peripheral_adv_all。'''&lt;br /&gt;
[[文件:Nrf rtt 22.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 工程说明 =====&lt;br /&gt;
相对于LOG打印的工程，我们新增了nRF_BLE分组，这个分组下包含的就是BLE协议相关的文件，我们在本实验中只使用了nrf_ble_scan.c下的扫描功能函数。&lt;br /&gt;
[[文件:Nrf group nrf ble.png|边框|居中|无框|296x296像素]]&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
在mian函数中，我们新增了扫描功能的scan_init()初始化函数，并且在初始化所有功能之后，我们调用了发起扫描的函数scan_start()。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;296&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    scan_init();            // 初始化扫描&lt;br /&gt;
    &lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;1.2_ble_central_scan_all&amp;quot;);&lt;br /&gt;
    &lt;br /&gt;
    scan_start();           // 开始扫描&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数及scan_evt_handler()回调函数 ======&lt;br /&gt;
首先我们看一下我们的扫描功能的初始化函数，可以看到我们最终调用的是库函数中的nrf_ble_scan_init()去初始化我们扫描，在这个函数中，有两个参数需要我们关注，一个是init_scan（这个参数携带的我们对于扫描的设置），另一个是scan_evt_handler（当我们扫描到设备之后，将会由这个回调函数返回事件信息给我们）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;155&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
    &lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;首先我们看一下扫描的参数设置，这边我们设置为主动扫描，扫描间隔是100ms，扫描窗口是50ms，扫描的持续时间设置为0（设置为0，则一直扫描，除非我们调用停止扫描的函数），另外我们设置扫描附近的所有BLE设备（不做扫描限制）。&lt;br /&gt;
{{Note|text=扫描模式分为两种：&lt;br /&gt;
1.被动扫描：只扫描从机设备的广播数据&lt;br /&gt;
&lt;br /&gt;
2.主动扫描：扫描从机设备的广播数据以及扫描回调数据|type=info}}&lt;br /&gt;
[[文件:扫描窗口和扫描间隔.jpg|边框|居中|无框|800x800像素]]&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;55&amp;quot;&amp;gt;&lt;br /&gt;
// 定义扫描参数&lt;br /&gt;
static ble_gap_scan_params_t m_scan_params = &lt;br /&gt;
{&lt;br /&gt;
    .active        = 1,                            // 1为主动扫描，可获得广播数据及扫描回调数据&lt;br /&gt;
    .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,   // 扫描间隔：160*0.625 = 100ms  &lt;br /&gt;
    .window        = NRF_BLE_SCAN_SCAN_WINDOW,     // 扫描窗口：80*0.625 = 50ms   &lt;br /&gt;
    .timeout       = NRF_BLE_SCAN_SCAN_DURATION,   // 扫描持续的时间：设置为0，一直扫描，直到明确的停止扫描&lt;br /&gt;
    .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,   // 扫描所有BLE设备，不做限制&lt;br /&gt;
    .scan_phys     = BLE_GAP_PHY_1MBPS,            // 扫描1MBPS的PHY&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来我们查看一下我们处理扫描回调事件的函数，因为我们这个实验带给大家的是扫描附近所有的BLE广播设备，所以可以看到，我们判断的设备ID为NRF_BLE_SCAN_EVT_NOT_FOUND，这个ID代表的未被过滤的扫描数据。&lt;br /&gt;
&lt;br /&gt;
在这个事件ID下，我们根据p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;type.scan_response判断扫描到的数据是广播数据还是扫描回调数据，并且我们可以从这个消息结构体当中获取到上述的两包数据、以及设备的MAC、设备的RSSI信号强度等等。并且我们将扫描到的这些信息通过LOG向RTT格式化打印出来。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;99&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 未过滤的扫描数据&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_NOT_FOUND:&lt;br /&gt;
        {&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
          else  // 否则为广播数据&lt;br /&gt;
          {&lt;br /&gt;
            // 打印扫描的设备MAC&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_start()函数 ======&lt;br /&gt;
scan_start()发起扫描的函数，调用的是nordic提供的底层的nrf_ble_scan_start()函数实现的。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;83&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_start&lt;br /&gt;
//&lt;br /&gt;
// brief : 开始扫描&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_start(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t ret;&lt;br /&gt;
&lt;br /&gt;
    ret = nrf_ble_scan_start(&amp;amp;m_scan);&lt;br /&gt;
    APP_ERROR_CHECK(ret);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
相对于LOG实验，从机工程也是新增了nRF_BLE的分组，但是我们可以看到看到分组下的文件与主机工程是不同的。&lt;br /&gt;
&lt;br /&gt;
主机部分主要是扫描和获取服务相关，从机部分则是广播、连接参数和服务注册相关。&lt;br /&gt;
[[文件:Nrf group nrf ble p.png|边框|居中|无框|330x330像素]]&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
在mian()函数，我们新增了广播初始化的advertising_init()函数、以及发起广播的advertising_start()函数。并且我们还初始化了GAP，调用的是gap_params_init()函数。&lt;br /&gt;
&lt;br /&gt;
本来我们这一实验，只是想给大家带来广播和广播数据配置的展示的，但是为了能够让大家更直观的“看到”设备，所以我们便需要在广播数据中携带设备名称，那么问题来了，在nordic的协议栈中，除非我们去修改他提供的ble_advdata.c文件，不然我们没法直接去配置这个广播数据中的设备名称（这边给大家一个建议，除非对于协议栈的理解有把握，否则尽可能的不要去修改它）。&lt;br /&gt;
&lt;br /&gt;
为了完成上述的添加设备名称的宏愿，所以我们只能“委曲求全”的添加了gap的初始化。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;260&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    gap_params_init();      // 初始化GAP&lt;br /&gt;
    advertising_init();     // 初始化广播&lt;br /&gt;
    &lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;2.2_ble_peripheral_adv_all&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    advertising_start();    // 开启广播&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();  // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== gap_params_init()函数 ======&lt;br /&gt;
由于广播数据介绍的篇幅会大一些，所以我们先给大家说明一下gap_params_init()函数。&lt;br /&gt;
&lt;br /&gt;
在这个gap初始化的函数中，我们设置了设备名称设备的名称为GY-NRF52832。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;52&amp;quot;&amp;gt;&lt;br /&gt;
#define DEVICE_NAME  &amp;quot;GY-NRF52832&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;124&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gap_params_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化GAP&lt;br /&gt;
// details : 此功能将设置设备的所有必需的GAP（通用访问配置文件）参数。它还设置权限和外观。&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void gap_params_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t                err_code;&lt;br /&gt;
&lt;br /&gt;
    // 设置设备名称&lt;br /&gt;
    err_code = sd_ble_gap_device_name_set(NULL,&lt;br /&gt;
                                          (const uint8_t *) DEVICE_NAME,&lt;br /&gt;
                                          strlen(DEVICE_NAME));&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== advertising_init()函数 ======&lt;br /&gt;
{{Note|text=在这里给大家科普一下，BLE的广播数据和扫描回调数据都是由用户自定义的，对于数据的内容没有任何的限制。我们只需要遵循下面的广播数据结构要求就行。&lt;br /&gt;
&lt;br /&gt;
advdata[] = &lt;br /&gt;
{&lt;br /&gt;
  长度A，类型A，数据A，&lt;br /&gt;
  长度B，类型B，数据B&lt;br /&gt;
}|type=info}}&lt;br /&gt;
&lt;br /&gt;
有关广播数据中类型的定义见sdk离线文档：&amp;lt;code&amp;gt;file:///E:/project-nordic/nRF5_SDK_15.2.0_offline_doc/s132/group___b_l_e___g_a_p___a_d___t_y_p_e___d_e_f_i_n_i_t_i_o_n_s.html&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在我们的广播数据初始化函数中，我们首先定义了init.advdata.name_type为BLE_ADVDATA_FULL_NAME，这意味着我们的广播数据中将携带有刚刚GAP初始化中的全部设备名称。&lt;br /&gt;
&lt;br /&gt;
然后我们定义了init.srdata.include_ble_device_addr为true，这意味着我们的扫描回调设备中将会携带有设备的MAC地址。&lt;br /&gt;
&lt;br /&gt;
接下来我们设置了快慢广播，如下代码配置的结果是先快速广播18s（周期40ms），再慢速广播18s（周期100ms），最后停止广播。&lt;br /&gt;
&lt;br /&gt;
最后我们调用nordic提供的广播初始化函数ble_advertising_init()。&lt;br /&gt;
{{Note|text=我们展示的仅仅是nordic配置广播和扫描回调数据中的部分，除了上述的名称和设备地址。广播数据中还可以携带设备的类型、信号发射强度、连接参数、uuid等等，具体的大家可以go to define，查看ble_advdata_t结构体参数说明。|type=warning}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;85&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : advertising_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化广播&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void advertising_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t               err_code;&lt;br /&gt;
    ble_advertising_init_t init;&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;init, 0, sizeof(init));&lt;br /&gt;
&lt;br /&gt;
    // 广播数据包含所有设备名称（FULL NAME）&lt;br /&gt;
    init.advdata.name_type = BLE_ADVDATA_FULL_NAME;&lt;br /&gt;
//    // 广播数据只包含部分设备名称（SHORT NAME，长度为6）&lt;br /&gt;
//    init.advdata.name_type =  BLE_ADVDATA_SHORT_NAME;&lt;br /&gt;
//    init.advdata.short_name_len = 6;&lt;br /&gt;
    &lt;br /&gt;
    // 扫描回调数据中包含设备MAC地址&lt;br /&gt;
    init.srdata.include_ble_device_addr = true;&lt;br /&gt;
    &lt;br /&gt;
    &lt;br /&gt;
    // 配置广播周期，先快速广播18s（周期40ms），再慢速广播18s（周期100ms），最后停止广播&lt;br /&gt;
    init.config.ble_adv_fast_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_fast_interval = 64;       // 64*0.625 = 40ms&lt;br /&gt;
    init.config.ble_adv_fast_timeout  = 1800;     // 1800*10ms = 18s&lt;br /&gt;
    init.config.ble_adv_slow_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_slow_interval = 160;      // 160*0.625 = 100ms&lt;br /&gt;
    init.config.ble_adv_slow_timeout  = 1800;     // 1800*10ms = 18s&lt;br /&gt;
    &lt;br /&gt;
    err_code = ble_advertising_init(&amp;amp;m_advertising, &amp;amp;init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_advertising_conn_cfg_tag_set(&amp;amp;m_advertising, APP_BLE_CONN_CFG_TAG);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== advertising_start()函数 ======&lt;br /&gt;
advertising_start()作为我们开启广播功能的函数，我们调用了nordic协议栈中的ble_advertising_start()函数去实现功能。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;71&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : advertising_start&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于开启广播&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void advertising_start(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t err_code = ble_advertising_start(&amp;amp;m_advertising, BLE_ADV_MODE_FAST);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过通用扫描实验的学习，我们需要掌握的蓝牙协议有如下几点。&lt;br /&gt;
&lt;br /&gt;
主机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何配置扫描的参数&lt;br /&gt;
&lt;br /&gt;
2.如何发起扫描，并触类旁通，自行了解如何停止（大家自己了解一下，很容易）&lt;br /&gt;
&lt;br /&gt;
3.如何获取扫描到的附近的BLE从机设备的信息&lt;br /&gt;
&lt;br /&gt;
从机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何配置从机的广播数据、以及广播的参数（从机广播部分）&lt;br /&gt;
&lt;br /&gt;
2.了解如何设置从机设备的名称（这个是正式项目经常需要使用到的）&lt;br /&gt;
&lt;br /&gt;
3.了解如何开启从机广播，并触类旁通，自行了解如何关闭广播（大家自己了解一下，很容易）&lt;br /&gt;
&lt;br /&gt;
=== 带过滤的扫描与广播实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
过滤扫描实验1.3_ble_central_scan_filter与2.3_ble_peripheral_adv_filter。经过通用扫描实验的学习，大家应该对于蓝牙的扫描和广播有一定的了解了。那么在我们实际的项目使用中，可能会出现由于我们的附近可能存在的BLE设备太多，导致我们去扫描的时候，没法在第一时间找到我们想要的设备的情况，那么这个时候，我们有没有好的处理方法呢。&lt;br /&gt;
&lt;br /&gt;
上面的疑问答案是肯定的，因为我们在扫描的时候，可以先判断一下扫描到的广播数据或者扫描回调数据，仅返回我们需要的设备（也就是做一些数据的判断和限制），这样就可以快速的找到我们的设备。nordic对这部分处理的很细心，在BLE协议的扫描和广播中，都给我们开发者留好了限制的数据属性，我们以scan为例，可以看到限制的广播数据如下几种：名称，简称，地址，UUID以及容貌。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=typedef enum&lt;br /&gt;
{&lt;br /&gt;
    SCAN_NAME_FILTER,       /**&amp;lt; Filter for names. */&lt;br /&gt;
    SCAN_SHORT_NAME_FILTER, /**&amp;lt; Filter for short names. */&lt;br /&gt;
    SCAN_ADDR_FILTER,       /**&amp;lt; Filter for addresses. */&lt;br /&gt;
    SCAN_UUID_FILTER,       /**&amp;lt; Filter for UUIDs. */&lt;br /&gt;
    SCAN_APPEARANCE_FILTER, /**&amp;lt; Filter for appearances. */&lt;br /&gt;
} nrf_ble_scan_filter_type_t;|type=info}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机部分，可以看到上电先打印'''1.3_ble_central_scan_filter'''字样，然后会周期打印扫描到的2.3例程的从机设备信息（MAC、扫描回调数据、RSSI）。&lt;br /&gt;
&lt;br /&gt;
在扫描回调的数据的末尾，我们可以看到03030100的字样，这个就是我们实验限制扫描的UUID。&lt;br /&gt;
{{Note|text=03，// UUID数据长度&lt;br /&gt;
&lt;br /&gt;
03，// 16bit UUID数据类型&lt;br /&gt;
&lt;br /&gt;
01，00 // UUID 0x0001，这边是低位在前|type=info}}&lt;br /&gt;
[[文件:Nrf rtt 13.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
从机部分，可以看到上电先打印'''2.3_ble_peripheral_adv_filter'''字样。&lt;br /&gt;
[[文件:Nrf rtt 23.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机工程 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
本工程相对于上一章节的通用扫描实现，改动较小，只是对扫描返回的设备做出限制，不再像之前那样返回所有扫描到的设备。&lt;br /&gt;
&lt;br /&gt;
所以我们的修改内容集中在scan_init()函数以及scan_evt_handler()回调函数当中。&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数 ======&lt;br /&gt;
相对于通用扫描的主机程序，我们在限制扫描的初始化函数当中，新增了nrf_ble_scan_filter_set()函数用于限制扫描的设备，在这个例程中，我们给大家展示的是限制UUID扫描，大家也可以根据其他信息去进行限制。&lt;br /&gt;
&lt;br /&gt;
我们限制了扫描的UUID是16bit的0x0001，并且调用nrf_ble_scan_filters_enable()使能了这个限制。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;65&amp;quot;&amp;gt;&lt;br /&gt;
// 定义扫描限制的UUID&lt;br /&gt;
static ble_uuid_t const m_nus_uuid = {BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_BLE};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;162&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
&lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
    &lt;br /&gt;
    // 设置扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filter_set(&amp;amp;m_scan, SCAN_UUID_FILTER, &amp;amp;m_nus_uuid);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filters_enable(&amp;amp;m_scan, NRF_BLE_SCAN_UUID_FILTER, false);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()回调函数 ======&lt;br /&gt;
大家可以看到，我们对于扫描回调的事件ID判断进行了更改，上一章的通用扫描我们的判断的是NRF_BLE_SCAN_EVT_NOT_FOUND，这一章的限制扫描我们判断的是NRF_BLE_SCAN_EVT_FILTER_MATCH。&lt;br /&gt;
&lt;br /&gt;
由于上面的一系列对于扫描UUID的限制操作，所以我们的1.3主机实验的现象，才会仅扫描并返回给我们的2.3实验的从机设备。&lt;br /&gt;
&lt;br /&gt;
为了给大家更直观的找到限制的UUID，我们这边临时注视掉了广播数据的打印，只保留了包含了UUID的扫描回调数据打印。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;102&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 匹配的扫描数据（也就是过滤之后的）&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_FILTER_MATCH:&lt;br /&gt;
        {&lt;br /&gt;
          // 下面这一段我们只保留了扫描回调数据获取的部分，因为从机筛选广播的UUID在扫描回调数据&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
//          else  // 否则为广播数据&lt;br /&gt;
//          {&lt;br /&gt;
//            // 打印扫描的设备MAC&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
//                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;peer_addr.addr));&lt;br /&gt;
//            &lt;br /&gt;
//            if(p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
//            {&lt;br /&gt;
//              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
//                            Util_convertHex2Str(&lt;br /&gt;
//                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
//                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len));&lt;br /&gt;
//            }&lt;br /&gt;
//            else&lt;br /&gt;
//            {&lt;br /&gt;
//              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
//            }&lt;br /&gt;
//          }&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
从机部分相对于通用广播的从机，我们也仅仅增加了UUID的广播数据。所以这边我们查看一下广播数据初始化的函数advertising_init()。&lt;br /&gt;
&lt;br /&gt;
====== advertising_init() ======&lt;br /&gt;
大家可以看到，在广播数据初始化函数中，我们在init.srdata中增加了uuids_complete的定义，这样我们的广播数据中就会携带16bit的UUID 0x0001数据（这个数据在m_adv_uuids中被定义）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;53&amp;quot;&amp;gt;&lt;br /&gt;
// 定义广播的UUID&lt;br /&gt;
static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_BLE}};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;87&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : advertising_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化广播&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void advertising_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t               err_code;&lt;br /&gt;
    ble_advertising_init_t init;&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;init, 0, sizeof(init));&lt;br /&gt;
&lt;br /&gt;
    // 广播数据包含所有设备名称（FULL NAME）&lt;br /&gt;
    init.advdata.name_type = BLE_ADVDATA_FULL_NAME;&lt;br /&gt;
//    // 广播数据只包含部分设备名称（SHORT NAME，长度为6）&lt;br /&gt;
//    init.advdata.name_type =  BLE_ADVDATA_SHORT_NAME;&lt;br /&gt;
//    init.advdata.short_name_len = 6;&lt;br /&gt;
    &lt;br /&gt;
    // 扫描回调数据中包含16bit UUID：0x0001&lt;br /&gt;
    init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);&lt;br /&gt;
    init.srdata.uuids_complete.p_uuids  = m_adv_uuids;&lt;br /&gt;
    &lt;br /&gt;
    // 扫描回调数据中包含设备MAC地址&lt;br /&gt;
    init.srdata.include_ble_device_addr = true;&lt;br /&gt;
    &lt;br /&gt;
    // 配置广播周期，先快速广播18s（周期40ms），再慢速广播18s（周期100ms），最后停止广播&lt;br /&gt;
    init.config.ble_adv_fast_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_fast_interval = 64;       // 64*0.625 = 40ms&lt;br /&gt;
    init.config.ble_adv_fast_timeout  = 1800;     // 1800*10ms = 18s&lt;br /&gt;
    init.config.ble_adv_slow_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_slow_interval = 160;      // 160*0.625 = 100ms&lt;br /&gt;
    init.config.ble_adv_slow_timeout  = 18000;    // 18000*10ms = 180s&lt;br /&gt;
    &lt;br /&gt;
    err_code = ble_advertising_init(&amp;amp;m_advertising, &amp;amp;init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_advertising_conn_cfg_tag_set(&amp;amp;m_advertising, APP_BLE_CONN_CFG_TAG);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过这个扫描限制实验的学习，大家需要掌握的要点。&lt;br /&gt;
&lt;br /&gt;
主机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何添加一个扫描的限制并使能&lt;br /&gt;
&lt;br /&gt;
2.限制扫描的回调事件ID是NRF_BLE_SCAN_EVT_FILTER_MATCH&lt;br /&gt;
&lt;br /&gt;
3.自行编程，完成通过名称、地址等其他限制的扫描&lt;br /&gt;
&lt;br /&gt;
从机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何添加个人的广播数据，例如本章节的UUID（本质上还是广播数据的配置）&lt;br /&gt;
&lt;br /&gt;
=== 白名单扫描实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
白名单扫描实验1.4_ble_central_scan_whitelist与2.4_ble_peripheral_adv_whitelist。有关扫描的实验，在第一章的通用扫描中，我们了解到可以主机发起扫描，可以获取从机设备的一些信息，包含广播数据和扫描回调数据，以及从机设备的MAC。在第二章的限制扫描中，我们已经学习过如果根据广播数据和扫描回调数据去获取我们指定的从机设备。那么在这一章节我们将给大家带来，根据从机设备的MAC地址，去限制扫描。{{Note|text=注意：白名单扫描是根据扫描到的设备MAC，而不是指的广播数据中携带的MAC（假设我们将MAC地址加入到广播数据中）。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机部分，上电打印例程名称'''1.4_ble_central_scan_whitelist'''，如果成功设置白名单，则打印'''Successfully set whitelist!'''，然后如果附近有白名单中的设备，则会打印扫描到的白名单设备的信息。&lt;br /&gt;
[[文件:Nrf rtt 14.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
从机部分相较于前两章的内容，几乎没有改动，这边不做讲解。因为白名单的限制，是由主机部分完成。&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 工程说明 =====&lt;br /&gt;
本实验的修改内容集中在主机的扫描初始化以及扫描的回调事件处理函数中，所以下面只有主机部分的代码讲解，而没有从机部分的。&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数 ======&lt;br /&gt;
首先我们查看一下主机扫描的初始化函数，我们可以看到filter_policy过滤扫描参数，我们配置为BLE_GAP_SCAN_FP_WHITELIST，也就是白名单扫描模式。&lt;br /&gt;
&lt;br /&gt;
启用这个白名单模式，如果成功，则会在扫描回调事件处理函数中给我们返回NRF_BLE_SCAN_EVT_WHITELIST_REQUEST请求开启白名单的事件ID。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;54&amp;quot;&amp;gt;&lt;br /&gt;
// 定义扫描参数&lt;br /&gt;
static ble_gap_scan_params_t m_scan_params = &lt;br /&gt;
{&lt;br /&gt;
    .active        = 1,                            // 1为主动扫描，可获得广播数据及扫描回调数据&lt;br /&gt;
    .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,   // 扫描间隔：160*0.625 = 100ms  &lt;br /&gt;
    .window        = NRF_BLE_SCAN_SCAN_WINDOW,     // 扫描窗口：80*0.625 = 50ms   &lt;br /&gt;
    .timeout       = NRF_BLE_SCAN_SCAN_DURATION,   // 扫描持续的时间：设置为0，一直扫描，直到明确的停止扫描&lt;br /&gt;
    .filter_policy = BLE_GAP_SCAN_FP_WHITELIST,    // 扫描白名单设备&lt;br /&gt;
    .scan_phys     = BLE_GAP_PHY_1MBPS,            // 扫描1MBPS的PHY&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;181&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
    &lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()函数 ======&lt;br /&gt;
可以看到，当我们接收到NRF_BLE_SCAN_EVT_WHITELIST_REQUEST事件时，我们将去调用sd_ble_gap_whitelist_set函数设置我们的白名单设备。&lt;br /&gt;
&lt;br /&gt;
我们这边首先获取了从机的MAC地址：'''0xD363BFCE5C46'''，然后将其添加到白名单设备列表当中。&lt;br /&gt;
&lt;br /&gt;
当我们扫描到'''0xD363BFCE5C46'''后将打印他的广播和扫描回调数据、以及型号强度。&lt;br /&gt;
{{Note|text=这个地方，需要大家设置为自己的从设备MAC，不然无法使用此例程|type=tips}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;99&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t err_code;&lt;br /&gt;
    ble_gap_addr_t peer_addr;&lt;br /&gt;
    ble_gap_addr_t const * p_peer_addr;&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 白名单设置请求&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_WHITELIST_REQUEST:&lt;br /&gt;
        {&lt;br /&gt;
          memset(&amp;amp;peer_addr, 0x00, sizeof(peer_addr));&lt;br /&gt;
          peer_addr.addr_id_peer = 1;&lt;br /&gt;
          peer_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;&lt;br /&gt;
          peer_addr.addr[5] = 0xD3;&lt;br /&gt;
          peer_addr.addr[4] = 0x63;&lt;br /&gt;
          peer_addr.addr[3] = 0xBF;&lt;br /&gt;
          peer_addr.addr[2] = 0xCE;&lt;br /&gt;
          peer_addr.addr[1] = 0x5C;&lt;br /&gt;
          peer_addr.addr[0] = 0x46;&lt;br /&gt;
          p_peer_addr = &amp;amp;peer_addr; &lt;br /&gt;
          &lt;br /&gt;
          // 设置白名单&lt;br /&gt;
          err_code = sd_ble_gap_whitelist_set(&amp;amp;p_peer_addr, 0x01);&lt;br /&gt;
          if (err_code == NRF_SUCCESS)&lt;br /&gt;
          {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Successfully set whitelist!&amp;quot;);&lt;br /&gt;
          }&lt;br /&gt;
          APP_ERROR_CHECK(err_code);&lt;br /&gt;
        }break;&lt;br /&gt;
        &lt;br /&gt;
        // 扫描到的白名单设备数据&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_WHITELIST_ADV_REPORT:&lt;br /&gt;
        {&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
          else  // 否则为广播数据&lt;br /&gt;
          {&lt;br /&gt;
            // 打印扫描的设备MAC&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
无&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过白名单实验的学习，我们需要明白以下几点：&lt;br /&gt;
&lt;br /&gt;
1.我们使用白名单功能的前提是我们知道对方设备的MAC。&lt;br /&gt;
&lt;br /&gt;
2.白名单功能只需要主机完成&lt;br /&gt;
&lt;br /&gt;
3.了解NRF_BLE_SCAN_EVT_WHITELIST_REQUEST事件以及sd_ble_gap_whitelist_set()函数&lt;br /&gt;
&lt;br /&gt;
=== 通用连接实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
通用连接实验1.5_ble_central_conn_all与2.5_ble_peripheral_conn_all。从这一实验开始，我们将给大家介绍蓝牙连接相关的协议，由此对于蓝牙协议的学习进入到第二阶段。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机部分，先打印实验名称1.5_ble_central_conn_all，然后开始扫描周围的从机设备。当我们将两块开发板靠近的时候（当RSSI大于-30dBm），则会发起连接。然后打印连接成功的消息，并且打印连接参数。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=Connected. conn_DevAddr: 0xD363BFCE5C46  // 连接的对方设备MAC地址 &lt;br /&gt;
&lt;br /&gt;
conn_handle: 0x0000  // 连接的句柄，第一个连接的设备是0x0000，第二个是0x0001，以此类推&lt;br /&gt;
&lt;br /&gt;
conn_Param: 24,24,0,400  // 连接参数|type=tips}}&lt;br /&gt;
&lt;br /&gt;
[[文件:Nrf rtt 15.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
从机部分，先打印实验名称2.5_ble_peripheral_conn_all，当被主机成功连接之后，会打印连接成功的消息；当断开连接之后，会打印断开连接的句柄，并且会打印断开连接的原因。&lt;br /&gt;
{{Note|text=Connected. conn_DevAddr: 0xE51F64D6BFF4  // 连接的对方设备的MAC&lt;br /&gt;
&lt;br /&gt;
Connected. conn_handle: 0x0000  // 连接的句柄&lt;br /&gt;
&lt;br /&gt;
Connected. conn_Param: 24,24,0,400  // 连接参数&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Disconnected. conn_handle: 0x0, reason: 0x0008  // 断开连接的设备句柄，以及断开的原因|type=tips}}&lt;br /&gt;
[[文件:Nrf rtt 25.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
通用连接主机工程，是在通用扫描主机工程的基础上，新增了连接的过程，主要的修改集中在扫描事件处理函数，以及ble协议栈事件处理函数。&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()函数 ======&lt;br /&gt;
定义连接的参数，这个参数将在sd_ble_gap_connect()函数中被调用。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;65&amp;quot;&amp;gt;&lt;br /&gt;
// 定义连接参数&lt;br /&gt;
static ble_gap_conn_params_t m_conn_params = &lt;br /&gt;
{&lt;br /&gt;
    .min_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MIN_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最小连接间隔7.5ms&lt;br /&gt;
    .max_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MAX_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最大连接间隔30ms  &lt;br /&gt;
    .slave_latency      = NRF_BLE_SCAN_SLAVE_LATENCY,                                         // 隐藏周期0 &lt;br /&gt;
    .conn_sup_timeout   = MSEC_TO_UNITS(NRF_BLE_SCAN_SUPERVISION_TIMEOUT, UNIT_10_MS),        // 超时时间4000ms &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们可以看到，在扫描回调事件处理的函数中，前面一部分还是和之前一样的，就是获取扫描到的从机设备的信息。&lt;br /&gt;
&lt;br /&gt;
在159行，我们对扫描到的从机设备的RSSI进行了判断，我们人为定义，当设备的RSSI大于-30dBm的时候，也就是信号强度非常好，我们就默认连接该设备。&lt;br /&gt;
&lt;br /&gt;
首先，我们先定义准备连接的从机设备的地址和信息，配置m_addr.addr_type为BLE_GAP_ADDR_TYPE_RANDOM_STATIC，配置m_addr.addr为扫描到的设备地址p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr。&lt;br /&gt;
&lt;br /&gt;
配置好准备连接的设备的MAC信息，在发起连接之前，我们一定要先调用nrf_ble_scan_stop()函数去停止扫描，不然会出错。&lt;br /&gt;
&lt;br /&gt;
停止扫描之后，我们调用sd_ble_gap_connect()函数，发起对从机设备的连接。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;107&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 未过滤的扫描数据&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_NOT_FOUND:&lt;br /&gt;
        {&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
          else  // 否则为广播数据&lt;br /&gt;
          {&lt;br /&gt;
            // 打印扫描的设备MAC&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
          &lt;br /&gt;
          &lt;br /&gt;
          // 如果扫描到的设备信号强度大于-30dBm&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;rssi &amp;gt; (-30))&lt;br /&gt;
          {&lt;br /&gt;
            ret_code_t          err_code;&lt;br /&gt;
            &lt;br /&gt;
            // 配置准备连接的设备MAC&lt;br /&gt;
            ble_gap_addr_t m_addr;&lt;br /&gt;
            m_addr.addr_id_peer = 1;&lt;br /&gt;
            m_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;&lt;br /&gt;
            memcpy(m_addr.addr,p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr,BLE_GAP_ADDR_LEN);&lt;br /&gt;
            &lt;br /&gt;
            // 停止扫描&lt;br /&gt;
            nrf_ble_scan_stop();&lt;br /&gt;
            // 发起连接&lt;br /&gt;
            err_code = sd_ble_gap_connect(&amp;amp;m_addr,&amp;amp;m_scan_params,&amp;amp;m_conn_params,APP_BLE_CONN_CFG_TAG);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
          }&lt;br /&gt;
          &lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()函数 ======&lt;br /&gt;
当我们的主机设备进行连接，或者断开连接的时候，BLE的事件回调中，都会给我们状态事件提示。&lt;br /&gt;
&lt;br /&gt;
连接成功的状态BLE_GAP_EVT_CONNECTED，我们可以在这边获取到连接的设备的MAC、连接参数等等。&lt;br /&gt;
&lt;br /&gt;
断开连接的状态BLE_GAP_EVT_DISCONNECTED，我们可以获取到断开的连接的原因等等。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;210&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            // 如果需要异常断开重连，可以打开下面的注释&lt;br /&gt;
            // scan_start();  // 重新开始扫描&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
通用连接的从机工程，是在通用广播的从机工程的基础上修改的。&lt;br /&gt;
&lt;br /&gt;
由于从机设备是一直保持广播，等待被连接的，所以相对于主机程序，我们就不用去找发起连接的函数以及对方设备的MAC了。因此我们只需要关注BLE协议的回调函数的事件返回。&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()函数 ======&lt;br /&gt;
从机的连接与断开的状态和主机是一样的，能够获得的对方设备的信息也是一样的。&lt;br /&gt;
&lt;br /&gt;
连接成功的状态BLE_GAP_EVT_CONNECTED，我们可以在这边获取到连接的设备的MAC、连接参数等等。&lt;br /&gt;
&lt;br /&gt;
断开连接的状态BLE_GAP_EVT_DISCONNECTED，我们可以获取到断开的连接的原因等等。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;148&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            // No implementation needed.&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过主从机通用连接的实验，我们需要弄懂的问题点如下：&lt;br /&gt;
&lt;br /&gt;
主机部分：&lt;br /&gt;
&lt;br /&gt;
1.主机部分一定要先发起扫描，当扫描到设备之后，再去发起连接。（不扫描直接发起连接，如果周围没有这个从设备，会进入异常，并只能RST恢复）&lt;br /&gt;
&lt;br /&gt;
2.主机发起连接的时候，是利用从机的MAC地址进行连接&lt;br /&gt;
&lt;br /&gt;
3.连接以及断开连接的状态返回，可以获取对方设备信息以及断开原因&lt;br /&gt;
&lt;br /&gt;
从机部分：&lt;br /&gt;
&lt;br /&gt;
1.从机是一直广播等待被动连接的设备&lt;br /&gt;
&lt;br /&gt;
2.连接以及断开连接的状态返回，可以获取对方设备信息以及断开原因&lt;br /&gt;
&lt;br /&gt;
=== 过滤连接实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
过滤连接实验1.6_ble_central_conn_filter与2.6_ble_peripheral_conn_filter。&lt;br /&gt;
&lt;br /&gt;
过滤连接实验是从过滤扫描的实验集成下来的，它的目的是和过滤扫描实验一样的，也是为了快速的从一堆BLE设备中，找到我们的设备，并与之快速建立连接。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机上电后先打印实验名称1.6_ble_central_conn_filter，然后发起扫描，如果扫描到符合过滤的从机设备，则会打印扫描到的设备信息，然后发起连接，连接成功后会打印连接的一些信息。&lt;br /&gt;
[[文件:Nrf rtt 16.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
从机上电后先打印实验名称2.6_ble_peripheral_conn_filter，如果被主机扫描连接之后，则会打印连接的信息。&lt;br /&gt;
[[文件:Nrf rtt 26.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
过滤连接，我们主要关注的是扫描初始化中对于过滤连接设备的设置，以及过滤连接成功或者失败返回的扫描回调函数的事件ID。&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数 ======&lt;br /&gt;
连接参数定义，用于配置init_scan.p_conn_param参数，也就是我们的过滤扫描之后发起连接的连接参数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;65&amp;quot;&amp;gt;&lt;br /&gt;
// 定义连接参数&lt;br /&gt;
static ble_gap_conn_params_t m_conn_params = &lt;br /&gt;
{&lt;br /&gt;
    .min_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MIN_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最小连接间隔7.5ms&lt;br /&gt;
    .max_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MAX_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最大连接间隔30ms  &lt;br /&gt;
    .slave_latency      = NRF_BLE_SCAN_SLAVE_LATENCY,                                         // 隐藏周期0 &lt;br /&gt;
    .conn_sup_timeout   = MSEC_TO_UNITS(NRF_BLE_SCAN_SUPERVISION_TIMEOUT, UNIT_10_MS),        // 超时时间4000ms &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在扫描初始化函数中，我们可以看到我们初始化了init_scan.connect_if_match = 1，然后我们还配置了init_scan.p_conn_param = &amp;amp;m_conn_params，后面的限制UUID是和过滤扫描一样的。&lt;br /&gt;
&lt;br /&gt;
这样配置好之后，我们发起扫描，一旦扫描到我们的过滤后的设备，就会使用我们配置好的连接参数，对这个从机设备发起连接。连接成功或者失败，都会在扫描回调函数中进行事件通知。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;182&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    init_scan.connect_if_match = 1;&lt;br /&gt;
    init_scan.conn_cfg_tag = APP_BLE_CONN_CFG_TAG;&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
&lt;br /&gt;
    // 配置连接的参数&lt;br /&gt;
    init_scan.p_conn_param = &amp;amp;m_conn_params;&lt;br /&gt;
    &lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
    &lt;br /&gt;
    // 设置扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filter_set(&amp;amp;m_scan, SCAN_UUID_FILTER, &amp;amp;m_nus_uuid);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filters_enable(&amp;amp;m_scan, NRF_BLE_SCAN_UUID_FILTER, false);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()扫描回调函数 ======&lt;br /&gt;
在扫描回调函数中，过滤扫描的事件是和之前一样的，我们不做介绍。&lt;br /&gt;
&lt;br /&gt;
我们关注一下事件ID，NRF_BLE_SCAN_EVT_CONNECTED与NRF_BLE_SCAN_EVT_CONNECTING_ERROR，当我们设置init_scan.connect_if_match = 1后，如果扫描到并且成功连接上目标从设备，则返回连接成功的ID，如果失败了，则返回连接ERR。&lt;br /&gt;
{{Note|text=注意：由于这两个事件返回的信息和ble协议栈返回的事件ID携带的信息相同，所以这边我们不重复打印。&lt;br /&gt;
case NRF_BLE_SCAN_EVT_CONNECTED:&lt;br /&gt;
{&lt;br /&gt;
NRF_LOG_INFO(&amp;quot;SCAN CONNECTED!&amp;quot;); &lt;br /&gt;
&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
//                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;peer_addr.addr),&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.conn_handle,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
&lt;br /&gt;
//                         );&lt;br /&gt;
&lt;br /&gt;
}break;&lt;br /&gt;
        &lt;br /&gt;
case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:&lt;br /&gt;
{&lt;br /&gt;
NRF_LOG_INFO(&amp;quot;SCAN CONNECTING ERROR!&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Disconnected. reason: 0x%04x&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connecting_err.err_code);&lt;br /&gt;
&lt;br /&gt;
}break;|type=warning}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;111&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 匹配的扫描数据（也就是过滤之后的）&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_FILTER_MATCH:&lt;br /&gt;
        {&lt;br /&gt;
        ...&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        &lt;br /&gt;
        case NRF_BLE_SCAN_EVT_CONNECTED:&lt;br /&gt;
        {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;SCAN CONNECTED!&amp;quot;); &lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
//                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;peer_addr.addr),&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.conn_handle,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
//                         );&lt;br /&gt;
        }break;&lt;br /&gt;
        &lt;br /&gt;
        case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:&lt;br /&gt;
        {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;SCAN CONNECTING ERROR!&amp;quot;);&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Disconnected. reason: 0x%04x&amp;quot;,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connecting_err.err_code);&lt;br /&gt;
        }break;&lt;br /&gt;
        &lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分就不给大家介绍了，和前面的过滤广播的例程是重复的，仅修改了连接之后获取并打印对方设备的信息。&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过本实验的学习，大家需要了解到如何让主机在扫描的时候，去对过滤后的从机设备发起连接。&lt;br /&gt;
&lt;br /&gt;
1.了解init_scan.connect_if_match参数的功能。&lt;br /&gt;
&lt;br /&gt;
2.了解扫描回调事件ID中的NRF_BLE_SCAN_EVT_CONNECTED与NRF_BLE_SCAN_EVT_CONNECTING_ERROR。&lt;br /&gt;
&lt;br /&gt;
=== 连接参数更新实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
经过前两章的主从机扫描并连接的实验学习后，我们不难发现，连接之后主从机之间一个很重要的参数（返回连接成功的状态下，打印的数据），那就是连接参数。&lt;br /&gt;
&lt;br /&gt;
有关连接参数的详细说明，请大家查看本手册的第一章节有关蓝牙协议的介绍。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=主从机连接参数的配置流程：&lt;br /&gt;
&lt;br /&gt;
1、主从机连接成功之后，我们将以主机携带的连接参数，作为主从机连接的参数&lt;br /&gt;
&lt;br /&gt;
2、从机可以发起更新连接参数的请求&lt;br /&gt;
&lt;br /&gt;
3、主机接收到从机的参数更新请求，发起参数更新&lt;br /&gt;
&lt;br /&gt;
4、更新成功，将以新的参数作为连接参数|type=info}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机上电后发起扫描，一旦扫描到我们过滤的设备之后，就会发起连接。&lt;br /&gt;
&lt;br /&gt;
连接成功后，打印连接的handle，以及主从机之间的连接参数。&lt;br /&gt;
&lt;br /&gt;
当从机发起连接参数更新请求后，我们使用从机申请的连接参数，去发起连接参数的更新。&lt;br /&gt;
[[文件:Nrf rtt 17.png|边框|居中|无框|657x657像素]]&lt;br /&gt;
从机上电后广播，被主机连接后打印连接的handle，以及连接参数。&lt;br /&gt;
&lt;br /&gt;
5000ms后，从机发起参数更新请求，主机接收请求后发起更新，更新成功后，从机打印新的连接参数。&lt;br /&gt;
[[文件:Nrf rtt 27.png|边框|居中|无框|648x648像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
主机部分大部分和之前的过滤连接是一样的，唯一的区别在于接收从机连接参数更新请求，并发起更新。&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()回调函数 ======&lt;br /&gt;
在BLE事件回调中可以看到两个新增的事件处理，一个是连接参数更新请求BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST，另一个是连接参数更新BLE_GAP_EVT_CONN_PARAM_UPDATE。&lt;br /&gt;
&lt;br /&gt;
BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST：连接参数更新请求，这个状态是主机接收到从机发起更新参数的请求时返回，在这个状态下，我们可以获取从机申请更新的参数数值，并且调用sd_ble_gap_conn_param_update()函数去发起连接参数的更新。&lt;br /&gt;
&lt;br /&gt;
BLE_GAP_EVT_CONN_PARAM_UPDATE：连接参数更新，当连接参数完成更新的时候，会返回这个状态，在这个状态中，我们可以获取到更新完成后的新的连接参数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;202&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            // 如果需要异常断开重连，可以打开下面的注释&lt;br /&gt;
            // scan_start();  // 重新开始扫描&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 连接参数更新请求&lt;br /&gt;
        case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;conn_Param Update  Request&amp;quot;);&lt;br /&gt;
            &lt;br /&gt;
            sd_ble_gap_conn_param_update(p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                                                    &amp;amp;p_gap_evt-&amp;gt;params.conn_param_update_request.conn_params);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        // 连接参数更新&lt;br /&gt;
        case BLE_GAP_EVT_CONN_PARAM_UPDATE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;conn_Param Update: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.min_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.slave_latency,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分，我们主要关注两个地方，一个是连接参数的更新请求conn_params_init()函数，另一个是和主机部分一样的，连接参数更新成功的状态返回。&lt;br /&gt;
&lt;br /&gt;
====== conn_params_init()函数 ======&lt;br /&gt;
连接参数初始化函数，在这个函数中，我们配置了新的连接参数，以及连接参数更新的时间及尝试的次数，并且包含了两个回调函数，一个是更新是否成功的返回，一个是携带的连接参数是否正确的返回。&lt;br /&gt;
&lt;br /&gt;
我们配置了第一次更新的时间为连接成功后5s（由frist_xx_delay控制），后面的几次也是间隔5s（由next_xx_delay控制），然后尝试更新次数为3。这个尝试次数3的含义是，如果申请了3次更新，都未成功，那么将从on_conn_params_evt回调中返回更新失败。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;245&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : conn_params_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化连接参数模块的功能&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void conn_params_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t               err_code;&lt;br /&gt;
    ble_conn_params_init_t cp_init;&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;cp_init, 0, sizeof(cp_init));&lt;br /&gt;
&lt;br /&gt;
    cp_init.p_conn_params                  = &amp;amp;m_conn_params;&lt;br /&gt;
    cp_init.first_conn_params_update_delay = APP_TIMER_TICKS(5000);&lt;br /&gt;
    cp_init.next_conn_params_update_delay  = APP_TIMER_TICKS(5000);&lt;br /&gt;
    cp_init.max_conn_params_update_count   = 3;&lt;br /&gt;
    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;&lt;br /&gt;
    cp_init.disconnect_on_fail             = false;&lt;br /&gt;
    cp_init.evt_handler                    = on_conn_params_evt;&lt;br /&gt;
    cp_init.error_handler                  = conn_params_error_handler;&lt;br /&gt;
&lt;br /&gt;
    err_code = ble_conn_params_init(&amp;amp;cp_init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;可以看到，我们新的连接参数，连接间隔40ms，隐藏周期0，超时时间4s。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;57&amp;quot;&amp;gt;&lt;br /&gt;
// 定义连接参数（为了展示连接参数的更新，我们设置连接间隔为固定的20ms）&lt;br /&gt;
static ble_gap_conn_params_t m_conn_params = &lt;br /&gt;
{&lt;br /&gt;
    .min_conn_interval  = MSEC_TO_UNITS(40, UNIT_1_25_MS),  // 最小连接间隔40ms&lt;br /&gt;
    .max_conn_interval  = MSEC_TO_UNITS(40, UNIT_1_25_MS),  // 最大连接间隔40ms  &lt;br /&gt;
    .slave_latency      = 0,                                // 隐藏周期0 &lt;br /&gt;
    .conn_sup_timeout   = MSEC_TO_UNITS(2000, UNIT_10_MS),  // 超时时间4000ms &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在on_conn_params_evt()回调函数中，我们可以看到连接参数更新成功或者失败的返回。&lt;br /&gt;
&lt;br /&gt;
这边需要注意的问题点如下，以我们这个实验为例，例如我们的参数更新请求会尝试3次：&lt;br /&gt;
&lt;br /&gt;
1、如果第一次就更新成功，那么会直接返回更新SUCCESS，并且后面2次更新请求将不会再起作用&lt;br /&gt;
&lt;br /&gt;
2、如果三次更新都不成功（这个不成功，可能是更新失败，可能是主机不响应等等），那么才会返回更新failed&lt;br /&gt;
&lt;br /&gt;
也就是3次只要有一次更新成功就认为我们更新完成了，如果3次都失败，才会认为失败。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;211&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : on_conn_params_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理连接更新参数的事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : p_evt -&amp;gt; 接收到的连接模块返回的任务事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    if (p_evt-&amp;gt;evt_type == BLE_CONN_PARAMS_EVT_SUCCEEDED)&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;connParam Update Success&amp;quot;); &lt;br /&gt;
    }&lt;br /&gt;
      &lt;br /&gt;
    if (p_evt-&amp;gt;evt_type == BLE_CONN_PARAMS_EVT_FAILED)&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;connParam Update Failed&amp;quot;); &lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这边的conn_params_error_handler()错误回调函数，当我们申请更新的连接参数不符合协议时才会返回，也就是提示我们连接参数异常。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;232&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : conn_params_error_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理连接更新参数异常的事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : nrf_error -&amp;gt; 异常标志&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void conn_params_error_handler(uint32_t nrf_error)&lt;br /&gt;
{&lt;br /&gt;
    APP_ERROR_HANDLER(nrf_error);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()回调函数 ======&lt;br /&gt;
BLE事件回调函数，和主机一样的，当我们成功更新连接参数之后，返回BLE_GAP_EVT_CONN_PARAM_UPDATE事件，在这个事件中我们可以获取新的连接参数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;159&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        // 连接参数更新&lt;br /&gt;
        case BLE_GAP_EVT_CONN_PARAM_UPDATE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;conn_Param Update: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.min_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.slave_latency,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
          &lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
这一章的实验，想要交给大家的内容是如何配置连接参数。需要了解的重点如下：&lt;br /&gt;
&lt;br /&gt;
1、了解连接参数的含义，明白连接参数越小，通信的速率越快。&lt;br /&gt;
&lt;br /&gt;
2、了解主从机之间的连接参数的配置流程。&lt;br /&gt;
&lt;br /&gt;
3、了解从机如何申请更新连接参数，ble_conn_params_init()函数。&lt;br /&gt;
&lt;br /&gt;
4、了解主机如何更新连接参数，sd_ble_gap_conn_param_update()函数。&lt;br /&gt;
&lt;br /&gt;
=== MTU更新实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
MTU是我们的最大传输单元（Maximum Transmission Unit），也就是我们大家常说的一包数据最大可以多少字节。&lt;br /&gt;
&lt;br /&gt;
在BLE4.1往后的协议中，为了提高BLE的通信速率，我们可以配置MTU最大为251byte，但是这个251byte中有4字节的BLE通信数据包的协议头。也就是我们用户的一包数据最大是247字节，但是在nordic的NUS（串口服务）中，他又占用了3个字节作为特殊用途，这样我们用户可用的数据包长度就仅剩下244字节。&lt;br /&gt;
&lt;br /&gt;
这边我们需要注意的是，BLE协议头的4字节我们是不能占用的，但是例如NUS服务的3字节（这个相当于用户的私有协议，不是必须存在）。&lt;br /&gt;
{{Note|text=1、主机和从机都可以主动配置MTU大小&lt;br /&gt;
&lt;br /&gt;
2、如果主从机配置的MTU大小不同，则取小的数值|type=tips}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机上电之后扫描周围的从机，一旦扫描到符合的过滤的从设备，就会打印扫描到的信息，并且发起连接，连接成功之后，打印连接的参数以及对方设备的信息。&lt;br /&gt;
&lt;br /&gt;
连接成功之后，会打印Data len的大小，也就是我们的MTU。&lt;br /&gt;
[[文件:Nrf rtt 18.png|边框|居中|无框|657x657像素]]&lt;br /&gt;
从机上电之后广播，被主机连接之后，打印主机的设备信息以及连接参数。&lt;br /&gt;
&lt;br /&gt;
并且会打印Data len的大小，也就是我们的MTU。&lt;br /&gt;
[[文件:Nrf tft 28.png|边框|居中|无框|648x648像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
主机部分，我们仅仅新增了有关MTU大小设置的部分，也就是在我们的gatt_init()函数中。&lt;br /&gt;
&lt;br /&gt;
====== gatt_init()函数 ======&lt;br /&gt;
在gatt初始化的函数中，我们调用nrf_ble_gatt_att_mtu_central_set()函数去配置我们的MTU大小，在主机中我们配置MTU大小为100byte。&lt;br /&gt;
&lt;br /&gt;
并且最终的MTU大小的gatt的事件回调函数中展示，携带的任务ID为NRF_BLE_GATT_EVT_ATT_MTU_UPDATED。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;224&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化GATT&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    err_code = nrf_ble_gatt_init(&amp;amp;m_gatt, gatt_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
    &lt;br /&gt;
    err_code = nrf_ble_gatt_att_mtu_central_set(&amp;amp;m_gatt, 100);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== gatt_evt_handler()回调函数 ======&lt;br /&gt;
在gatt的事件回调函数中，我们接收到任务ID为NRF_BLE_GATT_EVT_ATT_MTU_UPDATED的事件之后，可以获取到我们的MTU大小。&lt;br /&gt;
&lt;br /&gt;
这边打印的Data len，是我们MTU减掉NUS占用的3个特殊字节之后的大小。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;205&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : GATT事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : p_gatt  gatt类型结构体&lt;br /&gt;
//         p_evt  gatt事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    if ((m_conn_handle == p_evt-&amp;gt;conn_handle) &amp;amp;&amp;amp; (p_evt-&amp;gt;evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;Data len is set to 0x%X(%d)&amp;quot;, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分与主机部分是一样的，我们都是新增了一个gatt的初始化。&lt;br /&gt;
&lt;br /&gt;
====== gatt_init()函数 ======&lt;br /&gt;
在从机的gatt初始化函数中，我们配置MTU的大小是80字节（由于主机是100字节，所以最终的MTU大小已从机的80字节为准）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;174&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化GATT&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    err_code = nrf_ble_gatt_init(&amp;amp;m_gatt, gatt_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    err_code = nrf_ble_gatt_att_mtu_periph_set(&amp;amp;m_gatt, 80);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== gatt_evt_handler()回调函数 ======&lt;br /&gt;
当我们在gatt的回调函数中，接收到NRF_BLE_GATT_EVT_ATT_MTU_UPDATED的事件ID，我们可以获取到当前的MTU的大小。&lt;br /&gt;
&lt;br /&gt;
这边的Data len是MTU减掉NUS中的3个特殊字节后的大小。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;155&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : GATT事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : p_gatt  gatt类型结构体&lt;br /&gt;
//         p_evt  gatt事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    if ((m_conn_handle == p_evt-&amp;gt;conn_handle) &amp;amp;&amp;amp; (p_evt-&amp;gt;evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;Data len is set to 0x%X(%d)&amp;quot;, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
MTU的大小和连接参数，与我们的通信速率息息相关。有关MTU大小的配置，有如下几个要点：&lt;br /&gt;
&lt;br /&gt;
1、主机和从机都可以配置MTU大小&lt;br /&gt;
&lt;br /&gt;
2、大部分情况下，我们根据从机的服务来确定MTU大小&lt;br /&gt;
&lt;br /&gt;
3、MTU越大，我们可以发送的数据包就越大&lt;br /&gt;
&lt;br /&gt;
4、当主从机配置的MTU大小不同时，我们以小的数值作准。&lt;br /&gt;
===Write/Read属性服务实验===&lt;br /&gt;
====实验简介====&lt;br /&gt;
通过前面实验的学习，我们已经能够完成主机成功连接从机，并且可以配置我们想要的连接参数（连接间隔、MTU等）。所以有了前面的铺垫，从这一章节开始，我们会给大家讲解如何进行主从机的通信。本来我们应该给大家继续在串口透传的程序基础上继续添加服务功能，但由于NUS服务相对比较复杂，所以我们会插入两篇章节（本章节及下一章节），分别给大家讲解LED的Write属性服务，以及BTN的Notify属性服务，然后再切回串口透传例程继续讲解，方便大家学习。&lt;br /&gt;
&lt;br /&gt;
谈到通信，就不得不给大家介绍一个名叫“GATT”的好同志，因为就是他帮我们管理蓝牙的通信，那么对于GATT而言，当两个设备成功连接之后，他们分别作为一下两个设备之一：&lt;br /&gt;
&lt;br /&gt;
⊙GATT服务器：包含特性数据库的设备（比如控制LED数据的是LED特性，传输UART数据的是UART特性）。可以通过一个GATT客户端写入或者读取数据&lt;br /&gt;
&lt;br /&gt;
⊙GATT客户端：向GATT服务器写入数据或者读取数据的设备&lt;br /&gt;
&lt;br /&gt;
其中我们的从机设备一般是作为GATT服务器去提供服务的，主机设备作为GATT客户端去向服务的特性数据库中写入或者读取数据。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=GATT特征值属性包含如下4个：&lt;br /&gt;
&lt;br /&gt;
Write（写）、Read（读）、Notify（通知）、Indicate（暗示）&lt;br /&gt;
&lt;br /&gt;
这4个属性当中，其中大家最常用的是3个，通俗的来讲：Write属性是用于主机给从机发送数据；Read属性是用于主机读取从机的数据；&lt;br /&gt;
Notify属性是用于从机给主机发送数据|type=info}}我们本章的实验，会给大家带来其中的write属性及Read属性的介绍，也就是教大家主机如何给从机发送数据，以及主机如何读取从机的数据。其中包含了从机的服务注册，以及主机的服务发现，相对于前面章节的内容，可能难度会大上一些，大家一定耐心查看，这个章节很重要。&lt;br /&gt;
&lt;br /&gt;
====实验现象====&lt;br /&gt;
主机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、扫描符合我们连接过滤要求的从机设备（根据LED服务的UUID过滤）&lt;br /&gt;
&lt;br /&gt;
2、成功连接我们的从机设备，并且更新连接参数和MTU&lt;br /&gt;
&lt;br /&gt;
3、发现服务，成功发现Ghostyu LED Service&lt;br /&gt;
&lt;br /&gt;
4、主机分别按下4个按键，给从机发送数据控制从机设备的4个LED依次点亮，并且同时从从机读取我们刚刚发送的数据&lt;br /&gt;
[[文件:Nrf52832dk-ble-gattwirte2.png|边框|居中|无框|680x680像素]]从机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、开启广播&lt;br /&gt;
&lt;br /&gt;
2、被主机成功连接，并交互连接参数&lt;br /&gt;
&lt;br /&gt;
3、等待主机获取服务（一般主机成功获取服务的时间在0.5s~1s之间，这个时间仅供大家参考）&lt;br /&gt;
&lt;br /&gt;
4、主机按下按键，从机接收到相应的LED状态数据并打印，并根据这个数据控制板子上的LED点亮[[文件:Nrf52832dk-ble-gattwirte1.png|边框|居中|无框|684x684像素]]&lt;br /&gt;
&lt;br /&gt;
====工程及源码讲解====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== gy_profile_led_c.c\.h与main.c ======&lt;br /&gt;
我们首先查看一下主机的服务客户端文件，里面包含了好几个函数，我们挨个介绍一下这些函数的功能。&lt;br /&gt;
&lt;br /&gt;
第一个还是客户端的初始化函数，对应从机的ble_led_init()注册服务的函数，我们需要利用ble_db_discovery_evt_register()函数注册一个待会服务发现的UUID。&lt;br /&gt;
&lt;br /&gt;
这里我们需要注意一下，不论是服务的发现，还是某一个特征的发现，都是需要根据UUID来判断的。这边可以看到我们注册UUID发现是和从机的服务中一样的（LED_UUID_BASE以及LED_UUID_SERVICE）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;83&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_c_init&lt;br /&gt;
//&lt;br /&gt;
// brief : LED服务客户端初始化函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向LED客户端结构的指针&lt;br /&gt;
//         p_ble_led_c_init -&amp;gt; 指向LED初始化结构的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_led_c_init(ble_led_c_t * p_ble_led_c, ble_led_c_init_t * p_ble_led_c_init)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t      err_code;&lt;br /&gt;
    ble_uuid_t    led_uuid;&lt;br /&gt;
    ble_uuid128_t led_base_uuid = {LED_UUID_BASE};&lt;br /&gt;
&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c);&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c_init);&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c_init-&amp;gt;evt_handler);&lt;br /&gt;
&lt;br /&gt;
    p_ble_led_c-&amp;gt;peer_led_db.led_handle         = BLE_GATT_HANDLE_INVALID;&lt;br /&gt;
    p_ble_led_c-&amp;gt;conn_handle                    = BLE_CONN_HANDLE_INVALID;&lt;br /&gt;
    p_ble_led_c-&amp;gt;evt_handler                    = p_ble_led_c_init-&amp;gt;evt_handler;&lt;br /&gt;
&lt;br /&gt;
    err_code = sd_ble_uuid_vs_add(&amp;amp;led_base_uuid, &amp;amp;p_ble_led_c-&amp;gt;uuid_type);&lt;br /&gt;
    if (err_code != NRF_SUCCESS)&lt;br /&gt;
    {&lt;br /&gt;
        return err_code;&lt;br /&gt;
    }&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    led_uuid.type = p_ble_led_c-&amp;gt;uuid_type;&lt;br /&gt;
    led_uuid.uuid = LED_UUID_SERVICE;&lt;br /&gt;
&lt;br /&gt;
    return ble_db_discovery_evt_register(&amp;amp;led_uuid);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;看完初始化函数，我们接下来得看下当我们注册好客服端，并且在main函数中调用发现函数之后，底层如何给我们返回，这里我们需要结合main.c文件的内容一起给大家介绍。&lt;br /&gt;
&lt;br /&gt;
在mian函数的中，我们可以看到注册了服务发现的功能，也就是数据库发现db_discovery_init()；，并且注册了GATT初始化gatt_init();，以及我们的LED服务的客户端初始化led_c_init();。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;527&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    timer_init();           // 初始化定时器&lt;br /&gt;
    GPIOTE_Init();          // 初始化IO&lt;br /&gt;
    BTN_Init(btn_evt_handler_t); // 初始化按键  &lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    db_discovery_init();    // 初始化数据库发现（用于发现服务）&lt;br /&gt;
    gatt_init();            // 初始化GATT&lt;br /&gt;
    led_c_init();           // 初始化LED_C&lt;br /&gt;
    scan_init();            // 初始化扫描&lt;br /&gt;
    &lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;demo0:simple central&amp;quot;);&lt;br /&gt;
    &lt;br /&gt;
    scan_start();           // 开始扫描&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当我们初始化好以上说明的3个函数（具体每个函数的代码，大家可以看下代码中的注册），一旦我们主机发现我们的从机，并成功连接之后，会进入BLE_GAP_EVT_CONNECTED状态。&lt;br /&gt;
&lt;br /&gt;
在这个状态下，我们就需要开始我们的服务发现了，调用ble_db_discovery_start()函数开始发现服务。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;345&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t            err_code;&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            m_conn_handle = p_gap_evt-&amp;gt;conn_handle;&lt;br /&gt;
            &lt;br /&gt;
            err_code = ble_led_c_handles_assign(&amp;amp;m_ble_led_c, m_conn_handle, NULL);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
            // 开始发现服务，NUS客户端等待发现结果&lt;br /&gt;
            err_code = ble_db_discovery_start(&amp;amp;m_db_disc, p_ble_evt-&amp;gt;evt.gap_evt.conn_handle);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
            break;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当成功发现服务之后，会进入db_disc_handler回调函数，在这个回调函数之中，因为我们这个工程仅需要处理led的服务，所以我们调用ble_led_c_on_db_disc_evt去发现led相关的特征值内容，其中会携带我们的ble_db_discovery_evt_t参数（底层返回的所有和服务数据库相关的信息都在这个参数里面）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;316&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : db_disc_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于处理数据库发现事件的函数&lt;br /&gt;
// details : 此函数是一个回调函数，用于处理来自数据库发现模块的事件。&lt;br /&gt;
//           根据发现的UUID，此功能将事件转发到各自的服务。&lt;br /&gt;
// &lt;br /&gt;
// param : p_event -&amp;gt; 指向数据库发现事件的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void db_disc_handler(ble_db_discovery_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    ble_led_c_on_db_disc_evt(&amp;amp;m_ble_led_c, p_evt);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;所以接下来，我们需要先判断一下，底层返回的ble_db_discovery_evt_t中携带的类型是否是BLE_DB_DISCOVERY_COMPLETE，也就是数据库成功的完成发现，且发现的UUID是LED_UUID_SERVICE。&lt;br /&gt;
&lt;br /&gt;
如果确实成功的发现我们的LED服务，接下来我们就需要从服务中取出我们需要的特征值，也就是LED_UUID_CHAR。我们需要从这个特征值当中获取我们用于通信的句柄（handle_value）。&lt;br /&gt;
&lt;br /&gt;
当我们一切都是按照正确的流程跑完，可以看到在这个函数的最后，它会给我们返回一个p_ble_led_c-&amp;gt;evt_handler(p_ble_led_c, &amp;amp;evt);，也就是向mian.c文件中给我们一个回调（ble_led_c_init初始化函数时注册的回调），其中携带的任务参数类型是BLE_LED_C_EVT_DISCOVERY_COMPLETE。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;32&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_c_on_db_disc_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理led服务发现的函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向LED客户端结构的指针&lt;br /&gt;
//         p_evt -&amp;gt; 指向从数据库发现模块接收到的事件的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_led_c_on_db_disc_evt(ble_led_c_t * p_ble_led_c, ble_db_discovery_evt_t const * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    // 判断LED服务是否发现完成&lt;br /&gt;
    if (p_evt-&amp;gt;evt_type == BLE_DB_DISCOVERY_COMPLETE &amp;amp;&amp;amp;&lt;br /&gt;
        p_evt-&amp;gt;params.discovered_db.srv_uuid.uuid == LED_UUID_SERVICE &amp;amp;&amp;amp;&lt;br /&gt;
        p_evt-&amp;gt;params.discovered_db.srv_uuid.type == p_ble_led_c-&amp;gt;uuid_type)&lt;br /&gt;
    {&lt;br /&gt;
        ble_led_c_evt_t evt;&lt;br /&gt;
&lt;br /&gt;
        evt.evt_type    = BLE_LED_C_EVT_DISCOVERY_COMPLETE;&lt;br /&gt;
        evt.conn_handle = p_evt-&amp;gt;conn_handle;&lt;br /&gt;
&lt;br /&gt;
        for (uint32_t i = 0; i &amp;lt; p_evt-&amp;gt;params.discovered_db.char_count; i++)&lt;br /&gt;
        {&lt;br /&gt;
            const ble_gatt_db_char_t * p_char = &amp;amp;(p_evt-&amp;gt;params.discovered_db.charateristics[i]);&lt;br /&gt;
            switch (p_char-&amp;gt;characteristic.uuid.uuid)&lt;br /&gt;
            {&lt;br /&gt;
                // 根据LED特征值的UUID，获取我们句柄handle_value&lt;br /&gt;
                case LED_UUID_CHAR:&lt;br /&gt;
                    evt.params.peer_db.led_handle = p_char-&amp;gt;characteristic.handle_value;&lt;br /&gt;
                    break;&lt;br /&gt;
&lt;br /&gt;
                default:&lt;br /&gt;
                    break;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        NRF_LOG_DEBUG(&amp;quot;Led Button Service discovered at peer.&amp;quot;);&lt;br /&gt;
        &lt;br /&gt;
        // 如果实例是在db_discovery之前分配的，则分配db_handles&lt;br /&gt;
        if (p_ble_led_c-&amp;gt;conn_handle != BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
        {&lt;br /&gt;
            if (p_ble_led_c-&amp;gt;peer_led_db.led_handle         == BLE_GATT_HANDLE_INVALID)&lt;br /&gt;
            {&lt;br /&gt;
                p_ble_led_c-&amp;gt;peer_led_db = evt.params.peer_db;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        p_ble_led_c-&amp;gt;evt_handler(p_ble_led_c, &amp;amp;evt);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;那么接下来，我们再去看一下mian.c中此回调函数下的处理。&lt;br /&gt;
&lt;br /&gt;
在ble_led_c_evt_handler回调函数下，我们判断传入的事件类型，可以看到正是刚刚的BLE_LED_C_EVT_DISCOVERY_COMPLETE事件，也就是代表我们已经成功的获取了我们指定服务（LED_UUID_SERVICE）下的指定特征值（LED_UUID_CHAR）的句柄（handle_value）。&lt;br /&gt;
&lt;br /&gt;
然后我们调用ble_led_c_handles_assign函数，去将我们的连接句柄connHandle以及特征值句柄handle_value，绑定给p_ble_led_c实例。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;272&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_led_c_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : LED服务事件&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none                 &lt;br /&gt;
static void ble_led_c_evt_handler(ble_led_c_t * p_ble_led_c, ble_led_c_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    switch (p_evt-&amp;gt;evt_type)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_LED_C_EVT_DISCOVERY_COMPLETE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Discovery complete.&amp;quot;);&lt;br /&gt;
            err_code = ble_led_c_handles_assign(&amp;amp;m_ble_led_c, p_evt-&amp;gt;conn_handle, &amp;amp;p_evt-&amp;gt;params.peer_db);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected to device with Ghostyu LED Service.&amp;quot;);&lt;br /&gt;
            break;&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当上述的流程都正确跑完，我们就可以进行最后一步的行动，也就是发送数据，在这个例程当中我们是利用按键触发来发送对应的LED的状态变化。&lt;br /&gt;
&lt;br /&gt;
我们到mian.c中，查看按键触发会调用的btn_evt_handler_t回调函数，在这个函数中，我们最后会调用LED服务数据发送的功能函数ble_led_led_status_send。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;495&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : btn_evt_handler_t&lt;br /&gt;
//&lt;br /&gt;
// brief : 按键触发回调函数&lt;br /&gt;
// &lt;br /&gt;
// param : butState -&amp;gt; 当前的按键值&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void btn_evt_handler_t (uint8_t butState)&lt;br /&gt;
{&lt;br /&gt;
  uint8_t buf[LED_UUID_CHAR_LEN] = {0x01,0x01,0x01,0x01};&lt;br /&gt;
  switch(butState)&lt;br /&gt;
  {&lt;br /&gt;
    case BUTTON_1:&lt;br /&gt;
      buf[0] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_2:&lt;br /&gt;
      buf[1] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_3:&lt;br /&gt;
      buf[2] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_4:&lt;br /&gt;
      buf[3] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    default:&lt;br /&gt;
      break;&lt;br /&gt;
  }&lt;br /&gt;
  ble_led_status_send(&amp;amp;m_ble_led_c,buf,LED_UUID_CHAR_LEN);    // 发送Wirte属性数据包&lt;br /&gt;
  ble_led_status_read(&amp;amp;m_ble_led_c);                          // 发送Read属性的读取消息&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;最后我们来分析一下这个发送函数，是如何使用我们刚刚一大圈代码处理，最终得到的connhandle以及handle_value的。&lt;br /&gt;
&lt;br /&gt;
首先先判断下数据的长度，是不是符合我们的特征值的长度限制（不能超过我们定义的特征值的大小，否则返回参数错误），这个判断是很有必要的！&lt;br /&gt;
&lt;br /&gt;
接下来我们判断一下connhandle是否为0xffff（BLE_CONN_HANDLE_INVALID），也就是尚未连接任何设备，如果没有连接，则返回状态无效。&lt;br /&gt;
&lt;br /&gt;
最后我们定义了ble_gattc_write_params_t结构体用于赋值我们需要发送的数据，其中值得注意的是.handle   = p_ble_led_c-&amp;gt;peer_led_db.led_handle，这个就是我们刚刚获得的handle_value（特征值句柄），其他参数大家依葫芦画瓢，比较好理解，就不给大家介绍了。最终我们调用sd_ble_gattc_write函数将数据发送出去。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;148&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_led_status_send&lt;br /&gt;
//&lt;br /&gt;
// brief : LED状态控制函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向要关联的LED结构实例的指针&lt;br /&gt;
//         p_string -&amp;gt; 发送的LED相关的数据&lt;br /&gt;
//         length -&amp;gt; 发送的LED相关的数据长度&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_led_led_status_send(ble_led_c_t * p_ble_led_c, uint8_t * p_string, uint16_t length)&lt;br /&gt;
{&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c);&lt;br /&gt;
&lt;br /&gt;
    if (length &amp;gt; LED_UUID_CHAR_LEN)&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_WARNING(&amp;quot;Content too long.&amp;quot;);&lt;br /&gt;
        return NRF_ERROR_INVALID_PARAM;&lt;br /&gt;
    }&lt;br /&gt;
    if (p_ble_led_c-&amp;gt;conn_handle == BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_STATE;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ble_gattc_write_params_t const write_params =&lt;br /&gt;
    {&lt;br /&gt;
        .write_op = BLE_GATT_OP_WRITE_CMD,&lt;br /&gt;
        .flags    = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,&lt;br /&gt;
        .handle   = p_ble_led_c-&amp;gt;peer_led_db.led_handle,&lt;br /&gt;
        .offset   = 0,&lt;br /&gt;
        .len      = length,&lt;br /&gt;
        .p_value  = p_string&lt;br /&gt;
    };&lt;br /&gt;
    &lt;br /&gt;
    return sd_ble_gattc_write(p_ble_led_c-&amp;gt;conn_handle, &amp;amp;write_params);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;上面一部分已经将我们的write属性的使用都讲解完了，最后我们再来看下Read属性部分的内容。&lt;br /&gt;
&lt;br /&gt;
首先是我们还是在main文件的按键回调函数中调用的ble_led_status_read(&amp;amp;m_ble_led_c);函数，去读取从机特征值中的数据的，这里我们直接分析一下这个函数。&lt;br /&gt;
&lt;br /&gt;
可以看到函数内容很简单，只调用了一个sd_ble_gattc_read函数去读取，包含的参数内容分别是我们的connhandle以及handle_value。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;209&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_status_read&lt;br /&gt;
//&lt;br /&gt;
// brief : 读取LED特征值&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向要关联的LED结构实例的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_led_status_read(ble_led_c_t * p_ble_led_c)&lt;br /&gt;
{&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c);&lt;br /&gt;
    return sd_ble_gattc_read(p_ble_led_c-&amp;gt;conn_handle,p_ble_led_c-&amp;gt;peer_led_db.led_handle,0);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当我们成功Read之后，底层的sotfdevice会通过ble_led_c_on_ble_evt函数给我们返回BLE_GATTC_EVT_READ_RSP事件。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;140&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_c_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_led_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    if ((p_context == NULL) || (p_ble_evt == NULL))&lt;br /&gt;
    {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ble_led_c_t * p_ble_led_c = (ble_led_c_t *)p_context;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            on_disconnected(p_ble_led_c, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
        case BLE_GATTC_EVT_READ_RSP:&lt;br /&gt;
            on_read(p_ble_led_c, p_ble_evt);&lt;br /&gt;
          break;&lt;br /&gt;
          &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在BLE_GATTC_EVT_READ_RSP事件中，我们调用on_read函数去处理我们读取的值，我们将读取到的值，通过RTT LOG打印出来。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;32&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :on_read&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理read事件的函数。&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; led服务结构体&lt;br /&gt;
//         p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_read(ble_led_c_t * p_ble_led_c, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    if (p_ble_led_c-&amp;gt;conn_handle == p_ble_evt-&amp;gt;evt.gap_evt.conn_handle)&lt;br /&gt;
    {&lt;br /&gt;
      NRF_LOG_INFO(&amp;quot;Recive State:%02X,%02X,%02X,%02X&amp;quot;,&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[0],&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[1],&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[2],&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[3]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
======gy_profile_led.c\.h======&lt;br /&gt;
我们首先查看一下他的服务文件，也就是gy_profile_led，我们我们就是通过这个服务来接收主机发送的LED控制数据的。&lt;br /&gt;
&lt;br /&gt;
可以看到这个服务初始化ble_led_init函数中对于服务以及他的特征值属性的初始化过程，首先我们先初始化一个回调（p_led-&amp;gt;led_write_handler = p_led_init-&amp;gt;led_write_handler;），这个回调是用来将gy_profile_led这一层的数据，上传给mian文件去处理。&lt;br /&gt;
&lt;br /&gt;
接下来是服务的添加，首先是调用sd_ble_uuid_vs_add去添加服务，然后给这个ble_uuid的服务的参数赋值，最后调用sd_ble_gatts_service_add函数去注册这个服务，这边我们注册的服务句柄是p_led-&amp;gt;service_handle。&lt;br /&gt;
&lt;br /&gt;
注册完服务之后，我们就要开始添加我们的特征值characteristic，特征值的添加也是一样的，首先配置特征值的参数，这些参数中我们主要关注一下ble_gatt_char_props_t，这个参数是用来定义特征值的属性的，可以看到我们的属性有如下几种：&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
/**@brief GATT Characteristic Properties. */&lt;br /&gt;
typedef struct&lt;br /&gt;
{&lt;br /&gt;
  /* Standard properties */&lt;br /&gt;
  uint8_t broadcast       :1; /**&amp;lt; 广播 */&lt;br /&gt;
  uint8_t read            :1; /**&amp;lt; 读 */&lt;br /&gt;
  uint8_t write_wo_resp   :1; /**&amp;lt; 写指令 */&lt;br /&gt;
  uint8_t write           :1; /**&amp;lt; 写*/&lt;br /&gt;
  uint8_t notify          :1; /**&amp;lt; 通知*/&lt;br /&gt;
  uint8_t indicate        :1; /**&amp;lt; 暗示 */&lt;br /&gt;
  uint8_t auth_signed_wr  :1; /**&amp;lt; 签名写指令 */&lt;br /&gt;
} ble_gatt_char_props_t;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;因为我们的led的服务，是手机写数据给开发板控制LED，所以我们要定义特征值写使能（add_char_params.char_props.write = 1;），另外当我们需要知道上次是发送的什么控制数据给开发板时，我们需要读一下数据，所以这边我们同样定义一下读使能（add_char_params.char_props.read  = 1;），当我们配置完特征值的属性之后，我们调用characteristic_add函数，去向刚刚注册的p_led-&amp;gt;service_handle服务中添加我们的特征值。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;53&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化LED服务&lt;br /&gt;
//&lt;br /&gt;
// param : p_led -&amp;gt; led服务结构体&lt;br /&gt;
//         p_led_init -&amp;gt; led服务初始化结构体&lt;br /&gt;
//&lt;br /&gt;
// return : uint32_t -&amp;gt; 成功返回SUCCESS，其他返回ERR NO.&lt;br /&gt;
uint32_t ble_led_init(ble_led_t * p_led, const ble_led_init_t * p_led_init)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t              err_code;&lt;br /&gt;
    ble_uuid_t            ble_uuid;&lt;br /&gt;
    ble_add_char_params_t add_char_params;&lt;br /&gt;
&lt;br /&gt;
    // 初始化服务结构体&lt;br /&gt;
    p_led-&amp;gt;led_write_handler = p_led_init-&amp;gt;led_write_handler;&lt;br /&gt;
&lt;br /&gt;
    // 添加服务（128bit UUID）&lt;br /&gt;
    ble_uuid128_t base_uuid = {LED_UUID_BASE};&lt;br /&gt;
    err_code = sd_ble_uuid_vs_add(&amp;amp;base_uuid, &amp;amp;p_led-&amp;gt;uuid_type);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_uuid.type = p_led-&amp;gt;uuid_type;&lt;br /&gt;
    ble_uuid.uuid = LED_UUID_SERVICE;&lt;br /&gt;
&lt;br /&gt;
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &amp;amp;ble_uuid, &amp;amp;p_led-&amp;gt;service_handle);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 添加LED特征值（属性是Write和Read、长度是4）&lt;br /&gt;
    memset(&amp;amp;add_char_params, 0, sizeof(add_char_params));&lt;br /&gt;
    add_char_params.uuid             = LED_UUID_CHAR;&lt;br /&gt;
    add_char_params.uuid_type        = p_led-&amp;gt;uuid_type;&lt;br /&gt;
    add_char_params.init_len         = LED_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.max_len          = LED_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.char_props.read  = 1;&lt;br /&gt;
    add_char_params.char_props.write = 1;&lt;br /&gt;
&lt;br /&gt;
    add_char_params.read_access  = SEC_OPEN;&lt;br /&gt;
    add_char_params.write_access = SEC_OPEN;&lt;br /&gt;
&lt;br /&gt;
    return characteristic_add(p_led-&amp;gt;service_handle, &amp;amp;add_char_params, &amp;amp;p_led-&amp;gt;led_char_handles);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;看完服务的初始化，我们来看下我们的服务注册之后是怎么来进行工作的，首先我们看下ble_led_on_ble_evt这个函数，这个函数在我们mian函数中注册BLE_LED_DEF(m_led);实例的时候被引用。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;15&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :BLE_LED_DEF&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化LED服务实例&lt;br /&gt;
//&lt;br /&gt;
// param : _name -&amp;gt; 实例的名称&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
#define BLE_LED_DEF(_name)                                                                          \&lt;br /&gt;
static ble_led_t _name;                                                                             \&lt;br /&gt;
NRF_SDH_BLE_OBSERVER(_name ## _obs,                                                                 \&lt;br /&gt;
                     BLE_LED_BLE_OBSERVER_PRIO,                                                     \&lt;br /&gt;
                     ble_led_on_ble_evt, &amp;amp;_name)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这个m_led实例注册，涉及到NRF_SDH_BLE_OBSERVER的使用，简单的理解就是利用这个注册了实例之后，当底层有GAP或者GATT消息返回的时候，就会触发ble_led_on_ble_evt函数。&lt;br /&gt;
&lt;br /&gt;
因为我们LED服务这里需要接收手机端write的LED控制数据，所以我们在事件判断中，判断是否出现GATT Write事件，一旦出现了，我们调用on_write函数去处理这个事件。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;28&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_led_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_led_t * p_led = (ble_led_t *)p_context;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // GATT Client Write事件&lt;br /&gt;
        case BLE_GATTS_EVT_WRITE:&lt;br /&gt;
            on_write(p_led, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;on_write函数用于处理接收的write数据，我们判断一下接收的数据是否符合我们的要求，如果符合，那么我们通过初始化函数中的回调函数，将接收到的值上传到main函数中去处理。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;7&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :on_write&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理Write事件的函数。&lt;br /&gt;
//&lt;br /&gt;
// param : p_led -&amp;gt; led服务结构体&lt;br /&gt;
//         p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_write(ble_led_t * p_led, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    ble_gatts_evt_write_t const * p_evt_write = &amp;amp;p_ble_evt-&amp;gt;evt.gatts_evt.params.write;&lt;br /&gt;
&lt;br /&gt;
    if (   (p_evt_write-&amp;gt;handle == p_led-&amp;gt;led_char_handles.value_handle)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_evt_write-&amp;gt;len &amp;lt;= LED_UUID_CHAR_LEN)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_led-&amp;gt;led_write_handler != NULL))&lt;br /&gt;
    {&lt;br /&gt;
        p_led-&amp;gt;led_write_handler((uint8_t*)p_evt_write-&amp;gt;data);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======main.c======&lt;br /&gt;
main文件中也不给大家全部介绍了，这个和蓝牙协议实验部分是重合的，我们只关注实验改动的部分。&lt;br /&gt;
&lt;br /&gt;
我们看下服务初始化的部分，可以看到调用了我们gy_profile_led中的ble_led_init函数初始化注册了我们的LED服务，并且通用注册了一个回调函数。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数led_write_handler中，我们可以获取到gy_profile_led中上传上来的接收到的wirte数据，并且利用这个数据进行LED的控制。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;195&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : nus_data_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于处理来自Nordic UART服务的数据的功能&lt;br /&gt;
// details : 该功能将处理从Nordic UART BLE服务接收的数据并将其发送到UART模块&lt;br /&gt;
//&lt;br /&gt;
// param : ble_nus_evt_t -&amp;gt; nus事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void led_write_handler(uint8_t * new_state)&lt;br /&gt;
{&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;Recive State:%02X,%02X,%02X,%02X&amp;quot;,new_state[0],new_state[1],new_state[2],new_state[3]);&lt;br /&gt;
    LED_Control(BSP_LED_0, new_state[0]);&lt;br /&gt;
    LED_Control(BSP_LED_1, new_state[1]);&lt;br /&gt;
    LED_Control(BSP_LED_2, new_state[2]);&lt;br /&gt;
    LED_Control(BSP_LED_3, new_state[3]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : services_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化复位（本例程展示NUS：Nordic Uart Service）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void services_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t           err_code;&lt;br /&gt;
    ble_led_init_t     led_init;&lt;br /&gt;
&lt;br /&gt;
    // Initialize NUS.&lt;br /&gt;
    memset(&amp;amp;led_init, 0, sizeof(led_init));&lt;br /&gt;
&lt;br /&gt;
    led_init.led_write_handler = led_write_handler;&lt;br /&gt;
&lt;br /&gt;
    err_code = ble_led_init(&amp;amp;m_led, &amp;amp;led_init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;span&amp;gt; 实验总结&amp;lt;/span&amp;gt; ====&lt;br /&gt;
通过这一章节的学习，我们需要掌握下面3个要点。&lt;br /&gt;
&lt;br /&gt;
1、从机如何注册一个自定的服务，并且在服务下添加自己的特征值功能&lt;br /&gt;
&lt;br /&gt;
2、主机如何针对指定的从机服务，去获取这个服务以及服务下指定特征值的句柄&lt;br /&gt;
&lt;br /&gt;
3、主机如何通过Write属性，向从机发送数据&lt;br /&gt;
&lt;br /&gt;
===Notify属性服务实验===&lt;br /&gt;
====实验简介====&lt;br /&gt;
通过Write属性服务实验的学习，我们已经知道了从机设备如何注册一个自定义服务（自定义的LED服务），然后主机如何去发现这个服务，并且利用这个服务的特征值与从机设备进行通信，控制从机设备的LED点亮。&lt;br /&gt;
&lt;br /&gt;
也就是说，我们已经学会了如何通过主机给从机发送数据，所以接下来我们本章节将会给大家讲解从机如何通过notify属性给主机发送数据。&lt;br /&gt;
====实验现象====&lt;br /&gt;
主机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、扫描符合我们连接过滤要求的从机设备（根据LED服务的UUID过滤）&lt;br /&gt;
&lt;br /&gt;
2、成功连接我们的从机设备，并且更新连接参数和MTU&lt;br /&gt;
&lt;br /&gt;
3、发现服务，成功发现Ghostyu BTN Service&lt;br /&gt;
&lt;br /&gt;
4、成功使用了从机服务的notify功能&lt;br /&gt;
[[文件:Nrf52832dk-ble-gattnotify2.png|边框|居中|无框|680x680像素]]&lt;br /&gt;
从机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、开启广播&lt;br /&gt;
&lt;br /&gt;
2、被主机成功连接，并交互连接参数&lt;br /&gt;
&lt;br /&gt;
3、等待主机获取服务（一般主机成功获取服务的时间在0.5s~1s之间，这个时间仅供大家参考）&lt;br /&gt;
&lt;br /&gt;
4、等待主机成功使能notify功能&lt;br /&gt;
&lt;br /&gt;
5、从机分别按下4个按键，给主机发送相应的notify数据包，控制主机LED点亮&lt;br /&gt;
[[文件:Nrf52832dk-ble-gattnotify1.png|边框|居中|无框|695x695px]]&lt;br /&gt;
&lt;br /&gt;
====工程及源码讲解====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== gy_profile_btn_c.c\.h及mian.c ======&lt;br /&gt;
这个实验主机部分的代码和上一章节的Write的主机是类似的，他的整个的服务发现流程是一样的，唯一不同的点是由于从机需要发送notify数据，所以我们的主机需要去使能从机的notify的功能。&lt;br /&gt;
&lt;br /&gt;
所以我们来看一下主机是在哪边，以怎样的方式去使能从机的notify。以及最终是怎么接收来自从机的数据的。&lt;br /&gt;
&lt;br /&gt;
首先看下使能notify的部分，通过上一实验的学习，我们已经知道当我们自己成功跑完获取服务的流程，最后会给main函数中的服务初始化的回调返回成功的事件。我们看下成功发现服务的事件BLE_BTN_C_EVT_DISCOVERY_COMPLETE，里面首先还是调用ble_btn_c_handles_assign函数将获取的句柄值和我们m_ble_btn_c实例绑定起来。然后就去调用ble_btn_c_tx_notif_enable函数去使能从机的notify功能。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;272&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_btn_c_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BTN服务事件&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none                 &lt;br /&gt;
static void ble_btn_c_evt_handler(ble_btn_c_t * p_ble_btn_c, ble_btn_c_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    switch (p_evt-&amp;gt;evt_type)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_BTN_C_EVT_DISCOVERY_COMPLETE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Discovery complete.&amp;quot;);&lt;br /&gt;
            err_code = ble_btn_c_handles_assign(&amp;amp;m_ble_btn_c, p_evt-&amp;gt;conn_handle, &amp;amp;p_evt-&amp;gt;params.peer_db);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected to device with Ghostyu BTN Service.&amp;quot;);&lt;br /&gt;
            &lt;br /&gt;
            err_code = ble_btn_c_tx_notif_enable(&amp;amp;m_ble_btn_c);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);  &lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Enable notification.&amp;quot;);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们来看下使能notify的函数的代码，不难发现其实就是一个write功能，不过不是上一个实验向我们的handle_value去发送数据，而是向cccd_handle去发送了一个0x01（BLE_GATT_HVX_NOTIFICATION），0x00的数据。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;179&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :cccd_configure&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于向CCCD发送数据，控制使能或者禁能notify功能&lt;br /&gt;
//&lt;br /&gt;
// param : conn_handle -&amp;gt; 连接的句柄&lt;br /&gt;
//         conn_handle -&amp;gt; CCCD的句柄&lt;br /&gt;
//         enable -&amp;gt; 使能或者禁能标识&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static uint32_t cccd_configure(uint16_t conn_handle, uint16_t cccd_handle, bool enable)&lt;br /&gt;
{&lt;br /&gt;
    uint8_t buf[BLE_CCCD_VALUE_LEN];&lt;br /&gt;
&lt;br /&gt;
    buf[0] = enable ? BLE_GATT_HVX_NOTIFICATION : 0;&lt;br /&gt;
    buf[1] = 0;&lt;br /&gt;
&lt;br /&gt;
    ble_gattc_write_params_t const write_params =&lt;br /&gt;
    {&lt;br /&gt;
        .write_op = BLE_GATT_OP_WRITE_REQ,&lt;br /&gt;
        .flags    = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,&lt;br /&gt;
        .handle   = cccd_handle,&lt;br /&gt;
        .offset   = 0,&lt;br /&gt;
        .len      = sizeof(buf),&lt;br /&gt;
        .p_value  = buf&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return sd_ble_gattc_write(conn_handle, &amp;amp;write_params);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_c_tx_notif_enable&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于使能从机btn服务的notify功能&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_btn_c -&amp;gt; 指向要关联的BTN结构实例的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_btn_c_tx_notif_enable(ble_btn_c_t * p_ble_btn_c)&lt;br /&gt;
{&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_btn_c);&lt;br /&gt;
&lt;br /&gt;
    if ( (p_ble_btn_c-&amp;gt;conn_handle == BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
       ||(p_ble_btn_c-&amp;gt;peer_btn_db.cccd_handle == BLE_GATT_HANDLE_INVALID)&lt;br /&gt;
       )&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_STATE;&lt;br /&gt;
    }&lt;br /&gt;
    return cccd_configure(p_ble_btn_c-&amp;gt;conn_handle,p_ble_btn_c-&amp;gt;peer_btn_db.cccd_handle, true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;看完了使能notify的函数，我们来看下接收从机数据的处理部分，首先是看到ble_btn_c_on_ble_evt函数，这个函数在我们调用BLE_BTN_C_DEF(m_ble_btn_c);注册实例的时候，就已经创建好了，用于接收底层的softdevice的消息返回。&lt;br /&gt;
&lt;br /&gt;
我们看下其中的BLE_GATTC_EVT_HVX（Handle Value Notification or Indication event）事件，在这个事件下我们接收到从机发送给我们的数据。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;146&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_c_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_btn_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    if ((p_context == NULL) || (p_ble_evt == NULL))&lt;br /&gt;
    {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ble_btn_c_t * p_ble_btn_c = (ble_btn_c_t *)p_context;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_GATTC_EVT_HVX:&lt;br /&gt;
            on_hvx(p_ble_btn_c, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
      &lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            on_disconnected(p_ble_btn_c, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;然后我们看下对于接收到的从机数据的处理，首先还是一样的，我们需要判断一下数据的来源是不是我们btn_handle。当确认都是正确的，然后我们将接收的数据复制给ble_btn_c_evt_t，然后通过它的回调上传到我们的main文件中，携带的事件ID为BLE_BTN_C_EVT_BTN_TX_EVT。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;32&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :on_hvx&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于处理从SoftDevice接收到的通知&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_btn_c -&amp;gt; 指向BTN Client结构的指针&lt;br /&gt;
//         p_ble_evt -&amp;gt; 指向接收到的BLE事件的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_hvx(ble_btn_c_t * p_ble_btn_c, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    if (   (p_ble_btn_c-&amp;gt;peer_btn_db.btn_handle != BLE_GATT_HANDLE_INVALID)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_ble_evt-&amp;gt;evt.gattc_evt.params.hvx.handle == p_ble_btn_c-&amp;gt;peer_btn_db.btn_handle)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_ble_btn_c-&amp;gt;evt_handler != NULL))&lt;br /&gt;
    {&lt;br /&gt;
        ble_btn_c_evt_t ble_btn_c_evt;&lt;br /&gt;
&lt;br /&gt;
        ble_btn_c_evt.evt_type = BLE_BTN_C_EVT_BTN_TX_EVT;&lt;br /&gt;
        ble_btn_c_evt.p_data   = (uint8_t *)p_ble_evt-&amp;gt;evt.gattc_evt.params.hvx.data;&lt;br /&gt;
        ble_btn_c_evt.data_len = p_ble_evt-&amp;gt;evt.gattc_evt.params.hvx.len;&lt;br /&gt;
&lt;br /&gt;
        p_ble_btn_c-&amp;gt;evt_handler(p_ble_btn_c, &amp;amp;ble_btn_c_evt);&lt;br /&gt;
        NRF_LOG_DEBUG(&amp;quot;Client sending data.&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来返回到我们的main文件中，我们在ble_btn_c_evt_handler回调中可以看到BLE_BTN_C_EVT_BTN_TX_EVT事件的处理，我们将接收到的从机数据用于控制相应的LED灯点亮。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;297&amp;quot;&amp;gt;&lt;br /&gt;
        case BLE_BTN_C_EVT_BTN_TX_EVT:&lt;br /&gt;
            NRF_LOG_DEBUG(&amp;quot;Receiving data.&amp;quot;);&lt;br /&gt;
            NRF_LOG_HEXDUMP_DEBUG(p_evt-&amp;gt;p_data, p_evt-&amp;gt;data_len);&lt;br /&gt;
            LED_Control(BSP_LED_0, p_evt-&amp;gt;p_data[0]);&lt;br /&gt;
            LED_Control(BSP_LED_1, p_evt-&amp;gt;p_data[1]);&lt;br /&gt;
            LED_Control(BSP_LED_2, p_evt-&amp;gt;p_data[2]);&lt;br /&gt;
            LED_Control(BSP_LED_3, p_evt-&amp;gt;p_data[3]);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
======gy_profile_btn.c\.h======&lt;br /&gt;
首先我们还是先看一下服务配置文件，首先还是注册一下服务，注册的服务句柄是p_btn-&amp;gt;service_handle。服务注册完成之后，我们注册按键的特征值，可以看到我们分别使能了按键的notify通知属性（add_char_params.char_props.notify = 1;），并且同样使能了read属性（add_char_params.char_props.read  = 1;）。&lt;br /&gt;
&lt;br /&gt;
这里我们需要注意的是下面的cccd_write_access参数被使能，上一实验大家都好理解需要使能write_access和read_access，因为我们需要使用读写，那么为什么这个例程要使能cccd_write_access呢，这边给大家简单说明一下CCCD。{{Note|text=Client Characteristic Configuration Descriptor（CCCD）是客户端特征配置描述符。当主机向CCCD中写入0x0001，此时使能notify；当写入0x0000时，此时禁止notify。&lt;br /&gt;
在nordic的协议栈当中，他的这个notify使能是交给用户自己处理的，也是说即便主机没有向cccd中写入0x0001去使能notify，我们同样可以直接利用notify去发送数据，只能这样不符合规范。|type=info}}&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;50&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化BTN服务&lt;br /&gt;
//&lt;br /&gt;
// param : p_btn -&amp;gt; btn服务结构体&lt;br /&gt;
//&lt;br /&gt;
// return : uint32_t -&amp;gt; 成功返回SUCCESS，其他返回ERR NO.&lt;br /&gt;
uint32_t ble_btn_init(ble_btn_t * p_btn)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t              err_code;&lt;br /&gt;
    ble_uuid_t            ble_uuid;&lt;br /&gt;
    ble_add_char_params_t add_char_params;&lt;br /&gt;
&lt;br /&gt;
    // 添加服务（128bit UUID）&lt;br /&gt;
    ble_uuid128_t base_uuid = {BTN_UUID_BASE};&lt;br /&gt;
    err_code = sd_ble_uuid_vs_add(&amp;amp;base_uuid, &amp;amp;p_btn-&amp;gt;uuid_type);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_uuid.type = p_btn-&amp;gt;uuid_type;&lt;br /&gt;
    ble_uuid.uuid = BTN_UUID_SERVICE;&lt;br /&gt;
&lt;br /&gt;
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &amp;amp;ble_uuid, &amp;amp;p_btn-&amp;gt;service_handle);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 添加BTN特征值（属性是Write和Read、长度是4）&lt;br /&gt;
    memset(&amp;amp;add_char_params, 0, sizeof(add_char_params));&lt;br /&gt;
    add_char_params.uuid             = BTN_UUID_CHAR;&lt;br /&gt;
    add_char_params.uuid_type        = p_btn-&amp;gt;uuid_type;&lt;br /&gt;
    add_char_params.init_len         = BTN_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.max_len          = BTN_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.char_props.read  = 1;&lt;br /&gt;
    add_char_params.char_props.notify = 1;&lt;br /&gt;
    &lt;br /&gt;
    add_char_params.read_access  = SEC_OPEN;&lt;br /&gt;
    add_char_params.cccd_write_access = SEC_OPEN;&lt;br /&gt;
&lt;br /&gt;
    return characteristic_add(p_btn-&amp;gt;service_handle, &amp;amp;add_char_params, &amp;amp;p_btn-&amp;gt;btn_char_handles);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;服务部分剩下的处理流程和上一实验是类型的，只不过上一个实验是处理的wirte属性，而这个实验是处理notify属性。&lt;br /&gt;
&lt;br /&gt;
首先在BLE事件处理的函数中，我们应该要处理CCCD_Write的数据的，所以在由softdevice返回消息的ble_btn_on_ble_evt函数中，我们需要处理一下BLE_GATTS_EVT_WRITE事件。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;30&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_btn_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_btn_t * p_btn = (ble_btn_t *)p_context;&lt;br /&gt;
  &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_GATTS_EVT_WRITE:&lt;br /&gt;
            on_write(p_btn, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在这个on_write函数中，我们接收到了主机发送过来的使能从机notify的数据，我们需要判断一下接收的数据的句柄是不是cccd_handle，以及接收的数据长度是不是2字节（使能数据：01 00）。如果成功使能了notify，那么我们打印&amp;quot;notification enabled&amp;quot;。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;10&amp;quot;&amp;gt;&lt;br /&gt;
static void on_write(ble_btn_t * p_btn, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    ble_gatts_evt_write_t const * p_evt_write = &amp;amp;p_ble_evt-&amp;gt;evt.gatts_evt.params.write;&lt;br /&gt;
    &lt;br /&gt;
    if ((p_evt_write-&amp;gt;handle == p_btn-&amp;gt;btn_char_handles.cccd_handle) &amp;amp;&amp;amp;&lt;br /&gt;
        (p_evt_write-&amp;gt;len == 2))&lt;br /&gt;
    {&lt;br /&gt;
      if (ble_srv_is_notification_enabled(p_evt_write-&amp;gt;data))&lt;br /&gt;
      {&lt;br /&gt;
          p_client.is_notification_enabled = true;&lt;br /&gt;
          NRF_LOG_INFO(&amp;quot;notification enabled&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else&lt;br /&gt;
      {&lt;br /&gt;
          p_client.is_notification_enabled = false;&lt;br /&gt;
          NRF_LOG_INFO(&amp;quot;notification disabled&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;除了上述的3个函数，我们的从机服务文件，就只剩下一个发送notify数据的函数了。首先我们一定要先判断一下是否已经使能的notify使能，并且判断数据长度是否符合要求。&lt;br /&gt;
&lt;br /&gt;
下面这个函数，就是我们notify发送数据的函数，他的参数我们只需要配置4个。&lt;br /&gt;
&lt;br /&gt;
type配置为BLE_GATT_HVX_NOTIFICATION，代表是notify属性的数据；&lt;br /&gt;
&lt;br /&gt;
handle我们需要配置为我们按键特征值的value.handle，代表的是按键特征值的Value这个列表的句柄；&lt;br /&gt;
&lt;br /&gt;
剩下的p_data和p_len就是我们需要发送的数据以及数据的长度。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;95&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_data_send&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理按键按下，状态更新的事件&lt;br /&gt;
//&lt;br /&gt;
// param : p_btn -&amp;gt; btn结构体&lt;br /&gt;
//         p_data -&amp;gt; 数据指针&lt;br /&gt;
//         p_length -&amp;gt; 数据长度&lt;br /&gt;
//         conn_handle -&amp;gt; 连接的句柄&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_btn_data_send(ble_btn_t *p_btn, uint8_t *p_data, uint16_t p_length, uint16_t conn_handle)&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    ble_gatts_hvx_params_t     hvx_params;&lt;br /&gt;
&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_btn);&lt;br /&gt;
&lt;br /&gt;
    if (conn_handle == BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_NOT_FOUND;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!p_client.is_notification_enabled)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_STATE;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (p_length &amp;gt; BTN_UUID_CHAR_LEN)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_PARAM;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;hvx_params, 0, sizeof(hvx_params));&lt;br /&gt;
    hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;&lt;br /&gt;
    hvx_params.handle = p_btn-&amp;gt;btn_char_handles.value_handle;&lt;br /&gt;
    hvx_params.p_data = p_data;&lt;br /&gt;
    hvx_params.p_len  = &amp;amp;p_length;&lt;br /&gt;
&lt;br /&gt;
    return sd_ble_gatts_hvx(conn_handle, &amp;amp;hvx_params);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======main.c======&lt;br /&gt;
首先我们还是需要添加一下服务初始化函数。其他的处理都是在gy_profile_btn文件中，所以main文件中就只剩下一个按键触发后调用notify发送的部分。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;194&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : services_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化复位（本例程展示NUS：Nordic Uart Service）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void services_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t           err_code;&lt;br /&gt;
&lt;br /&gt;
    err_code = ble_btn_init(&amp;amp;m_btn);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当有按键按下时，最终会将按键消息传递到这个回调中进行处理，我们根据按键触发的消息，对相应的buf值进行修改，最后调用ble_btn_data_send函数将数据发送给主机。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;351&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : btn_evt_handler_t&lt;br /&gt;
//&lt;br /&gt;
// brief : 按键触发回调函数&lt;br /&gt;
// &lt;br /&gt;
// param : butState -&amp;gt; 当前的按键值&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void btn_evt_handler_t (uint8_t butState)&lt;br /&gt;
{&lt;br /&gt;
  uint8_t buf[BTN_UUID_CHAR_LEN] = {0x01,0x01,0x01,0x01};&lt;br /&gt;
  switch(butState)&lt;br /&gt;
  {&lt;br /&gt;
    case BUTTON_1:&lt;br /&gt;
      buf[0] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_2:&lt;br /&gt;
      buf[1] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_3:&lt;br /&gt;
      buf[2] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_4:&lt;br /&gt;
      buf[3] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    default:&lt;br /&gt;
      break;&lt;br /&gt;
  }&lt;br /&gt;
  ble_btn_data_send(&amp;amp;m_btn, buf, BTN_UUID_CHAR_LEN, m_conn_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;span&amp;gt; &amp;lt;/span&amp;gt;实验总结 ====&lt;br /&gt;
经过本章内容的学习，我们需要对notify属性的服务有一定的了解，主要掌握以下3点：&lt;br /&gt;
&lt;br /&gt;
1、我们需要了解主机如何去使能从机的notify功能&lt;br /&gt;
&lt;br /&gt;
2、从机如何创建一个支持notify功能的特征值服务&lt;br /&gt;
&lt;br /&gt;
3、从机如何发送一个notify功能的数据包给主机&lt;br /&gt;
&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK%E5%8D%8F%E8%AE%AE%E6%A0%88%E5%AE%9E%E9%AA%8C&amp;diff=2876</id>
		<title>NRF52832DK协议栈实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK%E5%8D%8F%E8%AE%AE%E6%A0%88%E5%AE%9E%E9%AA%8C&amp;diff=2876"/>
		<updated>2020-05-20T06:12:05Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：/* 实验现象 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;我们的蓝牙协议实验部分，将会给大家带来最直观的蓝牙协议部分的学习，我们通过拆分的方式，带领大家深入了解蓝牙协议的主要功能部分。&lt;br /&gt;
&lt;br /&gt;
在进行蓝牙协议实验学习之前，我们有一些基础但十分重要的蓝牙协议介绍分享给大家，这个部分的蓝牙协议不像蓝牙协议手册那样，对每个参数进行说明，洋洋洒洒会有好几千页的说明文档，这样大家也很难去看完并记忆。&lt;br /&gt;
&lt;br /&gt;
我们针对的仅仅是大家在利用协议栈开发个人需求的时候，绕不开的蓝牙协议部分的内容进行介绍。首先我们先分析一下蓝牙主从机通信，都有哪些重要点。&lt;br /&gt;
&lt;br /&gt;
主机部分：扫描、发起连接、发现服务、通信。&lt;br /&gt;
&lt;br /&gt;
从机部分：广播、注册服务、被连接、通信。&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+蓝牙主从机通信协议要点&lt;br /&gt;
! colspan=&amp;quot;4&amp;quot; |主机&lt;br /&gt;
! colspan=&amp;quot;4&amp;quot; |从机&lt;br /&gt;
|-&lt;br /&gt;
!扫描&lt;br /&gt;
!连接&lt;br /&gt;
!服务&lt;br /&gt;
!通信&lt;br /&gt;
!广播&lt;br /&gt;
!服务&lt;br /&gt;
!连接&lt;br /&gt;
!通信&lt;br /&gt;
|-&lt;br /&gt;
|扫描参数①&lt;br /&gt;
|连接参数③&lt;br /&gt;
|服务句柄⑦&lt;br /&gt;
|属性Write⑨&lt;br /&gt;
|广播数据②&lt;br /&gt;
|服务⑥&lt;br /&gt;
|连接参数③&lt;br /&gt;
|属性Notify⑨&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|连接句柄④&lt;br /&gt;
|使能通知⑧&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|连接句柄④&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|MTU大小⑤&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|MTU大小⑤&lt;br /&gt;
|&lt;br /&gt;
|}{{Note|text=实验源码位于百度云盘归档资料中：归档资料/1-协议栈SDK/谷雨实验源码包/，代码的使用请参考《NRF52832DK入门手册》的蓝牙协议栈SDK一节|type=tips}}&lt;br /&gt;
&lt;br /&gt;
==== 扫描参数① ====&lt;br /&gt;
&lt;br /&gt;
===== 主机扫描核心参数 =====&lt;br /&gt;
主机扫描核心参数主要是4个，也就是说这4个参数是不可获取的，必须要配置的。分别是扫描间隔interval、扫描窗口window、扫描持续时长duration、扫描模式active。&lt;br /&gt;
&lt;br /&gt;
扫描间隔interval：两个连续的扫描窗口的起始时间的时间差，这个很好理解，就是第一次扫描起始时间和第二次扫描起始时间的时间差。&lt;br /&gt;
&lt;br /&gt;
扫描窗口window：就是指的单次扫描的时间。&lt;br /&gt;
&lt;br /&gt;
扫描持续时长duration：我们发起一次扫描持续的时间，发起一次扫描包含了N个扫描窗口。&lt;br /&gt;
&lt;br /&gt;
所以我们的扫描参数需要满足如下条件：&lt;br /&gt;
&lt;br /&gt;
1、duration ≥ interval ≥ window&lt;br /&gt;
&lt;br /&gt;
2、协议规定window和interval不能超过10.24s&lt;br /&gt;
&lt;br /&gt;
协议栈中一些特殊参数情况的处理：&lt;br /&gt;
&lt;br /&gt;
1、duration设置为0，持续扫描。也就是说设置为0代表无穷大&lt;br /&gt;
&lt;br /&gt;
2、duration小于interval，则至少广播一次&lt;br /&gt;
&lt;br /&gt;
扫描模式：&lt;br /&gt;
&lt;br /&gt;
1、active配置1，主动扫描模式，可获取从设备的广播数据以及扫描回调数据&lt;br /&gt;
&lt;br /&gt;
2、active配置0，被动扫描模式，只可以获取从设备的广播数据&lt;br /&gt;
[[文件:BLE技术 扫描窗口和扫描间隔.jpg|居中|无框|520x520像素]]&lt;br /&gt;
===== 主机扫描特殊应用参数 =====&lt;br /&gt;
extended：这个是用于BLE5.0协议中新增的大广播包数据，定义为1，才可以获取到外部大广播包&lt;br /&gt;
&lt;br /&gt;
filter_policy：过滤扫描的参数，这个是用于我们扫描的时候，过滤出我们想要的设备&lt;br /&gt;
&lt;br /&gt;
scan_phys：扫描的PHYs，这个同样是BLE5.0协议中新增的物理层协议，分别BLE_GAP_PHY_1MBPS、BLE_GAP_PHY_2MBPS等&lt;br /&gt;
&lt;br /&gt;
channel_mask：扫描的信道（暂时不清楚此参数如何使用）&lt;br /&gt;
&lt;br /&gt;
==== 广播数据② ====&lt;br /&gt;
&lt;br /&gt;
===== BLE4.x广播数据 =====&lt;br /&gt;
BLE4.x的蓝牙广播数据包，最大是31byte，遵循的方式如下，首先是数据的长度、紧接着是数据类型，最后才是数据内容。&lt;br /&gt;
&lt;br /&gt;
数据长度：某一个类型的数据长度，注意长度包含数据类型及数据内容。&lt;br /&gt;
&lt;br /&gt;
数据类型：BLE协议规定的数据类型，例如可发现标志flag是0x01，local name是0x09。&lt;br /&gt;
&lt;br /&gt;
数据内容：用户自定义数据。&lt;br /&gt;
{{Note|text=包含广播数据和扫描回调数据：&lt;br /&gt;
所有的广播数据内容，用户都是可以自定义，但一定要符合上面所说的数据结构，不然BLE设备无法正确识别。|type=tips}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+广播包的数据格式&lt;br /&gt;
!数据长度&lt;br /&gt;
!数据类型&lt;br /&gt;
!数据内容&lt;br /&gt;
|-&lt;br /&gt;
|0x02&lt;br /&gt;
|0x01（flag 可发现标志）&lt;br /&gt;
|0x06&lt;br /&gt;
|-&lt;br /&gt;
|0x06&lt;br /&gt;
|0x09（name 本地名称）&lt;br /&gt;
|0x4759303031（“GY001”）&lt;br /&gt;
|-&lt;br /&gt;
|...&lt;br /&gt;
|...&lt;br /&gt;
|...&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===== BLE5.x新增大广播包数据 =====&lt;br /&gt;
&lt;br /&gt;
==== 连接参数③ ====&lt;br /&gt;
参数如下：&lt;br /&gt;
&lt;br /&gt;
'''Connection Interval连接间隔'''：在BLE连接中，使用跳频方案。两个设备在特定时间仅在特定频道上彼此发送和接收数据。这些设备稍后在新的通道（协议栈的链路层处理通道切换）上通过这个约定的时间相遇。这次用于收发数据的相遇称为连接事件。如果没有要发送或接收的应用数据，则两台设备交换链路层数据来维护连接。两个连接事件之间的时间跨度，是以1.25 ms为单位，连接间隔的范围从最小值6（7.5 ms）到最大值3200（4.0 s）。&lt;br /&gt;
&lt;br /&gt;
不同的应用可能需要不同的连接间隔。&lt;br /&gt;
[[文件:连接参数1.jpg|边框|居中|无框|603x603像素]]&lt;br /&gt;
&lt;br /&gt;
'''Slave Latency从机延迟'''，此参数为从机（外设设备）提供跳过多个连接事件的能力。这种能力给外设设备更多的灵活性。如果外设没有要发送的数据，则可以跳过连接事件，保持睡眠并节省电量。外设设备选择是否在每个连接事件时间点上唤醒。虽然外设可以跳过连接事件，但不能超出从延迟参数允许的最大值。&lt;br /&gt;
[[文件:连接参数2.jpg|边框|居中|无框|525x525像素]]&lt;br /&gt;
&lt;br /&gt;
'''Supervision Time-out监控超时'''，是两次成功连接事件之间的最长时间。如果在此时间内没有成功的连接事件，设备将终止连接并返回到未连接状态。该参数值以10 ms为单位，监控超时值可以从最小值10（100 ms）到3200（32.0 s）。超时必须大于有效的连接间隔。&lt;br /&gt;
&lt;br /&gt;
===== Effective Connection Interval有效连接间隔 =====&lt;br /&gt;
有效连接间隔等于两个连接事件之间的时间跨度，假设从机跳过最大数量的连接事件，且允许从机延迟（如果从机延迟设置为0，则有效连接间隔等于实际连接间隔，）。&lt;br /&gt;
&lt;br /&gt;
从机延迟表示可以跳过的最大事件数。该数字的范围可以从最小值0（意味着不能跳过连接事件）到最大值499。最大值不能使有效连接间隔（见下列公式）大于16秒。间隔可以使用以下公式计算：&lt;br /&gt;
&lt;br /&gt;
Effective Connection Interval '''=''' '''('''Connection Interval''')''' × '''('''1 '''+''' '''['''Slave Latency'''])'''&lt;br /&gt;
&lt;br /&gt;
Consider the following example''':'''&lt;br /&gt;
* Connection Interval''':''' 80 '''('''100 ms''')'''&lt;br /&gt;
* Slave Latency''':''' 4&lt;br /&gt;
* Effective Connection Interval''':''' '''('''100 ms''')''' × '''('''1 '''+''' 4''')''' '''=''' 500 ms&lt;br /&gt;
当没有数据从从机发送到主机时，从机每500ms一个连接事件交互一次。&lt;br /&gt;
&lt;br /&gt;
===== 连接参数的注意事项 =====&lt;br /&gt;
在许多应用中，从机跳过最大连接事件数。选择正确的连接参数组在低功耗蓝牙设备的功率优化中起重要作用。以下列表给出了连接参数设置中权衡的总体概述。&lt;br /&gt;
&lt;br /&gt;
减少连接间隔如下：&lt;br /&gt;
* 增加两个设备的功耗&lt;br /&gt;
* 增加双向吞吐量&lt;br /&gt;
* 减少任一方向发送数据的时间&lt;br /&gt;
增加连接间隔如下：&lt;br /&gt;
* 降低两个设备的功耗&lt;br /&gt;
* 降低双向吞吐量&lt;br /&gt;
* 增加任一方向发送数据的时间&lt;br /&gt;
减少从机延迟（或将其设置为零）如下：&lt;br /&gt;
* 增加外围设备的功耗&lt;br /&gt;
* 减少外围设备接收从中央设备发送的数据的时间&lt;br /&gt;
增加从机延迟如下：&lt;br /&gt;
* 在周边没有数据发送期间，可以降低外设的功耗到主机设备&lt;br /&gt;
* 增加外设设备接收从主机设备发送的数据的时间&lt;br /&gt;
&lt;br /&gt;
==== 连接句柄④ ====&lt;br /&gt;
设备的连接句柄范围是从0x0000-0xFFFD，当然我们实际使用，也连接不了这么多的设备。&lt;br /&gt;
&lt;br /&gt;
连接的句柄是按照连接的先后顺序分配，开发者无法自己指定，也就是说第一个与之连接的设备将会被分配连接句柄（connHandle）0x0000，第二个是0x0001，以此类推。&lt;br /&gt;
&lt;br /&gt;
为什么句柄的最终范围是0xFFFD，因为后面的0xFFFE与0xFFFF是有特殊用于含义的。&lt;br /&gt;
&lt;br /&gt;
0xFFFE：代表的是正在连接的设备的句柄。当我们发起对一个设备的连接，但是迟迟没有连接上，我们可以调用断开连接的函数，利用这个0xFFFE句柄断开这个连接。&lt;br /&gt;
&lt;br /&gt;
0xFFFF：断开的句柄。当设备与之断开连接之后，句柄就会返回为0xFFFF。&lt;br /&gt;
&lt;br /&gt;
==== MTU大小⑤ ====&lt;br /&gt;
MTU的大小，在BLE4.0的是时候，最大是只有27byte，当更新到BLE4.1向后，我们支持的MTU最大是251字节。&lt;br /&gt;
&lt;br /&gt;
低功耗蓝牙协议栈支持链路层L2CAP PDU的分片和重组。这种分段支持允许构建在L2CAP之上的L2CAP和更高级协议（如属性协议（ATT））使用较大的有效负载大小，并减少与较大数据事务相关的开销。当使用分片时，较大的分组被分割成多个链路层分组，并由对等体设备的链路层重新组合。&lt;br /&gt;
[[文件:MTU1.jpg|边框|居中|无框|745x745像素]]&lt;br /&gt;
L2CAP PDU的大小还定义了属性协议最大传输单元（ATT_MTU）的大小。默认情况下，LE设备假设L2CAP PDU的大小为27字节，这对应于可以在单个连接事件数据包中传输的LE数据包的最大大小。在这种情况下，L2CAP协议头为4字节，导致ATT_MTU的默认大小为23。&lt;br /&gt;
&lt;br /&gt;
注意： 使用LE数据长度分机功能时，LE包的长度最多可达251字节。&lt;br /&gt;
&lt;br /&gt;
==== 服务⑥ ====&lt;br /&gt;
&lt;br /&gt;
==== 服务句柄⑦ ====&lt;br /&gt;
&lt;br /&gt;
==== 使能通知⑧ ====&lt;br /&gt;
&lt;br /&gt;
==== 属性⑨ ====&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙协议实验目录 ===&lt;br /&gt;
蓝牙协议实验部分，我们借由串口透传实验，一步一步拆分，给大家介绍蓝牙的协议方面。&lt;br /&gt;
&lt;br /&gt;
下面是我们的实验列表，里面的实验是循序渐进的进行深入讲解蓝牙协议的，建议大家不要跳跃学习。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+实验列表&lt;br /&gt;
!主机实验编号&lt;br /&gt;
!主机实验内容&lt;br /&gt;
|-&lt;br /&gt;
|1.0_ble_central_pm&lt;br /&gt;
|主机低功耗实验&lt;br /&gt;
|-&lt;br /&gt;
|1.1_ble_central_log&lt;br /&gt;
|主机LOG打印实验&lt;br /&gt;
|-&lt;br /&gt;
|1.2_ble_central_scan_all&lt;br /&gt;
|主机通用扫描实验&lt;br /&gt;
|-&lt;br /&gt;
|1.3_ble_central_scan_filter&lt;br /&gt;
|主机过滤扫描实验&lt;br /&gt;
|-&lt;br /&gt;
|1.4_ble_central_scan_whitelist&lt;br /&gt;
|主机白名单扫描实验&lt;br /&gt;
|-&lt;br /&gt;
|1.5_ble_central_conn_all&lt;br /&gt;
|主机通用连接实验&lt;br /&gt;
|-&lt;br /&gt;
|1.6_ble_central_conn_filter&lt;br /&gt;
|主机过滤连接实验&lt;br /&gt;
|-&lt;br /&gt;
|1.7_ble_central_update_connParam&lt;br /&gt;
|主机连接参数更新实验&lt;br /&gt;
|-&lt;br /&gt;
|1.8_ble_central_update_mtu&lt;br /&gt;
|主机MTU大小配置实验&lt;br /&gt;
|-&lt;br /&gt;
|1.9_ble_central_profile_led&lt;br /&gt;
|主机服务Client实验（Write/Read属性）&lt;br /&gt;
|-&lt;br /&gt;
|1.0_ble_central_profile_btn&lt;br /&gt;
|主机服务Client实验（Notify属性）&lt;br /&gt;
|-&lt;br /&gt;
|1.11_ble_central_profile_nus&lt;br /&gt;
|主机获取NUS服务实验&lt;br /&gt;
|-&lt;br /&gt;
|1.12_ble_central_nus_communication&lt;br /&gt;
|主机利用NUS服务收发通信实验&lt;br /&gt;
|-&lt;br /&gt;
|2.0_ble_peripheral_pm&lt;br /&gt;
|从机低功耗实验&lt;br /&gt;
|-&lt;br /&gt;
|2.1_ble_peripheral_log&lt;br /&gt;
|从机LOG打印实验&lt;br /&gt;
|-&lt;br /&gt;
|2.2_ble_peripheral_adv_all&lt;br /&gt;
|从机通用广播实验&lt;br /&gt;
|-&lt;br /&gt;
|2.3_ble_peripheral_adv_filter&lt;br /&gt;
|从机过滤广播实验&lt;br /&gt;
|-&lt;br /&gt;
|2.4_ble_peripheral_adv_whitelist&lt;br /&gt;
|从机白名单广播实验&lt;br /&gt;
|-&lt;br /&gt;
|2.5_ble_peripheral_conn_all&lt;br /&gt;
|从机通用连接实验&lt;br /&gt;
|-&lt;br /&gt;
|2.6_ble_peripheral_conn_filter&lt;br /&gt;
|从机过滤连接实验&lt;br /&gt;
|-&lt;br /&gt;
|2.7_ble_peripheral_update_connParam&lt;br /&gt;
|从机连接参数请求更新实验&lt;br /&gt;
|-&lt;br /&gt;
|2.8_ble_peripheral_update_mtu&lt;br /&gt;
|从机MTU大小配置实验&lt;br /&gt;
|-&lt;br /&gt;
|2.9_ble_peripheral_profile_led&lt;br /&gt;
|从机服务Server实验（Write属性）&lt;br /&gt;
|-&lt;br /&gt;
|2.10_ble_peripheral_profile_btn&lt;br /&gt;
|从机服务Server实验（Notify属性）&lt;br /&gt;
|-&lt;br /&gt;
|2.11_ble_peripheral_profile_nus&lt;br /&gt;
|从机注册NUS服务实验&lt;br /&gt;
|-&lt;br /&gt;
|2.12_ble_peripheral_nus_communication&lt;br /&gt;
|从机利用NUS服务收发通信实验&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 样例工程结构简介 ===&lt;br /&gt;
我们首先借由协议栈自带的串口透传例程，给大家说明一下蓝牙工程的结构，我们打开工程，查看workspace部分（每一个Group都是同等地位，以下说明不分先后）。&lt;br /&gt;
[[文件:Workspace group.png|边框|居中|无框|436x436像素]]&lt;br /&gt;
Application：主要就两个文件，一个是主函数 main.c 文件，这个文件是大家后续编程主要修改的文件。另一个 sdk_config.h 配置文件，NRF52相关的蓝牙参数、外设参数等等，我们在使用之前都需要在这个文件中配置或者是使能。&lt;br /&gt;
[[文件:Workspace application.png|边框|居中|无框|351x351像素]]&lt;br /&gt;
Board Definition：nordic给我们用户实现好的有关按键和LED灯的控制的文件，这个文件主要是nordic方便自己例程的展示实现的，在后续的编程中大家可以选择使用，或者自己根据个人需要重新实现。&lt;br /&gt;
[[文件:Workspace boarddefinition.png|边框|居中|无框|345x345像素]]&lt;br /&gt;
Board Support：这个分组和Board Definition是一起作用的，就是利用按键和LED灯进行一些功能的展示，比如按键的外部中断唤醒，比如用LED来指示当前蓝牙的状态等。也是一样的，后续编程中大家可以根据个人需要选择保留还是自己实现。&lt;br /&gt;
[[文件:Workspace boardsupport.png|边框|居中|无框|344x344像素]]&lt;br /&gt;
None：这个分组也是包含了两个文件，这两个文件都是和芯片相关的。arm_startup_nrf52.s 是芯片的启动文件，这个文件配置了芯片启动时的堆栈空间，中断向量等等参数。system_nrf52.c 文件是芯片的系统文件，配置了芯片的RAM、时钟、射频以及引脚端口等等。这两个文件都是芯片的必要文件，所以每一个工程中都是需要包含的。&lt;br /&gt;
[[文件:Workspace none.png|边框|居中|无框|332x332像素]]&lt;br /&gt;
nRF_BLE：这个分组包含的是蓝牙协议相关的配置文件，也就是我们协议栈实验部分主要要讲解的内容。主要有如下几个部分，广播、连接、扫描等等，由于我们展示的例程是串口透传从机，所以这边看不到扫描相关的文件。&lt;br /&gt;
[[文件:Workspace nrfble.png|边框|居中|无框|335x335像素]]&lt;br /&gt;
nRF_BLE_Services：这个分组是用于存放我们的蓝牙profile服务文件，像我们串口透传例程，就包含了NUS（nordic uart service）的通用配置文件。&lt;br /&gt;
[[文件:Workspace nrfbleservices.png|边框|居中|无框|325x325像素]]&lt;br /&gt;
nRF_Drivers：这个分组包含的是外设驱动文件，其中前缀是nrf开头的代表的是老的驱动文件，nrfx代表的新的驱动文件，我们当前使用的SDK15.2兼容新旧两种外设驱动文件。&lt;br /&gt;
[[文件:Workspace nrfdrivers.png|边框|居中|无框|330x330像素]]&lt;br /&gt;
nRF_Libraries：库函数文件的分组，里面包含了两个大类。一个是以app为前缀的文件，这部分是留给我们用户在应用层调用的库文件，基本上是按键、时钟等等一些外设的库。另一部分是以nrf为前缀的库文件，这个是和芯片相关的库，包括内存分配、打印以及电源管理等等。&lt;br /&gt;
[[文件:Workspace nrflibraries.png|边框|居中|无框|452x452像素]]&lt;br /&gt;
nRF_Log：这个是nordic做好的一个打印调试信息的功能分组，主要分为两个，一个是利用Jlink仿真器实现的RTT，另一个则是利用串口打印。这个部分的功能在下面的实验中有讲解。&lt;br /&gt;
[[文件:Workspace nrflog.png|边框|居中|无框|333x333像素]]&lt;br /&gt;
nRF_Segger_RTT：这个部分是Segger公司实现好的RTT的驱动文件，我们使用的时候只需要将文件添加到我们的工程中，然后调用API接口就行，这样我们就能将调试信息通过Jlink打印到RTT Viewer显示。这个部分的功能是配置上面的Log打印使用的，方便我们开发者调试程序。&lt;br /&gt;
[[文件:Workspace nrfseggerrtt.png|边框|居中|无框|324x324像素]]&lt;br /&gt;
nRF_SoftDevice：这里包含的文件主要是配置协议栈初始化的时候协议栈的参数设定，由于协议栈实际上是不开源的，而是留下了配置接口，这些配置接口通过客户配置相关协议栈的参数来设置协议栈运行状态。也就是在我们的stack初始化部分去初始化softdevice。&lt;br /&gt;
[[文件:Workspace softdevice.png|边框|居中|无框|327x327像素]]UTF8/UTF16 convert：utf8与utf16之间的转换。[[文件:Workspace utf8utf16.png|边框|居中|无框|359x359像素]]Output：文件输出分组，里面存放了两个文件，一个是图像数据文件.map，一个是可执行的.out文件。[[文件:Workspace output.png|边框|居中|无框|361x361像素]]&lt;br /&gt;
&lt;br /&gt;
=== 主从机最小工程（低功耗实验） ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
低功耗实验1.0_ble_central_pm与2.0_ble_peripheral_pm，这两个实验给大家带来的是最精简的主机以及从机例程，精简到什么程度呢，只保留了协议栈初始化以及电源管理部分。利用此实验，大家可以测试一下我们的BLE工程进入低功耗模式下的功耗情况。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
我们将万用表串联到电路中，并且打到电流档，此时我们可以看到功耗如下。&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
首先我们查看一下main.c文件，在此文件的mian()函数中，我们首先初始化了电源管理模块，然后初始化了BLE栈堆，最后在while大循环中我们调用空闲状态处理的函数。&lt;br /&gt;
&lt;br /&gt;
接下来我们分别针对这三个部分进行介绍。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;170&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== ble_stack_init()函数及ble_evt_handler()回调函数 ======&lt;br /&gt;
我们查看一下BLE协议栈初始化，这个部分是一个格式化的东西。&lt;br /&gt;
&lt;br /&gt;
首先调用nrf_sdh_enable_request()函数请求使能softdevice，原因在于ble协议、时钟、错误的回调以及中断的配置等，都需要这个sd（softdevice）支持。&lt;br /&gt;
&lt;br /&gt;
接下来我们要配置默认的ble协议，主要包含了RAM起始地址、本工程的角色（根据设备支持连接的角色来判断）、MTU的大小及UUID和属性表大小。&lt;br /&gt;
&lt;br /&gt;
RAM的起始地址在下面的位置可以看到，我们打开'''\ble_ghostyu\1.0_ble_central_pm\LaunchIOT\s132\iar'''下的'''ble_app_ghostyu_iar_nRF5x.icf'''文件（将此文件拖到IAR中就可以打开，可以看到__ICFEDIT_region_RAM_start__   = 0x200029e0）。&lt;br /&gt;
&lt;br /&gt;
然后我们携带RAM起始地址，使能BLE协议栈。&lt;br /&gt;
&lt;br /&gt;
在最后，我们注册了一个ble_evt_handler回调，在这个回调中我们处理BLE的事件返回。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;102&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_stack_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化BLE协议栈&lt;br /&gt;
// details : 初始化SoftDevice、BLE事件中断&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_stack_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    // SD使能请求，配置时钟，配置错误回调，中断（中断优先级栈堆默认设置）&lt;br /&gt;
    err_code = nrf_sdh_enable_request();&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // SD默认配置（如下），SD RAM起始地址配置（0x200029e0）&lt;br /&gt;
    // 作为从机时的最大连接数量0&lt;br /&gt;
    // 作为主机时的最大连接数据1（本工程是主机）&lt;br /&gt;
    // 初始化MTU大小23&lt;br /&gt;
    // 供应商特定的UUID数量1&lt;br /&gt;
    // 属性表大小248（必须是4的倍数，以字节为单位）&lt;br /&gt;
    // 配置服务更改特性数量0&lt;br /&gt;
    uint32_t ram_start = 0;&lt;br /&gt;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能BLE栈堆&lt;br /&gt;
    err_code = nrf_sdh_ble_enable(&amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 注册BLE事件的处理程序，所有BLE的事件都将分派ble_evt_handler回调&lt;br /&gt;
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;ble_evt_handler回调函数，用于处理BLE的事件回调，包含了COMMON、GAP、GATT Client、GATT Server、L2CAP等多种事件类型。在这个例程中，我们只给大家保留了最基础的两个GAP状态，分别为连接状态和断开连接状态。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;68&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_handle: 0x%x&amp;quot;,p_gap_evt-&amp;gt;conn_handle);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== power_management_init()及idle_state_handle()函数 ======&lt;br /&gt;
power_management_init()函数调用底层的nrf_pwr_mgmt_init()函数去初始化电源管理的部分。&lt;br /&gt;
&lt;br /&gt;
idle_state_handle()函数调用底层的nrf_pwr_mgmt_run()函数，用于处理空闲状态的功能（处理完所有的挂起事件，然后进入休眠，直到下一个事件发生）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;138&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : power_management_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化电源管理&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void power_management_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
    err_code = nrf_pwr_mgmt_init();&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : idle_state_handle&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理空闲状态的功能（用于主循环）&lt;br /&gt;
// details : 处理任何挂起的日志操作，然后休眠直到下一个事件发生&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void idle_state_handle(void)&lt;br /&gt;
{&lt;br /&gt;
    if (NRF_LOG_PROCESS() == false)&lt;br /&gt;
    {&lt;br /&gt;
        nrf_pwr_mgmt_run();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== ble_stack_init()函数 ======&lt;br /&gt;
从机部分大体上是和主机一样的，在nordic的协议栈例程中，（如果大家对BLE协议有一定的了解或者使用过其他厂家的BLE芯片）我们可以发现，nordic为了简化BLE的开发难度，可谓是不择手段，他减掉了很多的ble协议相关的内容（这里的减掉指的是放到底层处理，不需要开发者去配置），这其中就包含了GAP Role，也就是蓝牙的角色。&lt;br /&gt;
&lt;br /&gt;
ble_satck_init()函数在主机与从机中，唯一不同的点在初始化参数配置。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;99&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_stack_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化BLE协议栈&lt;br /&gt;
// details : 初始化SoftDevice、BLE事件中断&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_stack_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    // SD使能请求，配置时钟，配置错误回调，中断（中断优先级栈堆默认设置）&lt;br /&gt;
    err_code = nrf_sdh_enable_request();&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // SD默认配置（如下），SD RAM起始地址配置（0x20002a98）&lt;br /&gt;
    // 作为从机时的最大连接数量1（本工程为从机）&lt;br /&gt;
    // 作为主机时的最大连接数据0&lt;br /&gt;
    // 初始化MTU大小23&lt;br /&gt;
    // 供应商特定的UUID数量1&lt;br /&gt;
    // 属性表大小248（必须是4的倍数，以字节为单位）&lt;br /&gt;
    // 配置服务更改特性数量0&lt;br /&gt;
    uint32_t ram_start = 0;&lt;br /&gt;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能BLE栈堆&lt;br /&gt;
    err_code = nrf_sdh_ble_enable(&amp;amp;ram_start);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 注册BLE事件的处理程序，所有BLE的事件都将分派ble_evt_handler回调&lt;br /&gt;
    NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;主机初始化时设置的是作为主机时最大连接数量1，从机初始化时设置的是作为从机时最大连接数量1。这个配置的宏定义是在sdk_config.h文件中。我们go to define，进入nrf_sdh_ble_default_cfg_set()默认参数配置函数中查看，可以找到如下部分。&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_BLE_PERIPHERAL_LINK_COUNT与NRF_SDH_BLE_CENTRAL_LINK_COUNT，在定义最大连接设备数量的同时，也决定了它本身的角色属性。&lt;br /&gt;
&lt;br /&gt;
BLE中我们称Central中心设备为主机（发起连接的设备）、Peripheral外部设备为从机（广播等待被连接的设备）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;103&amp;quot;&amp;gt;&lt;br /&gt;
ret_code_t nrf_sdh_ble_default_cfg_set(uint8_t conn_cfg_tag, uint32_t * p_ram_start)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t ret_code;&lt;br /&gt;
&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    // Configure the connection roles.&lt;br /&gt;
    memset(&amp;amp;ble_cfg, 0, sizeof(ble_cfg));&lt;br /&gt;
    ble_cfg.gap_cfg.role_count_cfg.periph_role_count  = NRF_SDH_BLE_PERIPHERAL_LINK_COUNT;&lt;br /&gt;
#ifndef S112&lt;br /&gt;
    ble_cfg.gap_cfg.role_count_cfg.central_role_count = NRF_SDH_BLE_CENTRAL_LINK_COUNT;&lt;br /&gt;
    ble_cfg.gap_cfg.role_count_cfg.central_sec_count  = MIN(NRF_SDH_BLE_CENTRAL_LINK_COUNT,&lt;br /&gt;
                                                            BLE_GAP_ROLE_COUNT_CENTRAL_SEC_DEFAULT);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
    return NRF_SUCCESS;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
在低功耗主从机的学习中，我们可以了解到最精简的主从机工程。也就是只包含了softdevice与ble协议栈初始化、以及电源管理初始化。&lt;br /&gt;
&lt;br /&gt;
重点问题：&lt;br /&gt;
&lt;br /&gt;
1.了解如何定义一个BLE工程是主机还是从机，或者其他的属性（主从一体等等）。&lt;br /&gt;
&lt;br /&gt;
2.了解如何进行低功耗处理（main()函数中while循环的idle_state_handle空闲任务处理函数）。&lt;br /&gt;
&lt;br /&gt;
=== LOG打印实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
LOG打印实验1.1_ble_central_log与2.1_ble_peripheral_log，LOG打印实验是在低功耗实验的基础上，新增了LOG打印部分。&lt;br /&gt;
&lt;br /&gt;
那么为什么我们需要添加log功能，主要是因为在开发的过程中，我们几乎很难一次调通我们需求的功能，这个时候我们就需要一种好的方式去帮助我们调通，我们出现问题点在哪（查找bug），以及我们的流程进行到哪边了（流程监视）。说到这里，大家会说为什么不使用在线调试的方式解决问题呢，下面谈一谈我个人的使用感觉（仅针对BLE）。&lt;br /&gt;
&lt;br /&gt;
何时使用在线调试：&lt;br /&gt;
&lt;br /&gt;
1.针对细小的问题点，例如某个参数的数值是否正确&lt;br /&gt;
&lt;br /&gt;
2.针对不影响程序整机蓝牙功能运行的问题点，例如外设功能异常&lt;br /&gt;
&lt;br /&gt;
何时使用log：&lt;br /&gt;
&lt;br /&gt;
1.不能使用在线调试功能的时候（例如蓝牙连接状态下的通信调试，由于连接参数的限制，如果在线打断点调试，会导致异常断开）&lt;br /&gt;
&lt;br /&gt;
2.监测关键节点信息（有些问题会出现在一些特殊情况下，可能需要监测N多次，才会出现一次异常）&lt;br /&gt;
{{Note|text=注意：我们的LOG工程，仅针对RTT部分，没有介绍UART，UART的使用大家可以查看基础实验部分|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
我们打开J-Link RTT Viewer，选择我们的Jlink仿真器，可以看到log打印如下。&lt;br /&gt;
[[文件:Nrf rtt 11.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
提示：&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
我们在工程中，新增加了nRF_Log与nRF_Segger_RTT分组，这两个分组中分别包含了log打印的相关函数，以及RTT的相关函数。&lt;br /&gt;
{{Note|text=使用RTT，可以从目标微控制器输出信息，并以非常高的速度向应用程序发送输入，而不会影响目标的实时行为。&lt;br /&gt;
&lt;br /&gt;
SEGGER RTT可与任何J-Link模型和任何支持目标处理器一起使用，后者允许后台存储器访问，即Cortex-M和RX目标。&lt;br /&gt;
&lt;br /&gt;
RTT在两个方向上支持多个通道，直到主机和目标，可以用于不同的目的，并为用户提供最大的自由。&lt;br /&gt;
&lt;br /&gt;
默认实现每个方向使用一个通道，用于可打印的终端输入和输出。使用J-Link RTT Viewer，该通道可用于多个“虚拟”终端，允许使用一个目标缓冲区打印到多个窗口（例如一个用于标准输出，一个用于错误输出，一个用于调试输出）。例如，可以使用附加的up（到主机）通道来发送分析或事件跟踪数据。|type=info}}&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
log打印实验相对于前一章节的低功耗实验，新增的功能并不多，我们仅仅添加了LOG的RTT打印功能，这边我们首先还是看下mian函数。&lt;br /&gt;
&lt;br /&gt;
可以看到，我们新增了log_init()的LOG初始化函数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;188&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
&lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;1.1_ble_central_log&amp;quot;);&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== log_init()函数以及底层调用 ======&lt;br /&gt;
我们查看到log_init()函数，先调用NRF_LOG_INIT()函数初始化LOG，然后我们调用NRF_LOG_DEFAULT_BACKENDS_INIT()函数来决定LOG的底层调用。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;140&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : log_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化log打印&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void log_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code = NRF_LOG_INIT(NULL);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    NRF_LOG_DEFAULT_BACKENDS_INIT();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来我们追踪NRF_LOG_DEFAULT_BACKENDS_INIT()函数，在这个函数中，我们会引用到RTT功能。&lt;br /&gt;
&lt;br /&gt;
首先我们go to define，找到NRF_LOG_DEFAULT_BACKENDS_INIT()函数的定义，sdk_config.h中我们定义了NRF_LOG_ENABLED为1，也就是使能LOG打印。&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;61&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * @def NRF_LOG_DEFAULT_BACKENDS_INIT&lt;br /&gt;
 * @brief Macro for initializing default backends.&lt;br /&gt;
 *&lt;br /&gt;
 * Each backend enabled in configuration is initialized and added as a backend to the logger.&lt;br /&gt;
 */&lt;br /&gt;
#if NRF_LOG_ENABLED&lt;br /&gt;
#define NRF_LOG_DEFAULT_BACKENDS_INIT() nrf_log_default_backends_init()&lt;br /&gt;
#else&lt;br /&gt;
#define NRF_LOG_DEFAULT_BACKENDS_INIT()&lt;br /&gt;
#endif&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来我们追踪nrf_log_default_backends_init()函数，在sdk_config.h中，定义了NRF_LOG_BACKEND_RTT_ENABLED为1，也就是使能了RTT打印。&lt;br /&gt;
&lt;br /&gt;
在这个地方，我们会调用nrf_log_backend_rtt_init()函数初始化RTT，然后调用nrf_log_backend_add()添加新的后端接口，并调用nrf_log_backend_enable()将这个新的后端接口使能。&lt;br /&gt;
&lt;br /&gt;
这样处理之后，我们便可以使用面向RTT的log功能了。&lt;br /&gt;
{{Note|text=RTT部分的源码，这个大家有兴趣的可以自行了解，我们使用RTT功能的时候，只需要将RTT分组下的SEGGER_RTT_XX.c的三个文件添加到工程即可，不一定要了解这个源码|type=warning}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;58&amp;quot;&amp;gt;&lt;br /&gt;
void nrf_log_default_backends_init(void)&lt;br /&gt;
{&lt;br /&gt;
    int32_t backend_id = -1;&lt;br /&gt;
    (void)backend_id;&lt;br /&gt;
#if defined(NRF_LOG_BACKEND_RTT_ENABLED) &amp;amp;&amp;amp; NRF_LOG_BACKEND_RTT_ENABLED&lt;br /&gt;
    nrf_log_backend_rtt_init();&lt;br /&gt;
    backend_id = nrf_log_backend_add(&amp;amp;rtt_log_backend, NRF_LOG_SEVERITY_DEBUG);&lt;br /&gt;
    ASSERT(backend_id &amp;gt;= 0);&lt;br /&gt;
    nrf_log_backend_enable(&amp;amp;rtt_log_backend);&lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#if defined(NRF_LOG_BACKEND_UART_ENABLED) &amp;amp;&amp;amp; NRF_LOG_BACKEND_UART_ENABLED&lt;br /&gt;
    nrf_log_backend_uart_init();&lt;br /&gt;
    backend_id = nrf_log_backend_add(&amp;amp;uart_log_backend, NRF_LOG_SEVERITY_DEBUG);&lt;br /&gt;
    ASSERT(backend_id &amp;gt;= 0);&lt;br /&gt;
    nrf_log_backend_enable(&amp;amp;uart_log_backend);&lt;br /&gt;
#endif&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== NRF_LOG_XX()函数说明 ======&lt;br /&gt;
上面说明了如何去初始化LOG向RTT打印的功能，下面我们将给大家介绍一下LOG打印的函数，毕竟这个才是我们真正要用到的部分。&lt;br /&gt;
&lt;br /&gt;
LOG打印的函数一共有4个，分别为打印ERROR、WARNING、INFO、DEBUG。他们的功能看字面意思就可以明白，分别是打印错误、警告、用户信息、调试信息。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;111&amp;quot;&amp;gt;&lt;br /&gt;
#define NRF_LOG_ERROR(...)                     NRF_LOG_INTERNAL_ERROR(__VA_ARGS__)&lt;br /&gt;
#define NRF_LOG_WARNING(...)                   NRF_LOG_INTERNAL_WARNING( __VA_ARGS__)&lt;br /&gt;
#define NRF_LOG_INFO(...)                      NRF_LOG_INTERNAL_INFO( __VA_ARGS__)&lt;br /&gt;
#define NRF_LOG_DEBUG(...)                     NRF_LOG_INTERNAL_DEBUG( __VA_ARGS__)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;底层的打印函数大家自己go to define查看，我这边给大家各举一个例子，方便大家使用。&lt;br /&gt;
&lt;br /&gt;
其实他们的使用方式，都是和printf一样的，只是打印消息的级别不同而已。printf函数的使用大家不清楚的，可以百度查看一下。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
NRF_LOG_ERROR(&amp;quot;sd_ble_cfg_set() returned %s when attempting to set BLE_CONN_CFG_GAP.&amp;quot;,&lt;br /&gt;
                      nrf_strerror_get(ret_code));&lt;br /&gt;
                      &lt;br /&gt;
NRF_LOG_WARNING(&amp;quot;Change the RAM start location from 0x%x to 0x%x.&amp;quot;,&lt;br /&gt;
                      app_ram_start_link, *p_app_ram_start);&lt;br /&gt;
                      &lt;br /&gt;
NRF_LOG_INFO(&amp;quot;Shutdown started. Type %d&amp;quot;, m_pwr_mgmt_evt);&lt;br /&gt;
&lt;br /&gt;
NRF_LOG_DEBUG(&amp;quot;BLE event: 0x%x.&amp;quot;, p_ble_evt-&amp;gt;header.evt_id);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;上面一段话和大家说了nordic协议栈中4个标准的LOG打印函数，他们其实拥有相同的功能，但是打印的级别不同（针对不同的调试阶段和目的），我们我们如何控制这个打印的级别呢，也是在我们的sdk_config.h中，大家可以看到NRF_LOG_DEFAULT_LEVEL。默认的只能打印到info，如果大家需要使用debug打印，需要修改此处的值为4。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;7569&amp;quot;&amp;gt;&lt;br /&gt;
// &amp;lt;o&amp;gt; NRF_LOG_DEFAULT_LEVEL  - Default Severity level&lt;br /&gt;
 &lt;br /&gt;
// &amp;lt;0=&amp;gt; Off &lt;br /&gt;
// &amp;lt;1=&amp;gt; Error &lt;br /&gt;
// &amp;lt;2=&amp;gt; Warning &lt;br /&gt;
// &amp;lt;3=&amp;gt; Info &lt;br /&gt;
// &amp;lt;4=&amp;gt; Debug &lt;br /&gt;
&lt;br /&gt;
#ifndef NRF_LOG_DEFAULT_LEVEL&lt;br /&gt;
#define NRF_LOG_DEFAULT_LEVEL 3&lt;br /&gt;
#endif&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分与主机部分新增log功能相同，这边不再赘述。&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
LOG打印实验，大家需要掌握的三个要点如下：&lt;br /&gt;
&lt;br /&gt;
1.了解什么是RTT。&lt;br /&gt;
&lt;br /&gt;
2.了解nordic协议栈中LOG面向RTT功能的初始化、及打印函数的使用。&lt;br /&gt;
&lt;br /&gt;
3.要善于使用log功能，这样有利于我们程序的流程开发及异常问题的查找和处理。&lt;br /&gt;
&lt;br /&gt;
=== 通用广播与扫描实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
通用扫描实验1.2_ble_central_scan_all与2.2_ble_peripheral_adv_all，给大家带来的是主机的扫描功能展示，以及从机的广播功能展示。&lt;br /&gt;
&lt;br /&gt;
也就是从这一实验开始，我们才是真正进入到BLE协议的学习实验，我们将按照扫描、连接、获取服务、通信的流程，在接下来的实验中给大家介绍BLE协议。&lt;br /&gt;
&lt;br /&gt;
大家都清楚，低功耗蓝牙主从机间交互数据的方式一般来说是有两种，一种是连接之后通信（这个是蓝牙的主要功能），另一种就是本实验带来的主机扫描获取从机的广播数据。而上述的两种方式，不管是哪一种，都是需要扫描功能的，毕竟连接通信之前，我们的主机也是需要扫描到从机设备才可以发起连接。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机log打印如下，先打印当前例程的名称'''1.2_ble_central_scan_all'''，然后将会打印扫描到的从机设备的信息，格式如下:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
Device MAC： 0x010203040506         // 从机设备MAC地址&lt;br /&gt;
&lt;br /&gt;
adv data: 0x0102...xx...xx          // 从机设备广播数据&lt;br /&gt;
&lt;br /&gt;
scan data: 0x0102...xx...xx         // 从机设备扫描回调数据&lt;br /&gt;
&lt;br /&gt;
rssi: -xxdBm                        // 从机设备相当于当前主机的信号强度&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
[[文件:Nrf rtt 12.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
主机log打印如下，先打印当前例程的名称'''2.2_ble_peripheral_adv_all。'''&lt;br /&gt;
[[文件:Nrf rtt 22.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 工程说明 =====&lt;br /&gt;
相对于LOG打印的工程，我们新增了nRF_BLE分组，这个分组下包含的就是BLE协议相关的文件，我们在本实验中只使用了nrf_ble_scan.c下的扫描功能函数。&lt;br /&gt;
[[文件:Nrf group nrf ble.png|边框|居中|无框|296x296像素]]&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
在mian函数中，我们新增了扫描功能的scan_init()初始化函数，并且在初始化所有功能之后，我们调用了发起扫描的函数scan_start()。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;296&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    scan_init();            // 初始化扫描&lt;br /&gt;
    &lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;1.2_ble_central_scan_all&amp;quot;);&lt;br /&gt;
    &lt;br /&gt;
    scan_start();           // 开始扫描&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数及scan_evt_handler()回调函数 ======&lt;br /&gt;
首先我们看一下我们的扫描功能的初始化函数，可以看到我们最终调用的是库函数中的nrf_ble_scan_init()去初始化我们扫描，在这个函数中，有两个参数需要我们关注，一个是init_scan（这个参数携带的我们对于扫描的设置），另一个是scan_evt_handler（当我们扫描到设备之后，将会由这个回调函数返回事件信息给我们）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;155&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
    &lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;首先我们看一下扫描的参数设置，这边我们设置为主动扫描，扫描间隔是100ms，扫描窗口是50ms，扫描的持续时间设置为0（设置为0，则一直扫描，除非我们调用停止扫描的函数），另外我们设置扫描附近的所有BLE设备（不做扫描限制）。&lt;br /&gt;
{{Note|text=扫描模式分为两种：&lt;br /&gt;
1.被动扫描：只扫描从机设备的广播数据&lt;br /&gt;
&lt;br /&gt;
2.主动扫描：扫描从机设备的广播数据以及扫描回调数据|type=info}}&lt;br /&gt;
[[文件:扫描窗口和扫描间隔.jpg|边框|居中|无框|800x800像素]]&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;55&amp;quot;&amp;gt;&lt;br /&gt;
// 定义扫描参数&lt;br /&gt;
static ble_gap_scan_params_t m_scan_params = &lt;br /&gt;
{&lt;br /&gt;
    .active        = 1,                            // 1为主动扫描，可获得广播数据及扫描回调数据&lt;br /&gt;
    .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,   // 扫描间隔：160*0.625 = 100ms  &lt;br /&gt;
    .window        = NRF_BLE_SCAN_SCAN_WINDOW,     // 扫描窗口：80*0.625 = 50ms   &lt;br /&gt;
    .timeout       = NRF_BLE_SCAN_SCAN_DURATION,   // 扫描持续的时间：设置为0，一直扫描，直到明确的停止扫描&lt;br /&gt;
    .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,   // 扫描所有BLE设备，不做限制&lt;br /&gt;
    .scan_phys     = BLE_GAP_PHY_1MBPS,            // 扫描1MBPS的PHY&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来我们查看一下我们处理扫描回调事件的函数，因为我们这个实验带给大家的是扫描附近所有的BLE广播设备，所以可以看到，我们判断的设备ID为NRF_BLE_SCAN_EVT_NOT_FOUND，这个ID代表的未被过滤的扫描数据。&lt;br /&gt;
&lt;br /&gt;
在这个事件ID下，我们根据p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;type.scan_response判断扫描到的数据是广播数据还是扫描回调数据，并且我们可以从这个消息结构体当中获取到上述的两包数据、以及设备的MAC、设备的RSSI信号强度等等。并且我们将扫描到的这些信息通过LOG向RTT格式化打印出来。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;99&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 未过滤的扫描数据&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_NOT_FOUND:&lt;br /&gt;
        {&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
          else  // 否则为广播数据&lt;br /&gt;
          {&lt;br /&gt;
            // 打印扫描的设备MAC&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_start()函数 ======&lt;br /&gt;
scan_start()发起扫描的函数，调用的是nordic提供的底层的nrf_ble_scan_start()函数实现的。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;83&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_start&lt;br /&gt;
//&lt;br /&gt;
// brief : 开始扫描&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_start(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t ret;&lt;br /&gt;
&lt;br /&gt;
    ret = nrf_ble_scan_start(&amp;amp;m_scan);&lt;br /&gt;
    APP_ERROR_CHECK(ret);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
相对于LOG实验，从机工程也是新增了nRF_BLE的分组，但是我们可以看到看到分组下的文件与主机工程是不同的。&lt;br /&gt;
&lt;br /&gt;
主机部分主要是扫描和获取服务相关，从机部分则是广播、连接参数和服务注册相关。&lt;br /&gt;
[[文件:Nrf group nrf ble p.png|边框|居中|无框|330x330像素]]&lt;br /&gt;
&lt;br /&gt;
====== main()函数 ======&lt;br /&gt;
在mian()函数，我们新增了广播初始化的advertising_init()函数、以及发起广播的advertising_start()函数。并且我们还初始化了GAP，调用的是gap_params_init()函数。&lt;br /&gt;
&lt;br /&gt;
本来我们这一实验，只是想给大家带来广播和广播数据配置的展示的，但是为了能够让大家更直观的“看到”设备，所以我们便需要在广播数据中携带设备名称，那么问题来了，在nordic的协议栈中，除非我们去修改他提供的ble_advdata.c文件，不然我们没法直接去配置这个广播数据中的设备名称（这边给大家一个建议，除非对于协议栈的理解有把握，否则尽可能的不要去修改它）。&lt;br /&gt;
&lt;br /&gt;
为了完成上述的添加设备名称的宏愿，所以我们只能“委曲求全”的添加了gap的初始化。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;260&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    gap_params_init();      // 初始化GAP&lt;br /&gt;
    advertising_init();     // 初始化广播&lt;br /&gt;
    &lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;2.2_ble_peripheral_adv_all&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    advertising_start();    // 开启广播&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();  // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== gap_params_init()函数 ======&lt;br /&gt;
由于广播数据介绍的篇幅会大一些，所以我们先给大家说明一下gap_params_init()函数。&lt;br /&gt;
&lt;br /&gt;
在这个gap初始化的函数中，我们设置了设备名称设备的名称为GY-NRF52832。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;52&amp;quot;&amp;gt;&lt;br /&gt;
#define DEVICE_NAME  &amp;quot;GY-NRF52832&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;124&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gap_params_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化GAP&lt;br /&gt;
// details : 此功能将设置设备的所有必需的GAP（通用访问配置文件）参数。它还设置权限和外观。&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void gap_params_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t                err_code;&lt;br /&gt;
&lt;br /&gt;
    // 设置设备名称&lt;br /&gt;
    err_code = sd_ble_gap_device_name_set(NULL,&lt;br /&gt;
                                          (const uint8_t *) DEVICE_NAME,&lt;br /&gt;
                                          strlen(DEVICE_NAME));&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== advertising_init()函数 ======&lt;br /&gt;
{{Note|text=在这里给大家科普一下，BLE的广播数据和扫描回调数据都是由用户自定义的，对于数据的内容没有任何的限制。我们只需要遵循下面的广播数据结构要求就行。&lt;br /&gt;
&lt;br /&gt;
advdata[] = &lt;br /&gt;
{&lt;br /&gt;
  长度A，类型A，数据A，&lt;br /&gt;
  长度B，类型B，数据B&lt;br /&gt;
}|type=info}}&lt;br /&gt;
&lt;br /&gt;
有关广播数据中类型的定义见sdk离线文档：&amp;lt;code&amp;gt;file:///E:/project-nordic/nRF5_SDK_15.2.0_offline_doc/s132/group___b_l_e___g_a_p___a_d___t_y_p_e___d_e_f_i_n_i_t_i_o_n_s.html&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在我们的广播数据初始化函数中，我们首先定义了init.advdata.name_type为BLE_ADVDATA_FULL_NAME，这意味着我们的广播数据中将携带有刚刚GAP初始化中的全部设备名称。&lt;br /&gt;
&lt;br /&gt;
然后我们定义了init.srdata.include_ble_device_addr为true，这意味着我们的扫描回调设备中将会携带有设备的MAC地址。&lt;br /&gt;
&lt;br /&gt;
接下来我们设置了快慢广播，如下代码配置的结果是先快速广播18s（周期40ms），再慢速广播18s（周期100ms），最后停止广播。&lt;br /&gt;
&lt;br /&gt;
最后我们调用nordic提供的广播初始化函数ble_advertising_init()。&lt;br /&gt;
{{Note|text=我们展示的仅仅是nordic配置广播和扫描回调数据中的部分，除了上述的名称和设备地址。广播数据中还可以携带设备的类型、信号发射强度、连接参数、uuid等等，具体的大家可以go to define，查看ble_advdata_t结构体参数说明。|type=warning}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;85&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : advertising_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化广播&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void advertising_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t               err_code;&lt;br /&gt;
    ble_advertising_init_t init;&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;init, 0, sizeof(init));&lt;br /&gt;
&lt;br /&gt;
    // 广播数据包含所有设备名称（FULL NAME）&lt;br /&gt;
    init.advdata.name_type = BLE_ADVDATA_FULL_NAME;&lt;br /&gt;
//    // 广播数据只包含部分设备名称（SHORT NAME，长度为6）&lt;br /&gt;
//    init.advdata.name_type =  BLE_ADVDATA_SHORT_NAME;&lt;br /&gt;
//    init.advdata.short_name_len = 6;&lt;br /&gt;
    &lt;br /&gt;
    // 扫描回调数据中包含设备MAC地址&lt;br /&gt;
    init.srdata.include_ble_device_addr = true;&lt;br /&gt;
    &lt;br /&gt;
    &lt;br /&gt;
    // 配置广播周期，先快速广播18s（周期40ms），再慢速广播18s（周期100ms），最后停止广播&lt;br /&gt;
    init.config.ble_adv_fast_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_fast_interval = 64;       // 64*0.625 = 40ms&lt;br /&gt;
    init.config.ble_adv_fast_timeout  = 1800;     // 1800*10ms = 18s&lt;br /&gt;
    init.config.ble_adv_slow_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_slow_interval = 160;      // 160*0.625 = 100ms&lt;br /&gt;
    init.config.ble_adv_slow_timeout  = 1800;     // 1800*10ms = 18s&lt;br /&gt;
    &lt;br /&gt;
    err_code = ble_advertising_init(&amp;amp;m_advertising, &amp;amp;init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_advertising_conn_cfg_tag_set(&amp;amp;m_advertising, APP_BLE_CONN_CFG_TAG);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== advertising_start()函数 ======&lt;br /&gt;
advertising_start()作为我们开启广播功能的函数，我们调用了nordic协议栈中的ble_advertising_start()函数去实现功能。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;71&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : advertising_start&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于开启广播&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void advertising_start(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t err_code = ble_advertising_start(&amp;amp;m_advertising, BLE_ADV_MODE_FAST);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过通用扫描实验的学习，我们需要掌握的蓝牙协议有如下几点。&lt;br /&gt;
&lt;br /&gt;
主机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何配置扫描的参数&lt;br /&gt;
&lt;br /&gt;
2.如何发起扫描，并触类旁通，自行了解如何停止（大家自己了解一下，很容易）&lt;br /&gt;
&lt;br /&gt;
3.如何获取扫描到的附近的BLE从机设备的信息&lt;br /&gt;
&lt;br /&gt;
从机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何配置从机的广播数据、以及广播的参数（从机广播部分）&lt;br /&gt;
&lt;br /&gt;
2.了解如何设置从机设备的名称（这个是正式项目经常需要使用到的）&lt;br /&gt;
&lt;br /&gt;
3.了解如何开启从机广播，并触类旁通，自行了解如何关闭广播（大家自己了解一下，很容易）&lt;br /&gt;
&lt;br /&gt;
=== 带过滤的扫描与广播实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
过滤扫描实验1.3_ble_central_scan_filter与2.3_ble_peripheral_adv_filter。经过通用扫描实验的学习，大家应该对于蓝牙的扫描和广播有一定的了解了。那么在我们实际的项目使用中，可能会出现由于我们的附近可能存在的BLE设备太多，导致我们去扫描的时候，没法在第一时间找到我们想要的设备的情况，那么这个时候，我们有没有好的处理方法呢。&lt;br /&gt;
&lt;br /&gt;
上面的疑问答案是肯定的，因为我们在扫描的时候，可以先判断一下扫描到的广播数据或者扫描回调数据，仅返回我们需要的设备（也就是做一些数据的判断和限制），这样就可以快速的找到我们的设备。nordic对这部分处理的很细心，在BLE协议的扫描和广播中，都给我们开发者留好了限制的数据属性，我们以scan为例，可以看到限制的广播数据如下几种：名称，简称，地址，UUID以及容貌。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=typedef enum&lt;br /&gt;
{&lt;br /&gt;
    SCAN_NAME_FILTER,       /**&amp;lt; Filter for names. */&lt;br /&gt;
    SCAN_SHORT_NAME_FILTER, /**&amp;lt; Filter for short names. */&lt;br /&gt;
    SCAN_ADDR_FILTER,       /**&amp;lt; Filter for addresses. */&lt;br /&gt;
    SCAN_UUID_FILTER,       /**&amp;lt; Filter for UUIDs. */&lt;br /&gt;
    SCAN_APPEARANCE_FILTER, /**&amp;lt; Filter for appearances. */&lt;br /&gt;
} nrf_ble_scan_filter_type_t;|type=info}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机部分，可以看到上电先打印'''1.3_ble_central_scan_filter'''字样，然后会周期打印扫描到的2.3例程的从机设备信息（MAC、扫描回调数据、RSSI）。&lt;br /&gt;
&lt;br /&gt;
在扫描回调的数据的末尾，我们可以看到03030100的字样，这个就是我们实验限制扫描的UUID。&lt;br /&gt;
{{Note|text=03，// UUID数据长度&lt;br /&gt;
&lt;br /&gt;
03，// 16bit UUID数据类型&lt;br /&gt;
&lt;br /&gt;
01，00 // UUID 0x0001，这边是低位在前|type=info}}&lt;br /&gt;
[[文件:Nrf rtt 13.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
从机部分，可以看到上电先打印'''2.3_ble_peripheral_adv_filter'''字样。&lt;br /&gt;
[[文件:Nrf rtt 23.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机工程 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
本工程相对于上一章节的通用扫描实现，改动较小，只是对扫描返回的设备做出限制，不再像之前那样返回所有扫描到的设备。&lt;br /&gt;
&lt;br /&gt;
所以我们的修改内容集中在scan_init()函数以及scan_evt_handler()回调函数当中。&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数 ======&lt;br /&gt;
相对于通用扫描的主机程序，我们在限制扫描的初始化函数当中，新增了nrf_ble_scan_filter_set()函数用于限制扫描的设备，在这个例程中，我们给大家展示的是限制UUID扫描，大家也可以根据其他信息去进行限制。&lt;br /&gt;
&lt;br /&gt;
我们限制了扫描的UUID是16bit的0x0001，并且调用nrf_ble_scan_filters_enable()使能了这个限制。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;65&amp;quot;&amp;gt;&lt;br /&gt;
// 定义扫描限制的UUID&lt;br /&gt;
static ble_uuid_t const m_nus_uuid = {BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_BLE};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;162&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
&lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
    &lt;br /&gt;
    // 设置扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filter_set(&amp;amp;m_scan, SCAN_UUID_FILTER, &amp;amp;m_nus_uuid);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filters_enable(&amp;amp;m_scan, NRF_BLE_SCAN_UUID_FILTER, false);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()回调函数 ======&lt;br /&gt;
大家可以看到，我们对于扫描回调的事件ID判断进行了更改，上一章的通用扫描我们的判断的是NRF_BLE_SCAN_EVT_NOT_FOUND，这一章的限制扫描我们判断的是NRF_BLE_SCAN_EVT_FILTER_MATCH。&lt;br /&gt;
&lt;br /&gt;
由于上面的一系列对于扫描UUID的限制操作，所以我们的1.3主机实验的现象，才会仅扫描并返回给我们的2.3实验的从机设备。&lt;br /&gt;
&lt;br /&gt;
为了给大家更直观的找到限制的UUID，我们这边临时注视掉了广播数据的打印，只保留了包含了UUID的扫描回调数据打印。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;102&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 匹配的扫描数据（也就是过滤之后的）&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_FILTER_MATCH:&lt;br /&gt;
        {&lt;br /&gt;
          // 下面这一段我们只保留了扫描回调数据获取的部分，因为从机筛选广播的UUID在扫描回调数据&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
//          else  // 否则为广播数据&lt;br /&gt;
//          {&lt;br /&gt;
//            // 打印扫描的设备MAC&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
//                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;peer_addr.addr));&lt;br /&gt;
//            &lt;br /&gt;
//            if(p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
//            {&lt;br /&gt;
//              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
//                            Util_convertHex2Str(&lt;br /&gt;
//                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
//                            p_scan_evt-&amp;gt;params.filter_match.p_adv_report-&amp;gt;data.len));&lt;br /&gt;
//            }&lt;br /&gt;
//            else&lt;br /&gt;
//            {&lt;br /&gt;
//              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
//            }&lt;br /&gt;
//          }&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
从机部分相对于通用广播的从机，我们也仅仅增加了UUID的广播数据。所以这边我们查看一下广播数据初始化的函数advertising_init()。&lt;br /&gt;
&lt;br /&gt;
====== advertising_init() ======&lt;br /&gt;
大家可以看到，在广播数据初始化函数中，我们在init.srdata中增加了uuids_complete的定义，这样我们的广播数据中就会携带16bit的UUID 0x0001数据（这个数据在m_adv_uuids中被定义）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;53&amp;quot;&amp;gt;&lt;br /&gt;
// 定义广播的UUID&lt;br /&gt;
static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, BLE_UUID_TYPE_BLE}};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;87&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : advertising_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于初始化广播&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void advertising_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t               err_code;&lt;br /&gt;
    ble_advertising_init_t init;&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;init, 0, sizeof(init));&lt;br /&gt;
&lt;br /&gt;
    // 广播数据包含所有设备名称（FULL NAME）&lt;br /&gt;
    init.advdata.name_type = BLE_ADVDATA_FULL_NAME;&lt;br /&gt;
//    // 广播数据只包含部分设备名称（SHORT NAME，长度为6）&lt;br /&gt;
//    init.advdata.name_type =  BLE_ADVDATA_SHORT_NAME;&lt;br /&gt;
//    init.advdata.short_name_len = 6;&lt;br /&gt;
    &lt;br /&gt;
    // 扫描回调数据中包含16bit UUID：0x0001&lt;br /&gt;
    init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);&lt;br /&gt;
    init.srdata.uuids_complete.p_uuids  = m_adv_uuids;&lt;br /&gt;
    &lt;br /&gt;
    // 扫描回调数据中包含设备MAC地址&lt;br /&gt;
    init.srdata.include_ble_device_addr = true;&lt;br /&gt;
    &lt;br /&gt;
    // 配置广播周期，先快速广播18s（周期40ms），再慢速广播18s（周期100ms），最后停止广播&lt;br /&gt;
    init.config.ble_adv_fast_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_fast_interval = 64;       // 64*0.625 = 40ms&lt;br /&gt;
    init.config.ble_adv_fast_timeout  = 1800;     // 1800*10ms = 18s&lt;br /&gt;
    init.config.ble_adv_slow_enabled  = true;&lt;br /&gt;
    init.config.ble_adv_slow_interval = 160;      // 160*0.625 = 100ms&lt;br /&gt;
    init.config.ble_adv_slow_timeout  = 18000;    // 18000*10ms = 180s&lt;br /&gt;
    &lt;br /&gt;
    err_code = ble_advertising_init(&amp;amp;m_advertising, &amp;amp;init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_advertising_conn_cfg_tag_set(&amp;amp;m_advertising, APP_BLE_CONN_CFG_TAG);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过这个扫描限制实验的学习，大家需要掌握的要点。&lt;br /&gt;
&lt;br /&gt;
主机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何添加一个扫描的限制并使能&lt;br /&gt;
&lt;br /&gt;
2.限制扫描的回调事件ID是NRF_BLE_SCAN_EVT_FILTER_MATCH&lt;br /&gt;
&lt;br /&gt;
3.自行编程，完成通过名称、地址等其他限制的扫描&lt;br /&gt;
&lt;br /&gt;
从机部分：&lt;br /&gt;
&lt;br /&gt;
1.如何添加个人的广播数据，例如本章节的UUID（本质上还是广播数据的配置）&lt;br /&gt;
&lt;br /&gt;
=== 白名单扫描实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
白名单扫描实验1.4_ble_central_scan_whitelist与2.4_ble_peripheral_adv_whitelist。有关扫描的实验，在第一章的通用扫描中，我们了解到可以主机发起扫描，可以获取从机设备的一些信息，包含广播数据和扫描回调数据，以及从机设备的MAC。在第二章的限制扫描中，我们已经学习过如果根据广播数据和扫描回调数据去获取我们指定的从机设备。那么在这一章节我们将给大家带来，根据从机设备的MAC地址，去限制扫描。{{Note|text=注意：白名单扫描是根据扫描到的设备MAC，而不是指的广播数据中携带的MAC（假设我们将MAC地址加入到广播数据中）。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机部分，上电打印例程名称'''1.4_ble_central_scan_whitelist'''，如果成功设置白名单，则打印'''Successfully set whitelist!'''，然后如果附近有白名单中的设备，则会打印扫描到的白名单设备的信息。&lt;br /&gt;
[[文件:Nrf rtt 14.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
从机部分相较于前两章的内容，几乎没有改动，这边不做讲解。因为白名单的限制，是由主机部分完成。&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 工程说明 =====&lt;br /&gt;
本实验的修改内容集中在主机的扫描初始化以及扫描的回调事件处理函数中，所以下面只有主机部分的代码讲解，而没有从机部分的。&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数 ======&lt;br /&gt;
首先我们查看一下主机扫描的初始化函数，我们可以看到filter_policy过滤扫描参数，我们配置为BLE_GAP_SCAN_FP_WHITELIST，也就是白名单扫描模式。&lt;br /&gt;
&lt;br /&gt;
启用这个白名单模式，如果成功，则会在扫描回调事件处理函数中给我们返回NRF_BLE_SCAN_EVT_WHITELIST_REQUEST请求开启白名单的事件ID。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;54&amp;quot;&amp;gt;&lt;br /&gt;
// 定义扫描参数&lt;br /&gt;
static ble_gap_scan_params_t m_scan_params = &lt;br /&gt;
{&lt;br /&gt;
    .active        = 1,                            // 1为主动扫描，可获得广播数据及扫描回调数据&lt;br /&gt;
    .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,   // 扫描间隔：160*0.625 = 100ms  &lt;br /&gt;
    .window        = NRF_BLE_SCAN_SCAN_WINDOW,     // 扫描窗口：80*0.625 = 50ms   &lt;br /&gt;
    .timeout       = NRF_BLE_SCAN_SCAN_DURATION,   // 扫描持续的时间：设置为0，一直扫描，直到明确的停止扫描&lt;br /&gt;
    .filter_policy = BLE_GAP_SCAN_FP_WHITELIST,    // 扫描白名单设备&lt;br /&gt;
    .scan_phys     = BLE_GAP_PHY_1MBPS,            // 扫描1MBPS的PHY&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;181&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
    &lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()函数 ======&lt;br /&gt;
可以看到，当我们接收到NRF_BLE_SCAN_EVT_WHITELIST_REQUEST事件时，我们将去调用sd_ble_gap_whitelist_set函数设置我们的白名单设备。&lt;br /&gt;
&lt;br /&gt;
我们这边首先获取了从机的MAC地址：'''0xD363BFCE5C46'''，然后将其添加到白名单设备列表当中。&lt;br /&gt;
&lt;br /&gt;
当我们扫描到'''0xD363BFCE5C46'''后将打印他的广播和扫描回调数据、以及型号强度。&lt;br /&gt;
{{Note|text=这个地方，需要大家设置为自己的从设备MAC，不然无法使用此例程|type=tips}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;99&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t err_code;&lt;br /&gt;
    ble_gap_addr_t peer_addr;&lt;br /&gt;
    ble_gap_addr_t const * p_peer_addr;&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 白名单设置请求&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_WHITELIST_REQUEST:&lt;br /&gt;
        {&lt;br /&gt;
          memset(&amp;amp;peer_addr, 0x00, sizeof(peer_addr));&lt;br /&gt;
          peer_addr.addr_id_peer = 1;&lt;br /&gt;
          peer_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;&lt;br /&gt;
          peer_addr.addr[5] = 0xD3;&lt;br /&gt;
          peer_addr.addr[4] = 0x63;&lt;br /&gt;
          peer_addr.addr[3] = 0xBF;&lt;br /&gt;
          peer_addr.addr[2] = 0xCE;&lt;br /&gt;
          peer_addr.addr[1] = 0x5C;&lt;br /&gt;
          peer_addr.addr[0] = 0x46;&lt;br /&gt;
          p_peer_addr = &amp;amp;peer_addr; &lt;br /&gt;
          &lt;br /&gt;
          // 设置白名单&lt;br /&gt;
          err_code = sd_ble_gap_whitelist_set(&amp;amp;p_peer_addr, 0x01);&lt;br /&gt;
          if (err_code == NRF_SUCCESS)&lt;br /&gt;
          {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Successfully set whitelist!&amp;quot;);&lt;br /&gt;
          }&lt;br /&gt;
          APP_ERROR_CHECK(err_code);&lt;br /&gt;
        }break;&lt;br /&gt;
        &lt;br /&gt;
        // 扫描到的白名单设备数据&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_WHITELIST_ADV_REPORT:&lt;br /&gt;
        {&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
          else  // 否则为广播数据&lt;br /&gt;
          {&lt;br /&gt;
            // 打印扫描的设备MAC&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_whitelist_adv_report-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
无&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过白名单实验的学习，我们需要明白以下几点：&lt;br /&gt;
&lt;br /&gt;
1.我们使用白名单功能的前提是我们知道对方设备的MAC。&lt;br /&gt;
&lt;br /&gt;
2.白名单功能只需要主机完成&lt;br /&gt;
&lt;br /&gt;
3.了解NRF_BLE_SCAN_EVT_WHITELIST_REQUEST事件以及sd_ble_gap_whitelist_set()函数&lt;br /&gt;
&lt;br /&gt;
=== 通用连接实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
通用连接实验1.5_ble_central_conn_all与2.5_ble_peripheral_conn_all。从这一实验开始，我们将给大家介绍蓝牙连接相关的协议，由此对于蓝牙协议的学习进入到第二阶段。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机部分，先打印实验名称1.5_ble_central_conn_all，然后开始扫描周围的从机设备。当我们将两块开发板靠近的时候（当RSSI大于-30dBm），则会发起连接。然后打印连接成功的消息，并且打印连接参数。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=Connected. conn_DevAddr: 0xD363BFCE5C46  // 连接的对方设备MAC地址 &lt;br /&gt;
&lt;br /&gt;
conn_handle: 0x0000  // 连接的句柄，第一个连接的设备是0x0000，第二个是0x0001，以此类推&lt;br /&gt;
&lt;br /&gt;
conn_Param: 24,24,0,400  // 连接参数|type=tips}}&lt;br /&gt;
&lt;br /&gt;
[[文件:Nrf rtt 15.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
从机部分，先打印实验名称2.5_ble_peripheral_conn_all，当被主机成功连接之后，会打印连接成功的消息；当断开连接之后，会打印断开连接的句柄，并且会打印断开连接的原因。&lt;br /&gt;
{{Note|text=Connected. conn_DevAddr: 0xE51F64D6BFF4  // 连接的对方设备的MAC&lt;br /&gt;
&lt;br /&gt;
Connected. conn_handle: 0x0000  // 连接的句柄&lt;br /&gt;
&lt;br /&gt;
Connected. conn_Param: 24,24,0,400  // 连接参数&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Disconnected. conn_handle: 0x0, reason: 0x0008  // 断开连接的设备句柄，以及断开的原因|type=tips}}&lt;br /&gt;
[[文件:Nrf rtt 25.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
通用连接主机工程，是在通用扫描主机工程的基础上，新增了连接的过程，主要的修改集中在扫描事件处理函数，以及ble协议栈事件处理函数。&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()函数 ======&lt;br /&gt;
定义连接的参数，这个参数将在sd_ble_gap_connect()函数中被调用。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;65&amp;quot;&amp;gt;&lt;br /&gt;
// 定义连接参数&lt;br /&gt;
static ble_gap_conn_params_t m_conn_params = &lt;br /&gt;
{&lt;br /&gt;
    .min_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MIN_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最小连接间隔7.5ms&lt;br /&gt;
    .max_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MAX_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最大连接间隔30ms  &lt;br /&gt;
    .slave_latency      = NRF_BLE_SCAN_SLAVE_LATENCY,                                         // 隐藏周期0 &lt;br /&gt;
    .conn_sup_timeout   = MSEC_TO_UNITS(NRF_BLE_SCAN_SUPERVISION_TIMEOUT, UNIT_10_MS),        // 超时时间4000ms &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们可以看到，在扫描回调事件处理的函数中，前面一部分还是和之前一样的，就是获取扫描到的从机设备的信息。&lt;br /&gt;
&lt;br /&gt;
在159行，我们对扫描到的从机设备的RSSI进行了判断，我们人为定义，当设备的RSSI大于-30dBm的时候，也就是信号强度非常好，我们就默认连接该设备。&lt;br /&gt;
&lt;br /&gt;
首先，我们先定义准备连接的从机设备的地址和信息，配置m_addr.addr_type为BLE_GAP_ADDR_TYPE_RANDOM_STATIC，配置m_addr.addr为扫描到的设备地址p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr。&lt;br /&gt;
&lt;br /&gt;
配置好准备连接的设备的MAC信息，在发起连接之前，我们一定要先调用nrf_ble_scan_stop()函数去停止扫描，不然会出错。&lt;br /&gt;
&lt;br /&gt;
停止扫描之后，我们调用sd_ble_gap_connect()函数，发起对从机设备的连接。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;107&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 未过滤的扫描数据&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_NOT_FOUND:&lt;br /&gt;
        {&lt;br /&gt;
          // 判断是否为扫描回调数据&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;type.scan_response)&lt;br /&gt;
          {&lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在扫描回调数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;scan data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;rssi:  %ddBm&amp;quot;,p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;rssi);&lt;br /&gt;
          }&lt;br /&gt;
          else  // 否则为广播数据&lt;br /&gt;
          {&lt;br /&gt;
            // 打印扫描的设备MAC&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Device MAC:  %s&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr));&lt;br /&gt;
            &lt;br /&gt;
            if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len)    // 存在广播数据&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&lt;br /&gt;
                            Util_convertHex2Str(&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.p_data,&lt;br /&gt;
                            p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;data.len));&lt;br /&gt;
            }&lt;br /&gt;
            else&lt;br /&gt;
            {&lt;br /&gt;
              NRF_LOG_INFO(&amp;quot;adv data:  %s&amp;quot;,&amp;quot;NONE&amp;quot;);&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
          &lt;br /&gt;
          &lt;br /&gt;
          // 如果扫描到的设备信号强度大于-30dBm&lt;br /&gt;
          if(p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;rssi &amp;gt; (-30))&lt;br /&gt;
          {&lt;br /&gt;
            ret_code_t          err_code;&lt;br /&gt;
            &lt;br /&gt;
            // 配置准备连接的设备MAC&lt;br /&gt;
            ble_gap_addr_t m_addr;&lt;br /&gt;
            m_addr.addr_id_peer = 1;&lt;br /&gt;
            m_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;&lt;br /&gt;
            memcpy(m_addr.addr,p_scan_evt-&amp;gt;params.p_not_found-&amp;gt;peer_addr.addr,BLE_GAP_ADDR_LEN);&lt;br /&gt;
            &lt;br /&gt;
            // 停止扫描&lt;br /&gt;
            nrf_ble_scan_stop();&lt;br /&gt;
            // 发起连接&lt;br /&gt;
            err_code = sd_ble_gap_connect(&amp;amp;m_addr,&amp;amp;m_scan_params,&amp;amp;m_conn_params,APP_BLE_CONN_CFG_TAG);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
          }&lt;br /&gt;
          &lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()函数 ======&lt;br /&gt;
当我们的主机设备进行连接，或者断开连接的时候，BLE的事件回调中，都会给我们状态事件提示。&lt;br /&gt;
&lt;br /&gt;
连接成功的状态BLE_GAP_EVT_CONNECTED，我们可以在这边获取到连接的设备的MAC、连接参数等等。&lt;br /&gt;
&lt;br /&gt;
断开连接的状态BLE_GAP_EVT_DISCONNECTED，我们可以获取到断开的连接的原因等等。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;210&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            // 如果需要异常断开重连，可以打开下面的注释&lt;br /&gt;
            // scan_start();  // 重新开始扫描&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
通用连接的从机工程，是在通用广播的从机工程的基础上修改的。&lt;br /&gt;
&lt;br /&gt;
由于从机设备是一直保持广播，等待被连接的，所以相对于主机程序，我们就不用去找发起连接的函数以及对方设备的MAC了。因此我们只需要关注BLE协议的回调函数的事件返回。&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()函数 ======&lt;br /&gt;
从机的连接与断开的状态和主机是一样的，能够获得的对方设备的信息也是一样的。&lt;br /&gt;
&lt;br /&gt;
连接成功的状态BLE_GAP_EVT_CONNECTED，我们可以在这边获取到连接的设备的MAC、连接参数等等。&lt;br /&gt;
&lt;br /&gt;
断开连接的状态BLE_GAP_EVT_DISCONNECTED，我们可以获取到断开的连接的原因等等。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;148&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            // No implementation needed.&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过主从机通用连接的实验，我们需要弄懂的问题点如下：&lt;br /&gt;
&lt;br /&gt;
主机部分：&lt;br /&gt;
&lt;br /&gt;
1.主机部分一定要先发起扫描，当扫描到设备之后，再去发起连接。（不扫描直接发起连接，如果周围没有这个从设备，会进入异常，并只能RST恢复）&lt;br /&gt;
&lt;br /&gt;
2.主机发起连接的时候，是利用从机的MAC地址进行连接&lt;br /&gt;
&lt;br /&gt;
3.连接以及断开连接的状态返回，可以获取对方设备信息以及断开原因&lt;br /&gt;
&lt;br /&gt;
从机部分：&lt;br /&gt;
&lt;br /&gt;
1.从机是一直广播等待被动连接的设备&lt;br /&gt;
&lt;br /&gt;
2.连接以及断开连接的状态返回，可以获取对方设备信息以及断开原因&lt;br /&gt;
&lt;br /&gt;
=== 过滤连接实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
过滤连接实验1.6_ble_central_conn_filter与2.6_ble_peripheral_conn_filter。&lt;br /&gt;
&lt;br /&gt;
过滤连接实验是从过滤扫描的实验集成下来的，它的目的是和过滤扫描实验一样的，也是为了快速的从一堆BLE设备中，找到我们的设备，并与之快速建立连接。&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机上电后先打印实验名称1.6_ble_central_conn_filter，然后发起扫描，如果扫描到符合过滤的从机设备，则会打印扫描到的设备信息，然后发起连接，连接成功后会打印连接的一些信息。&lt;br /&gt;
[[文件:Nrf rtt 16.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
从机上电后先打印实验名称2.6_ble_peripheral_conn_filter，如果被主机扫描连接之后，则会打印连接的信息。&lt;br /&gt;
[[文件:Nrf rtt 26.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== 工程说明 ======&lt;br /&gt;
过滤连接，我们主要关注的是扫描初始化中对于过滤连接设备的设置，以及过滤连接成功或者失败返回的扫描回调函数的事件ID。&lt;br /&gt;
&lt;br /&gt;
====== scan_init()函数 ======&lt;br /&gt;
连接参数定义，用于配置init_scan.p_conn_param参数，也就是我们的过滤扫描之后发起连接的连接参数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;65&amp;quot;&amp;gt;&lt;br /&gt;
// 定义连接参数&lt;br /&gt;
static ble_gap_conn_params_t m_conn_params = &lt;br /&gt;
{&lt;br /&gt;
    .min_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MIN_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最小连接间隔7.5ms&lt;br /&gt;
    .max_conn_interval  = MSEC_TO_UNITS(NRF_BLE_SCAN_MAX_CONNECTION_INTERVAL, UNIT_1_25_MS),  // 最大连接间隔30ms  &lt;br /&gt;
    .slave_latency      = NRF_BLE_SCAN_SLAVE_LATENCY,                                         // 隐藏周期0 &lt;br /&gt;
    .conn_sup_timeout   = MSEC_TO_UNITS(NRF_BLE_SCAN_SUPERVISION_TIMEOUT, UNIT_10_MS),        // 超时时间4000ms &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在扫描初始化函数中，我们可以看到我们初始化了init_scan.connect_if_match = 1，然后我们还配置了init_scan.p_conn_param = &amp;amp;m_conn_params，后面的限制UUID是和过滤扫描一样的。&lt;br /&gt;
&lt;br /&gt;
这样配置好之后，我们发起扫描，一旦扫描到我们的过滤后的设备，就会使用我们配置好的连接参数，对这个从机设备发起连接。连接成功或者失败，都会在扫描回调函数中进行事件通知。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;182&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化扫描（未设置扫描数据限制）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t          err_code;&lt;br /&gt;
    nrf_ble_scan_init_t init_scan;&lt;br /&gt;
&lt;br /&gt;
    // 清空扫描结构体参数&lt;br /&gt;
    memset(&amp;amp;init_scan, 0, sizeof(init_scan));&lt;br /&gt;
    &lt;br /&gt;
    init_scan.connect_if_match = 1;&lt;br /&gt;
    init_scan.conn_cfg_tag = APP_BLE_CONN_CFG_TAG;&lt;br /&gt;
    &lt;br /&gt;
    // 配置扫描的参数&lt;br /&gt;
    init_scan.p_scan_param = &amp;amp;m_scan_params;&lt;br /&gt;
&lt;br /&gt;
    // 配置连接的参数&lt;br /&gt;
    init_scan.p_conn_param = &amp;amp;m_conn_params;&lt;br /&gt;
    &lt;br /&gt;
    // 初始化扫描&lt;br /&gt;
    err_code = nrf_ble_scan_init(&amp;amp;m_scan, &amp;amp;init_scan, scan_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
    &lt;br /&gt;
    // 设置扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filter_set(&amp;amp;m_scan, SCAN_UUID_FILTER, &amp;amp;m_nus_uuid);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 使能扫描的UUID限制&lt;br /&gt;
    err_code = nrf_ble_scan_filters_enable(&amp;amp;m_scan, NRF_BLE_SCAN_UUID_FILTER, false);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== scan_evt_handler()扫描回调函数 ======&lt;br /&gt;
在扫描回调函数中，过滤扫描的事件是和之前一样的，我们不做介绍。&lt;br /&gt;
&lt;br /&gt;
我们关注一下事件ID，NRF_BLE_SCAN_EVT_CONNECTED与NRF_BLE_SCAN_EVT_CONNECTING_ERROR，当我们设置init_scan.connect_if_match = 1后，如果扫描到并且成功连接上目标从设备，则返回连接成功的ID，如果失败了，则返回连接ERR。&lt;br /&gt;
{{Note|text=注意：由于这两个事件返回的信息和ble协议栈返回的事件ID携带的信息相同，所以这边我们不重复打印。&lt;br /&gt;
case NRF_BLE_SCAN_EVT_CONNECTED:&lt;br /&gt;
{&lt;br /&gt;
NRF_LOG_INFO(&amp;quot;SCAN CONNECTED!&amp;quot;); &lt;br /&gt;
&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
//                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;peer_addr.addr),&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.conn_handle,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
&lt;br /&gt;
//                         );&lt;br /&gt;
&lt;br /&gt;
}break;&lt;br /&gt;
        &lt;br /&gt;
case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:&lt;br /&gt;
{&lt;br /&gt;
NRF_LOG_INFO(&amp;quot;SCAN CONNECTING ERROR!&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Disconnected. reason: 0x%04x&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connecting_err.err_code);&lt;br /&gt;
&lt;br /&gt;
}break;|type=warning}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;111&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : scan_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理扫描回调事件&lt;br /&gt;
//&lt;br /&gt;
// param : scan_evt_t  扫描事件结构体&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void scan_evt_handler(scan_evt_t const * p_scan_evt)&lt;br /&gt;
{&lt;br /&gt;
    switch(p_scan_evt-&amp;gt;scan_evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 匹配的扫描数据（也就是过滤之后的）&lt;br /&gt;
        case NRF_BLE_SCAN_EVT_FILTER_MATCH:&lt;br /&gt;
        {&lt;br /&gt;
        ...&lt;br /&gt;
        } break;&lt;br /&gt;
&lt;br /&gt;
        &lt;br /&gt;
        case NRF_BLE_SCAN_EVT_CONNECTED:&lt;br /&gt;
        {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;SCAN CONNECTED!&amp;quot;); &lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
//                         Util_convertBdAddr2Str((uint8_t*)p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;peer_addr.addr),&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.conn_handle,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connected.p_connected-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
//                         );&lt;br /&gt;
        }break;&lt;br /&gt;
        &lt;br /&gt;
        case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:&lt;br /&gt;
        {&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;SCAN CONNECTING ERROR!&amp;quot;);&lt;br /&gt;
//            NRF_LOG_INFO(&amp;quot;Disconnected. reason: 0x%04x&amp;quot;,&lt;br /&gt;
//                         p_scan_evt-&amp;gt;params.connecting_err.err_code);&lt;br /&gt;
        }break;&lt;br /&gt;
        &lt;br /&gt;
        default:&lt;br /&gt;
           break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分就不给大家介绍了，和前面的过滤广播的例程是重复的，仅修改了连接之后获取并打印对方设备的信息。&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
经过本实验的学习，大家需要了解到如何让主机在扫描的时候，去对过滤后的从机设备发起连接。&lt;br /&gt;
&lt;br /&gt;
1.了解init_scan.connect_if_match参数的功能。&lt;br /&gt;
&lt;br /&gt;
2.了解扫描回调事件ID中的NRF_BLE_SCAN_EVT_CONNECTED与NRF_BLE_SCAN_EVT_CONNECTING_ERROR。&lt;br /&gt;
&lt;br /&gt;
=== 连接参数更新实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
经过前两章的主从机扫描并连接的实验学习后，我们不难发现，连接之后主从机之间一个很重要的参数（返回连接成功的状态下，打印的数据），那就是连接参数。&lt;br /&gt;
&lt;br /&gt;
有关连接参数的详细说明，请大家查看本手册的第一章节有关蓝牙协议的介绍。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=主从机连接参数的配置流程：&lt;br /&gt;
&lt;br /&gt;
1、主从机连接成功之后，我们将以主机携带的连接参数，作为主从机连接的参数&lt;br /&gt;
&lt;br /&gt;
2、从机可以发起更新连接参数的请求&lt;br /&gt;
&lt;br /&gt;
3、主机接收到从机的参数更新请求，发起参数更新&lt;br /&gt;
&lt;br /&gt;
4、更新成功，将以新的参数作为连接参数|type=info}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机上电后发起扫描，一旦扫描到我们过滤的设备之后，就会发起连接。&lt;br /&gt;
&lt;br /&gt;
连接成功后，打印连接的handle，以及主从机之间的连接参数。&lt;br /&gt;
&lt;br /&gt;
当从机发起连接参数更新请求后，我们使用从机申请的连接参数，去发起连接参数的更新。&lt;br /&gt;
[[文件:Nrf rtt 17.png|边框|居中|无框|657x657像素]]&lt;br /&gt;
从机上电后广播，被主机连接后打印连接的handle，以及连接参数。&lt;br /&gt;
&lt;br /&gt;
5000ms后，从机发起参数更新请求，主机接收请求后发起更新，更新成功后，从机打印新的连接参数。&lt;br /&gt;
[[文件:Nrf rtt 27.png|边框|居中|无框|648x648像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
主机部分大部分和之前的过滤连接是一样的，唯一的区别在于接收从机连接参数更新请求，并发起更新。&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()回调函数 ======&lt;br /&gt;
在BLE事件回调中可以看到两个新增的事件处理，一个是连接参数更新请求BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST，另一个是连接参数更新BLE_GAP_EVT_CONN_PARAM_UPDATE。&lt;br /&gt;
&lt;br /&gt;
BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST：连接参数更新请求，这个状态是主机接收到从机发起更新参数的请求时返回，在这个状态下，我们可以获取从机申请更新的参数数值，并且调用sd_ble_gap_conn_param_update()函数去发起连接参数的更新。&lt;br /&gt;
&lt;br /&gt;
BLE_GAP_EVT_CONN_PARAM_UPDATE：连接参数更新，当连接参数完成更新的时候，会返回这个状态，在这个状态中，我们可以获取到更新完成后的新的连接参数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;202&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            // 如果需要异常断开重连，可以打开下面的注释&lt;br /&gt;
            // scan_start();  // 重新开始扫描&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 连接参数更新请求&lt;br /&gt;
        case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;conn_Param Update  Request&amp;quot;);&lt;br /&gt;
            &lt;br /&gt;
            sd_ble_gap_conn_param_update(p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                                                    &amp;amp;p_gap_evt-&amp;gt;params.conn_param_update_request.conn_params);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        // 连接参数更新&lt;br /&gt;
        case BLE_GAP_EVT_CONN_PARAM_UPDATE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;conn_Param Update: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.min_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.slave_latency,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分，我们主要关注两个地方，一个是连接参数的更新请求conn_params_init()函数，另一个是和主机部分一样的，连接参数更新成功的状态返回。&lt;br /&gt;
&lt;br /&gt;
====== conn_params_init()函数 ======&lt;br /&gt;
连接参数初始化函数，在这个函数中，我们配置了新的连接参数，以及连接参数更新的时间及尝试的次数，并且包含了两个回调函数，一个是更新是否成功的返回，一个是携带的连接参数是否正确的返回。&lt;br /&gt;
&lt;br /&gt;
我们配置了第一次更新的时间为连接成功后5s（由frist_xx_delay控制），后面的几次也是间隔5s（由next_xx_delay控制），然后尝试更新次数为3。这个尝试次数3的含义是，如果申请了3次更新，都未成功，那么将从on_conn_params_evt回调中返回更新失败。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;245&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : conn_params_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化连接参数模块的功能&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void conn_params_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t               err_code;&lt;br /&gt;
    ble_conn_params_init_t cp_init;&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;cp_init, 0, sizeof(cp_init));&lt;br /&gt;
&lt;br /&gt;
    cp_init.p_conn_params                  = &amp;amp;m_conn_params;&lt;br /&gt;
    cp_init.first_conn_params_update_delay = APP_TIMER_TICKS(5000);&lt;br /&gt;
    cp_init.next_conn_params_update_delay  = APP_TIMER_TICKS(5000);&lt;br /&gt;
    cp_init.max_conn_params_update_count   = 3;&lt;br /&gt;
    cp_init.start_on_notify_cccd_handle    = BLE_GATT_HANDLE_INVALID;&lt;br /&gt;
    cp_init.disconnect_on_fail             = false;&lt;br /&gt;
    cp_init.evt_handler                    = on_conn_params_evt;&lt;br /&gt;
    cp_init.error_handler                  = conn_params_error_handler;&lt;br /&gt;
&lt;br /&gt;
    err_code = ble_conn_params_init(&amp;amp;cp_init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;可以看到，我们新的连接参数，连接间隔40ms，隐藏周期0，超时时间4s。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;57&amp;quot;&amp;gt;&lt;br /&gt;
// 定义连接参数（为了展示连接参数的更新，我们设置连接间隔为固定的20ms）&lt;br /&gt;
static ble_gap_conn_params_t m_conn_params = &lt;br /&gt;
{&lt;br /&gt;
    .min_conn_interval  = MSEC_TO_UNITS(40, UNIT_1_25_MS),  // 最小连接间隔40ms&lt;br /&gt;
    .max_conn_interval  = MSEC_TO_UNITS(40, UNIT_1_25_MS),  // 最大连接间隔40ms  &lt;br /&gt;
    .slave_latency      = 0,                                // 隐藏周期0 &lt;br /&gt;
    .conn_sup_timeout   = MSEC_TO_UNITS(2000, UNIT_10_MS),  // 超时时间4000ms &lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在on_conn_params_evt()回调函数中，我们可以看到连接参数更新成功或者失败的返回。&lt;br /&gt;
&lt;br /&gt;
这边需要注意的问题点如下，以我们这个实验为例，例如我们的参数更新请求会尝试3次：&lt;br /&gt;
&lt;br /&gt;
1、如果第一次就更新成功，那么会直接返回更新SUCCESS，并且后面2次更新请求将不会再起作用&lt;br /&gt;
&lt;br /&gt;
2、如果三次更新都不成功（这个不成功，可能是更新失败，可能是主机不响应等等），那么才会返回更新failed&lt;br /&gt;
&lt;br /&gt;
也就是3次只要有一次更新成功就认为我们更新完成了，如果3次都失败，才会认为失败。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;211&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : on_conn_params_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理连接更新参数的事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : p_evt -&amp;gt; 接收到的连接模块返回的任务事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    if (p_evt-&amp;gt;evt_type == BLE_CONN_PARAMS_EVT_SUCCEEDED)&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;connParam Update Success&amp;quot;); &lt;br /&gt;
    }&lt;br /&gt;
      &lt;br /&gt;
    if (p_evt-&amp;gt;evt_type == BLE_CONN_PARAMS_EVT_FAILED)&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;connParam Update Failed&amp;quot;); &lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这边的conn_params_error_handler()错误回调函数，当我们申请更新的连接参数不符合协议时才会返回，也就是提示我们连接参数异常。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;232&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : conn_params_error_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理连接更新参数异常的事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : nrf_error -&amp;gt; 异常标志&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void conn_params_error_handler(uint32_t nrf_error)&lt;br /&gt;
{&lt;br /&gt;
    APP_ERROR_HANDLER(nrf_error);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== ble_evt_handler()回调函数 ======&lt;br /&gt;
BLE事件回调函数，和主机一样的，当我们成功更新连接参数之后，返回BLE_GAP_EVT_CONN_PARAM_UPDATE事件，在这个事件中我们可以获取新的连接参数。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;159&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        // 断开连接&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Disconnected. conn_handle: 0x%x, reason: 0x%04x&amp;quot;,&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_gap_evt-&amp;gt;params.disconnected.reason);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        // 连接参数更新&lt;br /&gt;
        case BLE_GAP_EVT_CONN_PARAM_UPDATE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;conn_Param Update: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.min_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.max_conn_interval,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.slave_latency,&lt;br /&gt;
                         p_ble_evt-&amp;gt;evt.gap_evt.params.conn_param_update.conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
          &lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
这一章的实验，想要交给大家的内容是如何配置连接参数。需要了解的重点如下：&lt;br /&gt;
&lt;br /&gt;
1、了解连接参数的含义，明白连接参数越小，通信的速率越快。&lt;br /&gt;
&lt;br /&gt;
2、了解主从机之间的连接参数的配置流程。&lt;br /&gt;
&lt;br /&gt;
3、了解从机如何申请更新连接参数，ble_conn_params_init()函数。&lt;br /&gt;
&lt;br /&gt;
4、了解主机如何更新连接参数，sd_ble_gap_conn_param_update()函数。&lt;br /&gt;
&lt;br /&gt;
=== MTU更新实验 ===&lt;br /&gt;
&lt;br /&gt;
==== 实验简介 ====&lt;br /&gt;
MTU是我们的最大传输单元（Maximum Transmission Unit），也就是我们大家常说的一包数据最大可以多少字节。&lt;br /&gt;
&lt;br /&gt;
在BLE4.1往后的协议中，为了提高BLE的通信速率，我们可以配置MTU最大为251byte，但是这个251byte中有4字节的BLE通信数据包的协议头。也就是我们用户的一包数据最大是247字节，但是在nordic的NUS（串口服务）中，他又占用了3个字节作为特殊用途，这样我们用户可用的数据包长度就仅剩下244字节。&lt;br /&gt;
&lt;br /&gt;
这边我们需要注意的是，BLE协议头的4字节我们是不能占用的，但是例如NUS服务的3字节（这个相当于用户的私有协议，不是必须存在）。&lt;br /&gt;
{{Note|text=1、主机和从机都可以主动配置MTU大小&lt;br /&gt;
&lt;br /&gt;
2、如果主从机配置的MTU大小不同，则取小的数值|type=tips}}&lt;br /&gt;
&lt;br /&gt;
==== 实验现象 ====&lt;br /&gt;
主机上电之后扫描周围的从机，一旦扫描到符合的过滤的从设备，就会打印扫描到的信息，并且发起连接，连接成功之后，打印连接的参数以及对方设备的信息。&lt;br /&gt;
&lt;br /&gt;
连接成功之后，会打印Data len的大小，也就是我们的MTU。&lt;br /&gt;
[[文件:Nrf rtt 18.png|边框|居中|无框|657x657像素]]&lt;br /&gt;
从机上电之后广播，被主机连接之后，打印主机的设备信息以及连接参数。&lt;br /&gt;
&lt;br /&gt;
并且会打印Data len的大小，也就是我们的MTU。&lt;br /&gt;
[[文件:Nrf tft 28.png|边框|居中|无框|648x648像素]]&lt;br /&gt;
&lt;br /&gt;
==== 工程及源码讲解 ====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
主机部分，我们仅仅新增了有关MTU大小设置的部分，也就是在我们的gatt_init()函数中。&lt;br /&gt;
&lt;br /&gt;
====== gatt_init()函数 ======&lt;br /&gt;
在gatt初始化的函数中，我们调用nrf_ble_gatt_att_mtu_central_set()函数去配置我们的MTU大小，在主机中我们配置MTU大小为100byte。&lt;br /&gt;
&lt;br /&gt;
并且最终的MTU大小的gatt的事件回调函数中展示，携带的任务ID为NRF_BLE_GATT_EVT_ATT_MTU_UPDATED。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;224&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化GATT&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    err_code = nrf_ble_gatt_init(&amp;amp;m_gatt, gatt_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
    &lt;br /&gt;
    err_code = nrf_ble_gatt_att_mtu_central_set(&amp;amp;m_gatt, 100);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== gatt_evt_handler()回调函数 ======&lt;br /&gt;
在gatt的事件回调函数中，我们接收到任务ID为NRF_BLE_GATT_EVT_ATT_MTU_UPDATED的事件之后，可以获取到我们的MTU大小。&lt;br /&gt;
&lt;br /&gt;
这边打印的Data len，是我们MTU减掉NUS占用的3个特殊字节之后的大小。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;205&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : GATT事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : p_gatt  gatt类型结构体&lt;br /&gt;
//         p_evt  gatt事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    if ((m_conn_handle == p_evt-&amp;gt;conn_handle) &amp;amp;&amp;amp; (p_evt-&amp;gt;evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;Data len is set to 0x%X(%d)&amp;quot;, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
从机部分与主机部分是一样的，我们都是新增了一个gatt的初始化。&lt;br /&gt;
&lt;br /&gt;
====== gatt_init()函数 ======&lt;br /&gt;
在从机的gatt初始化函数中，我们配置MTU的大小是80字节（由于主机是100字节，所以最终的MTU大小已从机的80字节为准）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;174&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化GATT&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_init(void)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    err_code = nrf_ble_gatt_init(&amp;amp;m_gatt, gatt_evt_handler);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
    err_code = nrf_ble_gatt_att_mtu_periph_set(&amp;amp;m_gatt, 80);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== gatt_evt_handler()回调函数 ======&lt;br /&gt;
当我们在gatt的回调函数中，接收到NRF_BLE_GATT_EVT_ATT_MTU_UPDATED的事件ID，我们可以获取到当前的MTU的大小。&lt;br /&gt;
&lt;br /&gt;
这边的Data len是MTU减掉NUS中的3个特殊字节后的大小。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;155&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : gatt_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : GATT事件回调&lt;br /&gt;
//&lt;br /&gt;
// param : p_gatt  gatt类型结构体&lt;br /&gt;
//         p_evt  gatt事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    if ((m_conn_handle == p_evt-&amp;gt;conn_handle) &amp;amp;&amp;amp; (p_evt-&amp;gt;evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_INFO(&amp;quot;Data len is set to 0x%X(%d)&amp;quot;, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH, &lt;br /&gt;
                     p_evt-&amp;gt;params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 实验总结 ====&lt;br /&gt;
MTU的大小和连接参数，与我们的通信速率息息相关。有关MTU大小的配置，有如下几个要点：&lt;br /&gt;
&lt;br /&gt;
1、主机和从机都可以配置MTU大小&lt;br /&gt;
&lt;br /&gt;
2、大部分情况下，我们根据从机的服务来确定MTU大小&lt;br /&gt;
&lt;br /&gt;
3、MTU越大，我们可以发送的数据包就越大&lt;br /&gt;
&lt;br /&gt;
4、当主从机配置的MTU大小不同时，我们以小的数值作准。&lt;br /&gt;
===Write/Read属性服务实验===&lt;br /&gt;
====实验简介====&lt;br /&gt;
通过前面实验的学习，我们已经能够完成主机成功连接从机，并且可以配置我们想要的连接参数（连接间隔、MTU等）。所以有了前面的铺垫，从这一章节开始，我们会给大家讲解如何进行主从机的通信。本来我们应该给大家继续在串口透传的程序基础上继续添加服务功能，但由于NUS服务相对比较复杂，所以我们会插入两篇章节（本章节及下一章节），分别给大家讲解LED的Write属性服务，以及BTN的Notify属性服务，然后再切回串口透传例程继续讲解，方便大家学习。&lt;br /&gt;
&lt;br /&gt;
谈到通信，就不得不给大家介绍一个名叫“GATT”的好同志，因为就是他帮我们管理蓝牙的通信，那么对于GATT而言，当两个设备成功连接之后，他们分别作为一下两个设备之一：&lt;br /&gt;
&lt;br /&gt;
⊙GATT服务器：包含特性数据库的设备（比如控制LED数据的是LED特性，传输UART数据的是UART特性）。可以通过一个GATT客户端写入或者读取数据&lt;br /&gt;
&lt;br /&gt;
⊙GATT客户端：向GATT服务器写入数据或者读取数据的设备&lt;br /&gt;
&lt;br /&gt;
其中我们的从机设备一般是作为GATT服务器去提供服务的，主机设备作为GATT客户端去向服务的特性数据库中写入或者读取数据。&lt;br /&gt;
&lt;br /&gt;
{{Note|text=GATT特征值属性包含如下4个：&lt;br /&gt;
&lt;br /&gt;
Write（写）、Read（读）、Notify（通知）、Indicate（暗示）&lt;br /&gt;
&lt;br /&gt;
这4个属性当中，其中大家最常用的是3个，通俗的来讲：Write属性是用于主机给从机发送数据；Read属性是用于主机读取从机的数据；&lt;br /&gt;
Notify属性是用于从机给主机发送数据|type=info}}我们本章的实验，会给大家带来其中的write属性及Read属性的介绍，也就是教大家主机如何给从机发送数据，以及主机如何读取从机的数据。其中包含了从机的服务注册，以及主机的服务发现，相对于前面章节的内容，可能难度会大上一些，大家一定耐心查看，这个章节很重要。&lt;br /&gt;
&lt;br /&gt;
====实验现象====&lt;br /&gt;
主机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、扫描符合我们连接过滤要求的从机设备（根据LED服务的UUID过滤）&lt;br /&gt;
&lt;br /&gt;
2、成功连接我们的从机设备，并且更新连接参数和MTU&lt;br /&gt;
&lt;br /&gt;
3、发现服务，成功发现Ghostyu LED Service&lt;br /&gt;
&lt;br /&gt;
4、主机分别按下4个按键，给从机发送数据控制从机设备的4个LED依次点亮，并且同时从从机读取我们刚刚发送的数据&lt;br /&gt;
[[文件:Nrf52832dk-ble-gattwirte2.png|边框|居中|无框|680x680像素]]从机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、开启广播&lt;br /&gt;
&lt;br /&gt;
2、被主机成功连接，并交互连接参数&lt;br /&gt;
&lt;br /&gt;
3、等待主机获取服务（一般主机成功获取服务的时间在0.5s~1s之间，这个时间仅供大家参考）&lt;br /&gt;
&lt;br /&gt;
4、主机按下按键，从机接收到相应的LED状态数据并打印，并根据这个数据控制板子上的LED点亮[[文件:Nrf52832dk-ble-gattwirte1.png|边框|居中|无框|684x684像素]]&lt;br /&gt;
&lt;br /&gt;
====工程及源码讲解====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== gy_profile_led_c.c\.h与main.c ======&lt;br /&gt;
我们首先查看一下主机的服务客户端文件，里面包含了好几个函数，我们挨个介绍一下这些函数的功能。&lt;br /&gt;
&lt;br /&gt;
第一个还是客户端的初始化函数，对应从机的ble_led_init()注册服务的函数，我们需要利用ble_db_discovery_evt_register()函数注册一个待会服务发现的UUID。&lt;br /&gt;
&lt;br /&gt;
这里我们需要注意一下，不论是服务的发现，还是某一个特征的发现，都是需要根据UUID来判断的。这边可以看到我们注册UUID发现是和从机的服务中一样的（LED_UUID_BASE以及LED_UUID_SERVICE）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;83&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_c_init&lt;br /&gt;
//&lt;br /&gt;
// brief : LED服务客户端初始化函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向LED客户端结构的指针&lt;br /&gt;
//         p_ble_led_c_init -&amp;gt; 指向LED初始化结构的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_led_c_init(ble_led_c_t * p_ble_led_c, ble_led_c_init_t * p_ble_led_c_init)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t      err_code;&lt;br /&gt;
    ble_uuid_t    led_uuid;&lt;br /&gt;
    ble_uuid128_t led_base_uuid = {LED_UUID_BASE};&lt;br /&gt;
&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c);&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c_init);&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c_init-&amp;gt;evt_handler);&lt;br /&gt;
&lt;br /&gt;
    p_ble_led_c-&amp;gt;peer_led_db.led_handle         = BLE_GATT_HANDLE_INVALID;&lt;br /&gt;
    p_ble_led_c-&amp;gt;conn_handle                    = BLE_CONN_HANDLE_INVALID;&lt;br /&gt;
    p_ble_led_c-&amp;gt;evt_handler                    = p_ble_led_c_init-&amp;gt;evt_handler;&lt;br /&gt;
&lt;br /&gt;
    err_code = sd_ble_uuid_vs_add(&amp;amp;led_base_uuid, &amp;amp;p_ble_led_c-&amp;gt;uuid_type);&lt;br /&gt;
    if (err_code != NRF_SUCCESS)&lt;br /&gt;
    {&lt;br /&gt;
        return err_code;&lt;br /&gt;
    }&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    led_uuid.type = p_ble_led_c-&amp;gt;uuid_type;&lt;br /&gt;
    led_uuid.uuid = LED_UUID_SERVICE;&lt;br /&gt;
&lt;br /&gt;
    return ble_db_discovery_evt_register(&amp;amp;led_uuid);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;看完初始化函数，我们接下来得看下当我们注册好客服端，并且在main函数中调用发现函数之后，底层如何给我们返回，这里我们需要结合main.c文件的内容一起给大家介绍。&lt;br /&gt;
&lt;br /&gt;
在mian函数的中，我们可以看到注册了服务发现的功能，也就是数据库发现db_discovery_init()；，并且注册了GATT初始化gatt_init();，以及我们的LED服务的客户端初始化led_c_init();。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;527&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : main&lt;br /&gt;
//&lt;br /&gt;
// brief : 主函数&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
int main(void)&lt;br /&gt;
{&lt;br /&gt;
    // 初始化&lt;br /&gt;
    log_init();             // 初始化LOG打印，由RTT工作&lt;br /&gt;
    timer_init();           // 初始化定时器&lt;br /&gt;
    GPIOTE_Init();          // 初始化IO&lt;br /&gt;
    BTN_Init(btn_evt_handler_t); // 初始化按键  &lt;br /&gt;
    power_management_init();// 初始化电源控制&lt;br /&gt;
    ble_stack_init();       // 初始化BLE栈堆&lt;br /&gt;
    db_discovery_init();    // 初始化数据库发现（用于发现服务）&lt;br /&gt;
    gatt_init();            // 初始化GATT&lt;br /&gt;
    led_c_init();           // 初始化LED_C&lt;br /&gt;
    scan_init();            // 初始化扫描&lt;br /&gt;
    &lt;br /&gt;
    // 打印例程名称&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;demo0:simple central&amp;quot;);&lt;br /&gt;
    &lt;br /&gt;
    scan_start();           // 开始扫描&lt;br /&gt;
    &lt;br /&gt;
    // 进入主循环&lt;br /&gt;
    for (;;)&lt;br /&gt;
    {&lt;br /&gt;
        idle_state_handle();   // 空闲状态处理&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当我们初始化好以上说明的3个函数（具体每个函数的代码，大家可以看下代码中的注册），一旦我们主机发现我们的从机，并成功连接之后，会进入BLE_GAP_EVT_CONNECTED状态。&lt;br /&gt;
&lt;br /&gt;
在这个状态下，我们就需要开始我们的服务发现了，调用ble_db_discovery_start()函数开始发现服务。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;345&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件回调&lt;br /&gt;
// details : 包含以下几种事件类型：COMMON、GAP、GATT Client、GATT Server、L2CAP&lt;br /&gt;
//&lt;br /&gt;
// param : ble_evt_t  事件类型&lt;br /&gt;
//         p_context  未使用&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t            err_code;&lt;br /&gt;
    ble_gap_evt_t const * p_gap_evt = &amp;amp;p_ble_evt-&amp;gt;evt.gap_evt;&lt;br /&gt;
    ble_gap_evt_connected_t const * p_connected_evt = &amp;amp;p_gap_evt-&amp;gt;params.connected;&lt;br /&gt;
    &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // 连接&lt;br /&gt;
        case BLE_GAP_EVT_CONNECTED:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected. conn_DevAddr: %s\nConnected. conn_handle: 0x%04x\nConnected. conn_Param: %d,%d,%d,%d&amp;quot;,&lt;br /&gt;
                         Util_convertBdAddr2Str((uint8_t*)p_connected_evt-&amp;gt;peer_addr.addr),&lt;br /&gt;
                         p_gap_evt-&amp;gt;conn_handle,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.min_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.max_conn_interval,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.slave_latency,&lt;br /&gt;
                         p_connected_evt-&amp;gt;conn_params.conn_sup_timeout&lt;br /&gt;
                         );&lt;br /&gt;
            m_conn_handle = p_gap_evt-&amp;gt;conn_handle;&lt;br /&gt;
            &lt;br /&gt;
            err_code = ble_led_c_handles_assign(&amp;amp;m_ble_led_c, m_conn_handle, NULL);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
&lt;br /&gt;
            // 开始发现服务，NUS客户端等待发现结果&lt;br /&gt;
            err_code = ble_db_discovery_start(&amp;amp;m_db_disc, p_ble_evt-&amp;gt;evt.gap_evt.conn_handle);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
            break;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当成功发现服务之后，会进入db_disc_handler回调函数，在这个回调函数之中，因为我们这个工程仅需要处理led的服务，所以我们调用ble_led_c_on_db_disc_evt去发现led相关的特征值内容，其中会携带我们的ble_db_discovery_evt_t参数（底层返回的所有和服务数据库相关的信息都在这个参数里面）。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;316&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : db_disc_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于处理数据库发现事件的函数&lt;br /&gt;
// details : 此函数是一个回调函数，用于处理来自数据库发现模块的事件。&lt;br /&gt;
//           根据发现的UUID，此功能将事件转发到各自的服务。&lt;br /&gt;
// &lt;br /&gt;
// param : p_event -&amp;gt; 指向数据库发现事件的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void db_disc_handler(ble_db_discovery_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    ble_led_c_on_db_disc_evt(&amp;amp;m_ble_led_c, p_evt);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;所以接下来，我们需要先判断一下，底层返回的ble_db_discovery_evt_t中携带的类型是否是BLE_DB_DISCOVERY_COMPLETE，也就是数据库成功的完成发现，且发现的UUID是LED_UUID_SERVICE。&lt;br /&gt;
&lt;br /&gt;
如果确实成功的发现我们的LED服务，接下来我们就需要从服务中取出我们需要的特征值，也就是LED_UUID_CHAR。我们需要从这个特征值当中获取我们用于通信的句柄（handle_value）。&lt;br /&gt;
&lt;br /&gt;
当我们一切都是按照正确的流程跑完，可以看到在这个函数的最后，它会给我们返回一个p_ble_led_c-&amp;gt;evt_handler(p_ble_led_c, &amp;amp;evt);，也就是向mian.c文件中给我们一个回调（ble_led_c_init初始化函数时注册的回调），其中携带的任务参数类型是BLE_LED_C_EVT_DISCOVERY_COMPLETE。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;32&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_c_on_db_disc_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理led服务发现的函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向LED客户端结构的指针&lt;br /&gt;
//         p_evt -&amp;gt; 指向从数据库发现模块接收到的事件的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_led_c_on_db_disc_evt(ble_led_c_t * p_ble_led_c, ble_db_discovery_evt_t const * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    // 判断LED服务是否发现完成&lt;br /&gt;
    if (p_evt-&amp;gt;evt_type == BLE_DB_DISCOVERY_COMPLETE &amp;amp;&amp;amp;&lt;br /&gt;
        p_evt-&amp;gt;params.discovered_db.srv_uuid.uuid == LED_UUID_SERVICE &amp;amp;&amp;amp;&lt;br /&gt;
        p_evt-&amp;gt;params.discovered_db.srv_uuid.type == p_ble_led_c-&amp;gt;uuid_type)&lt;br /&gt;
    {&lt;br /&gt;
        ble_led_c_evt_t evt;&lt;br /&gt;
&lt;br /&gt;
        evt.evt_type    = BLE_LED_C_EVT_DISCOVERY_COMPLETE;&lt;br /&gt;
        evt.conn_handle = p_evt-&amp;gt;conn_handle;&lt;br /&gt;
&lt;br /&gt;
        for (uint32_t i = 0; i &amp;lt; p_evt-&amp;gt;params.discovered_db.char_count; i++)&lt;br /&gt;
        {&lt;br /&gt;
            const ble_gatt_db_char_t * p_char = &amp;amp;(p_evt-&amp;gt;params.discovered_db.charateristics[i]);&lt;br /&gt;
            switch (p_char-&amp;gt;characteristic.uuid.uuid)&lt;br /&gt;
            {&lt;br /&gt;
                // 根据LED特征值的UUID，获取我们句柄handle_value&lt;br /&gt;
                case LED_UUID_CHAR:&lt;br /&gt;
                    evt.params.peer_db.led_handle = p_char-&amp;gt;characteristic.handle_value;&lt;br /&gt;
                    break;&lt;br /&gt;
&lt;br /&gt;
                default:&lt;br /&gt;
                    break;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        NRF_LOG_DEBUG(&amp;quot;Led Button Service discovered at peer.&amp;quot;);&lt;br /&gt;
        &lt;br /&gt;
        // 如果实例是在db_discovery之前分配的，则分配db_handles&lt;br /&gt;
        if (p_ble_led_c-&amp;gt;conn_handle != BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
        {&lt;br /&gt;
            if (p_ble_led_c-&amp;gt;peer_led_db.led_handle         == BLE_GATT_HANDLE_INVALID)&lt;br /&gt;
            {&lt;br /&gt;
                p_ble_led_c-&amp;gt;peer_led_db = evt.params.peer_db;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        p_ble_led_c-&amp;gt;evt_handler(p_ble_led_c, &amp;amp;evt);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;那么接下来，我们再去看一下mian.c中此回调函数下的处理。&lt;br /&gt;
&lt;br /&gt;
在ble_led_c_evt_handler回调函数下，我们判断传入的事件类型，可以看到正是刚刚的BLE_LED_C_EVT_DISCOVERY_COMPLETE事件，也就是代表我们已经成功的获取了我们指定服务（LED_UUID_SERVICE）下的指定特征值（LED_UUID_CHAR）的句柄（handle_value）。&lt;br /&gt;
&lt;br /&gt;
然后我们调用ble_led_c_handles_assign函数，去将我们的连接句柄connHandle以及特征值句柄handle_value，绑定给p_ble_led_c实例。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;272&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_led_c_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : LED服务事件&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none                 &lt;br /&gt;
static void ble_led_c_evt_handler(ble_led_c_t * p_ble_led_c, ble_led_c_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    switch (p_evt-&amp;gt;evt_type)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_LED_C_EVT_DISCOVERY_COMPLETE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Discovery complete.&amp;quot;);&lt;br /&gt;
            err_code = ble_led_c_handles_assign(&amp;amp;m_ble_led_c, p_evt-&amp;gt;conn_handle, &amp;amp;p_evt-&amp;gt;params.peer_db);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected to device with Ghostyu LED Service.&amp;quot;);&lt;br /&gt;
            break;&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当上述的流程都正确跑完，我们就可以进行最后一步的行动，也就是发送数据，在这个例程当中我们是利用按键触发来发送对应的LED的状态变化。&lt;br /&gt;
&lt;br /&gt;
我们到mian.c中，查看按键触发会调用的btn_evt_handler_t回调函数，在这个函数中，我们最后会调用LED服务数据发送的功能函数ble_led_led_status_send。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;495&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : btn_evt_handler_t&lt;br /&gt;
//&lt;br /&gt;
// brief : 按键触发回调函数&lt;br /&gt;
// &lt;br /&gt;
// param : butState -&amp;gt; 当前的按键值&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void btn_evt_handler_t (uint8_t butState)&lt;br /&gt;
{&lt;br /&gt;
  uint8_t buf[LED_UUID_CHAR_LEN] = {0x01,0x01,0x01,0x01};&lt;br /&gt;
  switch(butState)&lt;br /&gt;
  {&lt;br /&gt;
    case BUTTON_1:&lt;br /&gt;
      buf[0] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_2:&lt;br /&gt;
      buf[1] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_3:&lt;br /&gt;
      buf[2] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_4:&lt;br /&gt;
      buf[3] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    default:&lt;br /&gt;
      break;&lt;br /&gt;
  }&lt;br /&gt;
  ble_led_status_send(&amp;amp;m_ble_led_c,buf,LED_UUID_CHAR_LEN);    // 发送Wirte属性数据包&lt;br /&gt;
  ble_led_status_read(&amp;amp;m_ble_led_c);                          // 发送Read属性的读取消息&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;最后我们来分析一下这个发送函数，是如何使用我们刚刚一大圈代码处理，最终得到的connhandle以及handle_value的。&lt;br /&gt;
&lt;br /&gt;
首先先判断下数据的长度，是不是符合我们的特征值的长度限制（不能超过我们定义的特征值的大小，否则返回参数错误），这个判断是很有必要的！&lt;br /&gt;
&lt;br /&gt;
接下来我们判断一下connhandle是否为0xffff（BLE_CONN_HANDLE_INVALID），也就是尚未连接任何设备，如果没有连接，则返回状态无效。&lt;br /&gt;
&lt;br /&gt;
最后我们定义了ble_gattc_write_params_t结构体用于赋值我们需要发送的数据，其中值得注意的是.handle   = p_ble_led_c-&amp;gt;peer_led_db.led_handle，这个就是我们刚刚获得的handle_value（特征值句柄），其他参数大家依葫芦画瓢，比较好理解，就不给大家介绍了。最终我们调用sd_ble_gattc_write函数将数据发送出去。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;148&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_led_status_send&lt;br /&gt;
//&lt;br /&gt;
// brief : LED状态控制函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向要关联的LED结构实例的指针&lt;br /&gt;
//         p_string -&amp;gt; 发送的LED相关的数据&lt;br /&gt;
//         length -&amp;gt; 发送的LED相关的数据长度&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_led_led_status_send(ble_led_c_t * p_ble_led_c, uint8_t * p_string, uint16_t length)&lt;br /&gt;
{&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c);&lt;br /&gt;
&lt;br /&gt;
    if (length &amp;gt; LED_UUID_CHAR_LEN)&lt;br /&gt;
    {&lt;br /&gt;
        NRF_LOG_WARNING(&amp;quot;Content too long.&amp;quot;);&lt;br /&gt;
        return NRF_ERROR_INVALID_PARAM;&lt;br /&gt;
    }&lt;br /&gt;
    if (p_ble_led_c-&amp;gt;conn_handle == BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_STATE;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ble_gattc_write_params_t const write_params =&lt;br /&gt;
    {&lt;br /&gt;
        .write_op = BLE_GATT_OP_WRITE_CMD,&lt;br /&gt;
        .flags    = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,&lt;br /&gt;
        .handle   = p_ble_led_c-&amp;gt;peer_led_db.led_handle,&lt;br /&gt;
        .offset   = 0,&lt;br /&gt;
        .len      = length,&lt;br /&gt;
        .p_value  = p_string&lt;br /&gt;
    };&lt;br /&gt;
    &lt;br /&gt;
    return sd_ble_gattc_write(p_ble_led_c-&amp;gt;conn_handle, &amp;amp;write_params);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;上面一部分已经将我们的write属性的使用都讲解完了，最后我们再来看下Read属性部分的内容。&lt;br /&gt;
&lt;br /&gt;
首先是我们还是在main文件的按键回调函数中调用的ble_led_status_read(&amp;amp;m_ble_led_c);函数，去读取从机特征值中的数据的，这里我们直接分析一下这个函数。&lt;br /&gt;
&lt;br /&gt;
可以看到函数内容很简单，只调用了一个sd_ble_gattc_read函数去读取，包含的参数内容分别是我们的connhandle以及handle_value。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;209&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_status_read&lt;br /&gt;
//&lt;br /&gt;
// brief : 读取LED特征值&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; 指向要关联的LED结构实例的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_led_status_read(ble_led_c_t * p_ble_led_c)&lt;br /&gt;
{&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_led_c);&lt;br /&gt;
    return sd_ble_gattc_read(p_ble_led_c-&amp;gt;conn_handle,p_ble_led_c-&amp;gt;peer_led_db.led_handle,0);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当我们成功Read之后，底层的sotfdevice会通过ble_led_c_on_ble_evt函数给我们返回BLE_GATTC_EVT_READ_RSP事件。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;140&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_c_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_led_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    if ((p_context == NULL) || (p_ble_evt == NULL))&lt;br /&gt;
    {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ble_led_c_t * p_ble_led_c = (ble_led_c_t *)p_context;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            on_disconnected(p_ble_led_c, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
        case BLE_GATTC_EVT_READ_RSP:&lt;br /&gt;
            on_read(p_ble_led_c, p_ble_evt);&lt;br /&gt;
          break;&lt;br /&gt;
          &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在BLE_GATTC_EVT_READ_RSP事件中，我们调用on_read函数去处理我们读取的值，我们将读取到的值，通过RTT LOG打印出来。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;32&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :on_read&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理read事件的函数。&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_led_c -&amp;gt; led服务结构体&lt;br /&gt;
//         p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_read(ble_led_c_t * p_ble_led_c, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    if (p_ble_led_c-&amp;gt;conn_handle == p_ble_evt-&amp;gt;evt.gap_evt.conn_handle)&lt;br /&gt;
    {&lt;br /&gt;
      NRF_LOG_INFO(&amp;quot;Recive State:%02X,%02X,%02X,%02X&amp;quot;,&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[0],&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[1],&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[2],&lt;br /&gt;
                   p_ble_evt-&amp;gt;evt.gattc_evt.params.read_rsp.data[3]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
======gy_profile_led.c\.h======&lt;br /&gt;
我们首先查看一下他的服务文件，也就是gy_profile_led，我们我们就是通过这个服务来接收主机发送的LED控制数据的。&lt;br /&gt;
&lt;br /&gt;
可以看到这个服务初始化ble_led_init函数中对于服务以及他的特征值属性的初始化过程，首先我们先初始化一个回调（p_led-&amp;gt;led_write_handler = p_led_init-&amp;gt;led_write_handler;），这个回调是用来将gy_profile_led这一层的数据，上传给mian文件去处理。&lt;br /&gt;
&lt;br /&gt;
接下来是服务的添加，首先是调用sd_ble_uuid_vs_add去添加服务，然后给这个ble_uuid的服务的参数赋值，最后调用sd_ble_gatts_service_add函数去注册这个服务，这边我们注册的服务句柄是p_led-&amp;gt;service_handle。&lt;br /&gt;
&lt;br /&gt;
注册完服务之后，我们就要开始添加我们的特征值characteristic，特征值的添加也是一样的，首先配置特征值的参数，这些参数中我们主要关注一下ble_gatt_char_props_t，这个参数是用来定义特征值的属性的，可以看到我们的属性有如下几种：&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
/**@brief GATT Characteristic Properties. */&lt;br /&gt;
typedef struct&lt;br /&gt;
{&lt;br /&gt;
  /* Standard properties */&lt;br /&gt;
  uint8_t broadcast       :1; /**&amp;lt; 广播 */&lt;br /&gt;
  uint8_t read            :1; /**&amp;lt; 读 */&lt;br /&gt;
  uint8_t write_wo_resp   :1; /**&amp;lt; 写指令 */&lt;br /&gt;
  uint8_t write           :1; /**&amp;lt; 写*/&lt;br /&gt;
  uint8_t notify          :1; /**&amp;lt; 通知*/&lt;br /&gt;
  uint8_t indicate        :1; /**&amp;lt; 暗示 */&lt;br /&gt;
  uint8_t auth_signed_wr  :1; /**&amp;lt; 签名写指令 */&lt;br /&gt;
} ble_gatt_char_props_t;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;因为我们的led的服务，是手机写数据给开发板控制LED，所以我们要定义特征值写使能（add_char_params.char_props.write = 1;），另外当我们需要知道上次是发送的什么控制数据给开发板时，我们需要读一下数据，所以这边我们同样定义一下读使能（add_char_params.char_props.read  = 1;），当我们配置完特征值的属性之后，我们调用characteristic_add函数，去向刚刚注册的p_led-&amp;gt;service_handle服务中添加我们的特征值。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;53&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化LED服务&lt;br /&gt;
//&lt;br /&gt;
// param : p_led -&amp;gt; led服务结构体&lt;br /&gt;
//         p_led_init -&amp;gt; led服务初始化结构体&lt;br /&gt;
//&lt;br /&gt;
// return : uint32_t -&amp;gt; 成功返回SUCCESS，其他返回ERR NO.&lt;br /&gt;
uint32_t ble_led_init(ble_led_t * p_led, const ble_led_init_t * p_led_init)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t              err_code;&lt;br /&gt;
    ble_uuid_t            ble_uuid;&lt;br /&gt;
    ble_add_char_params_t add_char_params;&lt;br /&gt;
&lt;br /&gt;
    // 初始化服务结构体&lt;br /&gt;
    p_led-&amp;gt;led_write_handler = p_led_init-&amp;gt;led_write_handler;&lt;br /&gt;
&lt;br /&gt;
    // 添加服务（128bit UUID）&lt;br /&gt;
    ble_uuid128_t base_uuid = {LED_UUID_BASE};&lt;br /&gt;
    err_code = sd_ble_uuid_vs_add(&amp;amp;base_uuid, &amp;amp;p_led-&amp;gt;uuid_type);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_uuid.type = p_led-&amp;gt;uuid_type;&lt;br /&gt;
    ble_uuid.uuid = LED_UUID_SERVICE;&lt;br /&gt;
&lt;br /&gt;
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &amp;amp;ble_uuid, &amp;amp;p_led-&amp;gt;service_handle);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 添加LED特征值（属性是Write和Read、长度是4）&lt;br /&gt;
    memset(&amp;amp;add_char_params, 0, sizeof(add_char_params));&lt;br /&gt;
    add_char_params.uuid             = LED_UUID_CHAR;&lt;br /&gt;
    add_char_params.uuid_type        = p_led-&amp;gt;uuid_type;&lt;br /&gt;
    add_char_params.init_len         = LED_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.max_len          = LED_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.char_props.read  = 1;&lt;br /&gt;
    add_char_params.char_props.write = 1;&lt;br /&gt;
&lt;br /&gt;
    add_char_params.read_access  = SEC_OPEN;&lt;br /&gt;
    add_char_params.write_access = SEC_OPEN;&lt;br /&gt;
&lt;br /&gt;
    return characteristic_add(p_led-&amp;gt;service_handle, &amp;amp;add_char_params, &amp;amp;p_led-&amp;gt;led_char_handles);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;看完服务的初始化，我们来看下我们的服务注册之后是怎么来进行工作的，首先我们看下ble_led_on_ble_evt这个函数，这个函数在我们mian函数中注册BLE_LED_DEF(m_led);实例的时候被引用。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;15&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :BLE_LED_DEF&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化LED服务实例&lt;br /&gt;
//&lt;br /&gt;
// param : _name -&amp;gt; 实例的名称&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
#define BLE_LED_DEF(_name)                                                                          \&lt;br /&gt;
static ble_led_t _name;                                                                             \&lt;br /&gt;
NRF_SDH_BLE_OBSERVER(_name ## _obs,                                                                 \&lt;br /&gt;
                     BLE_LED_BLE_OBSERVER_PRIO,                                                     \&lt;br /&gt;
                     ble_led_on_ble_evt, &amp;amp;_name)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;这个m_led实例注册，涉及到NRF_SDH_BLE_OBSERVER的使用，简单的理解就是利用这个注册了实例之后，当底层有GAP或者GATT消息返回的时候，就会触发ble_led_on_ble_evt函数。&lt;br /&gt;
&lt;br /&gt;
因为我们LED服务这里需要接收手机端write的LED控制数据，所以我们在事件判断中，判断是否出现GATT Write事件，一旦出现了，我们调用on_write函数去处理这个事件。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;28&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_led_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_led_t * p_led = (ble_led_t *)p_context;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        // GATT Client Write事件&lt;br /&gt;
        case BLE_GATTS_EVT_WRITE:&lt;br /&gt;
            on_write(p_led, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;on_write函数用于处理接收的write数据，我们判断一下接收的数据是否符合我们的要求，如果符合，那么我们通过初始化函数中的回调函数，将接收到的值上传到main函数中去处理。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;7&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :on_write&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理Write事件的函数。&lt;br /&gt;
//&lt;br /&gt;
// param : p_led -&amp;gt; led服务结构体&lt;br /&gt;
//         p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_write(ble_led_t * p_led, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    ble_gatts_evt_write_t const * p_evt_write = &amp;amp;p_ble_evt-&amp;gt;evt.gatts_evt.params.write;&lt;br /&gt;
&lt;br /&gt;
    if (   (p_evt_write-&amp;gt;handle == p_led-&amp;gt;led_char_handles.value_handle)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_evt_write-&amp;gt;len &amp;lt;= LED_UUID_CHAR_LEN)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_led-&amp;gt;led_write_handler != NULL))&lt;br /&gt;
    {&lt;br /&gt;
        p_led-&amp;gt;led_write_handler((uint8_t*)p_evt_write-&amp;gt;data);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======main.c======&lt;br /&gt;
main文件中也不给大家全部介绍了，这个和蓝牙协议实验部分是重合的，我们只关注实验改动的部分。&lt;br /&gt;
&lt;br /&gt;
我们看下服务初始化的部分，可以看到调用了我们gy_profile_led中的ble_led_init函数初始化注册了我们的LED服务，并且通用注册了一个回调函数。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数led_write_handler中，我们可以获取到gy_profile_led中上传上来的接收到的wirte数据，并且利用这个数据进行LED的控制。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;195&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : nus_data_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于处理来自Nordic UART服务的数据的功能&lt;br /&gt;
// details : 该功能将处理从Nordic UART BLE服务接收的数据并将其发送到UART模块&lt;br /&gt;
//&lt;br /&gt;
// param : ble_nus_evt_t -&amp;gt; nus事件&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void led_write_handler(uint8_t * new_state)&lt;br /&gt;
{&lt;br /&gt;
    NRF_LOG_INFO(&amp;quot;Recive State:%02X,%02X,%02X,%02X&amp;quot;,new_state[0],new_state[1],new_state[2],new_state[3]);&lt;br /&gt;
    LED_Control(BSP_LED_0, new_state[0]);&lt;br /&gt;
    LED_Control(BSP_LED_1, new_state[1]);&lt;br /&gt;
    LED_Control(BSP_LED_2, new_state[2]);&lt;br /&gt;
    LED_Control(BSP_LED_3, new_state[3]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : services_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化复位（本例程展示NUS：Nordic Uart Service）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void services_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t           err_code;&lt;br /&gt;
    ble_led_init_t     led_init;&lt;br /&gt;
&lt;br /&gt;
    // Initialize NUS.&lt;br /&gt;
    memset(&amp;amp;led_init, 0, sizeof(led_init));&lt;br /&gt;
&lt;br /&gt;
    led_init.led_write_handler = led_write_handler;&lt;br /&gt;
&lt;br /&gt;
    err_code = ble_led_init(&amp;amp;m_led, &amp;amp;led_init);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;span&amp;gt; 实验总结&amp;lt;/span&amp;gt; ====&lt;br /&gt;
通过这一章节的学习，我们需要掌握下面3个要点。&lt;br /&gt;
&lt;br /&gt;
1、从机如何注册一个自定的服务，并且在服务下添加自己的特征值功能&lt;br /&gt;
&lt;br /&gt;
2、主机如何针对指定的从机服务，去获取这个服务以及服务下指定特征值的句柄&lt;br /&gt;
&lt;br /&gt;
3、主机如何通过Write属性，向从机发送数据&lt;br /&gt;
&lt;br /&gt;
===Notify属性服务实验===&lt;br /&gt;
====实验简介====&lt;br /&gt;
通过Write属性服务实验的学习，我们已经知道了从机设备如何注册一个自定义服务（自定义的LED服务），然后主机如何去发现这个服务，并且利用这个服务的特征值与从机设备进行通信，控制从机设备的LED点亮。&lt;br /&gt;
&lt;br /&gt;
也就是说，我们已经学会了如何通过主机给从机发送数据，所以接下来我们本章节将会给大家讲解从机如何通过notify属性给主机发送数据。&lt;br /&gt;
====实验现象====&lt;br /&gt;
主机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、扫描符合我们连接过滤要求的从机设备（根据LED服务的UUID过滤）&lt;br /&gt;
&lt;br /&gt;
2、成功连接我们的从机设备，并且更新连接参数和MTU&lt;br /&gt;
&lt;br /&gt;
3、发现服务，成功发现Ghostyu BTN Service&lt;br /&gt;
&lt;br /&gt;
4、成功使用了从机服务的notify功能&lt;br /&gt;
[[文件:Nrf52832dk-ble-gattnotify2.png|边框|居中|无框|680x680像素]]&lt;br /&gt;
从机设备流程：&lt;br /&gt;
&lt;br /&gt;
1、开启广播&lt;br /&gt;
&lt;br /&gt;
2、被主机成功连接，并交互连接参数&lt;br /&gt;
&lt;br /&gt;
3、等待主机获取服务（一般主机成功获取服务的时间在0.5s~1s之间，这个时间仅供大家参考）&lt;br /&gt;
&lt;br /&gt;
4、等待主机成功使能notify功能&lt;br /&gt;
&lt;br /&gt;
5、从机分别按下4个按键，给主机发送相应的notify数据包，控制主机LED点亮&lt;br /&gt;
[[文件:Nrf52832dk-ble-gattnotify1.png|边框|居中|无框|695x695px]]&lt;br /&gt;
&lt;br /&gt;
====工程及源码讲解====&lt;br /&gt;
&lt;br /&gt;
===== 主机部分 =====&lt;br /&gt;
&lt;br /&gt;
====== gy_profile_btn_c.c\.h及mian.c ======&lt;br /&gt;
这个实验主机部分的代码和上一章节的Write的主机是类似的，他的整个的服务发现流程是一样的，唯一不同的点是由于从机需要发送notify数据，所以我们的主机需要去使能从机的notify的功能。&lt;br /&gt;
&lt;br /&gt;
所以我们来看一下主机是在哪边，以怎样的方式去使能从机的notify。以及最终是怎么接收来自从机的数据的。&lt;br /&gt;
&lt;br /&gt;
首先看下使能notify的部分，通过上一实验的学习，我们已经知道当我们自己成功跑完获取服务的流程，最后会给main函数中的服务初始化的回调返回成功的事件。我们看下成功发现服务的事件BLE_BTN_C_EVT_DISCOVERY_COMPLETE，里面首先还是调用ble_btn_c_handles_assign函数将获取的句柄值和我们m_ble_btn_c实例绑定起来。然后就去调用ble_btn_c_tx_notif_enable函数去使能从机的notify功能。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;272&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : ble_btn_c_evt_handler&lt;br /&gt;
//&lt;br /&gt;
// brief : BTN服务事件&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none                 &lt;br /&gt;
static void ble_btn_c_evt_handler(ble_btn_c_t * p_ble_btn_c, ble_btn_c_evt_t * p_evt)&lt;br /&gt;
{&lt;br /&gt;
    ret_code_t err_code;&lt;br /&gt;
&lt;br /&gt;
    switch (p_evt-&amp;gt;evt_type)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_BTN_C_EVT_DISCOVERY_COMPLETE:&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Discovery complete.&amp;quot;);&lt;br /&gt;
            err_code = ble_btn_c_handles_assign(&amp;amp;m_ble_btn_c, p_evt-&amp;gt;conn_handle, &amp;amp;p_evt-&amp;gt;params.peer_db);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);&lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Connected to device with Ghostyu BTN Service.&amp;quot;);&lt;br /&gt;
            &lt;br /&gt;
            err_code = ble_btn_c_tx_notif_enable(&amp;amp;m_ble_btn_c);&lt;br /&gt;
            APP_ERROR_CHECK(err_code);  &lt;br /&gt;
            NRF_LOG_INFO(&amp;quot;Enable notification.&amp;quot;);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;我们来看下使能notify的函数的代码，不难发现其实就是一个write功能，不过不是上一个实验向我们的handle_value去发送数据，而是向cccd_handle去发送了一个0x01（BLE_GATT_HVX_NOTIFICATION），0x00的数据。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;179&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :cccd_configure&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于向CCCD发送数据，控制使能或者禁能notify功能&lt;br /&gt;
//&lt;br /&gt;
// param : conn_handle -&amp;gt; 连接的句柄&lt;br /&gt;
//         conn_handle -&amp;gt; CCCD的句柄&lt;br /&gt;
//         enable -&amp;gt; 使能或者禁能标识&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static uint32_t cccd_configure(uint16_t conn_handle, uint16_t cccd_handle, bool enable)&lt;br /&gt;
{&lt;br /&gt;
    uint8_t buf[BLE_CCCD_VALUE_LEN];&lt;br /&gt;
&lt;br /&gt;
    buf[0] = enable ? BLE_GATT_HVX_NOTIFICATION : 0;&lt;br /&gt;
    buf[1] = 0;&lt;br /&gt;
&lt;br /&gt;
    ble_gattc_write_params_t const write_params =&lt;br /&gt;
    {&lt;br /&gt;
        .write_op = BLE_GATT_OP_WRITE_REQ,&lt;br /&gt;
        .flags    = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,&lt;br /&gt;
        .handle   = cccd_handle,&lt;br /&gt;
        .offset   = 0,&lt;br /&gt;
        .len      = sizeof(buf),&lt;br /&gt;
        .p_value  = buf&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return sd_ble_gattc_write(conn_handle, &amp;amp;write_params);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_c_tx_notif_enable&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于使能从机btn服务的notify功能&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_btn_c -&amp;gt; 指向要关联的BTN结构实例的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_btn_c_tx_notif_enable(ble_btn_c_t * p_ble_btn_c)&lt;br /&gt;
{&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_ble_btn_c);&lt;br /&gt;
&lt;br /&gt;
    if ( (p_ble_btn_c-&amp;gt;conn_handle == BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
       ||(p_ble_btn_c-&amp;gt;peer_btn_db.cccd_handle == BLE_GATT_HANDLE_INVALID)&lt;br /&gt;
       )&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_STATE;&lt;br /&gt;
    }&lt;br /&gt;
    return cccd_configure(p_ble_btn_c-&amp;gt;conn_handle,p_ble_btn_c-&amp;gt;peer_btn_db.cccd_handle, true);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;看完了使能notify的函数，我们来看下接收从机数据的处理部分，首先是看到ble_btn_c_on_ble_evt函数，这个函数在我们调用BLE_BTN_C_DEF(m_ble_btn_c);注册实例的时候，就已经创建好了，用于接收底层的softdevice的消息返回。&lt;br /&gt;
&lt;br /&gt;
我们看下其中的BLE_GATTC_EVT_HVX（Handle Value Notification or Indication event）事件，在这个事件下我们接收到从机发送给我们的数据。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;146&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_c_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_btn_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    if ((p_context == NULL) || (p_ble_evt == NULL))&lt;br /&gt;
    {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ble_btn_c_t * p_ble_btn_c = (ble_btn_c_t *)p_context;&lt;br /&gt;
&lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_GATTC_EVT_HVX:&lt;br /&gt;
            on_hvx(p_ble_btn_c, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
      &lt;br /&gt;
        case BLE_GAP_EVT_DISCONNECTED:&lt;br /&gt;
            on_disconnected(p_ble_btn_c, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
&lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;然后我们看下对于接收到的从机数据的处理，首先还是一样的，我们需要判断一下数据的来源是不是我们btn_handle。当确认都是正确的，然后我们将接收的数据复制给ble_btn_c_evt_t，然后通过它的回调上传到我们的main文件中，携带的事件ID为BLE_BTN_C_EVT_BTN_TX_EVT。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;32&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :on_hvx&lt;br /&gt;
//&lt;br /&gt;
// brief : 用于处理从SoftDevice接收到的通知&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_btn_c -&amp;gt; 指向BTN Client结构的指针&lt;br /&gt;
//         p_ble_evt -&amp;gt; 指向接收到的BLE事件的指针&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void on_hvx(ble_btn_c_t * p_ble_btn_c, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    if (   (p_ble_btn_c-&amp;gt;peer_btn_db.btn_handle != BLE_GATT_HANDLE_INVALID)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_ble_evt-&amp;gt;evt.gattc_evt.params.hvx.handle == p_ble_btn_c-&amp;gt;peer_btn_db.btn_handle)&lt;br /&gt;
        &amp;amp;&amp;amp; (p_ble_btn_c-&amp;gt;evt_handler != NULL))&lt;br /&gt;
    {&lt;br /&gt;
        ble_btn_c_evt_t ble_btn_c_evt;&lt;br /&gt;
&lt;br /&gt;
        ble_btn_c_evt.evt_type = BLE_BTN_C_EVT_BTN_TX_EVT;&lt;br /&gt;
        ble_btn_c_evt.p_data   = (uint8_t *)p_ble_evt-&amp;gt;evt.gattc_evt.params.hvx.data;&lt;br /&gt;
        ble_btn_c_evt.data_len = p_ble_evt-&amp;gt;evt.gattc_evt.params.hvx.len;&lt;br /&gt;
&lt;br /&gt;
        p_ble_btn_c-&amp;gt;evt_handler(p_ble_btn_c, &amp;amp;ble_btn_c_evt);&lt;br /&gt;
        NRF_LOG_DEBUG(&amp;quot;Client sending data.&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;接下来返回到我们的main文件中，我们在ble_btn_c_evt_handler回调中可以看到BLE_BTN_C_EVT_BTN_TX_EVT事件的处理，我们将接收到的从机数据用于控制相应的LED灯点亮。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;297&amp;quot;&amp;gt;&lt;br /&gt;
        case BLE_BTN_C_EVT_BTN_TX_EVT:&lt;br /&gt;
            NRF_LOG_DEBUG(&amp;quot;Receiving data.&amp;quot;);&lt;br /&gt;
            NRF_LOG_HEXDUMP_DEBUG(p_evt-&amp;gt;p_data, p_evt-&amp;gt;data_len);&lt;br /&gt;
            LED_Control(BSP_LED_0, p_evt-&amp;gt;p_data[0]);&lt;br /&gt;
            LED_Control(BSP_LED_1, p_evt-&amp;gt;p_data[1]);&lt;br /&gt;
            LED_Control(BSP_LED_2, p_evt-&amp;gt;p_data[2]);&lt;br /&gt;
            LED_Control(BSP_LED_3, p_evt-&amp;gt;p_data[3]);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 从机部分 =====&lt;br /&gt;
&lt;br /&gt;
======gy_profile_btn.c\.h======&lt;br /&gt;
首先我们还是先看一下服务配置文件，首先还是注册一下服务，注册的服务句柄是p_btn-&amp;gt;service_handle。服务注册完成之后，我们注册按键的特征值，可以看到我们分别使能了按键的notify通知属性（add_char_params.char_props.notify = 1;），并且同样使能了read属性（add_char_params.char_props.read  = 1;）。&lt;br /&gt;
&lt;br /&gt;
这里我们需要注意的是下面的cccd_write_access参数被使能，上一实验大家都好理解需要使能write_access和read_access，因为我们需要使用读写，那么为什么这个例程要使能cccd_write_access呢，这边给大家简单说明一下CCCD。{{Note|text=Client Characteristic Configuration Descriptor（CCCD）是客户端特征配置描述符。当主机向CCCD中写入0x0001，此时使能notify；当写入0x0000时，此时禁止notify。&lt;br /&gt;
在nordic的协议栈当中，他的这个notify使能是交给用户自己处理的，也是说即便主机没有向cccd中写入0x0001去使能notify，我们同样可以直接利用notify去发送数据，只能这样不符合规范。|type=info}}&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;50&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化BTN服务&lt;br /&gt;
//&lt;br /&gt;
// param : p_btn -&amp;gt; btn服务结构体&lt;br /&gt;
//&lt;br /&gt;
// return : uint32_t -&amp;gt; 成功返回SUCCESS，其他返回ERR NO.&lt;br /&gt;
uint32_t ble_btn_init(ble_btn_t * p_btn)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t              err_code;&lt;br /&gt;
    ble_uuid_t            ble_uuid;&lt;br /&gt;
    ble_add_char_params_t add_char_params;&lt;br /&gt;
&lt;br /&gt;
    // 添加服务（128bit UUID）&lt;br /&gt;
    ble_uuid128_t base_uuid = {BTN_UUID_BASE};&lt;br /&gt;
    err_code = sd_ble_uuid_vs_add(&amp;amp;base_uuid, &amp;amp;p_btn-&amp;gt;uuid_type);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    ble_uuid.type = p_btn-&amp;gt;uuid_type;&lt;br /&gt;
    ble_uuid.uuid = BTN_UUID_SERVICE;&lt;br /&gt;
&lt;br /&gt;
    err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &amp;amp;ble_uuid, &amp;amp;p_btn-&amp;gt;service_handle);&lt;br /&gt;
    VERIFY_SUCCESS(err_code);&lt;br /&gt;
&lt;br /&gt;
    // 添加BTN特征值（属性是Write和Read、长度是4）&lt;br /&gt;
    memset(&amp;amp;add_char_params, 0, sizeof(add_char_params));&lt;br /&gt;
    add_char_params.uuid             = BTN_UUID_CHAR;&lt;br /&gt;
    add_char_params.uuid_type        = p_btn-&amp;gt;uuid_type;&lt;br /&gt;
    add_char_params.init_len         = BTN_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.max_len          = BTN_UUID_CHAR_LEN;&lt;br /&gt;
    add_char_params.char_props.read  = 1;&lt;br /&gt;
    add_char_params.char_props.notify = 1;&lt;br /&gt;
    &lt;br /&gt;
    add_char_params.read_access  = SEC_OPEN;&lt;br /&gt;
    add_char_params.cccd_write_access = SEC_OPEN;&lt;br /&gt;
&lt;br /&gt;
    return characteristic_add(p_btn-&amp;gt;service_handle, &amp;amp;add_char_params, &amp;amp;p_btn-&amp;gt;btn_char_handles);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;服务部分剩下的处理流程和上一实验是类型的，只不过上一个实验是处理的wirte属性，而这个实验是处理notify属性。&lt;br /&gt;
&lt;br /&gt;
首先在BLE事件处理的函数中，我们应该要处理CCCD_Write的数据的，所以在由softdevice返回消息的ble_btn_on_ble_evt函数中，我们需要处理一下BLE_GATTS_EVT_WRITE事件。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;30&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_led_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_on_ble_evt&lt;br /&gt;
//&lt;br /&gt;
// brief : BLE事件处理函数&lt;br /&gt;
//&lt;br /&gt;
// param : p_ble_evt -&amp;gt; ble事件&lt;br /&gt;
//         p_context -&amp;gt; ble事件处理程序的参数（暂时理解应该是不同的功能，注册时所携带的结构体参数）&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void ble_btn_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)&lt;br /&gt;
{&lt;br /&gt;
    ble_btn_t * p_btn = (ble_btn_t *)p_context;&lt;br /&gt;
  &lt;br /&gt;
    switch (p_ble_evt-&amp;gt;header.evt_id)&lt;br /&gt;
    {&lt;br /&gt;
        case BLE_GATTS_EVT_WRITE:&lt;br /&gt;
            on_write(p_btn, p_ble_evt);&lt;br /&gt;
            break;&lt;br /&gt;
            &lt;br /&gt;
        default:&lt;br /&gt;
            break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;在这个on_write函数中，我们接收到了主机发送过来的使能从机notify的数据，我们需要判断一下接收的数据的句柄是不是cccd_handle，以及接收的数据长度是不是2字节（使能数据：01 00）。如果成功使能了notify，那么我们打印&amp;quot;notification enabled&amp;quot;。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;10&amp;quot;&amp;gt;&lt;br /&gt;
static void on_write(ble_btn_t * p_btn, ble_evt_t const * p_ble_evt)&lt;br /&gt;
{&lt;br /&gt;
    ble_gatts_evt_write_t const * p_evt_write = &amp;amp;p_ble_evt-&amp;gt;evt.gatts_evt.params.write;&lt;br /&gt;
    &lt;br /&gt;
    if ((p_evt_write-&amp;gt;handle == p_btn-&amp;gt;btn_char_handles.cccd_handle) &amp;amp;&amp;amp;&lt;br /&gt;
        (p_evt_write-&amp;gt;len == 2))&lt;br /&gt;
    {&lt;br /&gt;
      if (ble_srv_is_notification_enabled(p_evt_write-&amp;gt;data))&lt;br /&gt;
      {&lt;br /&gt;
          p_client.is_notification_enabled = true;&lt;br /&gt;
          NRF_LOG_INFO(&amp;quot;notification enabled&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
      else&lt;br /&gt;
      {&lt;br /&gt;
          p_client.is_notification_enabled = false;&lt;br /&gt;
          NRF_LOG_INFO(&amp;quot;notification disabled&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;除了上述的3个函数，我们的从机服务文件，就只剩下一个发送notify数据的函数了。首先我们一定要先判断一下是否已经使能的notify使能，并且判断数据长度是否符合要求。&lt;br /&gt;
&lt;br /&gt;
下面这个函数，就是我们notify发送数据的函数，他的参数我们只需要配置4个。&lt;br /&gt;
&lt;br /&gt;
type配置为BLE_GATT_HVX_NOTIFICATION，代表是notify属性的数据；&lt;br /&gt;
&lt;br /&gt;
handle我们需要配置为我们按键特征值的value.handle，代表的是按键特征值的Value这个列表的句柄；&lt;br /&gt;
&lt;br /&gt;
剩下的p_data和p_len就是我们需要发送的数据以及数据的长度。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;95&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************************&lt;br /&gt;
// fn :ble_btn_data_send&lt;br /&gt;
//&lt;br /&gt;
// brief : 处理按键按下，状态更新的事件&lt;br /&gt;
//&lt;br /&gt;
// param : p_btn -&amp;gt; btn结构体&lt;br /&gt;
//         p_data -&amp;gt; 数据指针&lt;br /&gt;
//         p_length -&amp;gt; 数据长度&lt;br /&gt;
//         conn_handle -&amp;gt; 连接的句柄&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
uint32_t ble_btn_data_send(ble_btn_t *p_btn, uint8_t *p_data, uint16_t p_length, uint16_t conn_handle)&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    ble_gatts_hvx_params_t     hvx_params;&lt;br /&gt;
&lt;br /&gt;
    VERIFY_PARAM_NOT_NULL(p_btn);&lt;br /&gt;
&lt;br /&gt;
    if (conn_handle == BLE_CONN_HANDLE_INVALID)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_NOT_FOUND;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!p_client.is_notification_enabled)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_STATE;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (p_length &amp;gt; BTN_UUID_CHAR_LEN)&lt;br /&gt;
    {&lt;br /&gt;
        return NRF_ERROR_INVALID_PARAM;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    memset(&amp;amp;hvx_params, 0, sizeof(hvx_params));&lt;br /&gt;
    hvx_params.type   = BLE_GATT_HVX_NOTIFICATION;&lt;br /&gt;
    hvx_params.handle = p_btn-&amp;gt;btn_char_handles.value_handle;&lt;br /&gt;
    hvx_params.p_data = p_data;&lt;br /&gt;
    hvx_params.p_len  = &amp;amp;p_length;&lt;br /&gt;
&lt;br /&gt;
    return sd_ble_gatts_hvx(conn_handle, &amp;amp;hvx_params);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======main.c======&lt;br /&gt;
首先我们还是需要添加一下服务初始化函数。其他的处理都是在gy_profile_btn文件中，所以main文件中就只剩下一个按键触发后调用notify发送的部分。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;194&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : services_init&lt;br /&gt;
//&lt;br /&gt;
// brief : 初始化复位（本例程展示NUS：Nordic Uart Service）&lt;br /&gt;
//&lt;br /&gt;
// param : none&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
static void services_init(void)&lt;br /&gt;
{&lt;br /&gt;
    uint32_t           err_code;&lt;br /&gt;
&lt;br /&gt;
    err_code = ble_btn_init(&amp;amp;m_btn);&lt;br /&gt;
    APP_ERROR_CHECK(err_code);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;当有按键按下时，最终会将按键消息传递到这个回调中进行处理，我们根据按键触发的消息，对相应的buf值进行修改，最后调用ble_btn_data_send函数将数据发送给主机。&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot; start=&amp;quot;351&amp;quot;&amp;gt;&lt;br /&gt;
//******************************************************************&lt;br /&gt;
// fn : btn_evt_handler_t&lt;br /&gt;
//&lt;br /&gt;
// brief : 按键触发回调函数&lt;br /&gt;
// &lt;br /&gt;
// param : butState -&amp;gt; 当前的按键值&lt;br /&gt;
//&lt;br /&gt;
// return : none&lt;br /&gt;
void btn_evt_handler_t (uint8_t butState)&lt;br /&gt;
{&lt;br /&gt;
  uint8_t buf[BTN_UUID_CHAR_LEN] = {0x01,0x01,0x01,0x01};&lt;br /&gt;
  switch(butState)&lt;br /&gt;
  {&lt;br /&gt;
    case BUTTON_1:&lt;br /&gt;
      buf[0] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_2:&lt;br /&gt;
      buf[1] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_3:&lt;br /&gt;
      buf[2] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    case BUTTON_4:&lt;br /&gt;
      buf[3] = 0x00;&lt;br /&gt;
      break;&lt;br /&gt;
    default:&lt;br /&gt;
      break;&lt;br /&gt;
  }&lt;br /&gt;
  ble_btn_data_send(&amp;amp;m_btn, buf, BTN_UUID_CHAR_LEN, m_conn_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== &amp;lt;span&amp;gt; &amp;lt;/span&amp;gt;实验总结 ====&lt;br /&gt;
经过本章内容的学习，我们需要对notify属性的服务有一定的了解，主要掌握以下3点：&lt;br /&gt;
&lt;br /&gt;
1、我们需要了解主机如何去使能从机的notify功能&lt;br /&gt;
&lt;br /&gt;
2、从机如何创建一个支持notify功能的特征值服务&lt;br /&gt;
&lt;br /&gt;
3、从机如何发送一个notify功能的数据包给主机&lt;br /&gt;
&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E8%BD%AF%E4%BB%B6%E5%8A%9F%E8%83%BD%E7%A4%BA%E6%84%8F%E5%9B%BE.png&amp;diff=2873</id>
		<title>文件:BLE181-软件功能示意图.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:BLE181-%E8%BD%AF%E4%BB%B6%E5%8A%9F%E8%83%BD%E7%A4%BA%E6%84%8F%E5%9B%BE.png&amp;diff=2873"/>
		<updated>2020-05-14T18:22:02Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;BLE181 软件功能示意图&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E6%8E%A5%E6%94%B6%E9%80%9A%E7%9F%A5.png&amp;diff=2872</id>
		<title>文件:谷雨BLE调试工具-接收通知.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E6%8E%A5%E6%94%B6%E9%80%9A%E7%9F%A5.png&amp;diff=2872"/>
		<updated>2020-05-14T18:01:26Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨BLE调试工具 接收通知&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE.png&amp;diff=2871</id>
		<title>文件:谷雨BLE调试工具-发送数据.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE.png&amp;diff=2871"/>
		<updated>2020-05-14T17:54:33Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨BLE调试工具 发送数据&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E9%80%9A%E4%BF%A1%E6%9C%8D%E5%8A%A1.png&amp;diff=2870</id>
		<title>文件:谷雨BLE调试工具-通信服务.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:%E8%B0%B7%E9%9B%A8BLE%E8%B0%83%E8%AF%95%E5%B7%A5%E5%85%B7-%E9%80%9A%E4%BF%A1%E6%9C%8D%E5%8A%A1.png&amp;diff=2870"/>
		<updated>2020-05-14T17:43:16Z</updated>

		<summary type="html">&lt;p&gt;Ghostyu：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;谷雨BLE调试工具 通信服务&lt;/div&gt;</summary>
		<author><name>Ghostyu</name></author>
		
	</entry>
</feed>