杰瑞科技汇

Linux Python串口编程如何实现?

  1. 准备工作:安装必要的库和查找串口设备。
  2. 核心库 pyserial:介绍如何安装和使用这个最主流的 Python 串口库。
  3. 完整代码示例:包括发送数据和接收数据的完整脚本。
  4. 高级功能:介绍线程、超时、流控等进阶概念。
  5. 常见问题与排查:帮助你解决开发中可能遇到的坑。

准备工作

1 安装 pyserial

pyserial 是 Python 中最流行、功能最全面的串口通信库,你可以使用 pip 来安装它。

Linux Python串口编程如何实现?-图1
(图片来源网络,侵删)
# 安装最新稳定版
pip install pyserial
# 如果你在虚拟环境中,确保激活了该环境后再安装

2 查找串口设备

在 Linux 系统中,串口设备通常被表示为 /dev/ttyS* (板载串口) 或 /dev/ttyUSB* (USB转串口芯片,如CH340、FT232、CP2102等)、/dev/ttyACM* (Arduino等设备)。

如何找到你的设备?

  1. 使用 ls 命令: 插入你的串口设备(如USB转TTL模块)之前和之后,分别运行以下命令,对比差异。

    # 插入前
    ls /dev/ttyUSB*
    # 可能输出:无 或 ls: cannot access '/dev/ttyUSB*': No such file or directory
    # 插入后
    ls /dev/ttyUSB*
    # 可能输出:/dev/ttyUSB0
  2. 使用 dmesg 命令: 这个命令会显示内核的日志信息,当插入USB设备时,会有相关的信息输出。

    Linux Python串口编程如何实现?-图2
    (图片来源网络,侵删)
    dmesg | tail
    # 插入设备后,你可能会看到类似下面的输出:
    # [12345.678901] usb 1-1.2: new full-speed USB device number 4 using ehci-pci
    # [12345.800123] usb 1-1.2: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice= 1.00
    # [12345.800234] usb 1-1.2: New USB device strings: Mfr=0, Product=1, SerialNumber=2
    # [12345.800345] usb 1-1.2: Product: USB-SERIAL CH340
    # [12345.800456] usb 1-1.2: Manufacturer: wch.cn
    # [12345.800567] ch341 1-1.2:1.0: ch341-3 converter detected
    # [12345.800678] usb 1-1.2: ch341-3 converter now attached to ttyUSB0

    从上面的日志中,我们可以确定设备是 /dev/ttyUSB0

  3. 使用 python -m serial.tools.list_portspyserial 自带了一个非常方便的工具,可以直接列出所有可用的串口。

    python -m serial.tools.list_ports
    # 输出可能如下:
    # /dev/ttyUSB0 - USB-SERIAL CH340
    # /dev/ttyS0

核心库 pyserial 的使用

pyserial 的核心是 serial.Serial 类,通过创建这个类的实例,你就可以打开串口并进行读写操作。

1 打开串口

你需要导入 serial 库,并创建一个 Serial 对象。

Linux Python串口编程如何实现?-图3
(图片来源网络,侵删)
import serial
# --- 基本参数 ---
port = '/dev/ttyUSB0'  # 你的串口设备名
baudrate = 9600       # 波特率,必须和设备端一致
bytesize = serial.EIGHTBITS  # 数据位
parity = serial.PARITY_NONE  # 校验位
stopbits = serial.STOPBITS_ONE # 停止位
timeout = 1           # 超时时间,单位:秒
try:
    # 打开串口
    ser = serial.Serial(
        port=port,
        baudrate=baudrate,
        bytesize=bytesize,
        parity=parity,
        stopbits=stopbits,
        timeout=timeout
    )
    if ser.is_open:
        print(f"串口 {ser.name} 打开成功!")
        print(f"当前设置: {ser}")
except serial.SerialException as e:
    print(f"无法打开串口 {port}: {e}")
    exit()
# ... 在这里进行读写操作 ...
# 关闭串口
ser.close()
print(f"串口 {ser.name} 已关闭。")

参数解释:

  • port: 串口设备路径。
  • baudrate: 波特率(如 9600, 115200),这是最重要的参数,通信双方必须一致。
  • bytesize: 数据位,通常是 serial.EIGHTBITS (8位)。
  • parity: 校验位,可以是 serial.PARITY_NONE (无), serial.PARITY_EVEN (偶), serial.PARITY_ODD (奇)。
  • stopbits: 停止位,通常是 serial.STOPBITS_ONE (1位)。
  • timeout: 超时设置,非常重要!
    • timeout=0: 非阻塞模式,read() 立即返回,如果没有数据,返回空字节。
    • timeout=1: 阻塞模式,read() 最多等待1秒,如果1秒内没有数据,则返回已读取到的数据(如果有的话),如果完全没有数据,则返回空,这是最常用的设置。
    • timeout=None: 永久阻塞模式,read() 会一直等待,直到有数据到达。

2 写数据 (write)

使用 write() 方法向串口发送数据。注意write() 方法发送的是字节(bytes),而不是字符串。

# 发送字符串,需要先编码为字节
data_to_send = "Hello, Serial Port!"
ser.write(data_to_send.encode('utf-8')) # 使用 utf-8 编码
print(f"已发送: {data_to_send}")
# 也可以直接发送字节
data_bytes = b'\x01\x02\x03\x04' # 发送十六进制数据
ser.write(data_bytes)
print(f"已发送字节: {data_bytes}")

3 读数据 (read)

read() 方法从串口缓冲区读取数据。

