使用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>
疑问
- 低数据吞吐量下浏览器端仍出现
BufferOverrunError的原因是什么? - 使用Web Serial API高效处理串口数据以避免缓冲区溢出的策略或最佳实践有哪些?
- 该问题是否与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
相关产品推荐
相关产品推荐

