Netseye 发表于 2024-10-15 16:48

【2024 DigiKey创意大赛】【智能家居控制中心】【EP03】ESP32-S3-LCD-EV + BME680 BLE

<p>本次实现通过ESP32 BLE 来实现链接米家的温湿度计.esp32 存在2个蓝牙协议栈. Bluedroid - Dual-mode和 NimBLE - BLE only二选一. 最早使用了Bluedroid来实现这个功能.集成过程中发现esp-rainmarker 使用的是NimBLE,又不是一波令人窒息的操作...</p>

<h5>NimBLE: <a href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/nimble/index.html" target="_blank">https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/nimble/index.html</a></h5>

<h4>Bluedroid - Dual-mode: <a href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/bt_le.html" target="_blank">https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/bluetooth/bt_le.html</a></h4>

<h1>概念</h1>

<h3>Gap</h3>

<p>GAP 的广播工作流程</p>

<p>&nbsp;</p>

<div style="text-align: center;"></div>

<p>&nbsp;</p>

<p>&nbsp;&nbsp;GAP(<strong>Generic Access Profile</strong>,通用访问配置文件)是蓝牙协议栈中的一部分,负责定义蓝牙设备之间如何发现、连接和进行交互。GAP提供了设备连接和通信的基本框架。它主要包含以下几个方面:</p>

<h3>&nbsp;&nbsp;1. <strong>广播和扫描</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>广播(Advertising)</strong>:设备通过广播信道发送数据,其他设备可以扫描到这些广播数据。广播包含了设备的基本信息,例如名称、服务UUID等。</p>
        </li>
        <li data-list="bullet">
        <p><strong>扫描(Scanning)</strong>:设备监听广播信道以发现周围的蓝牙设备和其广播数据。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;2. <strong>连接</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>发起连接(Connection Initiation)</strong>:扫描设备可以发起与广播设备的连接,建立一条点对点的连接。</p>
        </li>
        <li data-list="bullet">
        <p><strong>连接参数(Connection Parameters)</strong>:GAP定义了在连接建立时如何设置连接参数,如连接间隔、超时时间等。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;3. <strong>角色</strong></h3>

<p>&nbsp;&nbsp;GAP规定了蓝牙设备可以扮演的四种角色:</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>广播者(Broadcaster)</strong>:只广播数据,不接收数据或发起连接。</p>
        </li>
        <li data-list="bullet">
        <p><strong>观察者(Observer)</strong>:只扫描广播数据,不发起连接。</p>
        </li>
        <li data-list="bullet">
        <p><strong>主设备(Central)</strong>:扫描并发起连接,通常是主控设备(如手机)。</p>
        </li>
        <li data-list="bullet">
        <p><strong>从设备(</strong><strong>Peripheral</strong><strong>)</strong>:广播并接受连接请求,通常是外围设备(如传感器)。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;4. <strong>配对与安全</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>配对(Pairing)</strong>:GAP定义了蓝牙设备如何通过加密进行配对,确保通信的安全性。</p>
        </li>
        <li data-list="bullet">
        <p><strong>密钥分发(Key Distribution)</strong>:配对后,设备之间可以交换密钥用于加密通信。</p>
        </li>
        <li data-list="bullet">
        <p><strong>安全等级(Security Levels)</strong>:GAP支持不同的安全等级,从没有加密到加密和认证。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;5. <strong>设备名称和可见性</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>设备名称(Device Name)</strong>:GAP允许设备广播其名称,以便用户识别。</p>
        </li>
        <li data-list="bullet">
        <p><strong>可见性(Visibility)</strong>:设备可以配置为可见或不可见,影响它是否能够被其他设备发现。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;6. <strong>连接管理</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>连接建立与终止</strong>:GAP定义了设备如何建立和终止蓝牙连接。</p>
        </li>
        <li data-list="bullet">
        <p><strong>连接参数更新(Connection Parameter Update)</strong>:在连接过程中,设备可以协商更新连接参数以优化性能。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;7. <strong>扩展功能</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>扩展广告(Extended Advertising)</strong>:如你提到的,GAP标准中的一部分允许广播更多的数据,支持长时间的广播周期。</p>
        </li>
        <li data-list="bullet">
        <p><strong>周期性广告(Periodic Advertising)</strong>:用于周期性发送广播数据,适用于广播数据不频繁更新的场景。</p>
        </li>
