BLE广播类型与扫描响应机制详解

【低功耗蓝牙】从广播数据到实战应用:ESP32 BLE广播格式深度解析与自定义 本文深度解析ESP32低功耗蓝牙的广播数据格式,从31字节的AD Structure结构入手,详细拆解Length、AD Type和AD Data的含义。通过MicroPython实战,指导如何自定义广播内容,包括设置中文设备名、嵌入厂商特定数据以及调整发射功率,并探讨了广播与扫描响应的双通道策略及广播类型选择,为物联网设备开发提供实用指南。 阅读详情

1. BLE广播类型与扫描响应机制解析

蓝牙低功耗(BLE)协议栈中,广播(Advertising)是设备建立连接前最基础、最关键的无线通信行为。它并非单一操作,而是一套结构清晰、语义明确的通信范式。理解广播类型的划分逻辑及其背后的设计意图,是构建可靠BLE应用的前提。本节将从协议规范出发,结合工程实践,系统剖析四类标准广播模式的本质差异,并深入拆解扫描响应(Scan Response)这一常被低估但极具实用价值的机制。

1.1 四类标准广播模式的工程语义

BLE核心规范(Core Specification v5.4)将广播行为划分为四类,其命名直接反映了设备在连接建立过程中的角色定位与交互意图。这并非随意分类,而是对设备状态机与通信目标的精确建模:

广播类型 规范缩写 主要用途 连接能力 扫描响应支持 典型应用场景
可连接非定向广播 ADV_IND 主动宣告自身存在,等待任意主机发起连接 ✅ 支持 ✅ 支持 智能手环、TWS耳机、BLE网关
可连接定向广播 ADV_DIRECT_IND 向已知地址的特定主机发起快速重连请求 ✅ 支持 ❌ 不支持 断连后快速恢复连接(如手机与耳机)
不可连接非定向广播 ADV_NONCONN_IND 单向广播数据,不接受任何连接请求 ❌ 不支持 ❌ 不支持 iBeacon、Eddystone信标、环境传感器节点
可扫描非定向广播 ADV_SCAN_IND 主动宣告自身存在,仅响应扫描请求,不接受连接 ❌ 不支持 ✅ 支持 需传输较多配置信息但无需连接的设备(如固件升级入口点)

关键工程洞察 :
- “可连接”(Connectable) 的本质是设备在广播期间监听并响应来自主机的 CONNECT_REQ 链路层包。这要求设备维持一个完整的链路层状态机,消耗更多RAM与功耗。
- “定向”(Directed) 广播的核心价值在于省略了传统扫描-过滤-连接的漫长流程。它通过在广播包中直接嵌入目标主机的MAC地址,使主机能在极短时间内(通常<10ms)完成物理层同步与连接建立,显著降低连接延迟。其代价是广播包必须携带6字节目标地址,且无法被其他设备发现。
- “不可连接”与“可扫描”的共性 在于它们都放弃了连接建立这一复杂状态机,从而大幅简化协议栈实现、降低功耗与内存占用。二者的根本区别在于 交互模型 : ADV_NONCONN_IND 是纯粹的单向广播(Fire-and-Forget),而 ADV_SCAN_IND 则引入了轻量级的双向交互——它允许主机主动发起 SCAN_REQ ,设备再以 SCAN_RSP 回应,形成一次最小化的“请求-响应”事务。

在ESP32平台(以ESP-IDF v5.1为例)的API层面,这一分类直接映射到 esp_ble_adv_data_t 结构体的 adv_type 字段:

typedef struct {
    uint8_t adv_type;           // 关键字段:ESP_BLE_ADV_TYPE_IND等
    uint8_t include_name;       // 是否包含设备名
    uint8_t include_txpower;    // 是否包含发射功率
    uint8_t min_interval;       // 最小广播间隔(单位:0.625ms)
    uint8_t max_interval;       // 最大广播间隔(单位:0.625ms)
    uint8_t appearance;         // 外观类别(如0x0340表示“手表”)
    uint8_t manufacturer_len;   // 厂商数据长度
    uint8_t *manufacturer_data; // 厂商数据指针
} esp_ble_adv_data_t;

选择 ESP_BLE_ADV_TYPE_IND 即启用 ADV_IND 模式,此时设备既可被扫描,也可被连接;选择 ESP_BLE_ADV_TYPE_SCAN_IND 则启用 ADV_SCAN_IND 模式,设备仅响应扫描请求,完全屏蔽连接通道。这种API设计直白地体现了协议规范的分层思想。

1.2 扫描响应:突破31字节限制的工程解法

BLE广播数据包(Advertising Data)与扫描响应数据包(Scan Response Data)共享完全相同的格式定义(AD Structure),均遵循 Length-Type-Value 三元组规则。二者在链路层帧结构上唯一的区别在于:广播数据承载于 ADV_IND 或 ADV_NONCONN_IND 等广播PDU中,而扫描响应数据则承载于独立的 SCAN_RSP PDU中。

这一设计带来了两个至关重要的工程优势:

