蓝牙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(&param, &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工具进行抓包

抓包图:

img

广播包

显示数据标记为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发出
  • 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地址设置与分析中介绍。
    nrf51822采用的是Random static address。在启动的时候协议栈从FICR里读取作为设备的蓝牙地址。如果用户需要使用Public address则需要使用sd_ble_gap_address_set()这个函数重新设定蓝牙地址。
    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:反馈

信道分区图:

img

广播协议包:

img

蓝牙广播FLAG:

img

数据包

ble数据包

img

Access address为接入 地址,接入地址由主设备提供。地址随机生成。但是遵从一定规律。不同于广播接入地址固定,具体规定查看蓝牙协议。

Direction连接方向。M为主机,S为从机

ACK status响应

Data Type数据类型

Data Header报头。

​ MESN:下一个预期序列号。SN:序列号。MD:更多数据,PDU-Length:PDU长度。没有为0

img

  • 00:保留
  • 11:链路层控制报文:用于管理连接
  • 10:高级报文开始:可用于一个完整报文
  • 01高层报文延续

LED读写数据包

img

写入01

img

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的设备扫描到,可以防止被位置设备扫描和跟踪。

格式如下:

img

特征:

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);