1. ESP32与BLE概览

各位同学,欢迎来到《ESP32低功耗蓝牙开发实战》的第一章。我是你们这门课的老朋友,一个在嵌入式无线领域摸爬滚打了十来年的工程师。今天咱们不聊虚的,直接上手,把ESP32这颗芯片、BLE 5.0的核心特性、开发环境,还有你的第一个蓝牙程序,一次性讲透。

1.1 ESP32芯片介绍

ESP32,乐鑫科技的明星产品。说实话,我第一次拿到这颗芯片的样片时,心里就一个想法:这玩意儿也太能打了。它集成了Wi-Fi和蓝牙,双核处理器,性能足够,价格还亲民。我当年做项目,为了同时搞定Wi-Fi和蓝牙,得用两颗芯片,布线麻烦得要死。ESP32直接把这事儿给简化了。

它的核心参数,我列个表,你一看就明白:

参数项 具体规格
处理器 Xtensa® 32-bit LX6 双核,主频 240MHz
内存 520KB SRAM + 4MB Flash(常见配置)
无线连接 Wi-Fi 802.11 b/g/n + 蓝牙 4.2/5.0
蓝牙特性 BLE + Classic Bluetooth,支持 Mesh
外设接口 SPI、I2C、UART、I2S、ADC、DAC、PWM、CAN
安全特性 硬件加密加速器(AES、SHA、RSA、ECC)
工作电压 2.3V ~ 3.6V
功耗 深度睡眠 5μA,BLE广播 80μA(典型)

你看这个功耗数据,深度睡眠才5微安。我有个项目做智能门锁,电池供电,用ESP32的深度睡眠加BLE低功耗广播,一节CR123A电池撑了两年多。嗯,这里要注意,低功耗不是光靠芯片,代码写得不好,功耗照样高。后面我们会专门讲怎么优化。

1.2 BLE 5.0核心特性

BLE 5.0,说白了就是蓝牙低功耗技术的第五个大版本。相比4.2,它提升了不少。我个人最看重的有三个点:

  • 4倍传输距离:理论最远能到400米(空旷环境)。我在一个仓库项目里实测过,隔了两堵墙,30米左右,数据包还能稳定收到。
  • 2倍传输速度:物理层速率从1Mbps提升到2Mbps。传固件升级包的时候,体验很明显。
  • 广播扩展:这个最实用。以前广播信道只有37/38/39三个,数据量小。现在可以在数据信道上发广播,数据包能到255字节。

为什么会这样?因为BLE 5.0引入了新的编码方式(Coded PHY),用更长的前导码来增强抗干扰能力。代价是速度会降下来,但距离上去了。你想想看,做智能家居,一个网关覆盖整栋楼,是不是很爽?

我画了一张图,帮你理清BLE 5.0的核心技术栈:

BLE 5.0 核心技术栈 应用层 (GATT/GAP) 主机层 (L2CAP / ATT / SM) 控制器层 (LL / PHY) 1M PHY | 2M PHY | Coded PHY (125k/500k) 广播扩展 (Advertising Ext.) 多角色 (Central/Peripheral) 💡 核心提升:距离×4 | 速度×2 | 广播容量×8 Coded PHY 用 125kbps 换 400米 传输距离

这张图把BLE 5.0的层次结构讲清楚了。应用层你写业务逻辑,主机层处理协议,控制器层管物理收发。广播扩展和多角色是5.0的特色,后面实战章节我们会反复用到。

1.3 ESP-IDF开发环境搭建

环境搭建,我估计是很多新手的第一道坎。别怕,跟着我的步骤来,十分钟搞定。

我个人习惯用Linux系统(Ubuntu 20.04/22.04),Windows用WSL也行。为什么?因为ESP-IDF在Linux下编译快,而且后面调试工具链支持更好。我曾经在Windows上折腾了半天,最后发现是路径空格的问题,气得我直接换了系统。

