先澄清一个常见误解:很多人以为在 Node.js 里用 console.log() 就能把数据送到物理串口,就像它在终端打印一样自然。实际上,这条路走不通。
console.log() 默认写入的是 process.stdout,也就是标准输出流,而标准输出最终指向的是终端——无论是 VSCode 的集成终端,还是系统自带的 shell 窗口。物理串口是另一回事,它是一个独立的硬件资源,在操作系统层面被映射为 /dev/ttyUSB0(Linux/macOS)或 COM3(Windows)这样的设备文件。要跟它打交道,必须借助专门的串口驱动库,比如 serialport,然后显式地打开设备、配置参数,才能进行读写操作。

为什么 console.log() 无法输出到串口
其实,串口和终端控制台完全是两码事。Node.js 本身不提供原生串口 API,所有操作都得依赖第三方模块。这里有几个关键点需要厘清:
console.log()只能写入 stdout/stderr,也就是终端窗口,跟串口设备八竿子打不着- 即便用
node --inspect模式调试,调试器的输出走的也是 V8 的 inspector 协议,跟串口毫无关系 - 有人可能尝试用
fs.writeSync(1, 'hello')强行写入文件描述符 1,结果还是打印到终端,因为文件描述符 1 指向的就是 stdout,而非串口设备
用 serialport 写入串口的最小可行步骤
那么,正确的做法是什么?很简单:先确认系统已经识别到串口设备(Linux/macOS 下运行 ls /dev/tty*,Windows 去设备管理器看 COM 端口号),然后安装并调用 serialport 库。
- 安装:
npm install serialport(注意 v12 及以上版本要求 Node.js ≥ 16.17) - 代码里必须指定波特率、数据位等参数,一个都不能少,否则
open()会静默失败,连个错误提示都没有 - 写入前一定要确认端口已经打开,
isOpen === true;如果没打开就调用write(),会直接抛出Error: Port is not open - 字符串需要转为 Buffer:用
port.write(Buffer.from('AT\r\n')),或者直接写port.write('AT\r\n'),后者会自动转成 ASCII 编码
来看一个完整的示例(serial-test.js):
const { SerialPort } = require('serialport');
const port = new SerialPort({
path: '/dev/ttyUSB0', // macOS 用 '/dev/tty.usbserial-XXXX',Windows 用 'COM3'
baudRate: 9600,
});
port.on('open', () => {
console.log('串口已打开');
port.write('Hello from Node!\r\n'); // 自动编码为 UTF-8
});
port.on('error', (err) => {
console.error('串口错误:', err.message);
});
VSCode 终端里运行串口脚本常踩的坑
VSCode 的集成终端对串口权限、路径、环境变量比独立终端更敏感,稍不注意就容易掉坑里。这里总结几个最常见的:
- Linux/macOS 下权限不足:报错
Error: Permission denied, cannot open /dev/ttyUSB0。解决方案是运行sudo usermod -a -G dialout $USER,然后重启系统(只需一次) - 路径写错:很多人会写成
./dev/ttyUSB0或dev/ttyUSB0,漏了开头的/。记住,必须是绝对路径 - 端口被占用:如果 Arduino IDE、minicom 或其他工具正在使用该串口,你得先关掉它们。可以用
lsof -i | grep tty查一下哪个进程占用了端口 - Node.js 版本不兼容:
serialportv12 要求 Node.js ≥ 16.17。如果用了 nvm 切换版本后没重启 VSCode 窗口,终端里node -v显示的和实际加载的版本可能不一致,导致莫名其妙的问题
调试串口通信时别忽略 data 事件监听
只写不读,就像闭着眼睛跟人说话——你永远不知道对方有没有收到,更不知道它回了什么。真正有效的调试需要同时处理收发:
- 加上
port.on('data', (chunk) => console.log('收到:', chunk.toString())),看看设备有没有返回数据 - 注意:有些设备要求结尾带
\r\n,有些只要\n,不匹配的话设备会直接无视你的指令 - 如果
data事件一直不触发,先用screen /dev/ttyUSB0 9600(macOS/Linux)或 PuTTY(Windows)手动测试一下,确认设备本身是否正常工作 - 避免在
data回调里做耗时操作,比如文件写入,否则容易丢包。高频数据建议用parser(比如ReadlineParser)来分帧处理
最后想强调一点:串口不是普通的管道,它有电气特性、缓冲区大小、流控机制。写进去的数据不代表设备立刻执行,也不代表马上有回传。最容易忽略的是——没有等待设备就绪就发指令,或者发得太快导致缓冲溢出。这些都是实际调试中反复出现的坑,值得格外留意。