目录
- 什么是串口通信?
- 为什么需要串口通信?
- 核心概念:波特率、数据位、停止位、校验位
- 环境准备
- 硬件准备
- 软件准备
- 使用 Node.js 和
serialport库- 安装
serialport - 查找可用串口
- 打开并读取串口数据
- 向串口写入数据
- 一个完整的示例:与 Arduino 交互
- 安装
- 使用 Python 和
pyserial库- 安装
pyserial - 查找可用串口
- 打开串口
- 读取串口数据
- 向串口写入数据
- 一个完整的示例:与 Arduino 交互
- 安装
- 实战项目:构建一个简单的串口监视器
- 常见问题与解决方案
- 总结与进阶
什么是串口通信?
串口通信是一种设备间非常常见的数据传输方式,它是一位一位地进行数据传输,就像单车道一样,一次只允许一辆车通过,相比于并口(一次传输多位数据),串口传输速度较慢,但所需的线缆少,抗干扰能力强,通信距离也更远。
常见的串口物理接口有:
- DB-9 (串口):老式台式电脑上常见的梯形接口。
- TTL 电平 (0V/5V 或 0V/3.3V):在电路板和模块之间直接通信,没有物理接口,需要飞线连接。
- USB 转串口:现在很多设备(如 Arduino、各种传感器模块)都通过 USB 接口连接电脑,但内部通信仍然是串口,你需要一个 USB 转串口模块(如 CH340, FTDI 芯片)来让电脑识别。
为什么需要串口通信?
串口通信因其简单、可靠和广泛的支持,至今仍在许多领域扮演重要角色:
- 嵌入式系统:与 Arduino, ESP8266, ESP32, STM32 等微控制器通信。
- 工业控制:连接 PLC、传感器、仪表等工业设备。
- 网络设备:配置路由器、交换机等网络设备。
- GPS 模块:从 GPS 接收器获取位置数据。
- 医疗设备:连接一些医疗监测仪器。
核心概念:波特率、数据位、停止位、校验位
在配置串口时,必须确保通信双方(如你的电脑和 Arduino)的这些参数完全一致,否则无法正常通信。
- 波特率:每秒传输的比特数,它决定了数据传输的速度,常见的有
9600,19200,38400,115200,必须两边设置相同。 - 数据位:实际传输的数据内容所占的位数,通常是
8位。 - 停止位:表示一个字符数据传输结束的位,通常是
1位。 - 校验位:用于简单的错误检测,可以是
无、奇偶校验、标记校验或空格校验,通常是无。
一个常见的串口配置是:9600, 8, N, 1,意思是波特率 9600,8 个数据位,无校验位,1 个停止位。
环境准备
硬件准备
- 一台电脑:Windows, macOS 或 Linux。
- 一个串口设备:这里我们以最常用的 Arduino UNO 为例,你也可以是任何其他支持串口的设备。
- USB 数据线:用于连接 Arduino 和电脑。
软件准备
- Arduino IDE:用于上传代码到 Arduino 并打开其内置的“串口监视器”进行调试。
- Node.js 或 Python 3:根据你选择的教程进行安装。
教程一:使用 Node.js 和 serialport 库
serialport 是 Node.js 中最流行、功能最强大的串口通信库。
1 安装 serialport
打开终端或命令行工具,执行以下命令:
# 创建一个新的项目文件夹 mkdir node-serial-demo cd node-serial-demo # 初始化 npm 项目 npm init -y # 安装 serialport npm install serialport
2 Arduino 端代码
打开 Arduino IDE,复制并上传以下代码,这个代码会每隔 1 秒向串口发送 "Hello from Arduino!"。
void setup() {
// 初始化串口,波特率设置为 9600
Serial.begin(9600);
}
void loop() {
// 向串口发送一条消息
Serial.println("Hello from Arduino!");
// 延时 1000 毫秒(1秒)
delay(1000);
}
上传成功后,在 Arduino IDE 的右上角点击“串口监视器”(一个带放大镜的图标),确保右下角的波特率设置为 9600,你应该能看到每秒都有一行新的输出。
3 Node.js 端代码
a. 查找可用串口
创建一个名为 list_ports.js 的文件,写入:
const { SerialPort } = require('serialport');
// 列出所有可用的串口
SerialPort.list().then(ports => {
console.log('可用串口列表:');
ports.forEach(port => {
console.log(`- 路径: ${port.path}, 制造商: ${port.manufacturer}, 串口号: ${port.serialNumber}`);
});
}).catch(err => {
console.error('无法列出串口:', err.message);
});
运行 node list_ports.js,你应该能在输出中找到你的 Arduino 设备(通常路径是 COM3 或 /dev/tty.usbserial-XXXX)。
b. 打开并读取串口数据
创建一个名为 read_serial.js 的文件:
const { SerialPort } = require('serialport');
// 替换成你自己的 Arduino 串口路径
const portPath = 'COM3'; // Windows: 'COM3', macOS/Linux: '/dev/tty.usbserial-XXXX'
// 创建串口对象
const port = new SerialPort({
path: portPath,
baudRate: 9600, // 必须和 Arduino 端的波特率一致
dataBits: 8,
stopBits: 1,
parity: 'none'
});
// 监听打开事件
port.on('open', () => {
console.log(`串口 ${portPath} 已打开,正在监听数据...`);
});
// 监听数据事件
// 使用 line 解析器,它会自动按换行符分割数据
const lineStream = port.pipe(new SerialPort.parsers.LineParser({ delimiter: '\r\n' }));
lineStream.on('data', (data) => {
// data 是一个字符串,不包含换行符
console.log(`收到数据: ${data}`);
});
// 监听错误事件
port.on('error', (err) => {
console.error('串口发生错误:', err.message);
});
运行 node read_serial.js,如果你的 Arduino 正在运行,你应该会在终端中看到每秒打印出 "Hello from Arduino!"。
c. 向串口写入数据
创建一个名为 write_serial.js 的文件:
const { SerialPort } = require('serialport');
const portPath = 'COM3'; // 替换成你自己的 Arduino 串口路径
const port = new SerialPort({
path: portPath,
baudRate: 9600
});
port.on('open', () => {
console.log(`串口 ${portPath} 已打开,`);
// 向 Arduino 发送 'L',可以用来点亮 LED
console.log('向 Arduino 发送 "L" (点亮 LED)');
port.write('L\n'); // \n 是换行符,Arduino 的 Serial.read() 会读取到它
// 3秒后发送 'H',熄灭 LED
setTimeout(() => {
console.log('向 Arduino 发送 "H" (熄灭 LED)');
port.write('H\n');
}, 3000);
});
port.on('error', (err) => {
console.error('串口发生错误:', err.message);
});
为了让这个例子完整,我们需要修改一下 Arduino 的代码,让它能接收指令并控制板载 LED。
修改后的 Arduino 代码:
void setup() {
Serial.begin(9600);
// 设置引脚 13 为输出,控制板载 LED
pinMode(13, OUTPUT);
}
void loop() {
// 检查串口是否有数据可读
if (Serial.available() > 0) {
// 读取一个字符
char command = Serial.read();
// 根据指令执行操作
if (command == 'L') {
digitalWrite(13, HIGH); // 点亮 LED
Serial.println("LED is ON");
} else if (command == 'H') {
digitalWrite(13, LOW); // 熄灭 LED
Serial.println("LED is OFF");
}
}
}
上传这个新代码到 Arduino,然后在 Node.js 中运行 node write_serial.js,你会看到 Arduino 的板载 LED 先亮 3 秒,然后熄灭,Node.js 的终端也会收到 Arduino 的反馈信息。
教程二:使用 Python 和 pyserial 库
pyserial 是 Python 中最经典的串口通信库。
1 安装 pyserial
打开终端或命令行工具,执行:
pip install pyserial
2 Arduino 端代码
与 Node.js 教程中的 修改后 Arduino 代码 相同,请确保它已上传到你的 Arduino。
3 Python 端代码
a. 查找可用串口
创建一个名为 list_ports.py 的文件:
import serial.tools.list_ports
ports = serial.tools.list_ports.comports()
print("可用串口列表:")
for port in ports:
print(f"- 设备: {port.device}, 描述: {port.description}, 制造商: {port.manufacturer}")
运行 python list_ports.py,找到你的 Arduino 设备(如 COM3 或 /dev/tty.usbserial-XXXX)。
b. 读取串口数据
创建一个名为 read_serial.py 的文件:
import serial
import time
# 替换成你自己的 Arduino 串口路径
port_path = 'COM3' # Windows: 'COM3', macOS/Linux: '/dev/tty.usbserial-XXXX'
try:
# 打开串口
ser = serial.Serial(port_path, 9600, timeout=1)
print(f"串口 {port_path} 已打开,正在监听数据...")
while True:
if ser.in_waiting > 0: # 检查是否有数据可读
line = ser.readline().decode('utf-8').rstrip() # 读取一行并解码
print(f"收到数据: {line}")
time.sleep(0.1) # 避免CPU占用过高
except serial.SerialException as e:
print(f"无法打开串口: {e}")
except KeyboardInterrupt:
print("程序终止。")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("串口已关闭。")
运行 python read_serial.py,你应该会看到每秒打印 "Hello from Arduino!"。
c. 向串口写入数据
创建一个名为 write_serial.py 的文件:
import serial
import time
port_path = 'COM3' # 替换成你自己的 Arduino 串口路径
try:
ser = serial.Serial(port_path, 9600, timeout=1)
print(f"串口 {port_path} 已打开。")
# 发送 'L' 点亮 LED
print("向 Arduino 发送 'L' (点亮 LED)")
ser.write(b'L\n') # pyserial 需要字节流,所以加 b 前缀
# 等待 3 秒
time.sleep(3)
# 发送 'H' 熄灭 LED
print("向 Arduino 发送 'H' (熄灭 LED)")
ser.write(b'H\n')
except serial.SerialException as e:
print(f"无法打开串口: {e}")
except KeyboardInterrupt:
print("程序终止。")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("串口已关闭。")
同样,运行这个脚本,你会看到 Arduino 的 LED 亮灭。
实战项目:构建一个简单的串口监视器
这个项目将实现一个类似 Arduino IDE 串口监视器的功能:可以实时显示串口收到的数据,并且可以输入并发送数据。
这里我们使用 Python 和 pyserial 来实现,因为它更简单。
代码 (serial_monitor.py):
import serial
import serial.tools.list_ports
import threading
import time
import sys
class SerialMonitor:
def __init__(self):
self.port = None
self.running = False
self.read_thread = None
def list_ports(self):
"""列出所有可用串口"""
ports = serial.tools.list_ports.comports()
if not ports:
print("未找到可用串口。")
return []
print("可用串口列表:")
for i, port in enumerate(ports):
print(f"{i+1}. {port.device} - {port.description}")
return ports
def connect(self, port_path, baudrate=9600):
"""连接到指定串口"""
try:
self.port = serial.Serial(port_path, baudrate, timeout=1)
print(f"成功连接到 {port_path},波特率 {baudrate}")
self.running = True
self.read_thread = threading.Thread(target=self._read_loop)
self.read_thread.daemon = True
self.read_thread.start()
return True
except serial.SerialException as e:
print(f"连接失败: {e}")
return False
def _read_loop(self):
"""在后台持续读取串口数据"""
while self.running and self.port and self.port.is_open:
if self.port.in_waiting > 0:
try:
data = self.port.readline().decode('utf-8').rstrip()
print(f"\n<< 收到: {data}\n>> ", end='') # 打印后重新显示提示符
except UnicodeDecodeError:
print("\n<< 收到无法解码的数据\n>> ", end='')
time.sleep(0.1)
def send_data(self, data):
"""向串口发送数据"""
if self.port and self.port.is_open:
try:
self.port.write((data + '\n').encode('utf-8'))
except Exception as e:
print(f"发送失败: {e}")
def disconnect(self):
"""断开连接"""
self.running = False
if self.read_thread and self.read_thread.is_alive():
self.read_thread.join(timeout=2)
if self.port and self.port.is_open:
self.port.close()
print("串口已断开。")
if __name__ == "__main__":
monitor = SerialMonitor()
# 列出并选择串口
ports = monitor.list_ports()
if not ports:
sys.exit(1) # 如果没有串口,退出程序
try:
choice = int(input("请选择串口编号: ")) - 1
if 0 <= choice < len(ports):
selected_port = ports[choice].device
if monitor.connect(selected_port):
print("串口监视器已启动,输入 'exit' 退出。")
# 在主线程中处理用户输入
while True:
try:
user_input = input(">> ")
if user_input.lower() == 'exit':
break
monitor.send_data(user_input)
except KeyboardInterrupt:
break
else:
print("无效的选择。")
except ValueError:
print("请输入有效的数字。")
finally:
monitor.disconnect()
如何运行:
- 将上述代码保存为
serial_monitor.py。 - 确保你的 Arduino 正在运行之前那个发送 "Hello from Arduino!" 的代码。
- 在终端运行
python serial_monitor.py。 - 从列表中选择你的 Arduino 对应的编号,然后回车。
- 你会看到 Arduino 发来的消息,你也可以在
>>提示符后输入任何内容,按回车发送给 Arduino。
常见问题与解决方案
-
Error: Port is not open或SerialException:- 原因:串口路径错误、设备未连接、或串口正被其他程序(如 Arduino IDE 的监视器)占用。
- 解决:1. 检查串口路径是否正确,2. 确保设备已连接,3. 关闭所有可能占用该串口的软件(特别是 Arduino IDE 的串口监视器)。
-
收不到数据或数据乱码:
- 原因:最常见的原因是 波特率不匹配,代码中设置的波特率和设备端的波特率不一致。
- 解决:检查并确保代码和设备(如 Arduino)中的
Serial.begin()或baudRate设置完全相同。
-
ENOENT: no such file or directory:- 原因:在 Linux/macOS 上,串口路径不存在或权限不足。
- 解决:1. 确认路径正确(如
/dev/tty.usbserial-XXXX),2. 如果是权限问题,你可能需要将用户添加到dialout组(Linux),或在命令前加sudo(不推荐)。
-
如何处理二进制数据(非文本)?
- Node.js: 不要使用
LineParser,直接监听'data'事件,data会是一个Buffer对象,你可以用data.toString('hex')或data.toString('base64')来查看其内容。 - Python: 不要用
.decode('utf-8'),直接读取ser.read(size)会返回bytes对象,你可以用data.hex()来查看十六进制表示。
- Node.js: 不要使用
总结与进阶
恭喜!你已经掌握了使用 Node.js 和 Python 进行串口通信的基本方法。
总结关键点:
- 参数一致:串口通信的“四要素”(波特率、数据位、停止位、校验位)必须两端一致。
- 资源管理:使用完毕后,务必关闭串口连接,以释放资源。
- 错误处理:始终考虑设备未连接、权限不足、数据格式错误等异常情况。
进阶方向:
- GUI 应用:使用
Electron(Node.js) 或PyQt/Tkinter(Python) 为你的串口工具创建图形用户界面。 - 数据可视化:将串口收到的数据(如传感器读数)实时绘制成图表。
- 协议解析:学习更复杂的通信协议(如 Modbus, CAN bus),并编写代码来解析它们。
- 多线程/异步:对于需要同时处理用户输入、网络请求和串口读取的复杂应用,深入学习多线程或异步编程模型。
希望这份教程对你有帮助!如果你有任何问题,随时可以提问。