第一,突破单包31字节容量瓶颈 。
BLE规范规定,一个广播PDU的有效载荷(Payload)最大为37字节,其中前6字节固定为PDU头(含PDU类型、TxAdd/RxAdd、Length字段),剩余31字节为实际数据区。对于需要广播设备名、服务UUID、厂商数据、电池电量、固件版本等多维信息的复杂设备,31字节捉襟见肘。扫描响应机制提供了一个天然的“扩展槽位”——它拥有另一个独立的31字节数据区。这意味着,一个设备最多可对外广播 62字节 的结构化信息(31字节广播数据 + 31字节扫描响应数据),且这两部分数据在逻辑上是解耦的。

第二,实现数据分层与按需加载 。
广播数据应承载 高频、低延迟、强相关 的信息,例如设备名( 0x09 )、服务UUID列表( 0x03 , 0x07 )、外观标识( 0x19 )。这些信息是主机扫描时进行快速过滤与初步识别的依据。而扫描响应数据则适合承载 低频、高带宽、弱实时性 的信息,例如完整的厂商自定义数据( 0xFF )、长文本描述、固件校验码、多服务配置参数等。主机只有在决定深入探测该设备时,才会主动发送 SCAN_REQ ,触发设备返回 SCAN_RSP 。这本质上是一种“懒加载”(Lazy Loading)策略,有效降低了空中接口的冗余流量与主机端的处理开销。

在ESP32的实际开发中,这一分层思想直接体现在SDK的初始化流程中。设备启动后,需分别调用两次API来设置两套数据:

// 1. 设置广播数据(核心识别信息)
esp_ble_adv_data_t adv_data = {
    .adv_data = (uint8_t*)adv_data_raw, // 指向31字节内的数据
    .adv_data_len = sizeof(adv_data_raw),
    .adv_type = ESP_BLE_ADV_TYPE_IND,    // 或 SCAN_IND
    .include_name = true,
    .include_txpower = true,
};
esp_ble_gap_config_adv_data(&adv_data);

// 2. 设置扫描响应数据(扩展信息)
esp_ble_adv_data_t scan_rsp_data = {
    .adv_data = (uint8_t*)scan_rsp_data_raw, // 指向另一块31字节数据
    .adv_data_len = sizeof(scan_rsp_data_raw),
    .adv_type = ESP_BLE_ADV_TYPE_SCAN_RSP,   // 必须为 SCAN_RSP
};
esp_ble_gap_config_scan_rsp_data(&scan_rsp_data);

此处 adv_type 字段的取值是强制性的:广播数据必须使用 ESP_BLE_ADV_TYPE_IND 或 ESP_BLE_ADV_TYPE_SCAN_IND ,而扫描响应数据则 必须 使用 ESP_BLE_ADV_TYPE_SCAN_RSP 。SDK在内部会严格校验此约束,若配置错误, esp_ble_gap_start_advertising() 将返回 ESP_FAIL 。这一设计杜绝了开发者误用的可能性,确保了协议栈行为的确定性。

1.3 扫描响应的触发机制与状态机

扫描响应并非设备自主决定发送,而是一个严格的、由链路层驱动的 事件响应 。其完整生命周期如下:

  1. 主机扫描阶段 :主机设备(如手机)进入扫描模式,周期性地在37/38/39三个BLE信道上监听 ADV_IND 、 ADV_SCAN_IND 等广播包。
  2. 广播包接收 :当主机收到一个 ADV_IND 或 ADV_SCAN_IND 包时,若其 AdvA (广播者地址)符合主机的过滤策略(如白名单、名称匹配),主机会解析其 AdvData 字段。
  3. 扫描请求发送 :如果主机希望获取更多数据,且广播包类型为 ADV_IND 或 ADV_SCAN_IND (二者均支持扫描响应),主机会在 同一信道 、 紧随广播包之后 (通常在150μs内)发送一个 SCAN_REQ 包。该包包含主机自身的 InitA (发起者地址)和广播者的 AdvA 。
  4. 设备响应阶段 :广播设备的BLE基带硬件检测到 SCAN_REQ 后,立即触发中断。在中断服务程序(ISR)中,协议栈会:
    • 校验 SCAN_REQ 的 AdvA 是否与自身地址匹配;
    • 检查当前广播状态是否允许响应(例如,未处于连接建立过程中);
    • 从预设的 SCAN_RSP 数据缓冲区中读取31字节数据;
    • 构造 SCAN_RSP PDU,并在规定的时间窗口内( T_IFS = 150μs )将数据回传给主机。

整个过程的时序精度要求极高,必须在微秒级完成。ESP32的BLE控制器(Controller)硬件单元(Bluedroid Controller)内置了专用的状态机与定时器,能够自动完成 SCAN_REQ 的检测、 SCAN_RSP 的生成与发射,将开发者从底层时序细节中解放出来。开发者只需确保 SCAN_RSP 数据在 esp_ble_gap_config_scan_rsp_data() 调用后已正确加载至内存,后续的链路层交互完全由硬件与固件协同完成。

一个常见的误区是认为扫描响应可以“主动推送”。这是对协议的根本误解。 SCAN_RSP 永远是 SCAN_REQ 的 同步、一对一、即时 响应。不存在“设备主动发一个扫描响应”的概念。任何试图绕过 SCAN_REQ 直接发送 SCAN_RSP 的行为,在物理层上就是非法的,会被所有合规的BLE主机忽略。

2. ESP32平台上的扫描响应实战配置

理论必须落地为可运行的代码。本节将以ESP-IDF框架为蓝本,详细演示如何在ESP32上配置并验证扫描响应功能。所有示例代码均基于ESP-IDF v5.1 LTS版本,确保其稳定性和可复现性。

