eefocus_4087784 发表于 2026-4-14 09:43:58

零知ESP32——BLE Mesh蓝牙组网红外灯控系统

​   ✔零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知开源编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!✔访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com




项目概述

本项目基于零知ESP32和BLE Mesh技术,实现了一款智能灯节点。手机通过nRF Mesh App可远程控制灯的开关与亮度,同时本地PIR人体感应器能自动开灯并延时关灯。系统采用PWM平滑渐变驱动LED,OLED实时显示配网状态、亮度及运动指示。项目模块化清晰,兼顾远程便捷与本地智能,适用于智能家居照明场景。
项目难点及解决方案

问题描述:手机控制与人体感应的互斥逻辑
解决方案:引入全局标志 s_pir_enabled 和接口 set_pir_enabled(),手机 “开灯” 或 “调节亮度到非零” 时,调用 set_pir_enabled(false) 禁用 PIR人体感应器。


一、系统接线部分1.1 硬件清单
                                                元器件                        型号                        数量                        说明               
                                                        主控板                        零知ESP32(ESP32-WROOM-32)                        5                        240MHz双核,内置BLE 5.0               
                                        OLED显示屏                        SSD1306 0.96寸 128×64                        5                        I2C接口,3.3V供电               
                                        LED灯珠模块                        LED+限流电阻模块                        5                        内置限流电阻               
                                        数据线                        USB Type-A to Micro-USB                        1                        烧录用               
                                        杜邦线                        公对母/母对母                        若干                        连接用               
                                        手机                        iOS/Android                        1                        安装nRF Mesh App               
                                        人体感应模块                        HC-SR505 人体感应模块                        1                        检测人体或动物的移动热源               
       

1.2 接线方案表
以下引脚定义严格依据 project_config.h 中的宏定义,三台设备接线完全相同。

                                                模块                        模块引脚                        ESP32引脚                        说明               
                                                        OLED SSD1306                        VCC                        3.3V                        注意:只能接3.3V               
                                        OLED SSD1306                        GND                        GND                        接地               
                                        OLED SSD1306                        SDA                        GPIO 21                        I2C数据,对应OLED_SDA_GPIO               
                                        OLED SSD1306                        SCL                        GPIO 22                        I2C时钟,对应OLED_SCL_GPIO               
                                        LED模块                        IN/SIG                        GPIO 5                        PWM控制,对应LED_GPIO               
                                        LED模块                        VCC                        3.3V                        LED模块用3.3V供电               
                                        LED模块                        GND                        GND                        接地               
                                        人体感应模块                        VCC                        5V                        默认工作电压4.5~20V               
                                        人体感应模块                        OUT                        GPIO 13                        输出引脚,对应PIR_GPIO               
                                        人体感应模块                        GND                        GND                        接地               
       
1.3 具体接线图


OLED VCC务必接3.3V引脚,ESP32的5V引脚会损坏OLED、GPIO 21和GPIO 22之间无需外接上拉电阻;人体感应模块的正极+不能接3.3V,低于工作电压,模块输出引脚数据不正常。

1.4 接线实物图


二、核心代码讲解本项目代码基于上篇文章新增了人体传感控灯的功能,核心代码仅针对新添加部分做讲解,更多可以回顾上篇文章
2.1 人体感应器初始化
初始化人体感应模块,配置引脚、触发电平和防抖延迟时间等

