4. 十六进制数据收发

任务说明

串口通信中,许多设备(如传感器、控制器)使用十六进制帧格式进行数据交换。本案例实现一个基于 pyserial 的十六进制数据收发工具,支持用户输入十六进制字符串发送,并将接收到的原始字节以十六进制形式解析显示,同时提供可读ASCII对照。

实现方案

程序通过命令行交互方式运行:用户输入十六进制字符串(如 01 03 00 00 00 0A),程序将其转换为字节流通过串口发送;接收线程持续读取串口数据,将每个字节格式化为两位大写十六进制并附带ASCII可见字符显示。串口参数(端口、波特率等)通过命令行参数指定,默认使用 COM3 与 9600 波特率。

代码实现


import serial
import serial.tools.list_ports
import threading
import time
import sys

def list_ports():
    """列出可用串口,辅助用户选择"""
    ports = serial.tools.list_ports.comports()
    if not ports:
        print("未检测到可用串口")
        return None
    for i, p in enumerate(ports):
        print(f"[{i}] {p.device} - {p.description}")
    return ports

def hex_to_bytes(hex_str):
    """将十六进制字符串转换为字节,支持空格或逗号分隔"""
    cleaned = hex_str.replace(',', ' ').strip()
    parts = cleaned.split()
    try:
        return bytes([int(x, 16) for x in parts])
    except ValueError:
        return None

def bytes_to_hex_display(data):
    """将字节转换为十六进制显示字符串,含ASCII对照"""
    hex_part = ' '.join(f'{b:02X}' for b in data)
    ascii_part = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in data)
    return hex_part, ascii_part

def receive_loop(ser, stop_event):
    """后台接收线程:读取数据并格式化显示"""
    buffer = b''
    while not stop_event.is_set():
        if ser.in_waiting > 0:
            data = ser.read(ser.in_waiting)
            buffer += data
            # 按换行符或固定长度切分(此处按每次读取直接显示)
            hex_str, ascii_str = bytes_to_hex_display(data)
            timestamp = time.strftime('%H:%M:%S')
            print(f"[{timestamp}] RX HEX: {hex_str}")
            print(f"[{timestamp}] RX ASCII: {ascii_str}")
        else:
            time.sleep(0.05)

def main():
    # 解析命令行参数:port, baudrate
    port = 'COM3'
    baudrate = 9600
    if len(sys.argv) >= 2:
        port = sys.argv[1]
    if len(sys.argv) >= 3:
        baudrate = int(sys.argv[2])

    # 打开串口
    try:
        ser = serial.Serial(port, baudrate, timeout=0.1)
        print(f"已打开 {port} @ {baudrate} bps")
    except serial.SerialException as e:
        print(f"串口打开失败: {e}")
        ports = list_ports()
        if ports:
            print("请使用上方列表中的端口号重试,例如: python script.py COM5 9600")
        return

    # 启动接收线程
    stop_event = threading.Event()
    rx_thread = threading.Thread(target=receive_loop, args=(ser, stop_event), daemon=True)
    rx_thread.start()

    print("输入十六进制数据发送,例如: 01 03 00 00 00 0A")
    print("输入 'exit' 或 'quit' 退出程序")
    try:
        while True:
            cmd = input("> ").strip()
            if cmd.lower() in ('exit', 'quit'):
                break
            if not cmd:
                continue
            data = hex_to_bytes(cmd)
            if data is None:
                print("格式错误:请输入十六进制字节,如 '01 A2 FF'")
                continue
            ser.write(data)
            hex_str, ascii_str = bytes_to_hex_display(data)
            timestamp = time.strftime('%H:%M:%S')
            print(f"[{timestamp}] TX HEX: {hex_str}")
            print(f"[{timestamp}] TX ASCII: {ascii_str}")
    except KeyboardInterrupt:
        pass
    finally:
        stop_event.set()
        ser.close()
        print("串口已关闭")

if __name__ == '__main__':
    main()

运行与验证

依赖安装:

pip install pyserial

运行方式:

python hex_serial.py COM3 9600

若本机只有一个串口且无硬件设备,可使用虚拟串口工具(如Windows的com0com或Linux的socat)创建一对互联的虚拟串口进行测试。例如使用socat:

socat -d -d pty,raw,echo=0 pty,raw,echo=0

该命令会输出两个伪终端路径(如 /dev/pts/3 和 /dev/pts/4),分别用于运行本程序与另一个串口调试助手。

验证步骤与判定标准:

  1. 启动程序后,在提示符后输入 01 03 00 00 00 0A 并回车,程序应显示 TX HEX: 01 03 00 00 00 0A 及对应ASCII行。
  2. 若使用虚拟串口对,在另一个终端用 cat /dev/pts/4 观察,应能收到相同的原始字节 01 03 00 00 00 0A(可用 xxd 查看)。
  3. 在另一个终端向对应串口发送字节(如 echo -ne '\x01\x03\x10\x20' > /dev/pts/4),本程序应打印 RX HEX: 01 03 10 20 与对应ASCII行。
  4. 输入非法格式(如 xyz)时,程序应提示格式错误且不发送任何数据。

满足以上条件即判定功能正确:十六进制数据能够被准确发送,接收到的字节能够以十六进制形式完整解析显示。

数据流与解析逻辑

十六进制收发数据流 用户输入 "01 03 00 00 00 0A" hex_to_bytes() 字节流 b'\x01\x03\x00\x00\x00\x0A' ser.write() 串口 物理链路 TX/RX ser.read() 接收原始字节 b'\x01\x03\x10\x20' bytes_to_hex_display() 格式化显示 HEX: 01 03 10 20 ASCII: . . . . 图例 发送路径 接收路径 串口设备 解析显示

上述数据流展示了从用户输入十六进制字符串到最终解析显示的完整路径。发送时通过 hex_to_bytes() 将字符串严格转换为字节;接收时通过 bytes_to_hex_display() 将每个字节拆分为十六进制与ASCII双视图,确保数据可读性与完整性校验。