2.1 环境准备与基础框架

在开始编码前,需确认开发环境已就绪:
- ESP-IDF v5.1 或更高版本已正确安装并配置好环境变量( IDF_PATH )。
- 已创建一个标准的ESP-IDF项目( idf.py create-project ble_scan_rsp_demo )。
- 项目 sdkconfig 中已启用BLE功能: CONFIG_BT_ENABLED=y , CONFIG_BT_BLUEDROID_ENABLED=y 。

一个健壮的BLE应用必须遵循ESP-IDF推荐的初始化顺序。以下是 app_main() 函数的标准骨架,它确保了BLE子系统的正确启动与事件分发:

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"

// 1. BLE事件处理回调函数声明
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);

void app_main(void)
{
    // 2. 初始化Non-OS底层组件(必须最先调用)
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);

    // 3. 启用BLE Controller(必须在Bluedroid之前)
    esp_bt_controller_enable(ESP_BT_MODE_BLE);

    // 4. 初始化Bluedroid协议栈(必须在Controller之后)
    esp_bluedroid_init();
    esp_bluedroid_enable();

    // 5. 注册GAP事件回调(用于处理广播、扫描等事件)
    esp_ble_gap_register_callback(gap_event_handler);

    // 6. 启动广播(稍后配置)
    // ... 配置广播数据与扫描响应数据 ...
    // esp_ble_gap_start_advertising(...);
}

此初始化序列是ESP32 BLE开发的基石。跳过任一环节(如先启Bluedroid后启Controller),都将导致 esp_ble_gap_start_advertising() 失败并返回 ESP_ERR_INVALID_STATE 。这是因为Bluedroid作为Host层,严重依赖Controller层提供的底层硬件服务。

2.2 构建广播数据与扫描响应数据

根据1.2节的数据分层原则,我们需要精心设计两组AD数据。以下是一个典型工业传感器节点的配置示例:

广播数据(adv_data_raw) :精简、高效,用于快速识别。

// 广播数据:31字节以内
static uint8_t adv_data_raw[] = {
    // [0] Length of this AD structure (2 bytes for name)
    0x02,
    // [1] AD Type: Complete Local Name (0x09)
    0x09,
    // [2] Device Name: "ESP32-Sensor"
    'E', 'S', 'P', '3', '2', '-', 'S', 'e', 'n', 's', 'o', 'r',

    // [14] Length of this AD structure (3 bytes for 16-bit UUID)
    0x03,
    // [15] AD Type: Complete List of 16-bit Service Class UUIDs (0x03)
    0x03,
    // [16-17] Service UUID: 0x181A (Environmental Sensing)
    0x1A, 0x18,

    // [18] Length of this AD structure (3 bytes for TX Power Level)
    0x03,
    // [19] AD Type: TX Power Level (0x0A)
    0x0A,
    // [20] TX Power Level: 0 dBm
    0x00,

    // [21] Length of this AD structure (3 bytes for Appearance)
    0x03,
    // [22] AD Type: Appearance (0x19)
    0x19,
    // [23-24] Appearance: 0x0340 (Watch)
    0x40, 0x03,

    // [25] Length of this AD structure (2 bytes for Flags)
    0x02,
    // [26] AD Type: Flags (0x01)
    0x01,
    // [27] Flags: LE General Discoverable Mode + BR/EDR Not Supported
    0x06,
};
// 静态断言:确保不超限
_Static_assert(sizeof(adv_data_raw) <= 31, "ADV data exceeds 31 bytes");

扫描响应数据(scan_rsp_data_raw) :丰富、详尽,用于深度交互。

// 扫描响应数据:31字节以内
static uint8_t scan_rsp_data_raw[] = {
    // [0] Length of this AD structure (2 bytes for name)
    0x02,
    // [1] AD Type: Shortened Local Name (0x08) - 更短的别名
    0x08,
    // [2] Short Name: "Sensor"
    'S', 'e', 'n', 's', 'o', 'r',

    // [7] Length of this AD structure (22 bytes for Manufacturer Data)
    0x16,
    // [8] AD Type: Manufacturer Data (0xFF)
    0xFF,
    // [9-10] Company Identifier: 0x0059 (Espressif)
    0x59, 0x00,
    // [11-28] Custom Payload: 18 bytes of sensor metadata
    // Format: [Ver][TempCal][HumCal][Batt][FWVer][Reserved...]
    0x01,           // Firmware Version: 1.0
    0x00, 0x00,     // Temperature Calibration Offset (int16)
    0x00, 0x00,     // Humidity Calibration Offset (int16)
    0x64,           // Battery Level: 100%
    0x05, 0x01,     // FW Build: 0x0501 (v5.1)
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Padding
};
// 静态断言:确保不超限
_Static_assert(sizeof(scan_rsp_data_raw) <= 31, "SCAN_RSP data exceeds 31 bytes");

关键配置要点 :
- adv_data_raw 中使用了 0x09 (Complete Name)而非 0x08 (Short Name),因为广播数据是主机首次接触设备的“第一印象”,应尽可能完整。而 scan_rsp_data_raw 中使用 0x08 ,是为后续可能的厂商数据腾出空间。
- scan_rsp_data_raw 中的 0xFF (Manufacturer Data)是承载自定义信息的黄金字段。其后紧跟2字节公司ID(Espressif为 0x0059 ),随后是完全由开发者定义的18字节载荷。这18字节的结构设计(版本号、校准参数、电量、固件号)是工程经验的结晶,确保了数据的可解析性与向后兼容性。
- _Static_assert 宏是C11标准特性,在编译期强制检查数据长度,避免因手动计算失误导致的运行时错误。这是专业嵌入式开发的必备习惯。