esp_err_t pir_sensor_init(int gpio_num, uint8_t trigger_level,                           uint32_t debounce_ms, uint32_t hold_ms){    if (gpio_num == GPIO_NUM_NC) return ESP_ERR_INVALID_ARG;    s_gpio_pin = gpio_num;    s_trigger_level = trigger_level;    s_debounce_ms = debounce_ms;    s_hold_ms = hold_ms;    // 配置 GPIO 输入    gpio_config_t io_conf = {      .pin_bit_mask = (1ULL << s_gpio_pin),      .mode = GPIO_MODE_INPUT,      .pull_up_en = GPIO_PULLUP_ENABLE,      .pull_down_en = GPIO_PULLDOWN_DISABLE,      .intr_type = GPIO_INTR_ANYEDGE,    };    esp_err_t ret = gpio_config(&io_conf);    if (ret != ESP_OK) return ret;    // 创建事件队列    s_event_queue = xQueueCreate(10, sizeof(uint32_t));    if (!s_event_queue) return ESP_ERR_NO_MEM;    // 安装 GPIO ISR 服务    ret = gpio_install_isr_service(0);    if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {      // ESP_ERR_INVALID_STATE 表示已经安装过,可以忽略      return ret;    }    // 添加中断处理    ret = gpio_isr_handler_add(s_gpio_pin, pir_isr_handler, (void*)s_gpio_pin);    if (ret != ESP_OK) return ret;    // 创建事件处理任务    xTaskCreate(pir_event_task, "pir_event", 4096, NULL, 10, NULL);    ESP_LOGI(TAG, "Initialized on GPIO %d, trigger level %d", s_gpio_pin, s_trigger_level);    return ESP_OK;}data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==


intr_type设置为GPIO_INTR_ANYEDGE(双边沿),如果是GPIO_INTR_POSEDGE 或者 GPIO_INTR_NEGEDGE,人离开后,不会触发下降沿中断,LED也不会熄灭

2.2 事件处理任务
处理 PIR红外人体感应传感器事件

static void pir_event_task(void *arg){    uint32_t io_num;    while (1) {      if (xQueueReceive(s_event_queue, &io_num, portMAX_DELAY)) {            int level = gpio_get_level((int)io_num);            bool triggered = (level == s_trigger_level);            if (triggered && !s_is_motion) {                // 检测到触发,先延时消抖                vTaskDelay(pdMS_TO_TICKS(s_debounce_ms));                if (gpio_get_level(s_gpio_pin) == s_trigger_level) {                  s_is_motion = true;                  ESP_LOGI(TAG, "Motion detected");                  if (s_motion_cb) s_motion_cb();                }            }            else if (!triggered && s_is_motion) {                // 信号消失,保持 hold_ms 时间再确认空闲                vTaskDelay(pdMS_TO_TICKS(s_hold_ms));                if (gpio_get_level(s_gpio_pin) != s_trigger_level) {                  s_is_motion = false;                  ESP_LOGI(TAG, "Idle");                  if (s_idle_cb) s_idle_cb();                }            }      }    }}data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==


对 PIR 传感器的信号进行消抖(s_debounce_ms)和保持时间判断(hold_ms),从而稳定地检测“有人移动”和“无人空闲状态”

2.3 蓝牙组网回调函数
static void mesh_generic_server_cb(      esp_ble_mesh_generic_server_cb_event_t event,      esp_ble_mesh_generic_server_cb_param_t *param){    if (event != ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT) return;    uint32_t op = param->ctx.recv_op;    /* OnOff Set */    if (op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET ||      op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) {      bool on = (param->value.state_change.onoff_set.onoff != 0);      g_local_led_on = on;      if (on) {            uint8_t bri = g_local_brightness ? g_local_brightness : 200;            g_local_brightness = bri;            led_pwm_set(true, bri);            set_pir_enabled(false);      } else {            led_pwm_set(false, 0);            set_pir_enabled(true);      }      ESP_LOGI(TAG, "OnOff SET: %s bri=%d, PIR %s",            on ? "ON" : "OFF", g_local_brightness,             on ? "disabled" : "enabled");    }    /* Level SET - absolute value (individual node slider) */    if (op == ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET ||      op == ESP_BLE_MESH_MODEL_OP_GEN_LEVEL_SET_UNACK) {      int16_t lv= param->value.state_change.level_set.level;      uint8_t bri = level_to_bri(lv);      g_local_brightness = bri;      g_local_led_on   = (bri > 0);      if (bri > 0)      {            led_pwm_set(g_local_led_on, bri);            set_pir_enabled(false);      }      else{            led_pwm_set(false, 0);            set_pir_enabled(true);      }      ESP_LOGI(TAG, "Level SET: lv=%d bri=%d (%d%%)", lv, bri, bri*100/255);    }}data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==




在 “开灯” 或者 “调整亮度(不为0)” 状态下设置set_pir_enabled(false),禁用 PIR 自动功能,在 “关灯” 时则打开

三、项目结果演示3.1 操作流程编译与烧录

①打开 main/project_config.h,将 #define NODE_ID 0修改为对应设备编号(0~2)​
②设置目标芯片,点击底部任务栏"Set Espressif Device Target"设置IDF_TARGET芯片为ESP32

③烧录并打开串口监视器,通过日志和操作数据进行调试


手机配网
①打开nRF Mesh App → Network → 右上角“+”扫描
通过UUID第三字节(00,01,02,03,04)识别节点,依次配网(选择No OOB)
②配网成功后,节点LED闪烁三次,OLED显示变为已配网状态,为每个节点的OnOff Server和Level Server绑定App Key 1



创建分组与订阅
在Groups界面创建 Node 0,1、Node 1,2、Node 0~2 三个分组,为每个节点的OnOff Server和Level Server订阅相应分组
功能控制

      单灯控制:在Network中选择节点,使用ON/OFF和Level滑块
      分组控制:在Groups中选择分组,使用ON/OFF和+/-按钮
      感应控制:模拟人体走动触发灯光的开关

3.2 视频演示
视频链接本视频展示了 基于ESP32三节点的BLE MESH 红外灯控系统 的操作流程。包括:三台ESP32在nRF Mesh iOS App的配网全过程、分组控制下的LED同步开关与PWM渐变调光效果,OLED屏显示以及模拟人物走动触发灯的开关。



四、常见问题解答(FAQ)
Q1:初始化后灯光亮起后不熄灭?A:确认 “人体感应器” 的供电电压是否大于4.5V,少于这个电压会导致传感器的引脚输出异常;另外,查看代码中的传感器的事件处理是否设置为GPIO_INTR_ANYEDGE(双边沿),如果不是,当传感器引脚输出为 0 时,并不会触发“关灯”操作
Q2:手机App无法扫描到设备?A:①确认ESP32已上电且串口日志显示 Open nRF Mesh App -> Scanner -> find ESP-BLE-MESH;②iOS需要在系统设置中开启nRF Mesh的蓝牙权限;③若该设备已被配网,需要先在App中将其重置(Reset Node)才能重新扫描到。
项目资源整合
ESP-BLE-MESH 架构:      ble-mesh-architecture
BLE Mesh API:                  bluetooth/esp-ble-mesh

页: [1]
查看完整版本: 零知ESP32——BLE Mesh蓝牙组网红外灯控系统