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

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、系统接线部分

1.1 硬件清单

1.2 接线方案表

1.3 具体接线图

1.4 接线实物图

二、核心代码讲解

2.1 人体感应器初始化

2.2 事件处理任务

2.3 蓝牙组网回调函数

三、项目结果演示

3.1 操作流程

3.2 视频演示

四、常见问题解答(FAQ)

Q1:初始化后灯光亮起后不熄灭?

Q2:手机App无法扫描到设备?


项目概述

        本项目基于零知ESP32BLE 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) 3 240MHz双核,内置BLE 5.0
OLED显示屏 SSD1306 0.96寸 128×64 3 I2C接口,3.3V供电
LED灯珠模块 LED+限流电阻模块 3 内置限流电阻
数据线 USB Type-A to Micro-USB 1 烧录用
杜邦线 公对母/母对母 若干 连接用
手机 iOS/Android 1 安装nRF Mesh App
人体感应模块 HC-SR505 人体感应模块 3 检测人体或动物的移动热源

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;
}

        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();
                }
            }
        }
    }
}

        对 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);
    }


}

        在 “开灯” 或者 “调整亮度(不为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,1Node 1,2Node 0~2 三个分组,为每个节点的OnOff Server和Level Server订阅相应分组

功能控制

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

3.2 视频演示

零知派-ESP32 BLE Mesh蓝牙组网红外灯控系统演示

本视频展示了 基于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

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