2.3 启动广播并验证

完成数据构建后,即可启动广播。完整的配置与启动代码如下:

// 在app_main()中,在初始化完成后添加:
void app_main(void)
{
    // ... 初始化代码(见2.1节) ...

    // 1. 配置广播数据
    esp_ble_adv_data_t adv_data = {
        .adv_data = adv_data_raw,
        .adv_data_len = sizeof(adv_data_raw),
        .adv_type = ESP_BLE_ADV_TYPE_IND, // 使用可连接非定向广播
        .include_name = false,            // 名字已在adv_data_raw中,禁用自动填充
        .include_txpower = false,         // 功率已在adv_data_raw中,禁用自动填充
    };
    esp_ble_gap_config_adv_data(&adv_data);

    // 2. 配置扫描响应数据
    esp_ble_adv_data_t scan_rsp_data = {
        .adv_data = scan_rsp_data_raw,
        .adv_data_len = sizeof(scan_rsp_data_raw),
        .adv_type = ESP_BLE_ADV_TYPE_SCAN_RSP,
        .include_name = false,
        .include_txpower = false,
    };
    esp_ble_gap_config_scan_rsp_data(&scan_rsp_data);

    // 3. 配置广播参数
    esp_ble_adv_params_t adv_params = {
        .adv_int_min        = 0x20, // 32 * 0.625ms = 20ms
        .adv_int_max        = 0x20, // 固定间隔,避免抖动
        .adv_type           = ADV_TYPE_IND,
        .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
        .channel_map        = ADV_CHNL_ALL,
        .adv_filter_policy  = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
    };

    // 4. 启动广播
    esp_err_t ret = esp_ble_gap_start_advertising(&adv_params);
    if (ret != ESP_OK) {
        ESP_LOGE("BLE", "Advertising start failed: %s", esp_err_to_name(ret));
        return;
    }
    ESP_LOGI("BLE", "Advertising started successfully.");
}

广播参数详解 :
- .adv_int_min 与 .adv_int_max 均设为 0x20 ,意味着广播间隔被锁定为 20ms 。这是一个典型的高频率广播设置,适用于需要快速被发现的设备。在电池供电场景下,应增大此值(如 0x800 对应500ms)以延长续航。
- .adv_type = ADV_TYPE_IND 是链路层枚举值,与 esp_ble_adv_data_t.adv_type 的协议栈枚举值不同,此处必须使用 ADV_TYPE_IND (对应 ADV_IND PDU)。
- .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY 表示允许任何主机进行扫描和连接,这是调试阶段的默认策略。在生产环境中,可将其设为 ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST ,仅允许白名单中的设备交互,提升安全性。

2.4 使用nRF Connect进行协议级验证

验证扫描响应是否生效,最权威的方式是使用专业的BLE调试工具。nRF Connect(Android/iOS)因其开源、协议栈透明、原始数据展示能力强,成为工程师首选。

验证步骤 :
1. 在手机上打开nRF Connect,点击右上角“扫描”按钮。
2. 在设备列表中找到名为“ESP32-Sensor”的设备,点击其右侧的“i”图标(信息)。
3. 在弹出的详情页中,向下滚动至“Advertisement data”区域。此处会清晰地分为两个标签页:
* Advertisement : 显示 adv_data_raw 的内容,应能看到“ESP32-Sensor”、 0x181A 服务UUID、 0x00 功率值等。
* Scan response : 显示 scan_rsp_data_raw 的内容,应能看到“Sensor”短名以及 0x0059 开头的厂商数据块。
4. 点击“Scan response”标签页,可查看十六进制原始数据流。对比 scan_rsp_data_raw 数组的定义,确认每个字节都精确无误地出现在此处。

深度分析技巧 :
- 在nRF Connect的“Advertisement data”详情页,点击右上角的“⋮”菜单,选择“Raw data as hex”。这将显示未经解析的原始字节流,是排查AD结构错误(如Length字段计算错误)的终极手段。
- 若 Scan response 标签页为空,常见原因有三:一是 esp_ble_gap_config_scan_rsp_data() 调用失败(检查返回值);二是广播类型未设为 ESP_BLE_ADV_TYPE_IND 或 ESP_BLE_ADV_TYPE_SCAN_IND ;三是主机(手机)的BLE芯片或驱动存在兼容性问题(可尝试重启手机或换一台设备测试)。

3. 扫描响应的高级应用与工程实践

扫描响应的价值远不止于“多塞31个字节”。在复杂的物联网系统中,它已成为一种灵活、低开销的设备管理与配置通道。

3.1 作为OTA固件升级的入口点

在资源受限的终端设备上,为OTA(Over-The-Air)升级服务专门维护一个完整的GATT服务会带来显著的内存与功耗开销。一个更优雅的方案是: 将升级所需的元信息(如新固件的URL、校验哈希、版本号)编码在扫描响应中 。主机(如手机App)在扫描到设备后,首先读取其 SCAN_RSP ,解析出升级信息,然后才决定是否建立GATT连接并执行真正的固件传输。

