1. 项目概述与设计初衷在嵌入式开发和物联网应用逐渐普及的今天我们身边有太多可以通过技术优化的小痛点。对我而言其中一个就是用药管理。无论是工作繁忙的上班族还是需要规律服药的家人忘记吃药是再常见不过的事。市面上的智能药盒要么功能复杂、价格昂贵要么依赖手机App而手机本身又是一个巨大的干扰源。我的目标很明确设计一个放在桌面上、足够简单、足够专注的设备它只做一件事——在正确的时间用最温和的方式提醒你该吃药了。这就是“Half Pill”智能药盒提醒器的由来。它基于Seeed Studio的XIAO ESP32-C3核心板搭配一块圆形的显示屏通过Wi-Fi接入家庭网络。整个设备没有复杂的按钮所有设置都通过一个优雅的手机网页完成。你只需要在浏览器里输入设备显示的IP地址就能添加、删除或查看服药计划。设备会通过NTP自动同步网络时间确保提醒准时所有数据都安全地保存在板载的EEPROM里即使断电也不会丢失。整个项目从3D建模、打印到代码编写、组装是一个典型的端到端物联网硬件开发实践非常适合想要入门嵌入式或智能硬件DIY的朋友。2. 核心硬件选型与原理剖析2.1 主控单元为什么是XIAO ESP32-C3在项目启动时主控芯片的选择是首要决策。我最终选择了Seeed Studio的XIAO ESP32-C3主要基于以下几点考量集成度与尺寸的平衡XIAO系列以其极致的紧凑尺寸著称ESP32-C3版本在指甲盖大小的空间内集成了ESP32-C3芯片、天线、USB-C接口和必要的电路。对于桌面提醒器这类需要精致外观的设备小巧的尺寸为外壳设计提供了巨大便利。相比之下标准的NodeMCU或D1 Mini开发板虽然常见但体积过大会破坏整体的美观性。性能与功耗的匹配ESP32-C3是一款基于RISC-V架构的单核Wi-Fi Bluetooth 5 (LE) SoC。对于本应用——驱动一块小屏幕、维护Wi-Fi连接、运行一个轻量级Web服务器、处理定时任务——它的性能绰绰有余。RISC-V架构在能效比上表现不错这对于长期插电使用的设备虽非首要但体现了技术选型的现代性。更重要的是它原生支持Wi-Fi这是实现网络时间同步和网页配置的基础。开发生态与成本ESP32系列拥有极其丰富的Arduino和ESP-IDF开发资源社区支持强大遇到问题容易找到解决方案。XIAO ESP32-C3完美兼容Arduino IDE大大降低了开发门槛。从成本看它是一款性价比极高的模块在实现所需功能的同时控制了项目的整体预算。注意XIAO ESP32-C3的IO口数量有限。在选择它时必须提前规划好所有需要连接的外设如屏幕、可能的蜂鸣器、传感器等确保引脚资源足够。本项目仅使用屏幕因此完全满足。2.2 显示单元圆形屏幕的交互哲学我选择了Seeed Studio专为XIAO设计的圆形显示屏。这个选择并非偶然而是交互设计的一部分。形式服务于功能药盒提醒的核心信息是“药名”和“时间”。圆形屏幕天然适合展示时钟、环形进度条等与时间相关的视觉元素。当提醒触发时屏幕中央可以醒目地显示药名周围用光晕或环形高亮作为视觉提示这种形式比长方屏更具亲和力和专注度。即插即用的便利性这块屏幕通过专用的连接器与XIAO底板对接无需焊接真正实现了插拔即用。它集成了显示驱动和电容触摸控制器虽然本项目未使用触摸功能节省了大量连接和调试屏幕底层驱动的时间让我能更专注于应用逻辑的开发。硬件兼容性保障使用同一品牌、专为彼此设计的核心板与屏幕最大程度避免了电源、信号电平不匹配或引脚定义冲突等问题。对于DIY项目这种“套件”式的兼容性能显著提高成功率减少在硬件连接阶段踩坑的可能性。2.3 数据持久化EEPROM的可靠性与局限设备需要存储用户设置的药名和服药时间且断电后不能丢失。这里没有使用外置的SD卡或Flash芯片而是利用了ESP32-C3内部自带的EEPROM模拟存储。工作原理ESP32的EEPROM实际上是在SPI Flash存储器中划出一块区域通过EEPROM库进行模拟读写。当你调用EEPROM.write()或EEPROM.put()时数据首先被写入微控制器的内存缓冲区只有在调用EEPROM.commit()后才会真正写入Flash。这个过程磨损的是Flash的特定扇区。容量与寿命考量ESP32-C3的EEPROM默认大小为4096字节4KB。对于存储20条记录每条记录包含药名假设最多20字符和时间信息如4字节的时间戳总需求远低于4KB完全足够。需要警惕的是Flash的擦写寿命通常为10万次。频繁地、无意义地调用commit()会加速损耗。因此在程序设计中我采用了“批量保存”策略用户在网页点击“配置”后所有数据一次性写入而非每次修改都保存极大地延长了存储寿命。实操心得首次使用EEPROM前最好在setup()函数中执行一次EEPROM.begin(size)来初始化指定大小。读取数据后建议进行简单的数据校验例如在存储时额外写入一个固定的“魔数”Magic Number读取时校验该魔数是否正确以此判断EEPROM中的数据是否有效或为初始乱码。3. 系统设计与软件架构详解3.1 整体工作流程与状态机设计设备的软件核心是一个清晰的状态机它定义了设备从启动到运行各个阶段的行为。理解这个状态机是理解整个项目逻辑的关键。状态一初始化与网络连接 (Boot Connect)设备上电后首先初始化串口用于调试、屏幕、并连接文件系统如果需要和EEPROM。随后它尝试连接预设的Wi-Fi网络。此阶段屏幕显示“Connecting to Wi-Fi…”之类的动态信息。连接成功后设备通过SNTP简单网络时间协议从NTP服务器获取当前时间并设置本地时钟。成功后屏幕会显示本机获取到的IP地址这是后续网页配置的入口。状态二Web服务器与配置模式 (Configuration Mode)设备启动一个异步Web服务器例如使用ESPAsyncWebServer库。当用户在浏览器中输入设备IP地址时服务器会返回一个HTML格式的配置页面。这个页面通过JavaScript与设备进行AJAX交互实现无刷新添加、删除、查看日程。用户点击“保存配置”后浏览器会将所有日程数据以JSON格式POST到设备的一个特定API端点。状态三运行与监控模式 (Running Mode)配置保存后设备进入主循环运行状态。程序会持续将当前时间与EEPROM中存储的所有服药时间进行比较。当匹配到某个提醒时间点时设备触发“提醒动作”——在当前设计中即改变屏幕显示内容以高亮方式展示药名。主循环会一直运行直到下一次通过网页修改配置并保存设备将重新加载日程数据。关键设计异步Web服务器我选择了异步Web服务器库而非传统的同步服务器如WiFiServer。原因是同步服务器在处理请求时会阻塞整个主循环这意味着在服务器响应网页请求的几百毫秒内设备无法检查时间、更新屏幕可能导致提醒延迟或错过。异步服务器则将所有网络事件放入队列在主循环中非阻塞地处理确保了时间监控任务的实时性。3.2 网页配置界面前后端数据交互配置界面是用户与硬件交互的桥梁其设计原则是简单、直观、无需安装额外App。前端实现 (HTML/JS) 我编写了一个单一的index.html文件包含一个表单用于输入药名和时间一个表格用于展示现有日程以及“添加”、“删除”、“保存”等按钮。这个文件被直接存储在ESP32的代码中通过PROGMEM方式存入当浏览器请求根路径时服务器将其发送出去。动态添加点击“添加”按钮JavaScript会在表格中动态插入一行新记录但此时并未发送到设备。数据暂存所有增删改操作仅在浏览器内存一个JavaScript数组中进行提供了良好的即时反馈。批量提交用户确认所有修改后点击“保存配置”JavaScript会将整个日程数组序列化为JSON字符串通过fetch()API的POST方法发送到设备的/configure端点。后端处理 (ESP32 C) 设备端的/configure端点处理POST请求。解析收到的JSON数据。进行基础校验如时间格式、数量是否超限20个。将数据转换为更节省空间的格式如将“14:30”字符串转换为一天中的分钟数。调用EEPROM.put()将数据存入缓冲区最后执行EEPROM.commit()写入Flash。返回一个成功的JSON响应给浏览器并自动重启或重载日程列表。优势这种设计将复杂的UI交互逻辑交给功能强大的浏览器处理ESP32只负责提供API和存储分工明确极大地减轻了微控制器的负担也使得界面可以做得更美观、响应更快。3.3 时间同步与定时检查机制精准的提醒依赖于精准的时间。NTP时间同步ESP32通过configTime()函数配置时区和NTP服务器地址如pool.ntp.org。连接Wi-Fi后它会自动在后台同步时间。我建议在代码中设置一个每24小时自动重新同步一次的软定时器以修正可能存在的微小时钟漂移。ESP32-C3的硬件RTC实时时钟在深度睡眠时精度尚可但在常态运行下依赖软件定时和NTP定期校准是更可靠的做法。定时检查算法 在主循环loop()中我使用millis()或getLocalTime()来获取当前时间。一个高效的做法是将EEPROM中存储的每个服药时间例如“08:00”“13:00”“20:00”转换为从午夜零点开始的分钟数如480, 780, 1200。同样将当前时间也转换为分钟数。int currentMinutes hour * 60 minute;然后遍历所有日程检查currentMinutes是否等于某个日程的分钟数。但这里有个细节如果程序在08:00:00检查了一次下一次循环可能在08:00:01就会错过。因此通常需要设置一个“提醒窗口期”例如当前时间与预定时间差在±1分钟内都视为触发提醒。触发后可以设置一个“已提醒”标志避免在同一分钟内重复触发屏幕刷新。注意事项避免在loop()中使用delay()。如果使用delay(1000)来每秒检查一次会阻塞整个程序导致网页请求无法响应。正确的做法是使用非阻塞的时间间隔检查例如记录上一次检查的时间戳当millis() - lastCheckTime 60000一分钟时才执行一次时间比对然后更新lastCheckTime。4. 硬件组装与外壳制作实战4.1 3D建模与结构设计要点我使用Autodesk Fusion 360进行外壳设计整个过程是典型的“由内而外”的逆向设计。第一步电子元件定位首先我将从Seeed官网下载的XIAO ESP32-C3和圆形显示屏的3D模型STEP或SLDPRT格式导入Fusion 360。在虚拟空间中我需要确定它们的相对位置主板固定设计支撑柱和螺丝孔M2规格来固定XIAO板。屏幕固定屏幕通常通过其PCB上的安装孔固定。需要设计对应的支柱和孔位M2或M3确保屏幕显示面与外壳前面板开口完美对齐。走线空间必须留出足够的空间容纳连接屏幕与主板的排线避免弯折过度或挤压。我通常在屏幕背面和主板之间预留5-10mm的空隙。天线安置ESP32-C3板载的PCB天线或外接天线需要远离金属部件和显示屏背板以确保信号质量。设计中需要为天线预留一个开阔的区域。第二步外壳体设计围绕定位好的电子元件绘制外壳的主体。分为前壳和后盖。前壳需要开一个精确的圆形视窗让屏幕露出。视窗边缘最好有一个微小的唇边从内部挡住屏幕边缘使外观更整洁。后盖需要为USB-C接口开槽。这里强烈推荐为90度弯头USB线设计一个通道或凹槽让线材可以优雅地引出而不是直角弯折影响美观和线材寿命。还需要设计散热孔虽然功耗不大以及用于固定到桌面支架的接口。合盖方式我采用了螺丝固定M3螺丝在四角设计螺丝柱。也可以考虑卡扣式但对3D打印的精度和材料韧性要求更高。第三步桌面支架设计一个可分离的桌面支架能提升使用体验。我设计了一个带有一定倾角的支架通过一个卡槽或单个螺丝与主机后盖连接。倾角例如15-20度需要经过测试确保在桌面上视线舒适。4.2 3D打印与后处理打印参数建议材料PLA增强PLA是不错的选择强度、精度和打印成功率都很好。PETG则更耐用、耐温。层高0.2mm层高可以在打印速度和表面光洁度之间取得良好平衡。对于显示窗口等关键部位可以尝试0.16mm以获得更光滑的边缘。填充率15%-20%的填充率足以提供足够的结构强度同时节省材料和时间。支撑对于外壳内部的螺丝柱、卡扣等悬空结构需要生成支撑。务必仔细检查切片预览确保支撑易于拆除且不损坏关键表面。后处理与组装去除支撑与打磨小心地移除所有支撑材料。对于合模线或粗糙表面可以使用砂纸从粗到细进行打磨。试装配在拧紧螺丝之前先进行“干装配”确保所有零件能顺利组合螺丝孔对齐屏幕能平整放入。电子部件安装先将天线如果使用外接天线安装到主板。然后将屏幕排线插入XIAO扩展板。务必在断电状态下操作整体组装将屏幕总成放入前壳用短螺丝固定。将主板放入后壳的固定柱上。连接屏幕与主板如果它们是分离的。最后合上前壳与后盖用长螺丝锁紧。功能测试先不要完全封死外壳连接USB线供电测试屏幕是否点亮、Wi-Fi能否连接、网页能否访问。确认一切正常后再完成最终组装。实操心得在打印外壳前最好先用硬纸板或泡沫板制作一个1:1的模型验证尺寸和人体工学。对于USB开槽打印一个小的测试件来验证你的数据线插头是否能顺利插入比打印完整个外壳才发现问题要节省大量时间和材料。5. 软件代码深度解析与配置5.1 开发环境搭建与库管理本项目在Arduino IDE中进行开发。除了安装ESP32板支持包两个图形库是关键。安装ESP32开发板支持打开Arduino IDE进入“文件”-“首选项”在“附加开发板管理器网址”中添加https://espressif.github.io/arduino-esp32/package_esp32_index.json打开“工具”-“开发板”-“开发板管理器”搜索“esp32”安装“Espressif Systems”提供的ESP32平台。安装必需的库Seeed_Arduino_RoundDisplay这是屏幕的底层驱动库负责与屏幕的硬件通信。Seeed_GFX这是一个图形库提供了画线、画圆、显示文字、图像等高层API依赖于前面的驱动库。ESPAsyncWebServer AsyncTCP用于实现异步Web服务器。这两个库通常不在Arduino库管理中需要手动下载。可以从GitHub分别为me-no-dev/ESPAsyncWebServer和me-no-dev/AsyncTCP下载ZIP文件然后通过“项目”-“加载库”-“添加.ZIP库…”来安装。EEPROMArduino核心库自带无需额外安装。项目文件结构 你的项目文件夹应包含half_pill.ino主程序文件。boot_img.h可能包含一个用于启动时显示的图像字节数组。driver.h可能包含一些硬件驱动相关的定义或封装函数。index.h存储了整个网页HTML/JS/CSS代码的字符串通常以const char数组形式存储。这是一个常见的技巧将网页前端代码直接嵌入固件。5.2 核心代码模块剖析主程序 (half_pill.ino) 框架#include // 包含所有必要的头文件 #include “driver.h” #include “boot_img.h” // 网络配置 const char* ssid “你的Wi-Fi名称”; const char* password “你的Wi-Fi密码”; // 全局对象定义 RoundDisplay display(240, 240); // 假设屏幕分辨率240x240 AsyncWebServer server(80); // Web服务器端口80 struct PillSchedule { char name[20]; int timeMinute; }; // 日程结构体 PillSchedule schedules[20]; int scheduleCount 0; void setup() { Serial.begin(115200); display.init(); // 初始化屏幕 EEPROM.begin(4096); // 初始化EEPROM loadSchedulesFromEEPROM(); // 从EEPROM加载已有日程 connectToWiFi(); // 连接Wi-Fi initWebServer(); // 初始化Web服务器路由 setupTimeSync(); // 设置NTP时间同步 } void loop() { checkAndTriggerReminders(); // 检查并触发提醒 server.handleClient(); // 处理网络客户端请求非阻塞 // 可以在这里添加屏幕刷新或其他非阻塞任务 }关键函数详解connectToWiFi()除了基本的WiFi.begin()还应加入重试机制和连接状态在屏幕上的反馈。initWebServer()在这里定义所有HTTP路由。server.on(“/“, HTTP_GET, [](AsyncWebServerRequest *request){ request-send_P(200, “text/html”, index_html); });用于发送网页。server.on(“/getSchedules”, HTTP_GET, [](AsyncWebServerRequest *request){ // 返回JSON格式的日程列表 });server.on(“/configure”, HTTP_POST, [](AsyncWebServerRequest *request){ // 接收并处理POST来的新日程数据保存至EEPROM });checkAndTriggerReminders()这是核心逻辑。获取当前时间转换为分钟数遍历schedules数组。如果发现匹配在误差窗口内并且该日程本次还未提醒则调用displayReminder(schedule.name)函数高亮显示提醒并标记为“已提醒”。每天零点需要重置所有“已提醒”标志。loadSchedulesFromEEPROM()和saveSchedulesToEEPROM()这两个函数负责数据结构与EEPROM字节流之间的转换。保存时可以先写入一个版本号或魔数再写入schedules数组和scheduleCount。读取时先校验魔数再读取数据。5.3 网页界面嵌入与配置将网页代码嵌入C程序的标准做法是使用PROGMEM程序存储区来存储这个巨大的字符串常量以节省宝贵的RAM。创建index.h文件 这个文件里定义了一个const char index_html[] PROGMEM R”rawliteral( … )rawliteral”;变量其中…部分就是你完整的HTML、CSS和JavaScript代码。现代Arduino IDE的编译工具链会自动将其放入Flash中。网页功能要点响应式设计使用CSS媒体查询确保在手机和电脑上都能良好显示。时间输入使用HTML5的控件它能在移动端弹出原生时间选择器体验很好。AJAX交互页面加载时自动发送GET请求到/getSchedules获取并渲染现有日程。点击“添加”在本地表格新增一行。点击“删除”从本地数组移除对应行。点击“保存”将本地数组JSON.stringify()后POST到/configure。用户反馈在发送AJAX请求后通过弹窗或动态文字提示“保存成功”或“保存失败”。6. 调试、优化与功能扩展思路6.1 常见问题排查指南在开发过程中你可能会遇到以下典型问题问题现象可能原因排查步骤与解决方案屏幕白屏或花屏1. 电源不足2. 屏幕初始化代码错误3. 排线接触不良1. 使用高质量的USB线和电源适配器5V/1A以上。2. 检查display.init()的参数和初始化序列是否正确。参考屏幕库的示例代码。3. 重新插拔屏幕排线确保锁扣扣紧。无法连接Wi-Fi1. SSID/密码错误2. 路由器信号弱或加密方式不支持3. 代码中网络配置错误1. 仔细检查代码中的ssid和password注意大小写和特殊字符。2. 将设备靠近路由器或尝试连接手机热点以排除路由器问题。3. 在setup()中增加WiFi.mode(WIFI_STA)明确设置为站点模式。查看串口打印的连接状态信息。网页无法访问1. IP地址错误2. 防火墙或网络隔离3. Web服务器未启动1. 确认屏幕显示的IP地址与手机/电脑在同一网段如都是192.168.1.x。2. 有些路由器会开启“客户端隔离”功能需关闭。尝试用电脑ping一下设备的IP。3. 检查initWebServer()是否被调用以及server.begin()是否执行。查看串口是否有错误日志。时间不同步1. NTP服务器无法访问2. 时区设置错误1. 确保Wi-Fi连接成功。尝试更换NTP服务器地址如cn.pool.ntp.org。2. 检查configTime()函数中的时区偏移参数如东八区为8*3600。EEPROM数据丢失1. 未调用EEPROM.commit()2. Flash扇区损坏1. 确保在修改数据后执行了EEPROM.commit()。2. 在首次使用或数据异常时在代码中加入初始化EEPROM默认值的逻辑。避免过于频繁的commit。6.2 性能与稳定性优化电源管理虽然本项目常插电但稳定的电源是基础。可以在USB输入端口附近增加一个10-100µF的电解电容以平滑可能存在的电压微小波动。看门狗定时器ESP32内置硬件看门狗。启用它可以在程序跑飞或陷入死循环时自动重启设备增强可靠性。esp_task_wdt_init(10, true); // 初始化看门狗超时时间10秒 esp_task_wdt_add(NULL); // 将当前任务加入看门狗监控 // 在主循环中定期喂狗 esp_task_wdt_reset();内存优化使用异步Web服务器本身就是为了避免阻塞。此外注意减少全局变量优先使用局部变量对于不变的字符串使用PROGMEM及时释放动态分配的内存如果使用了的话。错误恢复在连接Wi-Fi失败时可以尝试进入一个“配置模式”如开启一个AP热点用手机连接后配网。EEPROM数据读取失败时应有恢复默认值或上次已知良好备份的机制。6.3 潜在功能扩展方向基础版本已经实用但仍有丰富的扩展空间多提醒方式声音提醒增加一个无源蜂鸣器或小型扬声器模块连接到GPIO口。在触发提醒时可以播放一段简单的旋律或滴滴声。代码上需要增加音频驱动如使用tone()函数或PWM模拟。灯光提醒在屏幕周围增加一圈可编程RGB LED如WS2812提醒时发出柔和闪烁的光。物理提醒集成一个小型振动电机适合放在口袋或需要静音的场合。交互方式升级电容触摸圆形屏幕本身支持触摸。可以开发触摸界面实现滑动查看日程、点击确认服药等功能减少对网页的依赖。物理按钮增加1-2个实体按钮用于“延迟提醒”Snooze或“确认服药”Dismiss操作更直接。云端连接与远程管理接入物联网平台如阿里云、腾讯云IoT Hub通过平台的小程序或App远程查看设备状态、修改日程即使不在家也能为家人设置提醒。实现用药记录上传形成简单的服药日志。低功耗与电池供电如果想做成便携式需要改用电池供电。利用ESP32-C3的深度睡眠功能在非提醒时间让芯片进入深度睡眠仅靠RTC定时唤醒检查时间可极大延长续航。此时屏幕需要选择低功耗型号或仅在唤醒时点亮。药品库存管理这是一个更复杂的扩展。可以尝试在药盒底部增加重量传感器如HX711模块称重传感器通过重量变化粗略估计剩余药片数量并在UI中显示“低库存”预警。这些扩展都会增加硬件复杂度和软件工作量建议在完全掌握基础版本后选择一两个最感兴趣的方向进行尝试。每个扩展都可以作为一个独立的子项目来研究和实现。
基于ESP32-C3的智能药盒提醒器:从硬件选型到Web配置的物联网实践
1. 项目概述与设计初衷在嵌入式开发和物联网应用逐渐普及的今天我们身边有太多可以通过技术优化的小痛点。对我而言其中一个就是用药管理。无论是工作繁忙的上班族还是需要规律服药的家人忘记吃药是再常见不过的事。市面上的智能药盒要么功能复杂、价格昂贵要么依赖手机App而手机本身又是一个巨大的干扰源。我的目标很明确设计一个放在桌面上、足够简单、足够专注的设备它只做一件事——在正确的时间用最温和的方式提醒你该吃药了。这就是“Half Pill”智能药盒提醒器的由来。它基于Seeed Studio的XIAO ESP32-C3核心板搭配一块圆形的显示屏通过Wi-Fi接入家庭网络。整个设备没有复杂的按钮所有设置都通过一个优雅的手机网页完成。你只需要在浏览器里输入设备显示的IP地址就能添加、删除或查看服药计划。设备会通过NTP自动同步网络时间确保提醒准时所有数据都安全地保存在板载的EEPROM里即使断电也不会丢失。整个项目从3D建模、打印到代码编写、组装是一个典型的端到端物联网硬件开发实践非常适合想要入门嵌入式或智能硬件DIY的朋友。2. 核心硬件选型与原理剖析2.1 主控单元为什么是XIAO ESP32-C3在项目启动时主控芯片的选择是首要决策。我最终选择了Seeed Studio的XIAO ESP32-C3主要基于以下几点考量集成度与尺寸的平衡XIAO系列以其极致的紧凑尺寸著称ESP32-C3版本在指甲盖大小的空间内集成了ESP32-C3芯片、天线、USB-C接口和必要的电路。对于桌面提醒器这类需要精致外观的设备小巧的尺寸为外壳设计提供了巨大便利。相比之下标准的NodeMCU或D1 Mini开发板虽然常见但体积过大会破坏整体的美观性。性能与功耗的匹配ESP32-C3是一款基于RISC-V架构的单核Wi-Fi Bluetooth 5 (LE) SoC。对于本应用——驱动一块小屏幕、维护Wi-Fi连接、运行一个轻量级Web服务器、处理定时任务——它的性能绰绰有余。RISC-V架构在能效比上表现不错这对于长期插电使用的设备虽非首要但体现了技术选型的现代性。更重要的是它原生支持Wi-Fi这是实现网络时间同步和网页配置的基础。开发生态与成本ESP32系列拥有极其丰富的Arduino和ESP-IDF开发资源社区支持强大遇到问题容易找到解决方案。XIAO ESP32-C3完美兼容Arduino IDE大大降低了开发门槛。从成本看它是一款性价比极高的模块在实现所需功能的同时控制了项目的整体预算。注意XIAO ESP32-C3的IO口数量有限。在选择它时必须提前规划好所有需要连接的外设如屏幕、可能的蜂鸣器、传感器等确保引脚资源足够。本项目仅使用屏幕因此完全满足。2.2 显示单元圆形屏幕的交互哲学我选择了Seeed Studio专为XIAO设计的圆形显示屏。这个选择并非偶然而是交互设计的一部分。形式服务于功能药盒提醒的核心信息是“药名”和“时间”。圆形屏幕天然适合展示时钟、环形进度条等与时间相关的视觉元素。当提醒触发时屏幕中央可以醒目地显示药名周围用光晕或环形高亮作为视觉提示这种形式比长方屏更具亲和力和专注度。即插即用的便利性这块屏幕通过专用的连接器与XIAO底板对接无需焊接真正实现了插拔即用。它集成了显示驱动和电容触摸控制器虽然本项目未使用触摸功能节省了大量连接和调试屏幕底层驱动的时间让我能更专注于应用逻辑的开发。硬件兼容性保障使用同一品牌、专为彼此设计的核心板与屏幕最大程度避免了电源、信号电平不匹配或引脚定义冲突等问题。对于DIY项目这种“套件”式的兼容性能显著提高成功率减少在硬件连接阶段踩坑的可能性。2.3 数据持久化EEPROM的可靠性与局限设备需要存储用户设置的药名和服药时间且断电后不能丢失。这里没有使用外置的SD卡或Flash芯片而是利用了ESP32-C3内部自带的EEPROM模拟存储。工作原理ESP32的EEPROM实际上是在SPI Flash存储器中划出一块区域通过EEPROM库进行模拟读写。当你调用EEPROM.write()或EEPROM.put()时数据首先被写入微控制器的内存缓冲区只有在调用EEPROM.commit()后才会真正写入Flash。这个过程磨损的是Flash的特定扇区。容量与寿命考量ESP32-C3的EEPROM默认大小为4096字节4KB。对于存储20条记录每条记录包含药名假设最多20字符和时间信息如4字节的时间戳总需求远低于4KB完全足够。需要警惕的是Flash的擦写寿命通常为10万次。频繁地、无意义地调用commit()会加速损耗。因此在程序设计中我采用了“批量保存”策略用户在网页点击“配置”后所有数据一次性写入而非每次修改都保存极大地延长了存储寿命。实操心得首次使用EEPROM前最好在setup()函数中执行一次EEPROM.begin(size)来初始化指定大小。读取数据后建议进行简单的数据校验例如在存储时额外写入一个固定的“魔数”Magic Number读取时校验该魔数是否正确以此判断EEPROM中的数据是否有效或为初始乱码。3. 系统设计与软件架构详解3.1 整体工作流程与状态机设计设备的软件核心是一个清晰的状态机它定义了设备从启动到运行各个阶段的行为。理解这个状态机是理解整个项目逻辑的关键。状态一初始化与网络连接 (Boot Connect)设备上电后首先初始化串口用于调试、屏幕、并连接文件系统如果需要和EEPROM。随后它尝试连接预设的Wi-Fi网络。此阶段屏幕显示“Connecting to Wi-Fi…”之类的动态信息。连接成功后设备通过SNTP简单网络时间协议从NTP服务器获取当前时间并设置本地时钟。成功后屏幕会显示本机获取到的IP地址这是后续网页配置的入口。状态二Web服务器与配置模式 (Configuration Mode)设备启动一个异步Web服务器例如使用ESPAsyncWebServer库。当用户在浏览器中输入设备IP地址时服务器会返回一个HTML格式的配置页面。这个页面通过JavaScript与设备进行AJAX交互实现无刷新添加、删除、查看日程。用户点击“保存配置”后浏览器会将所有日程数据以JSON格式POST到设备的一个特定API端点。状态三运行与监控模式 (Running Mode)配置保存后设备进入主循环运行状态。程序会持续将当前时间与EEPROM中存储的所有服药时间进行比较。当匹配到某个提醒时间点时设备触发“提醒动作”——在当前设计中即改变屏幕显示内容以高亮方式展示药名。主循环会一直运行直到下一次通过网页修改配置并保存设备将重新加载日程数据。关键设计异步Web服务器我选择了异步Web服务器库而非传统的同步服务器如WiFiServer。原因是同步服务器在处理请求时会阻塞整个主循环这意味着在服务器响应网页请求的几百毫秒内设备无法检查时间、更新屏幕可能导致提醒延迟或错过。异步服务器则将所有网络事件放入队列在主循环中非阻塞地处理确保了时间监控任务的实时性。3.2 网页配置界面前后端数据交互配置界面是用户与硬件交互的桥梁其设计原则是简单、直观、无需安装额外App。前端实现 (HTML/JS) 我编写了一个单一的index.html文件包含一个表单用于输入药名和时间一个表格用于展示现有日程以及“添加”、“删除”、“保存”等按钮。这个文件被直接存储在ESP32的代码中通过PROGMEM方式存入当浏览器请求根路径时服务器将其发送出去。动态添加点击“添加”按钮JavaScript会在表格中动态插入一行新记录但此时并未发送到设备。数据暂存所有增删改操作仅在浏览器内存一个JavaScript数组中进行提供了良好的即时反馈。批量提交用户确认所有修改后点击“保存配置”JavaScript会将整个日程数组序列化为JSON字符串通过fetch()API的POST方法发送到设备的/configure端点。后端处理 (ESP32 C) 设备端的/configure端点处理POST请求。解析收到的JSON数据。进行基础校验如时间格式、数量是否超限20个。将数据转换为更节省空间的格式如将“14:30”字符串转换为一天中的分钟数。调用EEPROM.put()将数据存入缓冲区最后执行EEPROM.commit()写入Flash。返回一个成功的JSON响应给浏览器并自动重启或重载日程列表。优势这种设计将复杂的UI交互逻辑交给功能强大的浏览器处理ESP32只负责提供API和存储分工明确极大地减轻了微控制器的负担也使得界面可以做得更美观、响应更快。3.3 时间同步与定时检查机制精准的提醒依赖于精准的时间。NTP时间同步ESP32通过configTime()函数配置时区和NTP服务器地址如pool.ntp.org。连接Wi-Fi后它会自动在后台同步时间。我建议在代码中设置一个每24小时自动重新同步一次的软定时器以修正可能存在的微小时钟漂移。ESP32-C3的硬件RTC实时时钟在深度睡眠时精度尚可但在常态运行下依赖软件定时和NTP定期校准是更可靠的做法。定时检查算法 在主循环loop()中我使用millis()或getLocalTime()来获取当前时间。一个高效的做法是将EEPROM中存储的每个服药时间例如“08:00”“13:00”“20:00”转换为从午夜零点开始的分钟数如480, 780, 1200。同样将当前时间也转换为分钟数。int currentMinutes hour * 60 minute;然后遍历所有日程检查currentMinutes是否等于某个日程的分钟数。但这里有个细节如果程序在08:00:00检查了一次下一次循环可能在08:00:01就会错过。因此通常需要设置一个“提醒窗口期”例如当前时间与预定时间差在±1分钟内都视为触发提醒。触发后可以设置一个“已提醒”标志避免在同一分钟内重复触发屏幕刷新。注意事项避免在loop()中使用delay()。如果使用delay(1000)来每秒检查一次会阻塞整个程序导致网页请求无法响应。正确的做法是使用非阻塞的时间间隔检查例如记录上一次检查的时间戳当millis() - lastCheckTime 60000一分钟时才执行一次时间比对然后更新lastCheckTime。4. 硬件组装与外壳制作实战4.1 3D建模与结构设计要点我使用Autodesk Fusion 360进行外壳设计整个过程是典型的“由内而外”的逆向设计。第一步电子元件定位首先我将从Seeed官网下载的XIAO ESP32-C3和圆形显示屏的3D模型STEP或SLDPRT格式导入Fusion 360。在虚拟空间中我需要确定它们的相对位置主板固定设计支撑柱和螺丝孔M2规格来固定XIAO板。屏幕固定屏幕通常通过其PCB上的安装孔固定。需要设计对应的支柱和孔位M2或M3确保屏幕显示面与外壳前面板开口完美对齐。走线空间必须留出足够的空间容纳连接屏幕与主板的排线避免弯折过度或挤压。我通常在屏幕背面和主板之间预留5-10mm的空隙。天线安置ESP32-C3板载的PCB天线或外接天线需要远离金属部件和显示屏背板以确保信号质量。设计中需要为天线预留一个开阔的区域。第二步外壳体设计围绕定位好的电子元件绘制外壳的主体。分为前壳和后盖。前壳需要开一个精确的圆形视窗让屏幕露出。视窗边缘最好有一个微小的唇边从内部挡住屏幕边缘使外观更整洁。后盖需要为USB-C接口开槽。这里强烈推荐为90度弯头USB线设计一个通道或凹槽让线材可以优雅地引出而不是直角弯折影响美观和线材寿命。还需要设计散热孔虽然功耗不大以及用于固定到桌面支架的接口。合盖方式我采用了螺丝固定M3螺丝在四角设计螺丝柱。也可以考虑卡扣式但对3D打印的精度和材料韧性要求更高。第三步桌面支架设计一个可分离的桌面支架能提升使用体验。我设计了一个带有一定倾角的支架通过一个卡槽或单个螺丝与主机后盖连接。倾角例如15-20度需要经过测试确保在桌面上视线舒适。4.2 3D打印与后处理打印参数建议材料PLA增强PLA是不错的选择强度、精度和打印成功率都很好。PETG则更耐用、耐温。层高0.2mm层高可以在打印速度和表面光洁度之间取得良好平衡。对于显示窗口等关键部位可以尝试0.16mm以获得更光滑的边缘。填充率15%-20%的填充率足以提供足够的结构强度同时节省材料和时间。支撑对于外壳内部的螺丝柱、卡扣等悬空结构需要生成支撑。务必仔细检查切片预览确保支撑易于拆除且不损坏关键表面。后处理与组装去除支撑与打磨小心地移除所有支撑材料。对于合模线或粗糙表面可以使用砂纸从粗到细进行打磨。试装配在拧紧螺丝之前先进行“干装配”确保所有零件能顺利组合螺丝孔对齐屏幕能平整放入。电子部件安装先将天线如果使用外接天线安装到主板。然后将屏幕排线插入XIAO扩展板。务必在断电状态下操作整体组装将屏幕总成放入前壳用短螺丝固定。将主板放入后壳的固定柱上。连接屏幕与主板如果它们是分离的。最后合上前壳与后盖用长螺丝锁紧。功能测试先不要完全封死外壳连接USB线供电测试屏幕是否点亮、Wi-Fi能否连接、网页能否访问。确认一切正常后再完成最终组装。实操心得在打印外壳前最好先用硬纸板或泡沫板制作一个1:1的模型验证尺寸和人体工学。对于USB开槽打印一个小的测试件来验证你的数据线插头是否能顺利插入比打印完整个外壳才发现问题要节省大量时间和材料。5. 软件代码深度解析与配置5.1 开发环境搭建与库管理本项目在Arduino IDE中进行开发。除了安装ESP32板支持包两个图形库是关键。安装ESP32开发板支持打开Arduino IDE进入“文件”-“首选项”在“附加开发板管理器网址”中添加https://espressif.github.io/arduino-esp32/package_esp32_index.json打开“工具”-“开发板”-“开发板管理器”搜索“esp32”安装“Espressif Systems”提供的ESP32平台。安装必需的库Seeed_Arduino_RoundDisplay这是屏幕的底层驱动库负责与屏幕的硬件通信。Seeed_GFX这是一个图形库提供了画线、画圆、显示文字、图像等高层API依赖于前面的驱动库。ESPAsyncWebServer AsyncTCP用于实现异步Web服务器。这两个库通常不在Arduino库管理中需要手动下载。可以从GitHub分别为me-no-dev/ESPAsyncWebServer和me-no-dev/AsyncTCP下载ZIP文件然后通过“项目”-“加载库”-“添加.ZIP库…”来安装。EEPROMArduino核心库自带无需额外安装。项目文件结构 你的项目文件夹应包含half_pill.ino主程序文件。boot_img.h可能包含一个用于启动时显示的图像字节数组。driver.h可能包含一些硬件驱动相关的定义或封装函数。index.h存储了整个网页HTML/JS/CSS代码的字符串通常以const char数组形式存储。这是一个常见的技巧将网页前端代码直接嵌入固件。5.2 核心代码模块剖析主程序 (half_pill.ino) 框架#include // 包含所有必要的头文件 #include “driver.h” #include “boot_img.h” // 网络配置 const char* ssid “你的Wi-Fi名称”; const char* password “你的Wi-Fi密码”; // 全局对象定义 RoundDisplay display(240, 240); // 假设屏幕分辨率240x240 AsyncWebServer server(80); // Web服务器端口80 struct PillSchedule { char name[20]; int timeMinute; }; // 日程结构体 PillSchedule schedules[20]; int scheduleCount 0; void setup() { Serial.begin(115200); display.init(); // 初始化屏幕 EEPROM.begin(4096); // 初始化EEPROM loadSchedulesFromEEPROM(); // 从EEPROM加载已有日程 connectToWiFi(); // 连接Wi-Fi initWebServer(); // 初始化Web服务器路由 setupTimeSync(); // 设置NTP时间同步 } void loop() { checkAndTriggerReminders(); // 检查并触发提醒 server.handleClient(); // 处理网络客户端请求非阻塞 // 可以在这里添加屏幕刷新或其他非阻塞任务 }关键函数详解connectToWiFi()除了基本的WiFi.begin()还应加入重试机制和连接状态在屏幕上的反馈。initWebServer()在这里定义所有HTTP路由。server.on(“/“, HTTP_GET, [](AsyncWebServerRequest *request){ request-send_P(200, “text/html”, index_html); });用于发送网页。server.on(“/getSchedules”, HTTP_GET, [](AsyncWebServerRequest *request){ // 返回JSON格式的日程列表 });server.on(“/configure”, HTTP_POST, [](AsyncWebServerRequest *request){ // 接收并处理POST来的新日程数据保存至EEPROM });checkAndTriggerReminders()这是核心逻辑。获取当前时间转换为分钟数遍历schedules数组。如果发现匹配在误差窗口内并且该日程本次还未提醒则调用displayReminder(schedule.name)函数高亮显示提醒并标记为“已提醒”。每天零点需要重置所有“已提醒”标志。loadSchedulesFromEEPROM()和saveSchedulesToEEPROM()这两个函数负责数据结构与EEPROM字节流之间的转换。保存时可以先写入一个版本号或魔数再写入schedules数组和scheduleCount。读取时先校验魔数再读取数据。5.3 网页界面嵌入与配置将网页代码嵌入C程序的标准做法是使用PROGMEM程序存储区来存储这个巨大的字符串常量以节省宝贵的RAM。创建index.h文件 这个文件里定义了一个const char index_html[] PROGMEM R”rawliteral( … )rawliteral”;变量其中…部分就是你完整的HTML、CSS和JavaScript代码。现代Arduino IDE的编译工具链会自动将其放入Flash中。网页功能要点响应式设计使用CSS媒体查询确保在手机和电脑上都能良好显示。时间输入使用HTML5的控件它能在移动端弹出原生时间选择器体验很好。AJAX交互页面加载时自动发送GET请求到/getSchedules获取并渲染现有日程。点击“添加”在本地表格新增一行。点击“删除”从本地数组移除对应行。点击“保存”将本地数组JSON.stringify()后POST到/configure。用户反馈在发送AJAX请求后通过弹窗或动态文字提示“保存成功”或“保存失败”。6. 调试、优化与功能扩展思路6.1 常见问题排查指南在开发过程中你可能会遇到以下典型问题问题现象可能原因排查步骤与解决方案屏幕白屏或花屏1. 电源不足2. 屏幕初始化代码错误3. 排线接触不良1. 使用高质量的USB线和电源适配器5V/1A以上。2. 检查display.init()的参数和初始化序列是否正确。参考屏幕库的示例代码。3. 重新插拔屏幕排线确保锁扣扣紧。无法连接Wi-Fi1. SSID/密码错误2. 路由器信号弱或加密方式不支持3. 代码中网络配置错误1. 仔细检查代码中的ssid和password注意大小写和特殊字符。2. 将设备靠近路由器或尝试连接手机热点以排除路由器问题。3. 在setup()中增加WiFi.mode(WIFI_STA)明确设置为站点模式。查看串口打印的连接状态信息。网页无法访问1. IP地址错误2. 防火墙或网络隔离3. Web服务器未启动1. 确认屏幕显示的IP地址与手机/电脑在同一网段如都是192.168.1.x。2. 有些路由器会开启“客户端隔离”功能需关闭。尝试用电脑ping一下设备的IP。3. 检查initWebServer()是否被调用以及server.begin()是否执行。查看串口是否有错误日志。时间不同步1. NTP服务器无法访问2. 时区设置错误1. 确保Wi-Fi连接成功。尝试更换NTP服务器地址如cn.pool.ntp.org。2. 检查configTime()函数中的时区偏移参数如东八区为8*3600。EEPROM数据丢失1. 未调用EEPROM.commit()2. Flash扇区损坏1. 确保在修改数据后执行了EEPROM.commit()。2. 在首次使用或数据异常时在代码中加入初始化EEPROM默认值的逻辑。避免过于频繁的commit。6.2 性能与稳定性优化电源管理虽然本项目常插电但稳定的电源是基础。可以在USB输入端口附近增加一个10-100µF的电解电容以平滑可能存在的电压微小波动。看门狗定时器ESP32内置硬件看门狗。启用它可以在程序跑飞或陷入死循环时自动重启设备增强可靠性。esp_task_wdt_init(10, true); // 初始化看门狗超时时间10秒 esp_task_wdt_add(NULL); // 将当前任务加入看门狗监控 // 在主循环中定期喂狗 esp_task_wdt_reset();内存优化使用异步Web服务器本身就是为了避免阻塞。此外注意减少全局变量优先使用局部变量对于不变的字符串使用PROGMEM及时释放动态分配的内存如果使用了的话。错误恢复在连接Wi-Fi失败时可以尝试进入一个“配置模式”如开启一个AP热点用手机连接后配网。EEPROM数据读取失败时应有恢复默认值或上次已知良好备份的机制。6.3 潜在功能扩展方向基础版本已经实用但仍有丰富的扩展空间多提醒方式声音提醒增加一个无源蜂鸣器或小型扬声器模块连接到GPIO口。在触发提醒时可以播放一段简单的旋律或滴滴声。代码上需要增加音频驱动如使用tone()函数或PWM模拟。灯光提醒在屏幕周围增加一圈可编程RGB LED如WS2812提醒时发出柔和闪烁的光。物理提醒集成一个小型振动电机适合放在口袋或需要静音的场合。交互方式升级电容触摸圆形屏幕本身支持触摸。可以开发触摸界面实现滑动查看日程、点击确认服药等功能减少对网页的依赖。物理按钮增加1-2个实体按钮用于“延迟提醒”Snooze或“确认服药”Dismiss操作更直接。云端连接与远程管理接入物联网平台如阿里云、腾讯云IoT Hub通过平台的小程序或App远程查看设备状态、修改日程即使不在家也能为家人设置提醒。实现用药记录上传形成简单的服药日志。低功耗与电池供电如果想做成便携式需要改用电池供电。利用ESP32-C3的深度睡眠功能在非提醒时间让芯片进入深度睡眠仅靠RTC定时唤醒检查时间可极大延长续航。此时屏幕需要选择低功耗型号或仅在唤醒时点亮。药品库存管理这是一个更复杂的扩展。可以尝试在药盒底部增加重量传感器如HX711模块称重传感器通过重量变化粗略估计剩余药片数量并在UI中显示“低库存”预警。这些扩展都会增加硬件复杂度和软件工作量建议在完全掌握基础版本后选择一两个最感兴趣的方向进行尝试。每个扩展都可以作为一个独立的子项目来研究和实现。