在Python中,可以使用pyserial库来连接串口并发送命令。pyserial是一个用于串行通信的第三方库,支持多种平台。
首先,需要安装pyserial库。可以使用pip进行安装:
pip install pyserial
使用pyserial连接串口并发送命令
以下是一个示例代码,展示了如何使用pyserial连接串口并发送测试命令:
import serial import time def connect_serial(port, baudrate): try: # 创建Serial对象并打开串口 ser = serial.Serial(port, baudrate, timeout=1) print(f"成功连接到串口 {port},波特率 {baudrate}") return ser except serial.SerialException as e: print(f"无法连接到串口:{e}") return None def send_command(ser, command): if ser is not None: try: # 发送命令并添加换行符 ser.write((command + '\n').encode()) print(f"已发送命令:{command}") # 等待响应 time.sleep(1) # 读取响应数据 response = ser.read_all().decode() print(f"接收到的响应:{response}") except Exception as e: print(f"发送命令时发生错误:{e}") def close_serial(ser): if ser is not None: ser.close() print("已关闭串口") # 示例参数 port = "COM3" # Windows上的端口,例如 "COM3" # port = "/dev/ttyUSB0" # Linux上的端口,例如 "/dev/ttyUSB0" baudrate = 9600 # 波特率,根据实际情况设置 # 测试命令 command = "TEST_COMMAND" # 连接串口 ser = connect_serial(port, baudrate) # 发送测试命令 send_command(ser, command) # 关闭串口 close_serial(ser)支持发送HEX格式的命令兼容代码参考
import serial import time def connect_serial(port, baudrate, timeout=1): """打开串口,返回 Serial 对象;失败返回 None。""" try: ser = serial.Serial(port, baudrate, timeout=timeout) print(f"成功连接到串口 {port},波特率 {baudrate}") return ser except serial.SerialException as e: print(f"无法连接到串口:{e}") return None def parse_hex(hex_str): """ 将 HEX 字符串解析为 bytes。 支持格式: "01 02 0A FF" "01020AFF" "0x01,0x02,0x0A" "01-02-0A-FF" """ # 去掉常见分隔符和 0x 前缀 cleaned = ( hex_str.strip() .replace("0x", "") .replace("0X", "") .replace(",", " ") .replace("-", " ") .replace(":", " ") ) cleaned = "".join(cleaned.split()) # 去掉所有空白 if len(cleaned) % 2 != 0: raise ValueError(f"HEX 字符串长度必须为偶数:{hex_str}") try: return bytes.fromhex(cleaned) except ValueError as e: raise ValueError(f"非法的 HEX 字符串:{hex_str}({e})") def send_command(ser, command, mode="ascii", append_newline=True): """ 向串口发送命令。 :param ser: 已打开的 Serial 对象 :param command: 命令内容。mode='ascii' 时为字符串;mode='hex' 时为 HEX 字符串 :param mode: 'ascii' 或 'hex' :param append_newline: 仅 ascii 模式有效;是否在末尾追加 '\n' """ if ser is None: print("串口未打开,无法发送。") return # ---------- 1. 把用户输入转换成字节流 ---------- try: if mode == "hex": payload = parse_hex(command) # HEX/二进制协议通常没有“换行符”概念,强制不追加 if append_newline: print("提示:HEX 模式下 append_newline 被忽略。") elif mode == "ascii": text = command + ("\n" if append_newline else "") payload = text.encode("utf-8") else: raise ValueError(f"不支持的模式:{mode},只能是 'ascii' 或 'hex'") except ValueError as e: print(f"命令解析失败:{e}") return # ---------- 2. 发送 ---------- try: ser.reset_input_buffer() # 清掉上一次残留,避免响应串台 ser.write(payload) ser.flush() # 确保数据真正写出 if mode == "hex": print(f"已发送 HEX:{payload.hex(' ').upper()}") else: print(f"已发送 ASCII:{command!r}") except Exception as e: print(f"发送命令时发生错误:{e}") return # ---------- 3. 读取响应 ---------- response = read_response(ser) if response: print(f"接收到的响应(HEX):{response.hex(' ').upper()}") # 尝试按文本显示,失败则跳过 try: print(f"接收到的响应(ASCII):{response.decode('utf-8')!r}") except UnicodeDecodeError: print("接收到的响应无法按 UTF-8 解码,仅显示 HEX。") else: print("未接收到响应(可能超时或设备无回复)。") def read_response(ser, idle_timeout=1.0, max_bytes=4096): """ 读取串口响应:在 idle_timeout 秒内没有新数据就认为读取结束。 比 time.sleep(1) + read_all() 更稳健。 """ data = bytearray() deadline = time.time() + idle_timeout last_rx = time.time() while time.time() < deadline and len(data) < max_bytes: waiting = ser.in_waiting if waiting: data.extend(ser.read(waiting)) last_rx = time.time() deadline = last_rx + idle_timeout # 每收到数据就重置空闲计时 else: time.sleep(0.01) return bytes(data) def close_serial(ser): if ser is not None and ser.is_open: ser.close() print("已关闭串口") # ==================== 示例 ==================== if __name__ == "__main__": port = "COM3" # Windows;Linux 用 "/dev/ttyUSB0" baudrate = 9600 ser = connect_serial(port, baudrate) if ser is None: raise SystemExit(1) try: # 示例 1:发送 ASCII 文本命令(自动加换行) send_command(ser, "TEST_COMMAND", mode="ascii", append_newline=True) # 示例 2:发送 HEX 命令(不带换行) # 等价于发送字节 0x01 0x03 0x00 0x00 0x00 0x0A 0xCD 0xC5 send_command(ser, "01 03 00 00 00 0A CD C5", mode="hex") # 示例 3:紧凑写法的 HEX send_command(ser, "01030000000ACDC5", mode="hex") finally: close_serial(ser)原理说明:
原理说明
1. 串口发送的本质是字节流,不是字符串
ser.write()的参数必须是bytes/bytearray,不是str。原代码(command + '\n').encode()做的是两件事:
command + '\n':按文本协议的约定,在命令后追加换行符作为帧结束符;.encode():按某种字符编码(默认 UTF-8)把字符串转成字节。
这套流程只适用于“人可读的文本命令”,例如AT+RST\r\n。一旦设备要求的是二进制/HEX 协议(例如 Modbus RTU 的01 03 00 00 00 0A CD C5),再走.encode()就会把字符'0'、'1'、空格等按 ASCII 编码发出去,得到的是b'01 03 ...'这串文本的 ASCII 字节,而不是真正的二进制帧。
2. HEX 模式的核心是bytes.fromhex()
bytes.fromhex()把"01 03 0A FF"这样的十六进制文本解析成真正的字节b'\x01\x03\x0a\xff'。它允许空格分隔,但不允许0x前缀、逗号、冒号等,所以parse_hex()先做一次规范化清洗:
去掉
0x/0X把
,-:统一替换为空格去掉所有空白
校验长度为偶数(每个字节两位十六进制)
这一步是 HEX 支持的关键:用户输入的是“十六进制表示”,程序要把它还原成“二进制字节”。
3. 为什么 HEX 模式不应自动追加\n
换行符是文本协议的帧分隔约定。二进制协议(Modbus、自定义帧、二进制指令)的帧边界通常由帧头、长度字段、校验码(CRC/LRC)或固定超时决定,而不是\n。如果强行在 HEX 帧后追加0x0A,会:
破坏帧结构;
导致 CRC 校验失败;
让设备把
0x0A当成下一帧的起始字节。
所以优化后的代码在mode='hex'时忽略append_newline,并给出提示。如果某些设备确实需要0x0D 0x0A结尾,应由调用者显式写进 HEX 字符串,例如"01 03 ... 0D 0A",这样语义清晰、可控。