(一)蓝牙低功耗(BLE)基础教程--基于nRF5x系列SOC

  本系列教程的目的是帮助读者了解nRF5x系列SOC和蓝牙低功耗(BLE)的基础知识。本篇将介绍蓝牙广播的相关知识,以一种简单有趣的方式向读者介绍蓝牙的基本功能,为读者进一步深入理解BLE相关知识提供了良好的开端。在这里我们不会提到BLE协议栈的具体细节,在进行具体实验之前会粗略介绍一下将要遇到的BLE基础概念。
  学习新的知识最有效的方法有两种,一是将新知识与已掌握的旧知识联系起来,类比交叉分析学习,另一种是直观地体验新知识,新事物,新感觉。
这里写图片描述

  如何在nRF5x系列SOC上使用BLE协议栈在此不做介绍,读者可以尝试官方提供的例程,进行更直观的体验。将某例程下载到SOC中后,可以打开智能手机的蓝牙或者使用nRF51 Dongle,搜索到该设备。蓝牙设备名称即为宏定义:#define DEVICE_NAME “HelloWorld” 中定义的蓝牙名称,如图即为HelloWorld。可以尝试修改名称,重新编译下载程序,再次搜索就会发现设备广播的蓝牙名称也跟着变化,设备名不能太长,太长会截断蓝牙名,只显示前面一部分。
  蓝牙设备地址即图中的0xFF83E39F68B5,类似于MAC地址,但不必确保其唯一性。接收信号强度标识(RSSI)即图中-43dBm。该值代表信号强度的好坏,将蓝牙设备与搜索设备远离或者用手盖住PCB上的蓝牙天线,会发现信号强度有所变化。
  展开上图中的HelloWorld蓝牙设备可以得到下图:
这里写图片描述
  在图中你可以看到,RSSI即上文提到的接收信号强度标识符;Address即上文提到的蓝牙设备地址;Address Type即蓝牙核心规范里定义的地址类型,包括Public address,Random Static address,Private Resolvable address和Private Non-Resolvable address,例程中的默认地址类型是Random Static address,即随机静态地址。在gap_params_init()函数里添加

ble_gap_addr_t gap_address;
gap_address.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE;
err_code = sd_ble_gap_address_set(BLE_GAP_ADDR_CYCLE_MODE_AUTO, &gap_address);
APP_ERROR_CHECK(err_code);// Check for errors

  这段代码将会设置地址类型为“Private Resolvable address”,即私有可分解地址。每次设备复位后,其蓝牙地址也会改变,该功能可以满足某些特定场合的安全性需求。
  简单来说,蓝牙设备有两种广播类型,即可连接和不可连接。如果只需要单向通讯,并广播一些数据,可以使用不可连接的广播形式,如利用蓝牙信标进行定位。另一方面,如果需要向特定设备发送大量数据,就需要采用可连接形式,如将心率传感器与智能手机相连。
  图中Bonded代表蓝牙设备是否与另一个设备绑定,绑定的目的是利用一个共有的连接密钥来为两个特定的设备建立连接。连接密钥是在绑定配对过程中创建的,并保存在两个设备中,以便后续识别通信。
  接下来介绍广播数据。在官方例程的advertising_init()函数中,有一个结构体变量advdata,可以用来配置将要发送的第一个广播数据包。该广播数据包中包含包含了设备名,以及定义的一些广播选项标志。这些标志是必需的,我们随后将展开讨论。

static void advertising_init(void)
{
    uint32_t      err_code;
    ble_advdata_t advdata;  // Struct containing advertising parameters

    // Build advertising data struct to pass into @ref ble_advertising_init.
    memset(&advdata, 0, sizeof(advdata));

    advdata.name_type               = BLE_ADVDATA_FULL_NAME;
    advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;

    ble_adv_modes_config_t options = {0};
    options.ble_adv_fast_enabled  = BLE_ADV_FAST_ENABLED;
    options.ble_adv_fast_interval = APP_ADV_INTERVAL;
    options.ble_adv_fast_timeout  = APP_ADV_TIMEOUT_IN_SECONDS;

    err_code = ble_advertising_init(&advdata, NULL, &options, on_adv_evt, NULL);
    APP_ERROR_CHECK(err_code);
}

  在例程中查看变量类型 ble_advdata_t 的定义,可以发现其包含了许多数据结构和选项,但是需要知道的是每个广播数据包不能超过31个字节,所以需要仔细查看要发送的数据。尝试改变广播名类型
advdata.name_type = BLE_ADVDATA_SHORT_NAME; // Use a shortened name
添加advdata.short_name_len = 6; // Advertise only first 6 letters of name,此时可以发现,当定义的蓝牙设备名超过6个字符时,首次连接设备时会发现其后的字符没有显示,当再次连接时可以在设备列表里看到整个蓝牙名。
  广播数据结构体 ble_advdata_t 里包含一个很有意思的区域,即p_manuf_specific_data,你可以存放任何你想存放的数据,如下面的代码所示:

