You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Popen子进程记录多GPS接收器串口数据无输出问题排查

子进程读取GPS串口失败:单独运行正常,主进程启动则无输入

问题描述

我需要在单台电脑上同时记录3台GPS接收器的串口输出以对比性能,于是写了主程序通过Popen启动子进程,每个子进程负责读取指定串口、筛选GGA格式NMEA数据并写入CSV。但测试时发现:

  • 单独运行子进程脚本swap-c_ReadCOM.py,能正常捕获串口输入并生成CSV
  • 主程序启动子进程时,子进程无法捕获串口输入,仅输出b' ',正则匹配失败,无CSV生成

主程序代码

import time
from datetime import datetime
from subprocess import Popen, PIPE


save_dir = "output_csvs/"

sparkfun_port = "COM7"
sparkfun_baud = "38400"

trimble_port = "COM6"
trimble_baud = "38400"

duro_port = "COM5"
duro_baud = "115200"

if __name__ == "__main__":
    # 生成文件前缀
    file_prefix = datetime.now().strftime("%m-%d-%Y-%H:%M:%S")
    trial_length = input("How long is this trial (in min)? ")
    antenna = input("Which GPS antenna is being used? ")
    file_prefix += "_" + antenna + trial_length + "min"

    # 生成每个接收器的文件路径
    sparkfun_path = save_dir + file_prefix + "_sparkfun.csv"
    trimble_path = save_dir + file_prefix + "_trimble.csv"
    duro_path = save_dir + file_prefix + "_duro.csv"

    # 启动子进程
    sparkfun = Popen(['python', './swap-c_ReadCOM.py', sparkfun_port, sparkfun_baud, sparkfun_path],
                     stdin=PIPE, stdout=PIPE, stderr=PIPE)
    trimble = Popen(['python', './swap-c_ReadCOM.py', trimble_port, trimble_baud, trimble_path],
                    stdin=PIPE, stdout=PIPE, stderr=PIPE)  
    duro = Popen(['python', './swap-c_ReadCOM.py', duro_port, duro_baud, duro_path], stdin=PIPE, stdout=PIPE,
                    stderr=PIPE)  

    # 等待试验结束
    time.sleep(int(trial_length)*60+1)
    print("Trial Complete")
    quit()

子进程脚本swap-c_ReadCOM.py代码

import sys
import serial
import re
import csv


def trim_checksum(decoded_str):
    idx = decoded_str.find('*')
    if idx != -1:
        return decoded_str[:idx]
    return decoded_str


filepath = str(sys.argv[3])
ser = serial.Serial(port=sys.argv[1], baudrate=int(sys.argv[2]), bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE)

while True:  # 主进程结束后会被终止
    ser_bytes = ser.readline()
    decoded_bytes = ser_bytes[0:len(ser_bytes) - 2].decode("utf-8")
    print(decoded_bytes)
    isGGA = re.search("\$\w\wGGA", decoded_bytes)
    if isGGA is not None:
        decoded_bytes = trim_checksum(decoded_bytes)
        with open(filepath, "a", newline='') as f:
            split = decoded_bytes.split(",")
            writer = csv.writer(f)
            writer.writerow(split)

解决办法

1. 调整子进程的管道与缓冲设置

你在Popen中设置了stdout=PIPE和stderr=PIPE,但子进程的print输出默认是缓冲的,数据会留在管道中无法实时输出,甚至可能阻塞子进程运行。可以做两种调整:

  • 不捕获子进程的输出,让它直接输出到终端:
# 修改Popen参数,去掉stdout和stderr的PIPE设置
sparkfun = Popen(['python', './swap-c_ReadCOM.py', sparkfun_port, sparkfun_baud, sparkfun_path])
  • 或者启用无缓冲模式,同时在子进程的print中强制刷新:
    主进程启动时加bufsize=0:
sparkfun = Popen(['python', './swap-c_ReadCOM.py', sparkfun_port, sparkfun_baud, sparkfun_path],
                 stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0)

子进程中修改print语句:

print(decoded_bytes, flush=True)

2. 给串口设置超时时间

子进程中serial.Serial默认没有超时,ser.readline()会一直阻塞等待数据,可能导致子进程假死。添加超时参数:

ser = serial.Serial(port=sys.argv[1], baudrate=int(sys.argv[2]), 
                    bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, 
                    stopbits=serial.STOPBITS_ONE, timeout=1)

3. 正确终止子进程,避免数据丢失

主程序用quit()直接退出会强制终止所有子进程,可能导致子进程还没把缓存中的数据写入文件。应该主动终止子进程:

# 替换原有的quit()
sparkfun.terminate()
trimble.terminate()
duro.terminate()
# 等待子进程结束
sparkfun.wait()
trimble.wait()
duro.wait()
print("Trial Complete")

4. 优化文件写入逻辑

子进程中每次写入都打开关闭文件,不仅效率低,还可能因为文件未及时刷新导致数据丢失。可以改为一次性打开文件,持续写入:

import sys
import serial
import re
import csv


def trim_checksum(decoded_str):
    idx = decoded_str.find('*')
    if idx != -1:
        return decoded_str[:idx]
    return decoded_str


filepath = str(sys.argv[3])
ser = serial.Serial(port=sys.argv[1], baudrate=int(sys.argv[2]), 
                    bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, 
                    stopbits=serial.STOPBITS_ONE, timeout=1)

# 一次性打开文件,保持写入状态
with open(filepath, "a", newline='') as f:
    writer = csv.writer(f)
    while True:
        ser_bytes = ser.readline()
        if not ser_bytes:
            continue
        decoded_bytes = ser_bytes[0:len(ser_bytes) - 2].decode("utf-8", errors='ignore')  # 加错误处理避免解码失败
        print(decoded_bytes, flush=True)
        isGGA = re.search(r"\$\w\wGGA", decoded_bytes)
        if isGGA is not None:
            decoded_bytes = trim_checksum(decoded_bytes)
            split = decoded_bytes.split(",")
            writer.writerow(split)
            f.flush()  # 强制刷新文件缓存

5. 确保子进程脚本路径正确

如果主程序运行的工作目录和脚本所在目录不一致,./swap-c_ReadCOM.py会找不到文件。可以改为绝对路径,比如:

import os
script_path = os.path.abspath("swap-c_ReadCOM.py")
sparkfun = Popen(['python', script_path, sparkfun_port, sparkfun_baud, sparkfun_path])

总结

优先调整管道缓冲和串口超时设置,这是最可能导致子进程无法读取串口的原因。同时优化文件写入和子进程终止逻辑,确保数据完整写入。

内容的提问来源于stack exchange,提问作者Mattman7306

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 07:29:52