</ul>

<p>&nbsp;&nbsp;GAP是设备发现、连接建立、连接管理、安全配对的基础层,支持BLE应用的各种交互方式。</p>

<h3>GATT</h3>

<p>(GATT服务端)和中心设备(GATT客户端)之间的数据交流流程,</p>

<div style="text-align: center;"></div>

<p>&nbsp;&nbsp;GATT(<strong>Generic Attribute Profile</strong>,通用属性配置文件)是蓝牙低功耗(BLE)协议栈中的一个重要部分,负责设备之间数据的组织、交换和传输。它基于属性(Attribute)的形式进行通信,定义了如何在已连接的设备之间通过服务和特征(Characteristics)进行数据传输。GATT的设计使得BLE设备能够以结构化的方式进行数据交互,主要包括以下内容:</p>

<h3>&nbsp;&nbsp;1. <strong>属性(Attribute)</strong></h3>

<p>&nbsp;&nbsp;属性是GATT通信的核心,表示设备内部的某一项数据,通常由一个16位或128位的UUID标识。属性包含三个关键元素:</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>句柄(Handle)</strong>:属性的唯一标识符,通常是一个16位的值,表示属性在设备上的位置。</p>
        </li>
        <li data-list="bullet">
        <p><strong>类型(Type)</strong>:属性的类型,通过UUID进行标识。例如,某属性可能是一个服务或特征。</p>
        </li>
        <li data-list="bullet">
        <p><strong>值(Value)</strong>:属性的实际数据,可以是任意数据类型。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;2. <strong>服务(Service)</strong></h3>

<p>&nbsp;&nbsp;服务是由一组相关的特征和属性组成的,代表一个逻辑功能。例如,心率服务可以包含多个特征来传输心率数据。</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>服务类型</strong>:</p>

        <ul>
                <li data-list="bullet">
                <p><strong>主要服务(Primary Service)</strong>:表示设备的主要功能服务。</p>
                </li>
                <li data-list="bullet">
                <p><strong>次要服务(Secondary Service)</strong>:依附于主要服务的辅助功能。</p>
                </li>
        </ul>
        </li>
</ul>

<h3>&nbsp;&nbsp;3. <strong>特征(Characteristic)</strong></h3>

<p>&nbsp;&nbsp;特征是GATT的核心数据单元,每个特征由一个值和可选的描述符组成。特征用于读写或通知数据传输。</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>特征值</strong><strong>(Characteristic Value)</strong>:特征的具体数据,可以被读取、写入或订阅。</p>
        </li>
        <li data-list="bullet">
        <p><strong>特征属性(Characteristic Properties)</strong>:定义特征支持的操作类型,如读取、写入、通知等。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;4. <strong>描述符(Descriptor)</strong></h3>

<p>&nbsp;&nbsp;描述符是特征的附加信息,描述特征的特定细节。例如,描述符可以定义特征值的单位或数据格式。</p>

<ul start="1">
        <li data-list="bullet">
        <p>常见的描述符包括:</p>

        <ul>
                <li data-list="bullet">
                <p><strong>客户端特征配置描述符(Client Characteristic Configuration Descriptor, CCCD)</strong>:用于配置通知或指示。</p>
                </li>
                <li data-list="bullet">
                <p><strong>特征用户描述符(Characteristic User Description, CUD)</strong>:对特征值的简要说明。</p>
                </li>
        </ul>
        </li>
</ul>

