结论:有时间戳,但精度不够,直接拿来验 1 秒会翻车
你的日志时间戳只有一处,在 _append_log() 里:
python
self.log_area.append(
f"[{datetime.now().strftime('%H:%M:%S')}] {text}")
%H:%M:%S ------ 秒级,没有毫秒 。而 datetime.now() 是截断 不是四舍五入,所以真实时刻 20:43:37.999 会显示成 20:43:37。
后果很直接:你要判断的差值本身就是 1 秒量级,而尺子的刻度也是 1 秒且带 ±1 s 的截断不确定度,"差 0 秒"和"差 1 秒"在日志上分不开。必须先把毫秒补上。
另外还有个更隐蔽的问题:这个戳打在 GUI 线程消化信号的那一刻 ,不是收到包的那一刻。你的 data_received 是跨线程连接(默认 AutoConnection → 排队到主线程事件循环),而主线程还挂着一个 10 ms 的 gnss_timer。正常情况下排队延迟只有几毫秒,但一旦日志刷得快或者界面卡一下,几十毫秒就出去了。验 1 秒这点抖动无所谓,但既然要改,一次改到位。
补丁一:时间戳打在 recvfrom 之后(精确到 ms)
UdpReceiverThread ------ 信号多带一个时间
python
class UdpReceiverThread(QThread):
data_received = pyqtSignal(str, float) # ← 加一个 float
def run(self):
while self.running:
try:
if not self.sock:
break
try:
data, addr = self.sock.recvfrom(4096)
t_rx = time.time() # ← 收到包立刻取时间
except socket.timeout:
continue
except OSError:
break
if not data:
continue
text = data.decode(errors="ignore").replace("\r", "")
for line in text.split("\n"):
line = line.strip()
if line:
self.data_received.emit(line, t_rx) # ← 带上
except Exception as e:
self.data_received.emit(f"[ERROR recv udp] {e}", time.time())
break
连接处
原来这行改掉:
python
# self.reader_thread.data_received.connect(
# lambda d: self._append_log(f"[RX] {d}"))
self.reader_thread.data_received.connect(self._on_udp_line)
新增方法:
python
def _on_udp_line(self, line, t_rx):
ts = datetime.fromtimestamp(t_rx).strftime("%H:%M:%S.%f")[:-3]
self._append_log(f"[RX {ts}] {line}", t_rx)
_append_log 签名加个可选参数、格式补毫秒
python
def _append_log(self, text, t_rx=None):
...
# 函数最后那两行(有三处 append,都要改)
self.log_area.append(
f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] {text}")
这样每条 RX 会有两个 戳:[GUI时刻] [RX 收包时刻] $pps,...。两者之差就是 GUI 排队延迟,顺手还能看出界面卡不卡。
补丁二:让它自己算差值(这才是省事的地方)
眼睛比对十几条日志容易看花。在 _append_log() 开头、$beam 那段解析之前插一段:
python
# ---- 解析 $pps,utc=... 并与 PC 时钟比对 ----
mp = re.search(
r"\$pps,utc=(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):([\d.]+)", text)
if mp and t_rx is not None:
y, mo, d, h, mi = map(int, mp.groups()[:5])
s = float(mp.group(6))
board_utc = datetime(y, mo, d, h, mi, int(s),
int(round((s - int(s)) * 1e6)))
pc_utc = datetime.utcfromtimestamp(t_rx)
err = (board_utc - pc_utc).total_seconds()
if abs(err) < 0.30:
verdict = "约定正确,不用改"
elif abs(err - 1.0) < 0.30:
verdict = "板子快 1 秒 → jd -= 1.0/86400.0"
elif abs(err + 1.0) < 0.30:
verdict = "板子慢 1 秒 → jd += 1.0/86400.0"
elif abs(abs(err) - 18.0) < 1.0 or abs(abs(err) - 19.0) < 1.0:
verdict = "疑似闰秒问题(GPS时 vs UTC),先查惯导协议"
else:
verdict = "偏差异常,先确认 PC 已对 NTP"
text += (f"\n ↳ 板子UTC={board_utc.strftime('%H:%M:%S.%f')[:-3]} "
f"PC_UTC={pc_utc.strftime('%H:%M:%S.%f')[:-3]} "
f"误差={err:+.3f}s → {verdict}")
注意这里是拿 UTC 比 UTC ,不用 bj= 那列。用北京时间比要多一层 +8 换算,一旦哪边搞反就是 8 小时的坑,白白给自己加难度。datetime.utcfromtimestamp(t_rx) 直接给出 PC 的 UTC。
三个必须先确认的前提
① PC 得对过时。 尺子不准,量什么都白搭。Windows 管理员 CMD:
w32tm /resync
w32tm /stripchart /computer:ntp.aliyun.com /samples:5 /dataonly
第二条打出来的偏移绝对值 < 0.1 s 才能开始验。
② 别拿 gnss_base_time 当参照物。 你代码里那套 current_gnss_time() 的基准来源有两个:板子回的报文,或者那个苏宁 API。前者是循环论证(用板子验板子),后者只在网络通的时候生效、失败了就静默退回板子时间。验 PPS 必须用板子之外的独立时钟,也就是 PC 的系统时钟。
③ 链路延迟可以忽略。 从你之前的日志看,SEND → RECV 大约 5 ms(20:43:37.140 → .146)。板子生成 $pps,utc= 的时刻比 PC 收包早这么点,相对 1 秒的判据完全不影响。所以上面判据里 0.30 s 的容差是很宽松的,够用。
操作顺序
- 打上面两个补丁
w32tm /resync,确认 PC 偏移 < 0.1 s- 固件那边把
PPS_JD_To_UTC+ 第二条Cmd_Reply加上(上一轮给的) - 等
valid=1(diff 窗口改成非对称之后才会出现) - 连点 10 次「查询PPS状态」,看日志里自动算出的
误差=那一列
判读只看一件事:10 次的误差是不是稳定在同一个值附近 。稳定在 ±0.3 s 内 → 收工;稳定在 +1.0 或 −1.0 → 按提示改那一行;忽大忽小跨越 1 秒 → 不是约定问题,是锚定在秒边界上打摆子,回头去看 dmin/dmax 的抖动幅度。
给出上位机完整的代码,我不想自己写了
组织完整的主机GUI代码及其所有集成功能。
组织完整的主机GUI代码及其所有集成功能。
整份文件可直接替换原文件,其它功能一行没动。改了这几处:
① 收包时间戳
UdpReceiverThread 的信号从 pyqtSignal(str) 变成 pyqtSignal(str, float),时间在 recvfrom() 返回的下一行就取,绕开了 GUI 事件队列的排队延迟。日志里每条 RX 会有两个戳:[GUI时刻] [RX 收包时刻],两者之差就是界面卡顿量,顺手能看出来。
② 所有日志戳补到毫秒
原来的 %H:%M:%S 是截断不是四舍五入,20:43:37.999 会显示成 37,验 1 秒必然翻车。三处 log_area.append 全改成 %H:%M:%S.%f 取前三位小数。
③ $pps,utc= 自动比对
_append_log() 开头新增一段,拿板子 UTC 直接减 PC UTC,每条回包后面自动挂一行结论:
[RX 20:43:37.146] $pps,utc=2026-07-15 12:43:37.021,bj=20:43:37.021
↳ 板子UTC=12:43:37.021 PC_UTC=12:43:37.146 误差=-0.125s → 约定正确,不用改 [样本 3]
④ 新增四个按钮(在 LEO 面板底下)
-
★ 连测10次PPS时间 --- 每 700 ms 发一次
$cmd,query pps,10 次发完自动出结论,不用手点 -
给出结论 --- 对已采集样本算均值/极差,直接告诉你改哪一行:
[PPS] === 结论(10 个样本)===
均值=+1.003s 最小=+0.981s 最大=+1.024s 极差=0.043s
✗ 板子快 1 秒 → 固件里 jd -= 1.0/86400.0
判据里极差优先 :如果极差 > 0.5 s,无论均值是多少都判"不是约定问题",因为那说明锚定在秒边界上打摆子,得回头看固件的 dmin/dmax。这一层顺序不能反,否则会把抖动误判成约定错误、改完更乱。
- 清空比对记录 / PC对时提醒 (弹窗给
w32tm命令)
用之前两件事:
- 管理员 CMD 跑
w32tm /resync,PC 没对时的话尺子本身就不准 - 固件那边先加上
PPS_JD_To_UTC()和第二条Cmd_Reply,并且 diff 窗口已经改成非对称、valid=1了------否则连测会返回"没采集到样本"
另外提醒一句:不要拿界面上「GNSS 信息」那一栏当参照物。它的基准来自板子回包(或者那个苏宁 API,失败时静默退回板子时间),用板子验板子是循环论证。这套比对走的是 PC 系统时钟,是独立的。