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

使用Web Serial API读取ESP32时遭遇BufferOverrunError问题求助

Web Serial API读取ESP32数据时BufferOverrunError问题排查与解决方案

问题描述

使用Web Serial API通过USB连接读取ESP32设备数据,浏览器频繁出现BufferOverrunError,尽管ESP32仅发送小型JSON负载或文本,数据吞吐量很低。

环境配置

  • ESP32间歇性发送文本与JSON数据
  • 波特率:19200
  • 浏览器:最新版Chrome
  • 实现方式:通过API请求端口、建立连接,循环调用read()持续读取数据

已尝试的解决方法

  • 打开端口时增大bufferSize参数
  • 在读取循环中添加延迟(setTimeout()或await暂停)
  • 分块处理数据

以上方法均未解决问题,短时间后仍会出现缓冲区溢出。

代码示例

ESP32(Arduino IDE)代码

void setup() {
  // put your setup code here, to run once:
  Serial.begin(19200);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("Hello");
  delay(2000);
}

HTML/JS代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>USB Serial Port Connection</title>
</head>
<body>
    <h1>Connect to USB Serial Port</h1>
    <!-- Button to initiate USB serial connection -->
    <button id="connectButton">Connect to USB</button>
    <!-- Button to disconnect from USB, disabled by default -->
    <button id="disconnectButton" disabled>Disconnect</button>
    <!-- Container to display the data received from the serial port -->
    <pre id="dataOutput"></pre>

    <script>
        let port, reader, isConnected = false;

        // Function to handle connection to the USB serial port
        async function connectSerialPort() {
            try {
                // Request the user to select a serial port (USB) from the available options
                port = await navigator.serial.requestPort();
                
                // Open the selected port with a baud rate of 115200
                await port.open({ baudRate: 19200 });

                // Get a reader to read data from the serial port
                reader = port.readable.getReader();

                // Set the connection status to true
                isConnected = true;
                // Disable the "Connect" button and enable the "Disconnect" button
                document.getElementById('connectButton').disabled = true;
                document.getElementById('disconnectButton').disabled = false;

                // Start reading data from the serial port
                readSerialData();
            } catch (error) {
                // Handle any errors that occur during the connection process
                console.error('Error connecting to the serial port:', error);
            }
        }

        // Function to handle disconnection from the USB serial port
        async function disconnectSerialPort() {
            if (reader) {
                // Cancel and release the reader when disconnecting
                await reader.cancel();
                reader.releaseLock();
            }
            if (port) {
                // Close the port to finish the disconnection process
                await port.close();
            }

            // Reset the connection status to false
            isConnected = false;
            // Re-enable the "Connect" button and disable the "Disconnect" button
            document.getElementById('connectButton').disabled = false;
            document.getElementById('disconnectButton').disabled = true;
        }

        // Function to continuously read data from the serial port
        async function readSerialData() {
            // Create a TextDecoder to convert the raw data into text
            const decoder = new TextDecoder();
            while (isConnected) {
                try {
                    // Read data from the serial port
                    const { value, done } = await reader.read();
                    // Stop reading if there is no more data
                    if (done) break;
                    // If data is received, decode it and append it to the output area
                    if (value) {
                        const decodedData = decoder.decode(value);
                        document.getElementById('dataOutput').textContent += decodedData;
                    }
                } catch (error) {
                    // Handle any errors that occur during data reading
                    console.error('Error reading from the serial port:', error);
                    break;
                }
            }
        }

        // Event listeners for the Connect and Disconnect buttons
        document.getElementById('connectButton').addEventListener('click', connectSerialPort);
        document.getElementById('disconnectButton').addEventListener('click', disconnectSerialPort);
    </script>
</body>
</html>

疑问

  1. 低数据吞吐量下浏览器端仍出现BufferOverrunError的原因是什么?
  2. 使用Web Serial API高效处理串口数据以避免缓冲区溢出的策略或最佳实践有哪些?
  3. 该问题是否与Chrome或Web Serial API的缓冲区处理方式有关,还是ESP32端的问题?

解答

1. 低吞吐量下出现BufferOverrunError的原因

  • 读取循环被阻塞:当前代码中,每次读取后直接执行同步DOM操作(textContent += decodedData),DOM操作本身耗时,会阻塞后续的reader.read()调用,导致串口缓冲区的数据无法及时被取走,最终积压溢出。即使单次数据量小,频繁的DOM操作累积也会拖慢读取流程。
  • TextDecoder使用不当:默认调用decoder.decode(value)时,解码器会认为当前数据是完整的,可能导致部分数据残留或解码延迟,间接影响读取效率。
  • bufferSize参数局限性:Web Serial API的底层缓冲区大小并非完全由bufferSize控制,部分平台或Chrome版本中该参数可能被忽略或存在上限,单纯增大参数无法解决根本问题。

2. 避免缓冲区溢出的策略与最佳实践

  • 异步批量更新DOM:将DOM操作移出读取循环,用队列存储数据,通过requestAnimationFrame批量更新,避免阻塞读取流程:
    let dataQueue = [];
    function updateOutput() {
      if (dataQueue.length > 0) {
        document.getElementById('dataOutput').textContent += dataQueue.join('');
        dataQueue = [];
      }
      requestAnimationFrame(updateOutput);
    }
    // 在readSerialData中替换原DOM操作:
    dataQueue.push(decodedData);
    
  • 流式解码文本:初始化TextDecoder时指定{stream: true},确保分块数据能正确拼接,避免解码延迟:
    const decoder = new TextDecoder('utf-8', { stream: true });
    
  • 使用管道API替代手动循环:利用readable.pipeTo()结合TransformStream自动处理数据,底层由浏览器优化读取速度,减少手动循环的阻塞:
    async function readSerialData() {
      const decoder = new TextDecoderStream('utf-8');
      const reader = decoder.readable.getReader();
      port.readable.pipeTo(decoder.writable);
      
      while (isConnected) {
        const { value, done } = await reader.read();
        if (done) break;
        dataQueue.push(value);
      }
    }
    
  • 移除不必要的手动延迟:人为添加延迟会降低读取效率,加剧缓冲区积压,应让reader.read()自然等待数据,不要打断读取流程。

3. 问题归属:Chrome/Web Serial还是ESP32?

从代码和场景来看,核心问题出在浏览器端的读取逻辑,而非ESP32:

  • ESP32仅每2秒发送一次小型数据,不存在数据过载情况;
  • Chrome的Web Serial API本身稳定,但手动读取循环中的阻塞操作(如同步DOM更新)导致缓冲区无法及时清空;
  • 部分Chrome版本对bufferSize支持有限,但这不是核心原因,优化读取逻辑即可解决问题。

当然可以检查ESP32串口发送是否存在异常(如意外连续发送),但根据提供的代码,这种可能性极低。


内容的提问来源于stack exchange,提问作者Julien

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 18:23:18