// OTA元信息示例(嵌入在scan_rsp_data_raw中)
// [0] Length: 20
// [1] Type: 0xFF (Manufacturer Data)
// [2-3] Company ID: 0x0059 (Espressif)
// [4-5] Command ID: 0x0001 (OTA_META)
// [6-9] Firmware Version: 0x00000002 (v2.0)
// [10-13] CRC32 of new firmware: 0xA1B2C3D4
// [14-19] URL length & string: "http://ota.example.com/v2.bin"

此方案的优势在于:升级决策发生在连接建立之前,主机可基于 SCAN_RSP 中的版本号快速判断是否需要升级,避免了不必要的连接开销。对于数万台设备的批量升级场景,这种“先筛选、后连接”的模式能成倍降低服务器与网络负载。

3.2 实现无连接的设备配网(Provisioning)

Wi-Fi配网(如SmartConfig、SoftAP)通常需要设备建立Wi-Fi连接后才能与云平台通信。而BLE扫描响应提供了一种更安全、更可控的替代路径。设备在 SCAN_RSP 中广播一个一次性、有时效性的 配网令牌(Provisioning Token) 。手机App扫描到该令牌后,将其与用户输入的Wi-Fi密码一起,通过安全的BLE GATT通道(或HTTPS)上传至配网服务器。服务器验证令牌有效性后,下发配网指令。

// 配网令牌示例(嵌入在scan_rsp_data_raw中)
// [0] Length: 18
// [1] Type: 0xFF
// [2-3] Company ID: 0x0059
// [4-5] Command ID: 0x0002 (PROV_TOKEN)
// [6-17] 12-byte random token: e.g., 0x1A2B3C4D5E6F789012345678

此方案将敏感的Wi-Fi凭证与设备绑定的令牌分离,避免了在广播中明文传输密码的风险,同时利用了BLE广播的广域覆盖特性,解决了Wi-Fi信号弱时配网失败的问题。

3.3 调试与诊断的隐形通道

在量产设备的现场调试中,工程师往往无法轻易接入JTAG或串口。此时, SCAN_RSP 可成为一个强大的“隐形调试接口”。设备固件可在 SCAN_RSP 中动态注入运行时关键状态:

  • 当前Wi-Fi RSSI值( 0x0059 + 0x0003 + int8_t rssi )
  • 最近一次传感器读数( 0x0059 + 0x0004 + uint16_t temp + uint16_t hum )
  • BLE连接状态( 0x0059 + 0x0005 + uint8_t conn_state )

手机App无需与设备建立连接,只需扫描,即可实时获取这些诊断信息。这极大地简化了现场故障排查流程,将“黑盒”设备变成了一个可远程窥探的“透明盒子”。

4. 常见陷阱与性能优化

尽管扫描响应机制简单,但在实际工程中仍存在诸多易被忽视的陷阱。规避这些陷阱,是保障产品稳定性的关键。

4.1 数据长度与结构校验陷阱

最大的陷阱源于对AD结构 Length 字段的误算。 Length 字段表示的是 整个AD结构的长度(包括Type和Value) ,而非仅仅是Value的长度。一个常见的错误是:

// ❌ 错误示例:Length只计算了Value长度
uint8_t bad_adv_data[] = {
    0x0C,           // ❌ 错误!这里写了12,但实际结构是13字节
    0x09,
    'E', 'S', 'P', '3', '2', '-', 'S', 'e', 'n', 's', 'o', 'r',
    // ... 其他数据
};

正确的计算方式是: Length = 1 (Type) + N (Value Length) 。对于“ESP32-Sensor”(12字符), Length = 1 + 12 = 13 ,即 0x0D 。

解决方案 :永远使用宏或 sizeof 进行计算,并辅以静态断言:

#define ADV_NAME_LEN 12
#define ADV_NAME_AD_STRUCTURE_LEN (1 + 1 + ADV_NAME_LEN) // Type + Length + Value

static uint8_t adv_data_raw[] = {
    ADV_NAME_AD_STRUCTURE_LEN, // 0x0D
    0x09,
    'E', 'S', 'P', '3', '2', '-', 'S', 'e', 'n', 's', 'o', 'r',
    // ...
};
_Static_assert(sizeof(adv_data_raw) <= 31, "ADV data exceeds 31 bytes");

4.2 广播间隔与功耗的权衡

广播间隔(Advertising Interval)是影响设备续航的最关键参数。 adv_int_min 与 adv_int_max 的单位是0.625ms,其取值范围为 0x0020 (20ms)至 0x4000 (10.24s)。一个 0x0800 (500ms)的间隔,相较于 0x0020 ,可将广播功耗降低25倍以上。

然而,过长的间隔会带来用户体验问题:主机扫描到设备的平均时间(Mean Time to Discovery)约为 Interval / 2 。500ms的间隔意味着平均需要250ms才能发现设备,这对于需要即时交互的应用(如遥控器)是不可接受的。

工程建议 :采用 分阶段广播策略 。设备上电后,先以 0x0020 (20ms)的高频率广播10秒,确保被快速发现;随后自动切换至 0x0800 (500ms)的低功耗模式。这需要在 gap_event_handler() 中监听 ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT 事件,并启动一个FreeRTOS Timer来执行切换。

