ESP32 WebServer驱动72颗WS2812 RGB灯的性能优化问题
优化WS2812 RGB灯100Hz刷新的网络延迟问题
当前使用AJAX刷新72颗WS2812 RGB灯状态时延迟严重,经排查初始连接环节耗时最长,现需优化方案以实现100Hz的灯效状态刷新频率。
当前使用的代码
#include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> const char* ssid = "ssid"; const char* password = "password"; WebServer server(80); bool refresh = true; void handleRoot() { String html = "<html><head><style>.led-box {width: 10px; height: 10px; border: 1px solid black; float: left; margin-right: 1px;}</style>"; html += "<script>"; html += "function updateLEDs() {"; html += " var xhr = new XMLHttpRequest();"; html += " xhr.open('GET', '/updateLEDs', true);"; html += " xhr.onreadystatechange = function() {"; html += " if (xhr.readyState == XMLHttpRequest.DONE) {"; html += " var colors = xhr.responseText.split(',');"; for (int i = 1; i <= 72; i++) { html += " document.getElementById('led" + String(i) + "').style.backgroundColor = '#' + colors[" + String(i - 1) + "];"; } html += " }"; html += " };"; html += " xhr.send();"; html += "}"; html += "setInterval(updateLEDs, 1000);"; html += "</script></head><body>"; for (int i = 1; i <= 72; i++) { html += "<div id='led" + String(i) + "' class='led-box'></div>"; } html += "</body></html>"; server.send(200, "text/html", html); } void handleUpdateLEDs() { String colorCodes = ""; for (int i = 1; i <= 72; i++) { if (i > 1) colorCodes += ","; colorCodes += String(random(0xFFFFFF), HEX); } server.send(200, "text/plain", colorCodes); } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi"); server.on("/", handleRoot); server.on("/updateLEDs", handleUpdateLEDs); server.begin(); Serial.println("HTTP server started"); } void loop() { server.handleClient(); }
核心优化方案
1. 改用WebSocket替代AJAX
AJAX每次请求都要建立TCP连接,握手开销是延迟的核心原因。WebSocket仅需一次连接即可持续双向通信,完全消除重复握手耗时,是高频数据传输的最优选择。
后端修改(使用WebSocketsServer库):
#include <WiFi.h> #include <WebSocketsServer.h> const char* ssid = "ssid"; const char* password = "password"; WebSocketsServer webSocket = WebSocketsServer(81); void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { switch(type) { case WStype_CONNECTED: Serial.printf("客户端[%u]已连接\n", num); break; case WStype_DISCONNECTED: Serial.printf("客户端[%u]已断开\n", num); break; } } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi已连接"); webSocket.begin(); webSocket.onEvent(webSocketEvent); Serial.println("WebSocket服务器已启动"); } void loop() { webSocket.loop(); // 按100Hz频率发送灯效数据 static unsigned long lastSendTime = 0; if (millis() - lastSendTime >= 10) { lastSendTime = millis(); String colorData = ""; for (int i = 0; i < 72; i++) { if (i > 0) colorData += ","; colorData += String(random(0xFFFFFF), HEX); } webSocket.broadcastTXT(colorData); } }
前端修改:
<html> <head> <style>.led-box {width: 10px; height: 10px; border: 1px solid black; float: left; margin-right: 1px;}</style> </head> <body> <% for (let i=1; i<=72; i++) { %> <div id='led<%=i%>' class='led-box'></div> <% } %> <script> // 提前缓存LED DOM节点,避免重复查询 const ledElements = []; window.onload = () => { for (let i=1; i<=72; i++) { ledElements.push(document.getElementById(`led${i}`)); } }; // 建立WebSocket连接 const ws = new WebSocket(`ws://${window.location.hostname}:81`); ws.onmessage = (event) => { const colors = event.data.split(','); // 批量更新LED样式 ledElements.forEach((el, idx) => { el.style.backgroundColor = `#${colors[idx]}`; }); }; </script> </body> </html>
2. 压缩数据传输格式
原方案用逗号分隔十六进制字符串,72颗灯的数据量约为493字节。改用二进制格式传输,每个RGB灯用3字节(R/G/B各1字节),总数据量仅216字节,大幅减少传输带宽和解析时间。
示例后端二进制发送:
void loop() { webSocket.loop(); static unsigned long lastSendTime = 0; if (millis() - lastSendTime >= 10) { lastSendTime = millis(); uint8_t colorData[72*3]; for (int i=0; i<72*3; i++) { colorData[i] = random(0xFF); } webSocket.broadcastBIN(colorData, sizeof(colorData)); } }
前端二进制解析:
ws.onmessage = (event) => { const data = new Uint8Array(event.data); for (let i=0; i<72; i++) { const r = data[i*3].toString(16).padStart(2, '0'); const g = data[i*3+1].toString(16).padStart(2, '0'); const b = data[i*3+2].toString(16).padStart(2, '0'); ledElements[i].style.backgroundColor = `#${r}${g}${b}`; } };
3. 优化ESP32 WiFi性能
- 禁用WiFi休眠,避免休眠导致的传输延迟:
WiFi.setSleepMode(WIFI_NONE_SLEEP); - 调整TCP窗口大小,提升传输效率(需ESP-IDF支持,Arduino环境可通过修改核心配置实现)。
4. 前端渲染优化
- 提前缓存所有LED的DOM节点,避免每次更新时重复调用
getElementById。 - 使用批量样式更新,减少DOM重排次数,可考虑将LED元素放入同一个容器,通过CSS变量或Canvas渲染进一步优化。
内容的提问来源于stack exchange,提问作者Szymon Tobolewski
相关产品推荐
相关产品推荐

