Python使用pyserial库通过串口发送数据的基础代码示例
详细讲解如何使用Python的pyserial库通过串口(Serial Port)发送数据,涵盖环境准备、端口检测、字符串与字节编码转换、基础发送代码示例、关键参数设置及常见问题排错。
在嵌入式开发、仪器控制与物联网设备调试过程中,串口(Serial Port/UART)是计算机与外部硬件交换数据最直接的通道。Python 官方标准库中并未包含串口通信模块,通常采用第三方社区标准库 pyserial 来完成硬件通信。利用 pyserial 发送数据的基本流程可以概括为三步:识别并打开指定串口、将文本或十六进制数据编码为字节流(bytes)、通过 write() 方法写入发送缓冲区。本文将通过简洁完整的代码示例,指导读者完成从环境配置、代码编写到数据发送的全流程,并逐一分析核心参数与常见陷阱。
一、操作准备:安装库与确认串口号
在编写代码前,必须确保安装了正确的模块,并获取到目标硬件在操作系统中分配的串口设备名称。
1. 正确安装 pyserial
请在终端(Terminal 或命令提示符)中运行以下命令安装:
pip install pyserial
pip install serial。PyPI 上的 serial 是另一个已废弃的早期包,安装后可能导致命名冲突。如果不小心安装了,请先执行 pip uninstall serial,再安装 pyserial。在代码中引入时,统一使用 import serial。
2. 查询当前可用的串口号
不同操作系统对串口的命名格式不同:
- Windows:通常为
COM1、COM3、COM4等,可在“设备管理器 ➔ 端口 (COM 和 LPT)”中查看。 - macOS:通常形如
/dev/cu.usbserial-xxx或/dev/cu.usbmodemxxx。 - Linux:通常为
/dev/ttyUSB0或/dev/ttyACM0。
你也可以直接使用 pyserial 自带的工具函数,用一段简短的脚本列出本机当前所有已识别的串口:
import serial.tools.list_ports
ports = serial.tools.list_ports.comports()
for p in ports:
print(f"设备名: {p.device} | 描述: {p.description}")
二、基础代码示例:发送字符串与十六进制数据
串口通信本质上只能传输原始二进制字节(bytes),不能直接传递 Python 3 的 Unicode 字符串。因此,发送文本时必须显式调用 .encode() 进行编码;若发送控制指令(如 Modbus RTU 或特定传感器协议),则需构造原始的十六进制字节串。
1. 基础发送代码(推荐使用 with 上下文管理器)
以下代码以波特率 9600 为例,展示了打开串口、发送换行结尾的字符串、发送 Hex 十六进制数据,并安全关闭串口的完整过程:
import serial
import time
# 1. 定义串口配置参数(请根据实际连接的设备修改端口名称与波特率)
PORT_NAME = 'COM3' # Windows 为 'COMx';Linux/macOS 为 '/dev/ttyUSB0' 等
BAUD_RATE = 9600 # 通信波特率,须与下位机保持完全一致
TIMEOUT = 1.0 # 读超时时间,单位秒
try:
# 2. 打开串口,推荐使用 with 语句确保使用完毕后串口自动释放
with serial.Serial(PORT_NAME, BAUD_RATE, timeout=TIMEOUT) as ser:
print(f"成功打开串口: {ser.name}")
# 部分单片机(如 Arduino)在串口打开时会发生复位,等待 1.5 秒确保设备就绪
time.sleep(1.5)
# 3. 示例一:发送普通文本字符串(必须转换为 bytes 格式)
message_str = "HELLO_DEVICE\r\n"
bytes_sent = ser.write(message_str.encode('utf-8'))
print(f"已发送字符串数据,实际写入字节数: {bytes_sent}")
# 4. 示例二:发送原始十六进制指令(例如传感器读数帧:01 03 00 00 00 02 C4 0B)
hex_command = bytes.fromhex("01 03 00 00 00 02 C4 0B")
ser.write(hex_command)
print(f"已发送十六进制指令: {hex_command.hex()}")
# 5. 等待发送缓冲区中的数据完全排空输出
ser.flush()
except serial.SerialException as e:
print(f"串口打开或通信失败,错误信息: {e}")
成功打开串口: COM3已发送字符串数据,实际写入字节数: 14已发送十六进制指令: 010300000002c40b
三、关键参数详解与底层数据流转
串口是点对点的异步通信方式,收发双方没有统一的时钟线,因此双方必须在物理层事先约定相同的波特率、数据位、校验位和停止位配置(通常简称为串口参数格式,如“9600, 8, N, 1”)。
1. 关键初始化参数对照
| 参数名称 | 默认值 | 说明与常用选值 |
|---|---|---|
port |
None | 串口设备名称,例如 'COM3'、'/dev/ttyUSB0'。 |
baudrate |
9600 | 通信波特率。常用 9600、19200、38400、57600、115200。数值代表每秒传输的符号位(bit)数。 |
bytesize |
serial.EIGHTBITS (8) | 数据位长度,通常为 8 位,也可选 FIVEBITS、SIXBITS、SEVENBITS。 |
parity |
serial.PARITY_NONE ('N') | 奇偶校验位,通常无校验。可选 PARITY_EVEN(偶校验)、PARITY_ODD(奇校验)。 |
stopbits |
serial.STOPBITS_ONE (1) | 停止位长度,常用 1 位,也可选 STOPBITS_TWO (2)。 |
timeout |
None (阻塞) | 读操作的超时时间(秒)。设为 0 表示非阻塞立即返回;设为具体数值(如 1.0)表示最多等待 1 秒。 |
write_timeout |
None (阻塞) | 写操作的超时时间(秒)。如果串口发送缓冲区已满且无法排出,超时后将抛出异常。 |
2. 从 Python 对象到硬件引脚的数据流转
当在 Python 中执行 ser.write(data) 时,并不是瞬间直接作用在硬件引脚上的。数据需要先经过操作系统的内核驱动,再复制到 USB 转串口芯片(或主板 UART 控制器)的硬件缓冲区中,最后按照指定的波特率时钟逐位转换成 TX 引脚的高低电平。
四、发送并接收验证:双向交互完整示例
在实际工程中,发送指令后往往需要验证下位机的应答(即“发-收”闭环模式)。以下是一个通用的发送请求并等待响应的完整工程代码模板:
import serial
import time
def send_command_and_get_reply(port, baudrate, cmd, timeout=2.0):
"""
向串口发送一条字符串指令,并读取单行回复。
"""
try:
with serial.Serial(port=port, baudrate=baudrate, timeout=timeout) as ser:
# 避免 Arduino / ESP32 复位初期丢失字符
time.sleep(1.5)
# 规范:指令末尾附加回车换行符,便于下位机检测结束
full_cmd = cmd.strip() + "\r\n"
ser.write(full_cmd.encode('utf-8'))
ser.flush()
print(f"已发送: {full_cmd.strip()}")
# 读取下位机回复(readline 会等待直到读到 \n 或超时)
response_bytes = ser.readline()
if response_bytes:
response_str = response_bytes.decode('utf-8', errors='replace').strip()
print(f"下位机回复: {response_str}")
return response_str
else:
print("等待回复超时,未收到下位机响应。")
return None
except serial.SerialException as err:
print(f"通信异常: {err}")
return None
# 示例调用
if __name__ == "__main__":
send_command_and_get_reply('COM3', 9600, 'PING')
五、四大核心注意事项与排错矩阵
很多初学者在编写串口发送代码时,经常遇到“代码执行成功但硬件没有任何反应”或“报错无法打开端口”的问题。以下为四个最关键的踩坑点及解决方法。
- 串口打开与 Arduino 自动复位现象:许多基于 USB 串口芯片的微控制器(如 Arduino Uno/Nano)将 DTR(数据终端就绪)引脚连接到芯片的 RESET 线上。当 Python 打开串口时,驱动通常会默认拉低 DTR,触发单片机重启(Bootloader 启动运行通常需要 1~2 秒)。如果代码在
serial.Serial()之后立刻发送数据,微控制器此时还在执行启动引导程序,无法接收用户数据,从而导致数据丢失。因此,打开串口后加入time.sleep(1.5)是一种常见且稳妥的习惯。 - 显式调用
ser.flush()保证发送完整:ser.write()仅仅负责把数据从 Python 进程拷贝进操作系统的发送缓冲区,并立即返回写入的字节数。如果程序在发送后立刻退出或关闭串口,操作系统可能尚未将缓冲区内容完全串行移出。调用ser.flush()会阻塞等待直到所有缓冲区数据物理发送完毕。 - 硬件连线常识(交叉连接):若使用两根线进行串口直连,计算机的
TXD(发送引脚)必须连接目标设备的RXD(接收引脚),计算机的RXD连接设备的TXD,并且双方的GND(地线)必须共地,否则无法形成电平回路。 - 操作权限问题(Linux 与 macOS):在 Linux 系统下,普通用户通常不在
dialout权限组,尝试打开/dev/ttyUSB0会触发PermissionError。可通过终端执行sudo usermod -a -G dialout $USER并重新登录解决。
六、总结
掌握 Python 通过 pyserial 发送数据的核心在于理清三个环节:端口名与波特率的正确匹配、Python 字符串与二进制 bytes 的严格转换,以及合理的生命周期管理(推荐使用 with 语句配合 flush 确保数据发毕与句柄释放)。在此基础代码之上,你可以进一步拓展数据包校验(如 CRC16)、帧头帧尾封装以及异步多线程收发架构,构建稳定高效的上位机通信系统。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













