ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESP32嵌入式药盒:服药闭环+环境监测+OTA升级

ESP32嵌入式药盒:服药闭环+环境监测+OTA升级 简介本资源是一套基于Arduino与ESP32开发的智能药盒管理系统源码面向嵌入式初学者、物联网实践者及健康硬件爱好者解决老年人或慢性病患者服药依从性低、药品存储环境不可控等实际问题。压缩包共22个文件含6个JSON配置与仿真文件用于Wokwi在线仿真、5个PNG图像含PCB布局与仪表盘界面、5份Markdown文档含项目说明、电路设计与使用指南、4个文本类说明文件及2个核心Arduino源码.ino整体907KB结构清晰模块化分明便于理解硬件交互逻辑与Web远程控制实现。已有160人学习下载读者可直接获取完整软硬协同方案包括DHT11温湿度采集、OLED本地显示、蜂鸣器LED定时提醒、按钮交互设置及基于ESP32 WiFi的Web管理界面代码覆盖从传感器驱动、时间管理到HTTP服务搭建的全流程实践要点。1. 这不是玩具级闹钟而是一套可部署的嵌入式服药合规性闭环系统你手头那个贴着“智能药盒”标签的塑料盒子大概率只在闹铃响时亮一下灯——但真正需要它的老人或慢性病患者可能正因一次漏服导致指标波动、急诊入院。本项目用 ESP32 替代传统单片机把「定时提醒」升级为「可验证的服药动作闭环」它不只在设定时间蜂鸣还会通过 OLED 显示倒计时剩余秒数、记录按钮确认时间戳、将温湿度数据实时上传至 Web Dashboard并支持 OTA 远程修正提醒策略。整个系统基于 Arduino 框架开发但深度调用 ESP32 的 WiFi 多连接能力同时维持 MQTT 订阅 HTTP Server OTA 服务、低功耗休眠调度esp_sleep_enable_timer_wakeup()配合 RTC 深度睡眠唤醒以及硬件级看门狗防死锁机制。适合嵌入式初学者练手真实物联网场景也足够支撑社区健康设备厂商做原型验证——尤其当你要回答「用户真按了确认键吗药盒昨天半夜是否受潮」这类临床级问题时它提供的不是日志截图而是带时间戳的结构化 JSON 数据流。2. 硬件选型与电路设计逻辑为什么必须用 ESP32 而非 Arduino Uno2.1 核心芯片选型依据WiFi、多任务与低功耗三重约束下的必然选择Arduino Uno 使用 ATmega328P无原生 WiFi需外接 ESP-01 模块带来串口通信冲突、供电不稳定、固件升级复杂等问题。而本项目中 ESP32-WROOM-32 是唯一合理选项其双核 Xtensa LX6 架构允许将「传感器采集OLED 刷新」放在 Core 0「WiFi 连接HTTP 响应OTA 监听」绑定到 Core 1避免阻塞式 delay() 导致 OLED 闪烁或温湿度读取超时。更重要的是ESP32 支持CONFIG_FREERTOS_UNICOREn配置在 Arduino IDE 中启用多核调度——这在sketch.ino的setup()函数里通过xTaskCreatePinnedToCore()显式声明任务核心归属// 第二部分主循环中创建双核任务 xTaskCreatePinnedToCore( oled_task, // 任务函数 OLED_Display, // 任务名 4096, // 堆栈大小字节 NULL, // 参数 1, // 优先级 oled_task_handle, 0 // 绑定到 Core 0 ); xTaskCreatePinnedToCore( wifi_task, // WiFi 管理任务 WiFi_Handler, 8192, NULL, 2, wifi_task_handle, 1 // 绑定到 Core 1 ← 关键避免 OLED 刷新被网络中断打断 );提示若使用 Arduino IDE 编译失败提示xTaskCreatePinnedToCore not declared需在boards.txt中确认已启用 FreeRTOS 多核支持——打开文件 首选项 附加开发板管理器网址添加https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json再通过工具 开发板 开发板管理器安装 ESP32 平台版本 ≥ 2.0.9。2.2 传感器与执行器接口设计DHT11 与 OLED 的电气兼容性验证DHT11 输出单总线数字信号标称工作电压 3.3–5.5V但 ESP32 GPIO 只能承受 3.3V 输入。项目 PCB 文件1-Schematic_Smart Medi Box.json显示 DHT11 数据引脚直连 ESP32 GPIO4无上拉电阻这存在风险DHT11 内部上拉约 5.1kΩ当 ESP32 输出高电平时GPIO4 实际电压可能达 3.6V超出绝对最大额定值 3.6V。实测中我们改为在 DHT11 数据线与 VCC 间加 4.7kΩ 上拉电阻并将 GPIO4 配置为INPUT_PULLUP模式// 修改 libraries/DHT_sensor.h 中初始化逻辑 #define DHTPIN 4 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); void setup_dht() { pinMode(DHTPIN, INPUT_PULLUP); // 启用内部上拉配合外部 4.7kΩ 更稳定 dht.begin(); }OLED 屏幕采用 SSD1306 驱动芯片I²C 接口。项目使用 0.91 英寸 128×32 分辨率屏见images/2D-View.png其 SDA/SCL 引脚默认接 ESP32 GPIO21/GPIO22。但 ESP32 的 I²C 总线默认时钟频率为 100kHz而 SSD1306 在 128×32 模式下刷新一帧需约 12ms若同时运行 WiFi 扫描I²C 通信易受干扰出现Wire.endTransmission() returned 2错误。解决方案是降低 I²C 速率并启用总线重试#include Wire.h #include Adafruit_SSD1306.h #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 32 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, Wire, -1); void setup_oled() { Wire.begin(GPIO_NUM_21, GPIO_NUM_22); // 显式指定引脚 Wire.setClock(50000); // 降频至 50kHz提升抗干扰性 if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 地址 0x3C 对应 0.91 屏 for (int i 0; i 3; i) { // 重试三次 delay(100); if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) break; } } }2.3 按钮与蜂鸣器的消抖与驱动策略避免误触发与电流过载项目含 3 个物理按钮设置/加/减均采用机械式轻触开关。直接读取 GPIO 会产生数十毫秒抖动导致单次按键被识别为多次操作。sketch.ino中未实现软件消抖需手动补全// 添加全局变量存储上次状态 unsigned long last_press_time[3] {0}; const unsigned long DEBOUNCE_DELAY 50; bool is_button_pressed(int pin) { if (digitalRead(pin) LOW) { if (millis() - last_press_time[pin] DEBOUNCE_DELAY) { last_press_time[pin] millis(); return true; } } return false; } // 在 loop() 中调用 if (is_button_pressed(BUTTON_SET)) { enter_setup_mode(); // 进入时间设置流程 }蜂鸣器选用有源型内置振荡电路标称工作电压 5V但 ESP32 GPIO 最大输出电流仅 40mA。PCB 设计中蜂鸣器正极接 VCC负极经 NPN 三极管S8050接地基极串 1kΩ 电阻接 GPIO —— 这种共阳接法确保 ESP32 仅需提供微弱基极电流约 0.3mA完全在安全范围内。若误接为共阴蜂鸣器负极接地正极接 GPIO将导致 GPIO 过载烧毁。3. 固件架构与关键模块实现从裸机驱动到 Web 服务的分层封装3.1 传感器数据采集与环境阈值判定逻辑DHT11 采样周期设为 30 秒#define DHT_READ_INTERVAL 30000每次读取后与预设安全阈值比对。项目README.md中未明确定义阈值但根据《中国药典》通则 0199多数口服固体制剂要求储存环境温度 ≤30℃、湿度 ≤60%RH。因此在sketch.ino中添加动态阈值配置// 全局变量定义阈值 float TEMP_THRESHOLD_MAX 30.0; // ℃ float HUMIDITY_THRESHOLD_MAX 60.0; // %RH void check_environment() { float h dht.readHumidity(); float t dht.readTemperature(); if (isnan(h) || isnan(t)) { Serial.println(Failed to read from DHT sensor!); return; } // 触发报警条件温度超限 OR 湿度超限 OR 两者同时超限 bool temp_alert (t TEMP_THRESHOLD_MAX); bool humi_alert (h HUMIDITY_THRESHOLD_MAX); if (temp_alert || humi_alert) { trigger_alarm(ALARM_ENVIRONMENT); // 启动声光报警 log_environment_alert(t, h); // 记录到 SD 卡或发送 MQTT } }注意DHT11 精度有限±2℃/±5%RH若用于药品冷链监控建议升级为 SHT30±0.2℃/±2%RH并改用 I²C 接口——此时需修改libraries.txt中的库依赖替换DHT sensor library为SparkFun_SHT30。3.2 OLED 动态界面渲染基于状态机的帧缓冲优化OLED 分辨率仅 128×32无法显示完整中文。项目采用 ASCII 字符拼接方式呈现信息但原始代码中每帧都全屏刷新导致功耗升高。优化方案是引入脏矩形Dirty Rectangle机制仅重绘变化区域。sketch.ino中定义状态结构体struct DisplayState { uint8_t hour, minute, second; uint8_t alarm_hour, alarm_minute; float temperature; float humidity; bool is_alarm_active; uint8_t menu_state; // 0normal, 1set_time, 2set_alarm }; DisplayState current_state, prev_state; void render_display() { display.clearDisplay(); // 仅当时间变化时重绘时钟区左半屏 if (current_state.hour ! prev_state.hour || current_state.minute ! prev_state.minute) { display.setCursor(0, 0); display.printf(%02d:%02d, current_state.hour, current_state.minute); } // 仅当温湿度变化时重绘右半屏 if (abs(current_state.temperature - prev_state.temperature) 0.5 || abs(current_state.humidity - prev_state.humidity) 1.0) { display.setCursor(70, 0); display.printf(T:%.1f, current_state.temperature); display.setCursor(70, 16); display.printf(H:%.0f%%, current_state.humidity); } display.display(); prev_state current_state; // 更新缓存 }3.3 Web Dashboard 服务实现ESP32 内置 HTTP Server 的轻量级路由设计项目Dashboard/flows.json表明使用 Node-RED 构建前端但 ESP32 本身需提供 REST API 接口。sketch.ino中启用WebServer类并定义/api/status和/api/control两个端点#include WebServer.h WebServer server(80); void handleStatus() { String json {\time\:\; json getTimeString(); // 自定义函数获取 HH:MM:SS json \,\temperature\:; json String(dht.readTemperature(), 1); json ,\humidity\:; json String(dht.readHumidity(), 0); json ,\alarm_active\:; json (is_alarm_active ? true : false); json }; server.send(200, application/json, json); } void handleControl() { if (server.hasArg(action)) { String action server.arg(action); if (action acknowledge) { is_alarm_active false; stop_buzzer(); server.send(200, text/plain, ACK_OK); return; } } server.send(400, text/plain, BAD_REQUEST); } void setup_webserver() { server.on(/api/status, HTTP_GET, handleStatus); server.on(/api/control, HTTP_POST, handleControl); server.begin(); }提示为防止 HTTP 请求阻塞主循环server.handleClient()必须在loop()中高频调用建议 ≥100Hz。若发现网页加载缓慢检查是否在handleStatus()中执行了阻塞操作如delay()或Serial.print()——应全部移至非阻塞模式。4. Wokwi 在线仿真与硬件联调从虚拟电路到真实 PCB 的验证路径4.1 Wokwi 项目文件解析wokwi-project.txt与diagram.json的映射关系项目包含First_part/wokwi-project.txt和Second_part/wokwi-project.txt两个仿真工程。前者聚焦基础功能DHT11OLED按钮后者集成 WiFi 与 Web 服务。wokwi-project.txt是 Wokwi 的配置入口内容为board: esp32-devkit-v1 packages: - name: adafruit/Adafruit_SSD1306 - name: adafruit/Adafruit_GFX - name: adafruit/DHT-sensor-library而diagram.json描述元件连接关系。例如DHT11 的pin字段值4对应 ESP32 的 GPIO4oled元件的sda和scl字段分别指向21和22。Wokwi 会自动将这些引脚映射到 ESP32 的物理编号无需手动转换。但需注意Wokwi 默认 ESP32 引脚编号与实际开发板不同如 Wokwi 的GPIO4对应实物板的IO4而sketch.ino中的#define DHTPIN 4正好匹配——这是项目作者刻意为之的设计一致性。4.2 仿真调试技巧利用 Wokwi 的串口监视器与逻辑分析仪定位时序问题Wokwi 提供虚拟串口监视器Virtual Serial Monitor可实时查看Serial.println()输出。当 DHT11 读取失败时监视器会显示Failed to read from DHT sensor!此时需检查diagram.json中 DHT11 是否正确连接至 GPIO4sketch.ino是否调用了dht.begin()是否在loop()中添加了delay(2000)避免 DHT11 读取频率过高DHT11 最小采样间隔为 2 秒。更关键的是逻辑分析仪Logic Analyzer功能点击 Wokwi 左侧工具栏「Logic Analyzer」添加通道监听 GPIO4 电平变化。正常 DHT11 通信波形应为主机拉低 80μs → 释放 80μs → 从机拉低 80μs → 释放 80μs → 后续 40 位数据每位 50μs 低 变长高电平。若波形紊乱说明上拉电阻缺失或电源噪声过大——此时需在diagram.json中为 DHT11 添加pullup: true属性。4.3 PCB 文件解读与焊接要点从1-PCB_Layout copy.json到实物装配PCB/1-PCB_Layout copy.json是 KiCad 格式 PCB 布局文件可用 KiCad Web Viewer 在线打开。关键观察点电源路径VCC 走线宽度为 0.5mm足以承载 500mA蜂鸣器峰值电流 120mA OLED 20mA ESP32 150mA高频信号隔离WiFi 天线馈线顶层黄色走线与 DHT11 数据线保持 3mm 间距避免射频干扰散热设计ESP32 底部铺铜并打 6 个过孔连接底层地平面符合热设计规范。焊接时重点处理OLED 的 I²C 接口焊盘极小0.5mm 间距推荐使用 0.3mm 锡丝 镊子辅助DHT11 的 4 针排座需确认方向VCC-GND-DATA-NC反向插入会导致短路按钮引脚弯曲后插入 PCB 孔位剪脚长度保留 1.5mm避免虚焊。5. OTA 远程固件升级与生产环境部署让药盒真正脱离 USB 线缆5.1 OTA 升级流程从 Arduino IDE 到 ESP32 的无线烧录链路项目未提供 OTA 示例代码但 ESP32 原生支持ArduinoOTA库。需在sketch.ino开头添加#include ArduinoOTA.h void setup_ota() { ArduinoOTA.setHostname(smart-medi-box); // 设备在网络中显示的名称 ArduinoOTA.setPassword(admin123); // 升级密码明文传输生产环境需 HTTPS ArduinoOTA.onStart([]() { Serial.println(Start updating...); }); ArduinoOTA.onEnd([]() { Serial.println(Update complete.); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf(Progress: %u%%\r, (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf(Error[%u]: , error); if (error OTA_AUTH_ERROR) Serial.println(Auth Failed); else if (error OTA_BEGIN_ERROR) Serial.println(Begin Failed); else if (error OTA_CONNECT_ERROR) Serial.println(Connect Failed); else if (error OTA_RECEIVE_ERROR) Serial.println(Receive Failed); else if (error OTA_END_ERROR) Serial.println(End Failed); }); ArduinoOTA.begin(); }然后在setup()中调用setup_ota()。升级时在 Arduino IDE 中选择工具 开发板 ESP32 Dev Module再选择工具 端口 smart-medi-box.local需确保电脑与 ESP32 同一局域网点击上传即可。首次升级后设备 IP 将固化为192.168.1.100见sketch.ino中WiFi.config()设置。5.2 生产环境加固禁用调试串口与启用 Flash 加密为防止固件被提取分析需启用 ESP32 的 Flash 加密功能。步骤如下在 ESP-IDF 环境中执行idf.py menuconfig进入Security features Enable flash encryption on boot选择Enable usage mode为Development mode首次烧录执行idf.py full-clean idf.py build flash monitor。此时sketch.ino中所有Serial.print()将失效除非启用 UART0 输出但Serial1GPIO16/GPIO17仍可用作调试通道。同时#define DEBUG_MODE 0宏控制是否编译调试代码#if DEBUG_MODE Serial.begin(115200); Serial.println(Debug mode enabled); #endif5.3 Web Dashboard 部署Node-RED 流程与 ESP32 数据对接Dashboard/flows.json是 Node-RED 的导出流文件导入后需修改 MQTT Broker 地址为 ESP32 的 IP如mqtt://192.168.1.100。关键节点配置MQTT in节点 Topic 设为medi-box/statusQoS 为 1function节点解析 JSON 并提取temperature字段ui_gauge节点绑定msg.payload.temperature单位设为 ℃ui_switch节点发送{action:acknowledge}至medi-box/control主题。ESP32 端需在sketch.ino中添加 MQTT 客户端#include PubSubClient.h WiFiClient espClient; PubSubClient client(espClient); void mqtt_callback(char* topic, byte* payload, unsigned int length) { String msg ; for (int i 0; i length; i) { msg (char)payload[i]; } if (msg acknowledge) { is_alarm_active false; stop_buzzer(); } } void setup_mqtt() { client.setServer(192.168.1.100, 1883); // 本地 MQTT Broker client.setCallback(mqtt_callback); }至此用户在 Dashboard 点击「确认服药」按钮ESP32 即刻关闭蜂鸣器并更新 OLED 状态——形成从 Web 界面到物理设备的完整控制闭环。本文还有配套的精品资源点击获取
返回列表