static void advertising_init(void)
{
    uint32_t      err_code;
    ble_advdata_t advdata;  // Struct containing advertising parameters

    ble_advdata_manuf_data_t        manuf_data; // Variable to hold manufacturer specific data
    uint8_t data[]                      = "SomeData!"; // Our data to adverise
    manuf_data.company_identifier       = 0x0059; // Nordics company ID
    manuf_data.data.p_data              = data;     
    manuf_data.data.size                = sizeof(data);

    // Build advertising data struct to pass into @ref ble_advertising_init.
    memset(&advdata, 0, sizeof(advdata));

    advdata.name_type               = BLE_ADVDATA_SHORT_NAME;
    advdata.short_name_len          = 6;
    advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    advdata.p_manuf_specific_data   = &manuf_data;

    ble_adv_modes_config_t options = {0};
    options.ble_adv_fast_enabled  = BLE_ADV_FAST_ENABLED;
    options.ble_adv_fast_interval = APP_ADV_INTERVAL;
    options.ble_adv_fast_timeout  = APP_ADV_TIMEOUT_IN_SECONDS;

    err_code = ble_advertising_init(&advdata, NULL, &options, on_adv_evt, NULL);
    APP_ERROR_CHECK(err_code);
}

  编译并下载程序到SOC中,可以在搜索设备列表里找到对应的设备,可以发现 “ManufacturerSpecificData”,它包含一个十六进制的加密字符,前两个数是公司ID(0x0059),低位在前。公司ID是蓝牙SIG成员特有的唯一的十六位数。如果不是其成员,不得随意广播自己私制的公司ID。如果对 Bluetooth SIG member IDs 感兴趣可以自行查阅互联网。可以使用 0xFFFF 的ID为产品进行测试,但不能在最终发布的产品中使用,可以向蓝牙SIG申请特定的公司ID。ID之后的10个数据是实际的数据,即“SomeData!”的ASCII表示的数值,高位在前。最后的两个数字零代表字符串截止符号 ‘\0’ 。
  尝试改变”advdata” 结构体里的”p_tx_power_level” 并观察设备列表上的变化。”p_tx_power_level”表示了蓝牙设备的传输功率。利用该数字的大小可以让其他设备粗略估计与你的设备之间的距离。需要注意两点是”p_tx_power_level”的数值需要是既定的有效值,而且数值的更改并没有改变设备的实际传输功率,仅仅改变了广播的信息。
  尝试改变”advdata” 结构体里的 “include_appearance” ,使其等于true,找到 sd_ble_gap_appearance_set(0),将0改为“BLE_APPEARANCE_HID_MOUSE”,此时用智能手机搜索设备就可以看到类似下图的鼠标图标,这一特性对于手机apps的开发是非常有用的。
这里写图片描述
  如果想要广播超过31个字节,可以使用扫描响应数据。这种方法可以让扫描设备在检测到你的广播设备时,请求发送第二个广播数据包。在例程中的err_code = ble_advdata_set(&advdata, NULL)前,进行如下操作:
1.声明一个ble_advdata_manuf_data_t类型的变量manuf_data_response
2.与上一个广播数据包类似向manuf_data_response中加入广播数据
3.声明一个ble_advdata_t类型的变量advdata_response
4.设置advdata_response里的名称类型为BLE_ADVDATA_NO_NAME
5.在advdata_response中加入厂商特定数据
6.将err_code = ble_advertising_init(&advdata, NULL, &options, on_adv_evt, NULL); 改为 err_code = ble_advertising_init(&advdata, advdata_response, &options, on_adv_evt, NULL);
将程序编译下载到SOC中可以看到:
这里写图片描述

部分代码如下:

static void advertising_init(void)
{
    uint32_t      err_code;
    ble_advdata_t advdata;  // Struct containing advertising parameters

    ble_advdata_manuf_data_t        manuf_data; // Variable to hold manufacturer specific data
    uint8_t data[]                      = "SomeData!"; // Our data to adverise
    manuf_data.company_identifier       = 0x0059; // Nordics company ID
    manuf_data.data.p_data              = data;     
    manuf_data.data.size                = sizeof(data);

    // Build advertising data struct to pass into @ref ble_advertising_init.
    memset(&advdata, 0, sizeof(advdata));

    advdata.name_type               = BLE_ADVDATA_SHORT_NAME;
    advdata.short_name_len          = 3;
    advdata.flags                   = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
    advdata.p_manuf_specific_data   = &manuf_data;
    int8_t tx_power                 = -4;
    advdata.p_tx_power_level        = &tx_power;
    advdata.include_appearance      = true;

    // Prepare the scan response Manufacturer specific data packet
    ble_advdata_manuf_data_t                manuf_data_response;
    uint8_t                                 data_response[] = "Many_bytes_of_data"; // Remember there is a 0 terminator at the end of string
    manuf_data_response.company_identifier       = 0x0059;               
    manuf_data_response.data.p_data              = data_response;        
    manuf_data_response.data.size                = sizeof(data_response);

    ble_advdata_t   advdata_response;// Declare and populate a scan response packet

    // Always initialize all fields in structs to zero or you might get unexpected behaviour
    memset(&advdata_response, 0, sizeof(advdata_response));
    // Populate the scan response packet
    advdata_response.name_type               = BLE_ADVDATA_NO_NAME; 
    advdata_response.p_manuf_specific_data   = &manuf_data_response;

    ble_adv_modes_config_t options = {0};
    options.ble_adv_fast_enabled  = BLE_ADV_FAST_ENABLED;
    options.ble_adv_fast_interval = APP_ADV_INTERVAL;
    options.ble_adv_fast_timeout  = APP_ADV_TIMEOUT_IN_SECONDS;

    err_code = ble_advertising_init(&advdata, &advdata_response, &options, on_adv_evt, NULL);
    APP_ERROR_CHECK(err_code);
}

  到这里,你已经见识了蓝牙广播设备名称,各种广播选项,发送简单的数据等等,未完待续。。。

猜你喜欢

转载自blog.csdn.net/bi_jian/article/details/76037731