<h3>&nbsp;&nbsp;5. <strong>GATT 操作</strong></h3>

<p>&nbsp;&nbsp;GATT定义了设备如何通过服务和特征来交互数据,包括以下操作:</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>发现操作(Discovery Operations)</strong>:设备可以发现远程设备支持的服务、特征和描述符。这包括发现所有服务、发现特定特征、发现特征的描述符等。</p>
        </li>
        <li data-list="bullet">
        <p><strong>读取操作(Read Operations)</strong>:读取某个特征或描述符的值,通常主设备(Central)向从设备(Peripheral)发起。</p>
        </li>
        <li data-list="bullet">
        <p><strong>写入操作(Write Operations)</strong>:写入某个特征或描述符的值,可以是有响应的写入(Write with Response)或无响应的写入(Write without Response)。</p>
        </li>
        <li data-list="bullet">
        <p><strong>通知(Notification)和指示(Indication)</strong>:从设备可以主动向主设备发送特征值的变化。通知不需要确认,指示则需要主设备确认接收。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;6. <strong>GATT 服务协议栈</strong></h3>

<p>&nbsp;&nbsp;GATT作为一个协议栈分层,通常与其他蓝牙层次协同工作:</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>ATT</strong><strong>(Attribute Protocol)</strong>:GATT基于ATT协议构建,ATT提供了对设备属性进行读写的机制。GATT扩展了ATT的功能,加入了服务和特征的结构化数据模型。</p>
        </li>
        <li data-list="bullet">
        <p><strong>L2CAP(</strong><strong>Logical Link Control</strong><strong> and Adaptation Protocol)</strong>:ATT和GATT协议通过L2CAP层进行数据封装和传输。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;7. <strong>GATT 角色</strong></h3>

<p>&nbsp;&nbsp;GATT协议中,设备可以扮演以下两种角色:</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>GATT 服务器(Server)</strong>:保存数据并响应客户端的请求,通常是外围设备(如传感器)。</p>
        </li>
        <li data-list="bullet">
        <p><strong>GATT 客户端(Client)</strong>:发起请求来读取或写入数据,通常是主设备(如手机或平板)。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;8. <strong>标准化服务和特征</strong></h3>

<p>&nbsp;&nbsp;蓝牙组织定义了一系列标准化的服务和特征,用于特定应用场景。例如:</p>

<ul start="1">
        <li data-list="bullet">
        <p><strong>心率服务(Heart Rate Service)</strong>:用于传输心率数据。</p>
        </li>
        <li data-list="bullet">
        <p><strong>电池服务(Battery Service)</strong>:报告设备的电池电量。</p>
        </li>
        <li data-list="bullet">
        <p><strong>设备信息服务(Device Information Service, DIS)</strong>:提供设备的制造商信息、硬件版本等。</p>
        </li>
</ul>

<h3>&nbsp;&nbsp;9. <strong>长</strong><strong>特征值</strong></h3>

<p>&nbsp;&nbsp;GATT支持通过分块操作(Read Blob/Write Blob)来传输超出单个ATT帧限制的长特征值(通常大于20字节)。</p>

<h3>&nbsp;&nbsp;10. <strong>GATT 协议操作示例</strong></h3>

<ul start="1">
        <li data-list="bullet">
        <p><strong>连接后发现服务</strong>:在连接建立后,客户端通常会首先发现服务器支持的服务。</p>
        </li>
        <li data-list="bullet">
        <p><strong>读取心率数据</strong>:客户端可以读取&ldquo;心率特征&rdquo;值,获取实时心率。</p>
        </li>
        <li data-list="bullet">
        <p><strong>写入控制命令</strong>:客户端可以通过写入特定特征来控制设备的行为。</p>
        </li>
</ul>

<p><strong>GATT 结构</strong></p>

<p>GATT事务是建立在嵌套的Profiles,Services和Characteristics之上的,如下如所示:</p>

