4. Python操作UART:用pySerial玩转串口通信
串口通信这东西,说简单也简单,说复杂也复杂。我最早接触串口是在一个工控项目里,当时设备死活连不上,折腾了两天才发现是波特率配错了。从那以后,我就养成了一个习惯——先确认参数,再动手写代码。
这一章,咱们就聊聊怎么用Python的pySerial库操作UART。说白了,就是让电脑跟外部设备通过串口“聊天”。
4.1 pySerial库的安装与导入
先装库,这步没啥好说的:
pip install pyserial
装完之后,在代码里导入:
import serial
import serial.tools.list_ports
我个人习惯把list_ports也导进来,因为调试时经常要查设备接在哪个端口上。
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,至少能保证程序不会死等。
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串口编程的四个核心模块都画出来了。你写代码的时候,可以对照着检查自己有没有遗漏哪个环节。
4.7 常见问题与避坑指南
- 端口打不开:检查端口号是否正确,有没有被其他程序占用。Windows下可以用设备管理器查看,Linux下用
ls /dev/tty*。 - 收到乱码:99%是波特率不匹配。我曾经调试一个设备,文档写的是115200,实际是9600,折腾了我一下午。
- 数据丢包:接收速度跟不上发送速度。解决方案是增大缓冲区,或者用流控制(RTS/CTS)。
- 程序卡死:多半是
read()没有设timeout。记住,永远给read()设一个超时。