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

Node.js通过SerialPort向串口设备收发单字节数据问题求助

解决SerialPort串口通信问题:分步交互与超时处理

你的代码存在几个关键问题:直接访问内部_pool缓冲区会读取到未处理的冗余数据,没有按设备要求的分步流程执行通信,也没实现超时控制。以下是修正后的完整方案:

问题分析

  • 直接读取sp._pool是错误的:这是SerialPort的内部私有缓冲区,包含未清理的历史数据或未解析的字节,导致输出"随机值"
  • 未按设备要求的分步交互执行:设备需要先完成"发0x80→收0x81"的握手,再执行"发0x81→收大段数据",你的代码只发了0x80就开始盲目读数据
  • 缺少超时控制:需要在每一步交互中添加超时,避免程序无限等待

修正后的代码

import SerialPort from 'serialport';

// 初始化串口,添加10秒超时(串口无数据时触发timeout事件)
const sp = new SerialPort.SerialPort({
  path: "COM5",
  baudRate: 115200,
  timeout: 10000 // 10秒超时,单位毫秒
});

// 定义分步通信流程
async function communicateWithDevice() {
  try {
    // 第一步:发送0x80,等待接收0x81
    await sendByte(0x80);
    const handshakeResp = await readByte();
    if (handshakeResp !== 0x81) {
      throw new Error(`握手失败,收到字节:0x${handshakeResp.toString(16)}`);
    }
    console.log("握手成功,收到0x81");

    // 第二步:发送0x81,接收约130000字节数据
    await sendByte(0x81);
    const fullData = await readLargeData(130000);
    console.log(`成功接收数据,长度:${fullData.length}字节`);
    // 这里可以处理收到的fullData,比如保存到文件等

    // 通信完成后关闭串口
    await sp.close();
    process.exit(0);
  } catch (err) {
    console.error("通信出错:", err.message);
    await sp.close();
    process.exit(1);
  }
}

// 封装单字节发送函数
function sendByte(byte) {
  return new Promise((resolve, reject) => {
    const buff = Buffer.from([byte]);
    sp.write(buff, (err) => {
      if (err) reject(err);
      else {
        console.log(`已发送:0x${byte.toString(16)}`);
        resolve();
      }
    });
  });
}

// 封装单字节读取函数
function readByte() {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      reject(new Error("读取字节超时"));
    }, 10000);

    const onReadable = () => {
      const data = sp.read(1);
      if (data) {
        clearTimeout(timeout);
        sp.off('readable', onReadable);
        resolve(data.readUInt8(0));
      }
    };

    sp.on('readable', onReadable);
  });
}

// 封装大段数据读取函数
function readLargeData(expectedLength) {
  return new Promise((resolve, reject) => {
    let receivedData = [];
    let totalReceived = 0;
    const timeout = setTimeout(() => {
      reject(new Error("读取大段数据超时"));
    }, 10000);

    const onReadable = () => {
      let chunk;
      while ((chunk = sp.read()) !== null) {
        receivedData.push(...new Uint8Array(chunk));
        totalReceived += chunk.length;
        if (totalReceived >= expectedLength) {
          clearTimeout(timeout);
          sp.off('readable', onReadable);
          resolve(Uint8Array.from(receivedData.slice(0, expectedLength)));
          return;
        }
      }
    };

    sp.on('readable', onReadable);
  });
}

// 串口打开后启动通信流程
sp.on('open', () => {
  console.log("串口已打开,开始通信...");
  communicateWithDevice();
});

// 监听串口错误
sp.on('error', (err) => {
  console.error("串口错误:", err.message);
});

关键改动说明

  1. 避免访问内部缓冲区:用sp.read()方法读取指定长度的字节,这是官方推荐的安全方式,不会读取冗余数据
  2. 分步异步通信:用Promise封装发送/读取操作,严格按照"握手→获取数据"的流程执行,确保设备响应后再执行下一步
  3. 双重超时控制:
    • 初始化串口时设置timeout选项,串口无数据时会触发超时事件
    • 每个读取操作单独添加定时器,确保单步操作不会无限等待
  4. 大段数据处理:循环读取直到收到预期长度的数据,避免单次读取不完整

额外提示

  • 确保串口参数(波特率、奇偶校验、停止位等)和Matlab中使用的完全一致,Matlab能成功说明参数没问题,Python失败大概率是流程或读取方式错误
  • 如果设备发送的数据长度有微小波动,可以调整expectedLength的判断逻辑,比如允许±10字节的误差

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 18:36:24