蓝牙51822学习笔记四
目前手里面有几个项目用的是nrf51822这款蓝牙芯片。由于我从未接触过蓝牙协议,所以在很多地方磕磕绊绊的,所以最近准备系统学习一下该芯片。并做一下笔记放在我的博客里面。系统学习资料来源于B站青峰电子。视频名称为蓝牙nrf51822视频教程资料 编程开发 协议栈开发 青峰电子例程。
UUID
UUID的原理
UUID含义是通用唯一识别码(Universally Unique Identifier),这是一个软件建构的标准。UUID是指再一台机器上生成的数字,它保证对再同一时空中的所有机器都是唯一的。通常平台会提供生成的API。
在GATT层中,规范定义的所有属性都有一个UUID值,UUID是全球唯一的128位的号码,它用来识别不同的特性。
蓝牙技术联盟UUID
所有的蓝牙技术联盟定义UUID共用了一个基本的UUID。0x0000xxxx00001000800000805F9B34FB。为了进一步进化UUID,每一个蓝牙技术联盟的属性有一个唯一的16位UUID,以代替基本UUID的xxxx部分。例如心率测量特性使用0x2A37。
供应商特定UUID
SoftDevice根据蓝牙技术联盟定义UUID类似的方式定义UUID:先增加一个特定的基本UUID,再定义一个16位的UUID,将16位UUID加载到基本的UUID中。这种采用为所有的定制属性定义一个共用的基本UUID的方式使得应用变的更加简单。
UUID的设置
uint32_t ble_nus_init(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init)
{
uint32_t err_code;
ble_uuid_t ble_uuid;
//基本UUID,xxxx使用00 00占位,数组下标与显示方向相反
ble_uuid128_t nus_base_uuid = NUS_BASE_UUID;
if ((p_nus == NULL) || (p_nus_init == NULL))
{
return NRF_ERROR_NULL;
}
// Initialize the service structure.
p_nus->conn_handle = BLE_CONN_HANDLE_INVALID;
p_nus->data_handler = p_nus_init->data_handler;
p_nus->is_notification_enabled = false;
/**@snippet [Adding proprietary Service to S110 SoftDevice] */
// Add a custom base UUID.
//将16位UUID添加到128位基础UUID
err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
ble_uuid.type = p_nus->uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
// Add the service.
err_code = sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,
&ble_uuid,
&p_nus->service_handle);
/**@snippet [Adding proprietary Service to S110 SoftDevice] */
if (err_code != NRF_SUCCESS)
{
return err_code;
}
// Add the RX Characteristic.
err_code = rx_char_add(p_nus, p_nus_init);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
// Add the TX Characteristic.
err_code = tx_char_add(p_nus, p_nus_init);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
return NRF_SUCCESS;
}
串口透传
通过串口透传数据传输给蓝牙。
初始化串口
这个也太简单了吧。随便百度就OK。
服务的建立
服务ble_nus.c
双向数据传输
tx,rx两个子服务。
蓝牙遥控器的设计
基于蓝牙串口透传,通过蓝牙发起指令控制硬件,达到遥控器的目的。
nrf51822内部flash操作
flash事件派发
/**@brief Function for dispatching a system event to interested modules.
*
* @details This function is called from the System event interrupt handler after a system
* event has been received.
*
* @param[in] sys_evt System stack event.
*/
static void sys_evt_dispatch(uint32_t sys_evt)
{
pstorage_sys_event_handler(sys_evt);//需要有该函数,官方函数库,直接调用即可
ble_advertising_on_sys_evt(sys_evt);
}
// Register with the SoftDevice handler module for BLE events.
err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);//ble_stack_init函数中,协议栈系统事件
APP_ERROR_CHECK(err_code);
flash初始化
static pstorage_handle_t m_storage_handle; /**< Persistent storage handle for blocks requested by the module. */
pstorage_module_param_t param;
// Initialize persistent storage module.
err_code = pstorage_init();//只能初始化一次,我曾经初始化两次而出现问题
//dm_init中有初始化
//All context with respect to a particular device is stored contiguously.
param.block_size = ALL_CONTEXT_SIZE;//块大小
param.block_count = DEVICE_MANAGER_MAX_BONDS;//块数目
param.cb = dm_pstorage_cb_handler;//回调
err_code = pstorage_register(¶m, &m_storage_handle);
flash读写
pstorage_handle_t block_handle;
err_code = pstorage_block_identifier_get(&m_storage_handle,0,&block_handle);//获取ID,有注册多次时需要用到,一次的话可以不调用
err_code = pstorage_clear(&block_handle, ALL_CONTEXT_SIZE);//擦除
err_code = pstorage_store(&block_handle,data,size,offset);//存储
err_code = pstorage_load(data,&block_handle,size,offset);//读取
pstorage_update(&block_handle,data,size,offset);//更新
flash存储接收内容
通过回调函数来进行存储,例程函数名及注释如下:
/**@brief Function for pstorage module callback.
*
* @param[in] p_handle Identifies module and block for which callback is received.
* @param[in] op_code Identifies the operation for which the event is notified.
* @param[in] result Identifies the result of flash access operation.
* NRF_SUCCESS implies, operation succeeded.
* @param[in] p_data Identifies the application data pointer. In case of store operation, this
* points to the resident source of application memory that application can now
* free or reuse. In case of clear, this is NULL as no application pointer is
* needed for this operation.
* @param[in] data_len Length of data provided by the application for the operation.
*/
static void dm_pstorage_cb_handler(pstorage_handle_t * p_handle,
uint8_t op_code,
uint32_t result,
uint8_t * p_data,
uint32_t data_len)
{}
源代码太多,影响阅读,所以不贴代码,需要的话,自行阅读。
存储接收流程为手机发送数据->蓝牙数据处理->写入
此流程与例程功能并不相同。
蓝牙回调函数中如下代码:
pstorage_handle_t block_handle;
pstorage_block_identifier_get(&m_storage_handle,0,&block_handle);//注册一次可以不调用
pstorage_update(&block_handle,need_storage_data,len,offset);//更新数据
远程修改参数
流程:
蓝牙发送参数->蓝牙数据存储参数->重启初始化已经修改的参数
蓝牙温湿度采集
DHT11
单总线,很多开发板例程中都有它。也大多数使用的是模拟的IIC。
到这里了,不会还有人不会使用api去发送、接收蓝牙数据吧?不会吧不会吧!
广播包和数据包分析
使用Packet Sniffer工具进行抓包
抓包图:
广播包
显示数据标记为11个部分。
- Packet Sniffer抓包序列数,依次计数
- 抓取包的延时
- 广播包表示的是广播信道,数据包表示的是数据信道。
数据链路层的两种信道:
A:广播信道,提供给还没有建立连接的蓝牙设备提供发射广播、扫描、建立连接的信道。BLE有三个广播信道,37,38,39.广播设备分别在这三个信道各发送一次广播信号。传统蓝牙信道有16-32个,而BLE只有三个。因此BLE广播时间短。
B:数据信道:提供给已经建立蓝牙连接的主从端提供可靠的数据通信信道。BLE规定有37个。为加强通讯的可靠性,避开干扰。BLE设备通过自适应跳频的方式在37个信道上传。 - 接入地址:0xBE89BED6。所有BLE设备的广播帧都是使用的该地址,不分厂家
- PDU type是广播类型(2 BYTE)。
- 以ADV开头的帧表示为广播帧。是由advertiser发出的。有四种类型:
①ADV_IND:通用的可以建立连接的广播,nrf51822通常发此广播
②ADV_DIRECT_IND:快速广播。广播最长发射事件为1.28S。
③ADV_NONCONN_IND:不能建立连接的广播信号,ibeacon发此类广播 - ADV_SCAN_IND为扫描帧,由scanner发出。
- ADV_SCAN_REQ:为扫描请求帧,由scanner发出。只在scanner想从advertiser获取更多广播数据时发出。相应的,当ADV_SCAN_REQ被发出以后,advertiser会以SCAN_RSP作为回应
- SCAN_RSP:ADV_SCAN_REQ回应。
- CONNECT_REQ为scanner向advertiser发出
- 以ADV开头的帧表示为广播帧。是由advertiser发出的。有四种类型:
- Txadd Rxadd用来表示发送该广播帧的蓝牙设备的蓝牙地址类型。1表示random address,0表示为public address。
蓝牙协议规定,任何一个蓝牙设备必须拥有一个唯一的48bit的地址,用以标识身份。而且,在广播的时候,必须要把蓝牙地址广播出去。蓝牙地址有以下四种:
- public address:该地址是公司通过IEEE申请获得的,称为OUI。这个地址是固定地址,全球唯一,不可修改。用24bit表(LSB)示公司名,24bit(MSB)用来分配给不同的产品类型。
- random static address:是设备上电的时候随机生成或者芯片厂家在生产芯片的时候,随机烧录的不重复的48bit的蓝牙地址。nrf51822属于后者。该地址存放在FICR中,用户不可修改。最高位的后2bit必须位1。
- private Non-Resolvable address和Private Resolvable address不常用,mac地址设置与分析中介绍。
length表示PDU payload大小 - 蓝牙设备地址
- PDU payload
0E 09 4C 65 64 42 75 74 74 6F 6E 44 65 6D 6F为1组。0E代表本字段长度 09为LOCAL NAME,也就是蓝牙设备名称。可查ASCII表。表示该设备名称为LedButtonDemo.
03 19 34 12为1组,03为本字段长度:19为APPEARANCE(外观,例如BLE_APPEARANCE_HID_MOUSE就会在手机或者PC中显示有鼠标图案),因为蓝牙拉松数据是低位在前,所以3412其实为1234.0x1234转成10进制为4660.APPEARANCE这个AD TYPE是新添加的TYPE,所以在CORE 4.0核心协议中找不到
02 01 05为1组,02为字段长度,01是FLAGS 06表示设备只支持BLE,不支持传统蓝牙。05为04+01,见后图。 - CRC:校验
- RSSI:信号强度
- FCS:反馈
信道分区图:
广播协议包:
蓝牙广播FLAG:
数据包
ble数据包
Access address为接入 地址,接入地址由主设备提供。地址随机生成。但是遵从一定规律。不同于广播接入地址固定,具体规定查看蓝牙协议。
Direction连接方向。M为主机,S为从机
ACK status响应
Data Type数据类型
Data Header报头。
MESN:下一个预期序列号。SN:序列号。MD:更多数据,PDU-Length:PDU长度。没有为0
- 00:保留
- 11:链路层控制报文:用于管理连接
- 10:高级报文开始:可用于一个完整报文
- 01高层报文延续
LED读写数据包
读
写
写入01
mac地址分析与设置
BLE设备地址类型
一个BLE设备,可以使用两种类型的地址(一个BLE设备可同时具备两种地址):Public Device Address(公共设备地址)和Random Device Address(随机设备地址)。而Random Device Address又分为Static Device Address(静态设备地址)和Private Device Address(私密设备 地址)两类。其中Private Device Address又可以分为Non-resolvable private Address(不可解析私密地址)和Resolvable private Address(可解析私密地址)。
公共设备地址
在通信系统中,设备地址是用来唯一识别一个物理设备的,如TCP/IP网络中的MAC地址、传统蓝牙中的蓝牙地址等。对设备地址而言,一个重要的特性就是唯一性。对经典蓝牙(BR/EDR)来说,其设备地址是一个48bit的数字,称作“48-bit universal LAN MAC addresses”。正常情况下,该地址需要向IEEE申请(就是购买)。IEEE保证地址的唯一性。这种地址分配方式,在BLE中保留下来,就是公共设备地址。由24-bit的company_id和24bit的company_assigned组成(已在本文中介绍)。
随机设备地址
在BLE时代,公共设备地址不够用了,原因如下:
1:需要购买,不小开销
2:申请与管理是相当繁琐、复杂。维护成本增大
3: 安全因素,BLE很大一部分应用场景是广播通讯。意味只要设备地址,就可以获取所有信息。不安全。
因此BLE协议新增该地址。设备地址不是固定分配,而是设备启动后随机生成
静态设备地址
该地址是设备在上电随机生成的地址,nrf51822官方例程默认使用静态地址。
特征总结:
1:最高两位必须为11
2:剩余最后46位是一个随机数,不可全为0也不可全为1.
3:在一个上电周期内保持不变
4:下一次上电可以改变,但不是强制的。如果改变,上次保存的连接信息将会失效
应用场景总结:
1:46位随机数,可解决唯一性问题。相同概率很小
2:地址随机生成,可解决公共设备地址带来的费用和维护问题
私密设备地址
此地址更近一步,通过定时更新、地址加密方法,提高蓝牙地址的可靠性和安全性。根据地址是否解密,又分为两类。
不可解析私密地址
该地址会定时更新,更新周期由GAP规定,称作T_GAP,建议值是15分钟。
特征总结:
1:最高两位必须为00
2:剩余最后46位是一个随机数,不可全为0也不可全为1.
3:以T_GAP为周期定时更新。
自己有可能都不知道变成什么地址了,沙雕一样的方式,该地址类型不常用。
可解析私密地址
该地址比较有用,通过一个随机数和IRK密码生成,因此只可能被拥有相同IRK的设备扫描到,可以防止被位置设备扫描和跟踪。
格式如下:
特征:
A:由两部分组成,高位是随机数部分,最高两个bit为10,用于表示地址类型。低位时随机数和IRK经过hash运算得到的hash值。运算公式为hash=ah(IRK,prand)
B:当对端BLE设备扫描到该类型蓝牙地址后,会使用保存在本机的IRK,和该地址中的prand,进行同样的hash运算,并将运算结果和地址中的hash字段进行比较,相同的时候才进行后续操作。该过程称为解析。
C:以T_GAP为周期,定时更新,哪怕在广播、扫描、已连接过程中也可能改变。
D:不能单独使用,因此使用该类型的地址的话,设备需要同时具备公共设备地址或者静态设备地址中的一种。
地址配置
此时可以看广播包数据分析的第六部分和第七部分 ,如果是随机地址可通过查看地址最高两位来判断地址类型。
函数api:
SVCALL(SD_BLE_GAP_ADDRESS_SET, uint32_t, sd_ble_gap_address_set(uint8_t addr_cycle_mode, const ble_gap_addr_t *p_addr));
//addr_cycle_mode :
//①:BLE_GAP_ADDR_CYCLE_MODE_AUTO:该模式下,设置为可解析私密地址或者不可解析私密地址。协议栈内部会自动周期性根据结构体p_addr中的addr_type元素生成地址
//②:BLE_GAP_ADDR_CYCLE_MODE_NONE:该模式下,可以使用public地址和static random类型,addr中存放的最高两位必须为11,不然会被认为无效且自动替换。替换为蓝牙mac地址。和没有使用该函数一样。
默认状态:
官方demo是没有主动调用过该函数,都默认使用静态设备地址。是芯片出厂就设置的。
这两个视频应该是放反了
蓝牙信号rssi读取
rssi接收的信号强度指示。用于判断连接质量。以及是否增大广播发送强度。
为什么是负值
无线信号多为mW级别,对他进行了极化,转换为dbm而已。
公式:Rssi=10 * log(p)
将P带入即可。
API
/**@brief Start reporting the received signal strength to the application.
*
* A new event is reported whenever the RSSI value changes, until @ref sd_ble_gap_rssi_stop is called.
*
* @param[in] conn_handle Connection handle.
* @param[in] threshold_dbm Minimum change in dBm before triggering the @ref BLE_GAP_EVT_RSSI_CHANGED event. Events are disabled if threshold_dbm equals @ref BLE_GAP_RSSI_THRESHOLD_INVALID.
* @param[in] skip_count Number of RSSI samples with a change of threshold_dbm or more before sending a new @ref BLE_GAP_EVT_RSSI_CHANGED event.
*
* @retval ::NRF_SUCCESS Successfully activated RSSI reporting.
* @retval ::NRF_ERROR_INVALID_STATE Disconnection in progress. Invalid state to perform operation.
* @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied.
*/
SVCALL(SD_BLE_GAP_RSSI_START, uint32_t, sd_ble_gap_rssi_start(uint16_t conn_handle, uint8_t threshold_dbm, uint8_t skip_count));
//开始测量信号强度
/**@brief Get the received signal strength for the last connection event.
*
* @param[in] conn_handle Connection handle.
* @param[out] p_rssi Pointer to the location where the RSSI measurement shall be stored.
*
* @ref sd_ble_gap_rssi_start must be called to start reporting RSSI before using this function. @ref NRF_ERROR_NOT_FOUND
* will be returned until RSSI was sampled for the first time after calling @ref sd_ble_gap_rssi_start.
*
* @retval ::NRF_SUCCESS Successfully read the RSSI.
* @retval ::NRF_ERROR_NOT_FOUND No sample is available.
* @retval ::NRF_ERROR_INVALID_ADDR Invalid pointer supplied.
* @retval ::BLE_ERROR_INVALID_CONN_HANDLE Invalid connection handle supplied.
* @retval ::NRF_ERROR_INVALID_STATE RSSI reporting is not ongoing, or disconnection in progress.
*/
SVCALL(SD_BLE_GAP_RSSI_GET, uint32_t, sd_ble_gap_rssi_get(uint16_t conn_handle, int8_t *p_rssi));
//获取信号强度
蓝牙发射功率的设置
nrf51822可以设置9个发送等级分别是-40,-30,-20,-16,-12,-8,-4,0,4dBm。如果没有设置,则默认为0dbm。
1,广播初始化:设置广播的tx_power_level,用于初始化广播的发送功率。
#define TX_POWER_LEVEL 0
static void advertising_init(void){
int8_t tx_power_level=TX_POWER_LEVEL;
advdata.p_tx_power_level = &tx_power_level;
ble_advertisingOinit(&advdata,&scanrsp,&options,on_adv_evt,NULL);
}
2,GAP初始化:通过sd_ble_gap_tx_power_set设置。该函数可动态修改发送功率。
errcode = sd_ble_gap_tx_power_set(TX_POWER_LEVEL);
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!