<p></p>

<p>&nbsp;&nbsp;总结来说,GATT负责组织和传输设备之间的属性数据,通过服务和特征的形式,使BLE设备能够以标准化的方式进行数据交互。</p>

<p>&nbsp;</p>

<h1>实践</h1>

<p>首先我们需要通过GAP协议进行扫描和链接,在ble 5.0以上存在着<strong>Advertising </strong>和<strong> Extended Advertising</strong>两种不同的方式.可以通过 <code class="hljs">idf.py menuconfig</code> 进行修改<code class="hljs">Enable extended advertising</code> esp32芯片的板子就不要尝试了,用不了.</p>

<pre>
<code>(Top) → Component config → Bluetooth → NimBLE Options → Enable BLE 5 feature
                                                      Espressif IoT Development Framework Configuration
[*] Enable 2M Phy
[*] Enable coded Phy
[ ] Enable extended advertising
(0) Maximum number of periodic advertising syncs
[ ] Enable GATT caching----</code></pre>

<h3>开启蓝牙</h3>

<pre>
<code class="language-cpp">void app_ble_init(void) {
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
      ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    ESP_ERROR_CHECK(nvs_flash_erase());
    ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

ret = nimble_port_init();
if (ret != ESP_OK) {
    ESP_LOGE(tag, "Failed to init nimble %d ", ret);
    return;
}

ESP_LOGI(tag, "init nimble success");

/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;

/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);

/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-blecent");
assert(rc == 0);

/* XXX Need to have template for store */
ble_store_config_init();

nimble_port_freertos_init(blecent_host_task);
}
</code></pre>

<h3>实现cb_event和开启扫描</h3>

<pre>
&nbsp;</pre>

<pre>
<code class="language-cpp">
/**
* Initiates the GAP general discovery procedure.
*/
static void blecent_scan(void) {
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;

/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &amp;own_addr_type);
if (rc != 0) {
    MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
    return;
}

/* Tell the controller to filter duplicates; we don't want to process
   * repeated advertisements from the same device.
   */
disc_params.filter_duplicates = 1;

/**
   * Perform a passive scan.I.e., don't send follow-up scan requests to
   * each advertiser.
   */
disc_params.passive = 0;

/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;

rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &amp;disc_params,
                  blecent_gap_event, NULL);
if (rc != 0) {
    MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n", rc);
}
}

static void blecent_on_reset(int reason) {
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}

static void blecent_on_sync(void) {
int rc;

/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);

/* Begin scanning for a peripheral to connect to. */
blecent_scan();
}

void blecent_host_task(void *param) {
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();

nimble_port_freertos_deinit();
}


/* This function showcases stack init and deinit procedure. */
static void stack_init_deinit(void) {
int rc;
while (1) {

    vTaskDelay(1000);

    ESP_LOGI(tag, "Deinit host");

    rc = nimble_port_stop();
    if (rc == 0) {
      nimble_port_deinit();
    } else {
      ESP_LOGI(tag, "Nimble port stop failed, rc = %d", rc);
      break;
    }

    vTaskDelay(1000);

    ESP_LOGI(tag, "Init host");

    rc = nimble_port_init();
    if (rc != ESP_OK) {
      ESP_LOGI(tag, "Failed to init nimble %d ", rc);
      break;
    }

    nimble_port_freertos_init(blecent_host_task);

    ESP_LOGI(tag, "Waiting for 1 second");
}
}</code></pre>

<h3>链接米家温湿度计</h3>

<h4>温度计定义</h4>

<pre>
&nbsp;</pre>

<pre>
<code class="language-cpp">/*** The UUID of the service containing the subscribable characterstic ***/
static const ble_uuid_t *remote_svc_uuid =
    BLE_UUID128_DECLARE(0xa6, 0xa3, 0x7d, 0x99, 0xf2, 0x6f, 0x1a, 0x8a, 0x0c,
                        0x4b, 0x0a, 0x7a, 0xb0, 0xcc, 0xe0, 0xeb);