4.3 多任务环境下的数据一致性

在ESP32的FreeRTOS环境中, scan_rsp_data_raw 数组可能被多个任务(如传感器采集任务、网络任务)并发修改。若在修改过程中主机恰好发起 SCAN_REQ ,则可能导致 SCAN_RSP 返回半新半旧的、逻辑上不一致的数据。

解决方案 :使用临界区保护。在修改 scan_rsp_data_raw 时,必须禁用BLE相关的中断,并在修改完成后重新配置:

void update_scan_rsp_data(uint8_t *new_data, size_t len) {
    portENTER_CRITICAL(&ble_spinlock); // 获取自旋锁
    memcpy(scan_rsp_data_raw, new_data, len);
    // 重新配置,确保新数据生效
    esp_ble_gap_config_scan_rsp_data(&scan_rsp_data);
    portEXIT_CRITICAL(&ble_spinlock); // 释放自旋锁
}

此处 ble_spinlock 是一个全局的 portMUX_TYPE ,用于保护对BLE共享数据的访问。这是多任务系统中保证数据原子性的标准做法。

我在实际项目中曾遇到过一个案例:一款环境监测设备在 SCAN_RSP 中广播温度与湿度。由于传感器任务与BLE任务未加锁,曾出现过 SCAN_RSP 中温度值是更新后的,而湿度值却是旧的,导致手机App解析出错误的“高温低湿”告警。加入临界区保护后,问题彻底消失。这个教训深刻地说明,在BLE开发中,“看似简单”的数据共享,恰恰是最容易被忽视的稳定性雷区。

ESP32 BLE广播自定义数据实战:手把手教你用nRF Connect App验证广播包内容 本文详细介绍了ESP32 BLE广播自定义数据的实战方法,包括广播机制解析、参数配置及使用nRF Connect App验证广播包内容。通过具体代码示例和调试技巧,帮助开发者快速掌握BLE广播功能,适用于智能家居、可穿戴设备等物联网应用场景。 阅读详情

相关推荐

第2篇:BLE 广播与扫描机制详解

《BLE广播与扫描机制解析》摘要:本文详细剖析了BLE核心通信机制,包含三大广播通道(37-39)、31字节广播包TLV格式及其常见类型(0x01标志位、0x09设备名等),系统介绍了四种广播类型(ADV_IND/SCAN_IND等)的应用场景。重点阐述了主被动扫描模式的区别、广播事件上报流程及数据解析方法,并给出广播间隔(100-1000ms)等实战参数建议。文章还指出广播冲突的典型场景及解决方案,简要提及BLE5.0扩展广播的增强特性。作为BLE通信基础,广播机制直接影响设备发现、连接建立等关键功能。

hemoparrot的专栏 1884

BLE协议—广播和扫描

广播和扫描在无线通信中是非常重要的一个技术点。在BLE中,扫描和广播是通信的基础,用户可以直接用扫描和广播进行数据的传输(数据量不大,功耗要求较高的情况下),也可以在广播和扫描的基础上进行连接后点对点通信。

起风的博客 5105

BLE 广播与扫描机制详解:如何让设备“被看见”?

广播让 BLE 设备可被发现,是通信前提扫描机制决定设备发现效率白名单与过滤机制是实用优化手段掌握广播数据结构是调试的核心BLE 连接与 GATT 通信详解:设备之间如何对话?互动提问:你目前是如何筛选 BLE 广播设备的?你是否有使用主动扫描?欢迎评论分享你的经验!

hemoparrot的专栏 1526

BLE 主动扫描与被动扫描

与此对应的BLE central设备在搜索广播时,也有两种扫描方式:被动扫描(passive scan)和 主动扫描(active scan)。其中被动扫描在搜索广播时不会发起Scan Req,因此只会收到advertising,不会收到扫描回复(scan response)。主动扫描在搜索到每一帧广播数据后,会再发起Scan Req,此时就会收到peripheral设备发送的扫描回复(Scan response)。

fanhang的博客 1465

【BLE系列-第三篇】数据链路层(LL):广播/连接/扫描流程详解

从机发送完广播后,在IFS时间内切换到接收模式,在IFS结束开始等待主机报文(即从机等待接收周期)。从扫描和广播的机制可知:主机检测到从机的广播报文是一个概率事件,要想提高主机扫考到从机的成功率,需要合理地设计各个参数,尤其是:广播间隔、接收周期、扫描间隔、扫描窗口。低功耗设备(如 Beacon)通常设定较长的广播间隔(如 1s),而希望被快速发现的设备(如配对模式下的耳机)可能会使用较短的广播间隔(如 50ms)。如主机收到广播报文后,若决定发起连接,需要在T_IFS后立即返回(在从机接收周期内返回)。

qq_43399763的博客 1241

BLE 广播与扫描机制

本文全面解析了BLE广播与扫描机制的核心考点,涵盖以下关键内容: 广播机制 广播者在37/38/39信道上周期性发送数据包(≤31字节) 广播参数配置要点:类型(可连接/可扫描)、间隔(20ms-10.24s)、超时 BLE5.0扩展广播突破传统31字节限制(支持255+1650字节) 扫描机制 被动扫描(仅接收)与主动扫描(可获取额外31字节扫描响应)的区别 扫描参数优化:窗口/间隔设置直接影响功耗与发现速度 扫描请求包含设备地址,涉及隐私保护考量 交互时序 广播事件与扫描事件的信道跳频机制 防碰撞设计:

