零知ESP32——BLE Mesh蓝牙组网红外灯控系统
✔零知派(零知开源)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知开源编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
项目概述
本项目基于零知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) | 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,1、Node 1,2、Node 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
更多推荐
所有评论(0)