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

# 安装最新稳定版 pip install pyserial # 如果你在虚拟环境中,确保激活了该环境后再安装
2 查找串口设备
在 Linux 系统中,串口设备通常被表示为 /dev/ttyS* (板载串口) 或 /dev/ttyUSB* (USB转串口芯片,如CH340、FT232、CP2102等)、/dev/ttyACM* (Arduino等设备)。
如何找到你的设备?
-
使用
ls命令: 插入你的串口设备(如USB转TTL模块)之前和之后,分别运行以下命令,对比差异。# 插入前 ls /dev/ttyUSB* # 可能输出:无 或 ls: cannot access '/dev/ttyUSB*': No such file or directory # 插入后 ls /dev/ttyUSB* # 可能输出:/dev/ttyUSB0
-
使用
dmesg命令: 这个命令会显示内核的日志信息,当插入USB设备时,会有相关的信息输出。
(图片来源网络,侵删)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。 -
使用
python -m serial.tools.list_ports:pyserial自带了一个非常方便的工具,可以直接列出所有可用的串口。python -m serial.tools.list_ports # 输出可能如下: # /dev/ttyUSB0 - USB-SERIAL CH340 # /dev/ttyS0
核心库 pyserial 的使用
pyserial 的核心是 serial.Serial 类,通过创建这个类的实例,你就可以打开串口并进行读写操作。
1 打开串口
你需要导入 serial 库,并创建一个 Serial 对象。

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)
常见问题与排查
-
Permission denied(权限被拒绝)-
原因:普通用户没有权限访问
/dev/ttyUSB*设备文件,这些设备通常属于dialout或uucp用户组。 -
解决方法:将你的用户添加到相应的用户组。
# 查看设备属于哪个组 ls -l /dev/ttyUSB0 # 假设输出是 crw-rw---- 1 root dialout ... # 说明属于 dialout 组 # 将当前用户添加到 dialout 组 sudo usermod -a -G dialout $USER # 重要:添加组后需要**重新登录**或重启系统才能生效!
-
-
No such file or directory或SerialException- 原因:
port路径写错了,或者设备没有正确连接/识别。 - 解决方法:再次使用
ls /dev/ttyUSB*或dmesg确认设备名是否正确。
- 原因:
-
能发送,但接收不到数据,或接收到的数据是乱码
- 原因:
- 波特率不匹配:这是最常见的原因,请确保你的 Python 脚本和设备端的波特率完全一致(例如都是 115200)。
- 编码问题:接收到的数据没有正确解码,使用
.decode('utf-8')或.decode('ascii')。 - 数据格式问题:设备发送的是二进制数据(如 0x01, 0x02),而不是字符串,这时不要用
decode(),直接处理字节对象。 - 接线错误:TX (发送) 应该连接到 RX (接收),RX (接收) 应该连接到 TX (发送),GND (地) 必须连接在一起。
- 原因:
-
程序卡在
ser.read()或ser.readline()- 原因:
timeout设置得太长,或者对方根本没有发送数据。 - 解决方法:合理设置
timeout值,如果希望非阻塞,可以设置timeout=0,然后通过ser.in_waiting检查是否有数据。
- 原因:
希望这份详细的指南能帮助你在 Linux 下顺利地进行 Python 串口编程!