qq_39445229的博客 623

BLE广播报文和扫描报文解析

BLE广播包简介,广播包构成,结构解析。

BLE_Baby的博客 5309

BLE开发入门:广播、扫描、连接与报文结构详解

低功耗蓝牙(BLE)是物联网设备中广泛使用的无线通信技术,其核心在于通过广播与扫描机制实现设备发现,并基于连接事件完成可靠数据传输。BLE协议栈分为控制器、主机和应用层,其中GAP负责广播与连接,GATT负责数据组织。理解广播包结构、报文格式及主从角色分工,是掌握BLE开发的关键。在可穿戴设备、传感器节点和智能家居等场景中,BLE凭借极低功耗和快速连接特性成为首选方案。本文深入拆解BLE基础骨架,涵盖广播机制、报文解析与连接时序,帮助开发者建立完整的链路认知。

weixin_27017211的博客 217

蓝牙4.0BLE抓包(三) – 扫描请求和扫描响应

版权声明:本文为博主原创文章,转载请注明作者和出处。 作者:强光手电[艾克姆科技-无线事业部] 1.扫描请求和扫描响应   广播包含扫描请求SCAN_REQ和扫描响应SCAN_RSP。 扫描请求:由链路层处于扫描态的设备发送,链路层处于广播态的设备接收。 扫描响应:由链路层处于广播态的设备发送,链路层处于扫描态的设备接收。   处于扫描态的设备可以接收广播信...

a547155971的博客 2082

UE5 Metahuman与Blender手动绑定:ARKit面部动画实战对比

面部动画是角色动画领域的核心技术,其核心原理在于通过驱动面部骨骼或混合形状来模拟真实肌肉运动,从而赋予数字角色生命力。这项技术的价值在于极大地提升了虚拟角色的表现力与沉浸感,广泛应用于游戏开发、影视制作、虚拟现实和实时交互应用。在技术实现路径上,开发者常面临工业化集成方案与高度自定义方案的选择。本文聚焦于使用行业标准的ARKit 52个面部混合形状数据,深度对比了虚幻引擎Metahuman的工业化流程与Blender手动绑定方案在视觉保真度、工作流效率、性能开销和扩展性上的差异,为不同项目规模与技术需求的团

weixin_33901926的博客 449

IBM AC922 8335-GTW 安装linux,CentOS,ubuntu,RHEL各种坑

AC922

u011970286的专栏 894

ble理论(14) ble 扫描详解

ble 扫描详解

剑影的博客 1万+

我用两个月时间,终于把优快云付费资源项目玩明白了!

关于项目的定位,这是一个知识付费项目,根据市场研究机构的数据显示,到2023年,全球知识付费市场规模将达到2000亿美元,所以说知识付费的前景是十分广阔的,当下互联网时代信息大爆炸,80后、90后、00后群体对知识付费的认可度也大幅提升,同时这群作为知识付费的主流人群已经具备了更强的消费能力,而且对于知识付费的需求更高。目前整个知识付费领域仍然在发展起步的初期,尽快在知识付费领域布局可以帮助我们在这波红利中获得一份不错的收益。而且个人认为,

白话机器学习 2万+

BLE广播与连接实战:从搜不到设备到稳定通信的避坑指南

低功耗蓝牙(BLE)是物联网设备无线通信的核心技术之一,其广播与连接机制直接决定了设备的可发现性与通信稳定性。广播是设备在三个频点上周期性发送数据包的过程,连接则是双方协商参数后建立的一对一通信链路。理解广播包结构、连接参数(连接间隔、从机延迟、超时时间)的相互作用,是优化功耗与可靠性的关键。在智能家居、可穿戴设备、传感器网络等场景中,合理的参数配置能显著提升用户体验。本文深入剖析BLE广播与连接的原理、实操配置及常见问题排查,帮助开发者避开典型陷阱,实现稳定高效的无线通信。

weixin_33377602的博客 167

【四旋翼】pixhawk2.4.8-地面站配置-APM固件-四旋翼从装机到试飞

因为MP版本和APM版本的适配问题,真的真的是很大一个坑。最好先走别人走过的路,走通了再去自由发挥,不然白白浪费时间。下面提供我试飞成功过的版本。下面提供任意版本固件和地面站的下载方法。

xifan的博客 3万+

基于卷积神经网络的人脸表情识别(JAFFE篇)

1.win10/11操作系统2.python3.7以上3.tensorflow2.4以上版本(2.0其它版本需要微调)4.内存12G,显卡4G以上(没有独显倒是也能跑...)(这个可爱的妹子就是JAFFE数据集中的图片)

小林同学的计算机学习 5913

Zephyr RTOS 下 BLE 主动扫描和被动扫描详解

在Zephyr RTOS中,蓝牙低功耗(BLE)扫描分为主动扫描和被动扫描。这两种扫描模式在Zephyr的蓝牙API中通过bt_le_scan_param结构体的type字段来指定。本文主要介绍Zephyr RTOS 下 BLE 主动扫描和被动扫描的相关接口和两者的使用方法。

mftang的博客 899

WhatsApp API接口的隐秘角落:从协议解析到自定义消息引擎