搭建步骤:

  1. 安装依赖:打开终端,运行 sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
  2. 获取ESP-IDF:git clone --recursive https://github.com/espressif/esp-idf.git(建议用v5.1或以上版本,对BLE 5.0支持更好)
  3. 安装工具链:进入esp-idf目录,运行 ./install.sh esp32
  4. 设置环境变量:. ./export.sh(每次打开新终端都要执行,或者加到.bashrc里)

💡 小技巧:如果你网络不好,可以用乐鑫的国内镜像。把 git clone 换成 git clone --recursive https://gitee.com/EspressifSystems/esp-idf.git,下载速度能快不少。

验证环境是否装好:运行 idf.py --version,如果看到版本号,恭喜你,环境成了。

1.4 第一个Hello World程序

好了,环境搭好了,咱们写个程序跑起来。这个程序很简单:ESP32启动后,通过蓝牙广播一个"Hello World"字符串。你用手机上的nRF Connect或者LightBlue就能扫描到它。

先创建工程:

idf.py create-project hello_ble
cd hello_ble

然后打开 main/hello_ble.c,写入以下代码:

#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"

static const char *TAG = "HELLO_BLE";

// 广播数据
static esp_ble_adv_data_t adv_data = {
    .set_scan_rsp = false,
    .include_name = true,
    .include_txpower = true,
    .min_interval = 0x20,
    .max_interval = 0x40,
    .appearance = 0x00,
    .manufacturer_len = 0,
    .p_manufacturer_data = NULL,
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = 0,
    .p_service_uuid = NULL,
    .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};

// 广播参数
static esp_ble_adv_params_t adv_params = {
    .adv_int_min = 0x100,
    .adv_int_max = 0x200,
    .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,
};

void app_main(void) {
    ESP_LOGI(TAG, "Hello BLE 程序启动");

    // 初始化NVS
    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);

    // 初始化蓝牙控制器
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "蓝牙控制器初始化失败: %s", esp_err_to_name(ret));
        return;
    }

    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "蓝牙控制器使能失败: %s", esp_err_to_name(ret));
        return;
    }

    // 初始化Bluedroid栈
    esp_bluedroid_status_t bt_state = esp_bluedroid_get_status();
    if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
        ESP_ERROR_CHECK(esp_bluedroid_init());
        ESP_ERROR_CHECK(esp_bluedroid_enable());
    }

    // 注册GAP回调
    ESP_ERROR_CHECK(esp_ble_gap_register_callback(NULL));

    // 设置设备名称
    ESP_ERROR_CHECK(esp_ble_gap_set_device_name("ESP32_Hello_BLE"));

    // 配置广播数据
    ESP_ERROR_CHECK(esp_ble_gap_config_adv_data(&adv_data));

    // 启动广播
    ESP_ERROR_CHECK(esp_ble_gap_start_advertising(&adv_params));

    ESP_LOGI(TAG, "广播已启动,设备名称: ESP32_Hello_BLE");
}

⚠️ 注意:这段代码里,我故意把GAP回调注册成了NULL。为什么?因为咱们第一个程序只做广播,不需要处理连接事件。但实际项目中,你一定要实现回调函数,否则设备无法响应扫描请求。后面章节我会带你补上。

编译和烧录:

idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

看到终端输出 I (xxx) HELLO_BLE: 广播已启动,设备名称: ESP32_Hello_BLE,就说明成功了。打开手机上的nRF Connect,扫描,你应该能看到一个叫"ESP32_Hello_BLE"的设备。

🎯 验证要点:

  • 手机能扫描到设备名称
  • 广播数据中包含Tx Power信息
  • 设备不可连接(因为我们没配连接参数)

嗯,到这里,你的第一个ESP32 BLE程序就跑起来了。别看它简单,广播是BLE通信的基础。后面所有的数据交互,都是从广播开始的。我曾经在一个项目中,就是因为广播参数配错了,导致手机死活搜不到设备,查了两天才发现是广播间隔设得太长。你想想看,这种坑,我帮你踩过了,你就别再踩了。