主机使用例程:nRF5_SDK_17.1.0_ddde560\examples\ble_central\ble_app_blinky_c\pca10040\s132\arm5_no_packs
从机使用例程:nRF5_SDK_17.1.0_ddde560\examples\ble_peripheral\ble_app_blinky\pca10040\s132\arm5_no_packs
nrf52832
1. 空中数据包格式
1.1 Preamble
如果在LE 1M物理层上发送或者接收时前导码为1字节;在LE 2M物理层上发送或者接收时为2字节。前导码是由bit位1和bit位0交替组成的,由接入地址的LSB位决定。
LE 1M物理层:
若接入地址的LSB位为0:01010101
若接入地址的LSB位为1:10101010
LE 2M物理层:
若接入地址的LSB位为0:0101010101010101
若接入地址的LSB位为1:1010101010101010
1.2 Access Address
接入地址为4字节,广播接入地址:
所有广播接入地址的值都是固定的,其值为10001110100010011011111011010110b (0x8E89BED6)
数据接入地址:
这个地址是随机的,目的是使两个设备之间的每个链路层连接或者每个周期性的广播时拥有不同的访问地址。处于初始化状态的链路层将为它发送的每个初始化PDU生成一个新的访问地址;处于广播状态的链路层在每次设置启用周期性广播时,也会生成一个新的访问地址。这种随机的访问地址应该是一个32位的值。链路层生成的新的访问地址满足以下条件:
1.它不得是本设备上任何现有的链路层连接的访问地址。
2.它不得是任何启用的周期性广播的访问地址。
3.它不得有超过六个连续的0或1。
4.它不得是广播信道包的访问地址。
5.它不应是一个与广播信道包访问地址仅相差一位的序列。
6.不得所有4个字节相等。
7.不得超过24个比特位翻转。
8.在最后的6位中至少有2个比特位翻转。
1.3 PDU
PDU为协议数据单元(Protocol Data Unit)。
1.3.1 当PDU在主要或者次要广播信道上传输时,PDU格式
其中Header格式:
PDU Type:
RFU:1bit,保留给未来使用。
ChSel:1bit,表示通道选择;为1支持通道选择,为0不支持通道选择。
TxAdd:1bit,广播设备地址类型;为0表示公共地址,为1表示随机地址。
RxAdd:1bit,目标设备地址类型;为0表示公共地址,为1表示随机地址。
Length:8bit,表示Payload负载的长度;PDU的Payload负载长度与PDU类型有关。
1.3.2 当PDU在数据信道上传输时,PDU格式
其中Header格式:
相关字段的描述:
1.4 CRC
在每个链路层数据包的末尾都有一个24位的CRC。CRC多项式:x24 + x10 + x9 + x6 + x4 + x3 + x + 1
2. 主从机交互过程中SN和NESN变化
按照蓝牙核心规范,通信从主设备(设备A )发送一个链路层数据包开始,SN和NESN都设置为零。从这一点开始,在每次发生的数据包交换中,如果一切正常,设备A设置的SN字段的值将在0和1之间交替。因此,次设备(设备B )总是知道下一个要接收的数据包的SN值应该是多少,并对此进行检查。
2.1 正常交互
正常交互时SN和NESN变化图:
Nordic的协议栈正常交互时同蓝牙核心规范一致。
2.2 重传
如果设备B接收到一个SN值错误的数据包,则假定该数据包是接收到的前一个数据包的重传,承认该数据包,但不将其上传到协议栈进行进一步处理。如果设备A从设备B的应答中收到了一个未预期的NESN值,或者根本没有收到应答,它会重新发送原来使用相同SN值的数据包。不同的控制器实现可以自由地执行不同的算法,以确定通信失败的次数。
重传时SN和NESN变化图:
2.3 CRC错误
每个数据包包含一个CRC字段,加密后的数据包也包含一个MIC字段。在接收到数据包时,链路层检查CRC,如果MIC存在,则检查MIC。如果其中一个检查失败,则不承认该数据包,这通常会导致数据包的发起者重新发送该数据包。
Nordic的协议栈在出现CRC错误后,下一个连接事件直接把SN和NESN设置为1,跟蓝牙核心规范不相同。
3. 主从机连接抓包
3.1 从机广播
PDU Type为ADV_IND时,Payload格式:
PDU Type为SCAN_RSP时,Payload格式:
二者前6个字节表示广播设备的地址,后面由0-31个字节组成广播数据。
广播数据分为有效部分和无效部分,有效部分由一个个广播数据结构体组成,length为结构体长度,AD Type表示AD Data的类型定义。在工程中ble_gap.h文件中对AD Type有定义:
如果使用外观的话只能用SIG定义的外观,工程中相关定义在ble_types.h文件中:
上面3个AD structure中内容由从机在GAP参数初始化函数中设置:
在广播初始化函数中选择如何广播:
3.2 主机连接
发起连接的一方称为主机。PDU Type为CONNECT_IND或者AUX_CONNECT_REQ时,Payload格式:
InitA为主机地址,AdvA为广播地址,其中LLData格式为:
AA:链路层生成访问地址。
CRCInit:校验的初值,由链路层生成的随机值。
WinSize:建立连接时的连接窗口,值为WinSize1.25ms。
WinOffset:建立连接时的连接窗口偏移量,值为WinOffset1.25ms。
Interval:连接间隔,值为Interval1.25ms。
Latency:从机握手潜伏期。
Timeout:从机断开超时时间,值为Timeout10ms。
Chm:信道占用图。
Hop:调频算法的增量,值范围5-16。
SCA:睡眠时钟精度
主机工程在扫描初始化函数scan_init中调用了nrf_ble_scan_init函数,如果没有设置连接参数,则会使用默认的连接参数:
3.3 主机从机服务句柄值信息交换
L2CAP基本信息帧如下:
Channel ID也就是CID,其中0x0004表示属性协议:
ATT主机请求服务句柄值信息Payload如下:
ATT从机响应服务句柄值信息Payload如下:
句柄信息Handles Information包含如下:
UUID类型在ble_types.h中定义:
3.3.1 主机请求
128位私有服务的UUID就是从机工程中定义的基本UUID加上服务UUID:
#define LBS_UUID_BASE {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, \
0xDE, 0xEF, 0x12, 0x12, 0x00, 0x00, 0x00, 0x00}
#define LBS_UUID_SERVICE 0x1523
#define LBS_UUID_BUTTON_CHAR 0x1524
#define LBS_UUID_LED_CHAR 0x1525
3.3.2 从机响应
3.4 主机从机服务特征值信息交换
ATT主机请求服务特征值信息Payload如下:
ATT从机响应服务特征值信息Payload如下:
3.4.1 主机请求
句柄的值是唯一的,因此本次请求时起始句柄值从服务句柄值开始。
3.4.2 从机响应
从机工程主服务有两个特征值,一个是按键通知,一个是LED控制,因为MTU的值为默认值23,所以需要分两次交换特征性。
3.5 其他交互过程
此外还有其他的交互过程主要是针对按键服务打开通知功能。
查找信息请求(Find Information Request0x04)及响应(Find Information Response0x05)用于获取属性句柄与其关联类型的映射。这使得客户端可以在服务器上发现属性及其类型的列表。
写请求(Write Request0x12)及写响应(Write Response0x13)用于请求服务器写入一个属性的值,并在Write Response中确认已经写入成功。
3.6 连接参数更新
连接参数主要包括最小连接间隔、最大连接间隔、从机潜伏周期和连接超时时间。在最大连接间隔内要有一次成功的数据交互,可以是空的PDU;在连接超时时间到达时如果没有一次成功的数据交互,则认为连接断开;从机潜伏周期为从机能忽略多少个主机的连接事件,用于从机达到更好的功耗。设置时最小连接间隔和最大连接间隔步进为1.25ms,范围7.5ms至4s(值6至3200),连接超时时间步进为10ms,范围100ms至32s(值10至3200)。
在未进行连接参数更新时,主机和从机之间使用主机设置的连接间隔来进行交互,默认最大连接间隔为30ms,最小连接间隔为7.5ms。
3.6.1 从机请求
L2CAP的CID为0x0005,连接参数更新请求代码为0x12。
连接参数更新由从机发起,格式为:
其中Identifier字段长度为1字节,响应与请求匹配,请求设备设置此字段,响应设备在其响应中使用相同的值。
从机请求的这些连接参数就是在GAP参数初始化gap_params_init函数中设置的值。
static void gap_params_init(void)
{
ret_code_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);//连接模式,主要是是否需要加密
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *)DEVICE_NAME,
strlen(DEVICE_NAME));//设备名称设置
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_appearance_set(BLE_APPEARANCE_HID_MOUSE);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));//连接参数设置
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;//最小连接间隔
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;//最大连接间隔
gap_conn_params.slave_latency = SLAVE_LATENCY;//从设备延迟,从设备可以跳过连接事件
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;//两个成功的连接事件之间的最大间隔,超过后会认为连接丢失(100ms至32s之间)
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
}
3.6.2 主机响应
主机响应格式:
其中Result定义:
连接参数更新完成后,主机和从机之间的连接间隔会以从机请求成功的值来运行,从机设置的最大连接间隔为200ms,最小连接间隔为100ms。
从机什么时候去更新连接参数是由自己决定的,在从机工程中conn_params_init函数中有设置first_conn_params_update_delay参数表示从连接后多久更新连接参数。ble_conn_params_init函数中会创建一个软件定时器来处理连接参数更新的过程。工程中FIRST_CONN_PARAMS_UPDATE_DELAY宏定义为20s。
static void conn_params_init(void)
{
ret_code_t err_code;
ble_conn_params_init_t cp_init;
memset(&cp_init, 0, sizeof(cp_init));
cp_init.p_conn_params = NULL;//指向应用程序中设置的GAP连接参数,如果设置为NULL,则连接参数从主机获得
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;//初始化事件(连接或启动通知)到第一次连接参数更新的时间
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;//第一次更新后,下次发起更新申请的间隔时间
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;//放弃协商连接参数前最大重试次数
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;
cp_init.disconnect_on_fail = false;//设置为TRUE时如果连接参数更新失败,则会自动断开连接
cp_init.evt_handler = on_conn_params_evt;//连接参数更新结果对应的处理事件
cp_init.error_handler = conn_params_error_handler;//发生错误时的处理
err_code = ble_conn_params_init(&cp_init);
APP_ERROR_CHECK(err_code);
}
连接开始时间:
连接参数更新时间如下,时间间隔为20s:
3.7 数据交互
3.7.1 主机向从机写入数据
主机向从机写入数据需要用到写命令:
3.7.2 从机向主机通知
服务器可以随时发送属性值的通知。服务器也就是从机,客户端是主机。