python读取COM口信息并使用串口

python控制串口基于pySerial模块。

首先安装pySerial。

1
pip install pyserial

获取可用串口信息

1
2
3
4
5
import serial.tools.list_ports
ports = []
for port in serial.tools.list_ports.comports():
ports.append(port.name)
print(ports)

输出:

1
['COM1']

​ pySerial 包有一个 comports() 方法,它返回可用 COM 端口的列表。 此列表中的每个对象都是 ListPortInfo 类型。comports() 函数位于模块 list_ports 中,该模块位于工具中。 通过 import serial.tools.list_ports 来导入整个模块。

​ 下面这个示例展示了所有信息。

1
2
3
import serial.tools.list_ports

print([port.device for port in serial.tools.list_ports.comports()])

以下是 ListPortInfo 对象可以为我们提供的有关 COM 端口的详细信息:

对象描述
device完整的设备名称/路径。 当索引访问时,这将作为第一个元素返回。
name短设备名称。
description人类可读的描述。 当索引访问时,这将作为第二个元素返回。
hwid硬件 ID。 当索引访问时,这将作为第三个元素返回。
vidUSB 供应商 ID。
pidUSB 产品 ID。
serial_number字符串形式的 USB 序列号。
locationUSB 设备位置字符串。
manufacturerUSB 制造商字符串,由设备报告。
productUSB 产品字符串,由设备报告。
interface特定于接口的描述。

示例:

1
2
3
4
5
6
7
8
import serial.tools.list_ports

port_data = []
for port in serial.tools.list_ports.comports():
info = dict({"Name": port.name, "Description": port.description, "Manufacturer": port.manufacturer,
"Hwid": port.hwid})
port_data.append(info)
print (port_data)

输出:

1
2
[{'Name': 'COM1', 'Description': 'Communications Port (COM1)', 'Manufacturer': '(Standard port types)',
'Hwid': 'ROOT\\PORTS\\0000'}]

配置和打开串口

pySerial 配置和打开串口有两种方式,第一种方式是在调用函数接口打开串口时传入配置参数,第二种方式是先配置参数,然后再打开串口。

只介绍第一种。

1
2
3
4
5
6
7
8
9
# 方式1:调用函数接口打开串口时传入配置参数
import serial

ser = serial.Serial("COM17", 115200) # 打开COM17,将波特率配置为115200,其余参数使用默认值
if ser.isOpen(): # 判断串口是否成功打开
print("打开串口成功。")
print(ser.name) # 输出串口号
else:
print("打开串口失败。")

在使用 serial.Serial() 创建串口实例时,可以传入的参数很多,常用的参数如下(默认值用红色标记):

  • port - 串口设备名或 None
  • baudrate - 波特率,可以是50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000。
  • bytesize - 数据位,可取值为:FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
  • parity - 校验位,可取值为:PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE。
  • stopbits - 停止位,可取值为:STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TOW。
  • xonxoff - 软件流控,可取值为 True, False
  • rtscts - 硬件(RTS/CTS)流控,可取值为 True, False
  • dsr/dtr - 硬件(DSR/DTR)流控,可取值为 True, False
  • timeout - 读超时时间,可取值为 None, 0 或者其他具体数值(支持小数)。当设置为 None 时,表示阻塞式读取,一直读到期望的所有数据才返回;当设置为 0 时,表示非阻塞式读取,无论读取到多少数据都立即返回;当设置为其他数值时,表示设置具体的超时时间(以秒为单位),如果在该时间内没有读取到所有数据,则直接返回。
  • write_timeout: 写超时时间,可取值为 None, 0 或者其他具体数值(支持小数)。参数值起到的效果参考 timeout 参数。

关闭串口

直接调用close方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
import serial

ser = serial.Serial("COM17", 115200) # 打开 COM17,将波特率配置为115200,其余参数使用默认值
if ser.isOpen(): # 判断串口是否成功打开
print("打开串口成功。")
else:
print("打开串口失败。")

ser.close()
if ser.isOpen(): # 判断串口是否关闭
print("串口未关闭。")
else:
print("串口已关闭。")

串口发送数据

使用write()方法向串口发送数据。

注意事项:
○ write() 方法发送的数据必须是字节类型,通常需要对字符串进行encode编码。
○ write() 方法执行完成后,会将发送的字节数作为返回值。

1
2
3
4
5
6
7
import serial
ser =serial.Serial('COM3',9600,timeout=1)
data_to_send = "Hello, Serial!"
write_len = ser.write(data_to_send.encode())
print(f"已发送数据: {data_to_send}")
print("串口发出{}个字节。".format(write_len))
ser.close()

数据帧打包

在项目中,我需要与stm32进行串口通信,采用数据帧格式,需要先打包,再发送串口数据。

使用python的struct模块进行打包。具体方法为struct.pack(),官方文档参考:struct — 将字节解释为打包的二进制数据 — Python 3.14.0 文档 - Python 文档

  • struct.pack(format, v1, v2, )

    返回一个包含值 v1, v2, … 的 bytes 对象,这些值根据格式字符串 format 进行打包。参数必须与格式所要求的数值完全匹配。

    默认情况下,C 类型以机器的原生格式和字节顺序表示,并通过跳过填充字节(如果需要,根据 C 编译器使用的规则)进行正确对齐。这种行为被选择的目的是使打包的结构字节与相应 C 结构的内存布局完全匹配。是使用原生字节顺序和填充还是标准格式取决于应用程序。

    或者,格式字符串的第一个字符可以用于根据下表指示打包数据的字节顺序、大小和对齐方式:

字符 字节顺序 大小 对齐
@ 原生 原生 原生
= 原生 标准
< 小端 标准
> 大端 标准
! 网络 (= 大端) 标准

格式字符的含义如下;C 和 Python 值之间的转换根据其类型应该是显而易见的。‘标准大小’列是指使用标准大小时打包值的字节大小;也就是说,当格式字符串以 '<''>''!''=' 开头时。使用原生大小时,打包值的尺寸取决于平台。

格式 C 类型 Python 类型 标准大小 备注
x 填充字节 无值 (7)
c char 长度为 1 的字节 1
b signed char 整数 1 (1), (2)
B unsigned char 整数 1 (2)
? _Bool bool 1 (1)
h short 整数 2 (2)
H unsigned short 整数 2 (2)
i int 整数 4 (2)
I unsigned int 整数 4 (2)
l long 整数 4 (2)
L unsigned long 整数 4 (2)
q long long 整数 8 (2)
Q unsigned long long 整数 8 (2)
n ssize_t 整数 (3)
N size_t 整数 (3)
e (6) 浮点数 2 (4)
f 浮点数 浮点数 4 (4)
d double 浮点数 8 (4)
F float complex complex 8 (10)
D double complex complex 16 (10)
s char[] bytes (9)
p char[] bytes (8)
P void* 整数 (5)

数据帧打包实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import struct
import serial.tools.list_ports

# .......
self.frame_head = 0xd2
self.position_control_cmd = 0x01
self.click_CW_control_cmd = 0x02
self.click_CCW_control_cmd = 0x03
self.stop_control_cmd = 0x04
self.frame_end = 0xc3
# ........

def position_control_communicate(self):
data = struct.pack('=BBiiB', self.frame_head,self.position_control_cmd,
int(self.move_speed), int(self.move_position), self.frame_end)
print("发送位置控制指令,数据包内容:", data.hex())
try:
self.ser.write(data)
except Exception as e:
messagebox.showerror("错误", "串口未连接!", parent=self.ui)