1. 项目背景与升级思路去年用ESP32做的MQTT遥控风扇确实挺方便但每次都要打开手机APP发指令实在不够优雅。最近研究了一下ESP32的Web服务器功能发现完全可以把控制界面直接做到设备里这样只要手机/电脑连上ESP32创建的Wi-Fi热点打开浏览器就能调节风速、切换转向模式还能实时显示运行状态彻底摆脱对MQTT服务器的依赖。这个升级版方案的核心变化在于控制方式从依赖云端MQTT转为本地Web控制硬件成本零增加ESP32本身就能当服务器用户体验浏览器操作比专用APP更符合直觉网络要求完全局域网内运行断网也能用实测下来这种混合控制模式特别适合家用场景——平时用Web界面操作出门在外时依然可以启用MQTT远程控制两套方案互不冲突。2. 硬件准备与接线优化虽然沿用之前的L9110风扇模块和SG90舵机但这次我发现了几个可以优化的细节2.1 电源改造建议原方案用USB供电时满负荷运行偶尔会出现电压波动。建议给ESP32单独供电5V/2A电源适配器风扇电机接独立电源与ESP32共地在L9110模块的VCC和GND之间加装100μF电容改造后的接线示意图ESP32 GPIO25 → L9110 A-IA ESP32 GPIO26 → L9110 A-IB ESP32 GPIO18 → 水平舵机信号线 ESP32 GPIO17 → 垂直舵机信号线2.2 PWM通道分配技巧ESP32的LEDC控制器有16个通道但要注意水平/垂直舵机建议分别用通道0和150Hz频率风扇PWM用通道2频率可设1KHz以上避免使用通道6-7与Wi-Fi有冲突风险配置示例// 舵机PWM初始化 ledcSetup(0, 50, 16); // 通道0, 50Hz, 16位分辨率 ledcAttachPin(18, 0); ledcSetup(1, 50, 16); ledcAttachPin(17, 1); // 风扇PWM初始化 ledcSetup(2, 1000, 8); // 通道2, 1KHz, 8位分辨率 ledcAttachPin(25, 2);3. 双模网络架构实现3.1 同时运行Wi-Fi STAAP模式这是本项目的关键突破点——让ESP32既能连接路由器STA又能自建热点APWiFi.mode(WIFI_AP_STA); WiFi.softAP(SmartFan, 12345678); // 创建热点 WiFi.begin(ssid, password); // 连接路由器实测发现热点模式下客户端最大连接数约5个建议将AP信道设为6避免与STA信道冲突内存占用会增加约15%需注意优化网页资源3.2 Web服务器搭建实战使用ESPAsyncWebServer库比标准WebServer库更稳定安装依赖库pio lib install ESP Async WebServer pio lib install AsyncTCP基础服务端代码框架AsyncWebServer server(80); void setup(){ server.on(/, HTTP_GET, [](AsyncWebServerRequest *request){ request-send(SPIFFS, /index.html, text/html); }); server.on(/speed, HTTP_POST, [](AsyncWebServerRequest *request){ if(request-hasParam(value)){ int speed request-getParam(value)-value().toInt(); ledcWrite(2, speed); // 调节风扇转速 } request-send(200); }); SPIFFS.begin(); server.begin(); }4. 交互式网页设计技巧4.1 三合一控制界面用HTMLCSSJS实现响应式布局div classcontrol-panel !-- 风速滑块 -- input typerange min0 max255 classslider idspeedSlider !-- 转向模式选择 -- div classswitch-container labelinput typecheckbox idhSwitch水平摆动/label labelinput typecheckbox idvSwitch垂直摆动/label /div !-- 实时状态显示 -- div idstatusPanel 当前风速: span idspeedValue0/spanbr 电机温度: span idtempValue--/span℃ /div /div4.2 实时数据推送方案传统轮询方式太耗资源改用WebSocket实现双向通信服务端配置AsyncWebSocket ws(/ws); server.addHandler(ws); void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len){ if(type WS_EVT_DATA){ // 处理客户端指令 } }前端JS代码const ws new WebSocket(ws://${location.host}/ws); ws.onmessage (event) { const data JSON.parse(event.data); document.getElementById(tempValue).innerText data.temp; };5. 深度优化与故障排查5.1 内存泄漏预防措施Web服务器常见的内存问题及解决方案字符串拼接避免用String类改用char[]或std::stringWebSocket缓存定期调用cleanClients()SPIFFS文件句柄记得调用file.close()5.2 常见问题速查表现象可能原因解决方案网页加载不全SPIFFS文件损坏重新上传网页文件舵机抖动电源干扰加装470μF电容热点连不上信道冲突修改AP信道控制延迟高内存不足减少并发连接数5.3 性能测试数据在不同工况下的资源占用情况场景内存占用CPU负载响应延迟待机状态23%5%50ms3个网页连接45%30%80-120ms满负荷运行68%75%200-300ms建议同时连接的客户端不要超过3个长时间运行建议启用看门狗hw_timer_t *timer timerBegin(0, 80, true); timerAlarmWrite(timer, 3000000, true); // 3秒超时 timerAlarmEnable(timer);6. 项目扩展方向6.1 温度联动控制加装DS18B20传感器实现智能调速#include DallasTemperature.h OneWire oneWire(13); DallasTemperature sensors(oneWire); void checkTemp(){ sensors.requestTemperatures(); float temp sensors.getTempCByIndex(0); if(temp 30) ledcWrite(2, 200); else if(temp 25) ledcWrite(2, 150); }6.2 物理按键扩展保留硬件控制接口更可靠void IRAM_ATTR handleButton(){ static uint32_t lastPress 0; if(millis() - lastPress 200){ toggleFan(); lastPress millis(); } } attachInterrupt(0, handleButton, FALLING);6.3 OTA升级支持让Web界面也能上传固件AsyncElegantOTA.begin(server); server.begin();访问http://[IP]/update即可看到升级页面7. 完整代码结构核心逻辑分层实现├── /data # 网页资源 │ ├── index.html │ ├── style.css │ └── script.js ├── FanController # 风扇控制 │ ├── PWM.cpp │ └── Servo.cpp ├── Network # 网络服务 │ ├── WebServer.cpp │ └── MQTTClient.cpp └── main.cpp # 主程序关键配置项建议用结构体管理struct Config { char apSSID[32] SmartFan; char apPassword[32] 12345678; uint8_t maxSpeed 210; uint16_t hAngle 180; }; Config cfg; EEPROM.get(0, cfg); // 从EEPROM读取配置这个方案在我家已经稳定运行两个月最大的惊喜是家里老人也能轻松操作——他们现在直接打开手机浏览器就能调节风扇再也不用问我那个绿色图标的APP在哪了。遇到最多的问题反而是邻居跑来问为什么能搜到一个叫SmartFan的Wi-Fi信号看来得在网页里加个自定义热点名称的功能了。