# 读取1个字节
byte_read = ser.read(1)
print(f"读取到1个字节: {byte_read}")
# 读取10个字节
ten_bytes = ser.read(10)
print(f"读取到10个字节: {ten_bytes}")
# 读取一行,以换行符 '\n' 或 '\r' 
# 注意:如果对方没有发送换行符,这个方法会一直阻塞(取决于timeout设置)
line = ser.readline()
print(f"读取到一行: {line.decode('utf-8').strip()}") # 解码并去除首尾空白

完整代码示例

下面是一个完整的、可运行的示例,它打开串口,发送一个 "ping",然后等待并打印接收到的 "pong"。

前提: 你需要有一个连接到该串口的设备(比如另一块 Arduino 或另一个运行 Python 脚本的电脑),并且这个设备在收到 "ping" 后会回复 "pong"。

import serial
import time
def main():
    # --- 串口配置 ---
    port = '/dev/ttyUSB0'
    baudrate = 9600
    timeout = 1  # 1秒超时
    try:
        ser = serial.Serial(port, baudrate, timeout=timeout)
        print(f"成功打开串口: {ser.name}")
    except serial.SerialException as e:
        print(f"错误: 无法打开串口 {port}. {e}")
        return
    try:
        # 发送数据
        message = "ping"
        ser.write(message.encode('utf-8'))
        print(f"已发送: '{message}'")
        # 等待一小会儿,让设备有时间处理和回复
        time.sleep(0.5)
        # 读取响应
        if ser.in_waiting > 0:  # 检查输入缓冲区是否有数据
            response = ser.readline() # 读取一行
            print(f"收到响应: '{response.decode('utf-8').strip()}'")
        else:
            print("在超时时间内未收到任何数据。")
    finally:
        # 确保串口被关闭
        ser.close()
        print(f"串口 {port} 已关闭。")
if __name__ == '__main__':
    main()

高级功能

1 使用线程进行异步读写

在很多应用中,你希望一边发送数据,一边能及时接收数据,而不会因为等待接收而阻塞整个程序,这时,多线程就派上用场了。

import serial
import threading
import time
# 创建一个全局的串口对象
ser = None
def read_thread():
    """专门用于读取数据的线程函数"""
    global ser
    print("读取线程启动...")
    while True:
        if ser and ser.in_waiting > 0:
            data = ser.readline()
            print(f"[接收] {data.decode('utf-8').strip()}")
        time.sleep(0.1) # 短暂休眠,避免CPU空转
def main():
    global ser
    port = '/dev/ttyUSB0'
    baudrate = 9600
    try:
        ser = serial.Serial(port, baudrate, timeout=1)
        print(f"串口 {ser.name} 打开成功")
    except Exception as e:
        print(f"打开串口失败: {e}")
        return
    # 启动读取线程
    read_thread_obj = threading.Thread(target=read_thread, daemon=True)
    read_thread_obj.start()
    try:
        # 主线程负责发送数据
        while True:
            message = input("请输入要发送的消息 (输入 'exit' 退出): ")
            if message.lower() == 'exit':
                break
            ser.write(message.encode('utf-8'))
            print(f"[发送] {message}")
            time.sleep(0.5) # 发送后稍作等待
    finally:
        ser.close()
        print("串口已关闭,程序退出。")
if __name__ == '__main__':
    main()

2 流控

串口有硬件流控(RTS/CTS)和软件流控(XON/XOFF)。

  • 硬件流控:使用 rtscts=True 启用。
  • 软件流控:使用 xonxoff=True 启用。

大多数简单应用(如连接单片机)都不需要流控,保持默认值 False 即可。

ser = serial.Serial('/dev/ttyUSB0', 9600, rtscts=True, xonxoff=False)

常见问题与排查

  1. Permission denied (权限被拒绝)

    • 原因:普通用户没有权限访问 /dev/ttyUSB* 设备文件,这些设备通常属于 dialoutuucp 用户组。

    • 解决方法:将你的用户添加到相应的用户组。

      # 查看设备属于哪个组
      ls -l /dev/ttyUSB0
      # 假设输出是 crw-rw---- 1 root dialout ...
      # 说明属于 dialout 组
      # 将当前用户添加到 dialout 组
      sudo usermod -a -G dialout $USER
      # 重要:添加组后需要**重新登录**或重启系统才能生效!
  2. No such file or directorySerialException

    • 原因port 路径写错了,或者设备没有正确连接/识别。
    • 解决方法:再次使用 ls /dev/ttyUSB*dmesg 确认设备名是否正确。
  3. 能发送,但接收不到数据,或接收到的数据是乱码

    • 原因
      1. 波特率不匹配:这是最常见的原因,请确保你的 Python 脚本和设备端的波特率完全一致(例如都是 115200)。
      2. 编码问题:接收到的数据没有正确解码,使用 .decode('utf-8').decode('ascii')
      3. 数据格式问题:设备发送的是二进制数据(如 0x01, 0x02),而不是字符串,这时不要用 decode(),直接处理字节对象。
      4. 接线错误:TX (发送) 应该连接到 RX (接收),RX (接收) 应该连接到 TX (发送),GND (地) 必须连接在一起。
  4. 程序卡在 ser.read()ser.readline()

    • 原因timeout 设置得太长,或者对方根本没有发送数据。
    • 解决方法:合理设置 timeout 值,如果希望非阻塞,可以设置 timeout=0,然后通过 ser.in_waiting 检查是否有数据。

希望这份详细的指南能帮助你在 Linux 下顺利地进行 Python 串口编程!

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