杰瑞科技汇

Serialport 教程,如何快速上手串口通信?

目录

  1. 什么是串口通信?
  2. 为什么需要串口通信?
  3. 核心概念:波特率、数据位、停止位、校验位
  4. 环境准备
    • 硬件准备
    • 软件准备
  5. 使用 Node.js 和 serialport
    • 安装 serialport
    • 查找可用串口
    • 打开并读取串口数据
    • 向串口写入数据
    • 一个完整的示例:与 Arduino 交互
  6. 使用 Python 和 pyserial
    • 安装 pyserial
    • 查找可用串口
    • 打开串口
    • 读取串口数据
    • 向串口写入数据
    • 一个完整的示例:与 Arduino 交互
  7. 实战项目:构建一个简单的串口监视器
  8. 常见问题与解决方案
  9. 总结与进阶

什么是串口通信?

串口通信是一种设备间非常常见的数据传输方式,它是一位一位地进行数据传输,就像单车道一样,一次只允许一辆车通过,相比于并口(一次传输多位数据),串口传输速度较慢,但所需的线缆少,抗干扰能力强,通信距离也更远。

常见的串口物理接口有:

  • 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 个停止位。


环境准备

硬件准备

  1. 一台电脑:Windows, macOS 或 Linux。
  2. 一个串口设备:这里我们以最常用的 Arduino UNO 为例,你也可以是任何其他支持串口的设备。
  3. USB 数据线:用于连接 Arduino 和电脑。

软件准备

  1. Arduino IDE:用于上传代码到 Arduino 并打开其内置的“串口监视器”进行调试。
  2. Node.jsPython 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 串口监视器的功能:可以实时显示串口收到的数据,并且可以输入并发送数据。

这里我们使用 Pythonpyserial 来实现,因为它更简单。

代码 (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()

如何运行:

  1. 将上述代码保存为 serial_monitor.py
  2. 确保你的 Arduino 正在运行之前那个发送 "Hello from Arduino!" 的代码。
  3. 在终端运行 python serial_monitor.py
  4. 从列表中选择你的 Arduino 对应的编号,然后回车。
  5. 你会看到 Arduino 发来的消息,你也可以在 >> 提示符后输入任何内容,按回车发送给 Arduino。

常见问题与解决方案

  • Error: Port is not openSerialException

    • 原因:串口路径错误、设备未连接、或串口正被其他程序(如 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 和 Python 进行串口通信的基本方法。

总结关键点:

  1. 参数一致:串口通信的“四要素”(波特率、数据位、停止位、校验位)必须两端一致。
  2. 资源管理:使用完毕后,务必关闭串口连接,以释放资源。
  3. 错误处理:始终考虑设备未连接、权限不足、数据格式错误等异常情况。

进阶方向:

  • GUI 应用:使用 Electron (Node.js) 或 PyQt / Tkinter (Python) 为你的串口工具创建图形用户界面。
  • 数据可视化:将串口收到的数据(如传感器读数)实时绘制成图表。
  • 协议解析:学习更复杂的通信协议(如 Modbus, CAN bus),并编写代码来解析它们。
  • 多线程/异步:对于需要同时处理用户输入、网络请求和串口读取的复杂应用,深入学习多线程或异步编程模型。

希望这份教程对你有帮助!如果你有任何问题,随时可以提问。

分享:
扫描分享到社交APP
上一篇
下一篇