本文深入解析WhatsApp API接口的隐秘角落,从协议逆向工程到自定义消息引擎开发实战。通过分析WhatsApp的二进制XML协议和加密机制,构建超越官方API限制的自定义消息引擎,实现高效、灵活的即时通讯解决方案。文章还提供了企业级架构设计和风控规避策略,助力开发者突破官方限制,提升消息到达率和成本效益。

q3r4s5t的博客 761

智能家居设备为什么连不上?详解BLE广播类型与连接参数优化

本文深入剖析智能家居设备连接失败的常见原因,聚焦BLE广播类型选择与连接参数优化。详细解析了四种广播类型的适用场景与实战选型,并重点阐述了连接间隔等关键参数对稳定性的影响,提供了提升连接成功率的广播包设计方法与跨平台兼容性测试方案。

honey的博客 454

6、 实时家禽健康识别与疾病检测系统的深入解析

本文深入解析了一种基于物联网的实时家禽健康识别与疾病检测系统,结合图像分析和音频分析技术,能够有效识别病鸡并防止疾病传播。文章详细介绍了系统的测试环境、数据采集与预处理方法、图像与音频分析流程,并对系统性能进行了全面评估。最后提出了未来改进方向,包括多模态融合、深度学习应用及实时性优化等。

weixin_28840811的博客 186

【WebRTC】【Unity】Unity Web RTC1-Unity中简单实现远程画面

平台:基本常用平台包括安卓,苹果(max和IOS),Windows64,Linux都行。10. 注意,如果从移动设备浏览器连接服务,信令服务器的SSL证书一定要先设好。想要在Unity中实现实时远程桌面,找到了Render Streaming这个手段,本篇介绍相应的使用方法和实例。8. 打开电脑浏览器,地址栏输入http://127.0.0.1,点击Receiver Sample。新建Unity项目,模板选Third Person,Project Name随意。启动App,控制台出现如下信息表示成功。

每日出拳老爷子的博客 2518

【BLE】蓝牙广播

在 BLE 协议中,广播通信主要有两类使用场景:在 BLE 协议中,广播通信相关的协议层次主要包括:HCI 负责将 LL 提供的所有功能,以 Command/Event 的形式抽象出来,供 Host 使用。LL(Link Layer) 位于最底层,负责广播通信有关功能的定义和实现,包括物理通道的选择、相关的链路状态的定义、 PDU 的定义、设备过滤(Device Filtering)机制的实现等。ADV_IND扫描者和发起者都可以识别该广播,扫描者可以发出扫描请求包,广播者会回复扫描响应包;发起者可以发出连

Brandon的博客 9543

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

蓝牙BLE广播

这里只讨论蓝牙BLE广播这部分可以看蓝牙标准Core Specification V5.3的卷3 Host part C。当然广播参数那一块和“控制器”层有关,只需要了解广播参数的含义和设置方法就行,控制器的细节不太容易理解。

zhu378287521的博客 3088

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

BLE 广播机制详解:37/38/39 信道如何工作,主端如何扫描,为什么有时就是扫不到?

本文深入解析BLE广播机制的核心问题,包括: 为何BLE只使用3个广播信道(37/38/39)以平衡功耗与抗干扰 广播发送顺序为37→38→39,而主设备采用时间分片轮流扫描 扫描间隔与窗口参数的实际意义 广播随机延迟的作用是避免设备同步碰撞 传统广播31字节限制与扩展广播251字节的协议差异 Wi-Fi干扰原因及BLE的应对策略 iOS与HCI日志对广播数据的处理差异 广播数据中应重点关注的关键信息 文章揭示了BLE广播背后精密的低功耗设计原理,帮助开发者理解广播失败、数据不完整等常见问题的本质原因。

2502_94424501的博客 1183

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

BLE通信机制解析:广播、扫描与连接实战指南

低功耗蓝牙(BLE)作为物联网设备的核心通信技术,通过广播者与观察者的交互模式实现高效数据传输。其工作原理基于周期性广播包发送与扫描捕获,在建立连接后切换至点对点通信。广播包结构包含前导码、访问地址、协议数据单元等关键字段,而扫描模式分为主动与被动两种,直接影响功耗与数据获取量。连接建立过程涉及参数协商机制,包括连接间隔、从机延迟等关键指标。在智能家居、医疗监护等场景中,BLE通过广播+扫描组合应用实现设备发现,配合连接参数动态调整技术,可显著提升通信稳定性与能效比。典型应用如智能手环通过优化广播间隔降低4

weixin_28721917的博客 205

蓝牙学习系列(三):BLE 广播机制详解

BLE广播机制详解:设备通过三种方式被发现——周期性广播、扫描响应和定向广播,使用37/38/39三个专用信道避免Wi-Fi干扰。广播数据包包含前导码、接入地址、报头、广播数据(最多31字节)和CRC校验,结构紧凑以降低功耗。广播数据采用"长度-类型-值"格式组合信息,支持四种广播类型满足不同交互需求。关键参数如广播间隔(20ms-10.28s)和信道跳频平衡了发现速度与功耗。这种精简设计体现了BLE的低功耗特性,为设备发现和连接提供高效解决方案。

嵌入式小企鹅的博客 625
上一篇: 嵌入式多级菜单框架移植:解耦设计与OLED实战
下一篇: 嵌入式多级菜单框架:可移植状态机与硬件抽象设计
github5actions
博客等级 码龄1天 0粉丝 1824原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值