零知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]