You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 13:05:17