/*** The UUID of the subscribable chatacteristic ***/
static const ble_uuid_t *remote_chr_uuid =
    BLE_UUID128_DECLARE(0xa6, 0xa3, 0x7d, 0x99, 0xf2, 0x6f, 0x1a, 0x8a, 0x0c,
                        0x4b, 0x0a, 0x7a, 0xc1, 0xcc, 0xe0, 0xeb);

static uint8_t target_addr = {0xE1, 0x0A, 0x04, 0x38, 0xC1, 0xA4};


typedef struct {
float temperature;
float humidity;
float voltage;
float battery;
} Result;

Result processBuffer(const uint8_t *buff) {
Result result = {0, 0, 0, 0};

// Temperature conversion
int16_t temp = (int16_t)(buff | (buff &lt;&lt; 8)); // Little-endian
result.temperature = temp / 100.0f;

// Humidity conversion
result.humidity = (float)buff;

// Voltage conversion
int16_t voltageRaw = (int16_t)(buff | (buff &lt;&lt; 8)); // Little-endian
result.voltage = voltageRaw / 1000.0f;

// Battery percentage calculation
result.battery = ((result.voltage - 2) / (3.261 - 2)) * 100;
result.battery = (result.battery &lt; 0) ? 0
                   : (result.battery &gt; 100)
                     ? 100
                     : result.battery; // Clamp between 0 and 100

return result;
}</code></pre>

<h4>BLE读取信息</h4>

<pre>
&nbsp;</pre>

<pre>
<code class="language-cpp">static int blecent_on_read(uint16_t conn_handle,
                           const struct ble_gatt_error *error,
                           struct ble_gatt_attr *attr, void *arg) {
MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error-&gt;status,
            conn_handle);
if (error-&gt;status == 0) {
    MODLOG_DFLT(INFO, " attr_handle=%d value=", attr-&gt;handle);
    print_mbuf(attr-&gt;om);

    Result result = processBuffer(attr-&gt;om-&gt;om_data);

    printf("Temperature: %.2f\n", result.temperature);
    printf("Humidity: %.2f\n", result.humidity);
    printf("Voltage: %.3f\n", result.voltage);
    printf("Battery: %.2f%%\n", result.battery);
}
MODLOG_DFLT(INFO, "\n");

return 0;
}

