4. Python操作UART:用pySerial玩转串口通信

串口通信这东西,说简单也简单,说复杂也复杂。我最早接触串口是在一个工控项目里,当时设备死活连不上,折腾了两天才发现是波特率配错了。从那以后,我就养成了一个习惯——先确认参数,再动手写代码。

这一章,咱们就聊聊怎么用Python的pySerial库操作UART。说白了,就是让电脑跟外部设备通过串口“聊天”。

4.1 pySerial库的安装与导入

先装库,这步没啥好说的:

pip install pyserial

装完之后,在代码里导入:

import serial
import serial.tools.list_ports

我个人习惯把list_ports也导进来,因为调试时经常要查设备接在哪个端口上。

小技巧: 如果你在Linux上遇到权限问题,记得把用户加到dialout组里:sudo usermod -a -G dialout $USER。Windows用户一般不用操心这个。

4.2 串口参数配置——参数配不对,通信全白费

串口通信的核心参数就四个:波特率、数据位、停止位、校验位。这四个参数必须收发双方完全一致,否则数据就是一堆乱码。

参数 常用值 说明
波特率 9600, 115200, 460800 每秒传输的符号数,越高越快
数据位 7, 8 一帧中有效数据的位数,通常用8位
停止位 1, 1.5, 2 帧结束标志,常用1位
校验位 None, Even, Odd 检错机制,无校验最常用

来看一个典型的配置代码:

ser = serial.Serial(
    port='COM3',          # Windows下是COM口,Linux下是/dev/ttyUSB0
    baudrate=115200,      # 波特率
    bytesize=8,           # 数据位
    parity='N',           # 校验位:N-无,E-偶校验,O-奇校验
    stopbits=1,           # 停止位
    timeout=1             # 超时时间,单位秒
)

这里有个坑,我踩过好几次——timeout参数。如果不设timeout,read()会一直阻塞,程序就卡死了。设了timeout,至少能保证程序不会死等。

注意: 波特率不是越高越好。115200在短距离没问题,但如果你用长线缆或者环境干扰大,建议降到9600。我曾经在一个项目里用460800,结果数据全是错的,降到115200就正常了。

4.3 串口读写操作——发数据、收数据

配置好串口之后,就可以开始通信了。pySerial的读写接口非常直观。

4.3.1 发送数据

# 发送字符串(需要编码成bytes)
ser.write(b'Hello, UART!\n')

# 发送十六进制数据
ser.write(bytes([0x01, 0x02, 0x03, 0xFC]))

# 发送整数
ser.write(struct.pack('<H', 1234))  # 小端模式,2字节

你想想看,发送数据时最容易犯什么错?忘记编码。Python 3里字符串和bytes是严格区分的,直接传字符串会报错。

4.3.2 接收数据

# 读取指定字节数
data = ser.read(10)  # 读10个字节

# 读取一行(直到换行符)
line = ser.readline()  # 注意:会包含换行符

# 读取所有可用数据
all_data = ser.read(ser.in_waiting)

我个人最常用的是readline(),前提是设备发送的数据以换行符结尾。如果设备发的是二进制协议,那就用read()指定长度。

经验之谈: 接收数据时,永远不要假设一次就能读完所有数据。串口是流式传输,数据可能分多次到达。正确的做法是循环读取,直到满足条件。

4.4 串口缓冲区管理——别让数据“堵车”

缓冲区管理是串口编程里最容易忽视、也最容易出问题的地方。pySerial内部维护了两个缓冲区:发送缓冲区和接收缓冲区。

# 清空接收缓冲区
ser.reset_input_buffer()

# 清空发送缓冲区
ser.reset_output_buffer()

# 查看缓冲区数据量
bytes_in = ser.in_waiting   # 接收缓冲区中等待读取的字节数
bytes_out = ser.out_waiting # 发送缓冲区中等待发送的字节数

为什么要清空缓冲区?我给你讲个真实案例。有一次我在调试一个传感器模块,每次上电后读到的第一包数据都是乱的。后来发现,模块上电时会发一串初始化信息,这些信息残留在缓冲区里,被我当成有效数据读走了。解决方案很简单——在正式通信前清空一次接收缓冲区。

建议: 每次打开串口后,先调用ser.reset_input_buffer(),确保缓冲区是干净的。这已经成了我的“肌肉记忆”。

4.5 完整示例:一个简单的串口收发程序

把上面的知识点串起来,写一个完整的示例:

import serial
import time

def main():
    # 配置串口
    ser = serial.Serial(
        port='COM3',
        baudrate=115200,
        bytesize=8,
        parity='N',
        stopbits=1,
        timeout=1
    )
    
    # 清空缓冲区
    ser.reset_input_buffer()
    ser.reset_output_buffer()
    
    print(f"串口已打开: {ser.portstr}")
    
    try:
        while True:
            # 发送数据
            cmd = b'AT\r\n'
            ser.write(cmd)
            print(f"发送: {cmd}")
            
            # 接收响应
            time.sleep(0.1)  # 给设备一点处理时间
            response = ser.read(ser.in_waiting)
            if response:
                print(f"接收: {response}")
            
            time.sleep(1)
            
    except KeyboardInterrupt:
        print("用户中断")
    finally:
        ser.close()
        print("串口已关闭")

if __name__ == '__main__':
    main()

这个例子虽然简单,但包含了串口编程的完整流程:打开→配置→清空缓冲区→读写→关闭。嗯,这里要注意,finally块里一定要关闭串口,否则下次打开时会报“端口被占用”。

4.6 知识结构总览

下面这张图把本章的核心知识点串起来了,你可以对照着回顾一下:

pySerial串口编程知识体系 pySerial库 串口参数配置 发送数据 接收数据 缓冲区管理 波特率 数据位/停止位 校验位/超时 write() bytes编码 read() readline() in_waiting reset_input reset_output 核心原则:参数一致 → 清空缓冲区 → 循环读写 → 异常处理 避坑:忘记编码、忘记超时、忘记清空缓冲区

这张图把pySerial串口编程的四个核心模块都画出来了。你写代码的时候,可以对照着检查自己有没有遗漏哪个环节。

4.7 常见问题与避坑指南

  • 端口打不开:检查端口号是否正确,有没有被其他程序占用。Windows下可以用设备管理器查看,Linux下用ls /dev/tty*。
  • 收到乱码:99%是波特率不匹配。我曾经调试一个设备,文档写的是115200,实际是9600,折腾了我一下午。
  • 数据丢包:接收速度跟不上发送速度。解决方案是增大缓冲区,或者用流控制(RTS/CTS)。
  • 程序卡死:多半是read()没有设timeout。记住,永远给read()设一个超时。
一句话总结: 串口编程就是“参数配好、缓冲区清干净、读写循环、异常处理”。把这四步做到位,基本不会出大问题。