static void blecent_read_write_subscribe(const struct peer *peer) {
const struct peer_chr *chr;
int rc;

/* Read the supported-new-alert-category characteristic. */
chr = peer_chr_find_uuid(
      peer,
      remote_svc_uuid, remote_chr_uuid);
if (chr == NULL) {
    MODLOG_DFLT(ERROR, "Error \n");
    goto err;
}

rc = ble_gattc_read(peer-&gt;conn_handle, chr-&gt;chr.val_handle, blecent_on_read,
                      NULL);
if (rc != 0) {
    MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n", rc);
    goto err;
}

return;
err:
/* Terminate the connection. */
ble_gap_terminate(peer-&gt;conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}


static void blecent_on_disc_complete(const struct peer *peer, int status,
                                     void *arg) {

if (status != 0) {
    /* Service discovery failed.Terminate the connection. */
    MODLOG_DFLT(ERROR,
                "Error: Service discovery failed; status=%d "
                "conn_handle=%d\n",
                status, peer-&gt;conn_handle);
    ble_gap_terminate(peer-&gt;conn_handle, BLE_ERR_REM_USER_CONN_TERM);
    return;
}

/* Service discovery has completed successfully.Now we have a complete
   * list of services, characteristics, and descriptors that the peer
   * supports.
   */
MODLOG_DFLT(INFO,
            "Service discovery complete; status=%d "
            "conn_handle=%d\n",
            status, peer-&gt;conn_handle);

/* Now perform three GATT procedures against the peer: read,
   * write, and subscribe to notifications for the ANS service.
   */
blecent_read_write_subscribe(peer);
}


static int blecent_gap_event(struct ble_gap_event *event, void *arg) {
struct ble_gap_conn_desc desc;
struct ble_hs_adv_fields fields;
#if MYNEWT_VAL(BLE_HCI_VS)
#if MYNEWT_VAL(BLE_POWER_CONTROL)
struct ble_gap_set_auto_pcl_params params;
#endif
#endif
int rc;

switch (event-&gt;type) {
case BLE_GAP_EVENT_DISC:
    rc = ble_hs_adv_parse_fields(&amp;fields, event-&gt;disc.data,
                                 event-&gt;disc.length_data);
    if (rc != 0) {
      return 0;
    }

    /* An advertisment report was received during GAP discovery. */
    print_adv_fields(&amp;fields);

    /* Try to connect to the advertiser if it looks interesting. */
    blecent_connect_if_interesting(&amp;event-&gt;disc);
    return 0;

case BLE_GAP_EVENT_EXT_DISC:
    /* An advertisment report was received during GAP discovery. */
    ext_print_adv_report(&amp;event-&gt;disc);

    blecent_connect_if_interesting(&amp;event-&gt;disc);
    return 0;

default:
    return 0;
}
}
</code></pre>

<p>结果:</p>

<div style="text-align: center;"></div>

<h2>优化</h2>

<p>目前提供的是代码核心部分和简单的测试逻辑.项目中会做成可以手动控制扫码设备.通过触摸屏点击对应的蓝牙设备进行连接.并支持多个设备连接.</p>

<h4>&nbsp;</h4>

Netseye 发表于 2024-10-15 17:08

<h3>后记</h3>

<p>关于如何发现米家蓝牙设备地址和关于idf中的数据大小端问题做个总结:</p>

<h3>如何发现米家设备:</h3>

<ol start="1">
        <li data-list="number">
        <p>https://github.com/rytilahti/python-miio</p>
        </li>
        <li data-list="number">
        <p>通过手机ble app 找名字<code>LYWSD03MMC</code>或者自己写一个工具</p>
        </li>
        <li data-list="number">
        <p>https://lywsd02mmc.bilaldurrani.com/类似的一些web端的ble工具</p>
        </li>
        <li data-list="number">
        <p>其他...就不列举了</p>
        </li>
</ol>

<h3>大小端转换问题:</h3>

<p>我这边写了一个小工具来实现</p>

<p>&nbsp;</p>

<pre>
<code class="language-cpp">#include &lt;stdint.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

void hexStringToByteArray(const char *hexString, uint8_t *byteArray) {
size_t len = strlen(hexString);
for (size_t i = 0; i &lt; len; i += 2) {
    sscanf(hexString + i, "%2hhx", &amp;byteArray);
}
}

void printByteArray(const uint8_t *byteArray) {
printf("BLE_UUID128_DECLARE(");
for (size_t i = 15; i &lt; 16; i--) {
    printf("0x%02x", byteArray);
    if (i &gt; 0) {
      printf(", ");
    }
}
printf(")\n");
}

int main() {
const char *hexStrings[] = {
    "ebe0ccb07a0a4b0c8a1a6ff2997da3a6",
    "ebe0ccc17a0a4b0c8a1a6ff2997da3a6"
};
uint8_t byteArray; // 32 hex digits = 16 bytes

for (size_t j = 0; j &lt; sizeof(hexStrings) / sizeof(hexStrings); j++) {
    hexStringToByteArray(hexStrings, byteArray);
    printf("Little-endian Byte array for %s: ", hexStrings);
    printByteArray(byteArray);
}

return 0;
}
</code></pre>

<p>&nbsp;</p>
页: [1]
查看完整版本: 【2024 DigiKey创意大赛】【智能家居控制中心】【EP03】ESP32-S3-LCD-EV + BME680 BLE