上位机时间戳精度不够,时间无法对比怎么解决

结论:有时间戳,但精度不够,直接拿来验 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 的容差是很宽松的,够用。


操作顺序

  1. 打上面两个补丁
  2. w32tm /resync,确认 PC 偏移 < 0.1 s
  3. 固件那边把 PPS_JD_To_UTC + 第二条 Cmd_Reply 加上(上一轮给的)
  4. valid=1(diff 窗口改成非对称之后才会出现)
  5. 连点 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 命令)

用之前两件事:

  1. 管理员 CMD 跑 w32tm /resync,PC 没对时的话尺子本身就不准
  2. 固件那边先加上 PPS_JD_To_UTC() 和第二条 Cmd_Reply,并且 diff 窗口已经改成非对称、valid=1 了------否则连测会返回"没采集到样本"

另外提醒一句:不要拿界面上「GNSS 信息」那一栏当参照物。它的基准来自板子回包(或者那个苏宁 API,失败时静默退回板子时间),用板子验板子是循环论证。这套比对走的是 PC 系统时钟,是独立的。

相关推荐
現実君1 小时前
关于MCU下位替换FPGA的经验与方案
单片机·嵌入式硬件·fpga开发
m0_547486661 小时前
《单片机原理及应用》全套PPT课件2026版
单片机·嵌入式硬件
十月的皮皮2 小时前
STM32从零到量产开发:四路继电器工业控制模块开发原理小灶 · 合集
stm32·单片机·嵌入式硬件·stm32cubemx·hal库
小羊先生car2 小时前
F429-HAL 库驱动框架(2026/7/22)
单片机·嵌入式硬件
小羊先生car2 小时前
F429-SysTick(2026/7/22)
stm32·单片机·嵌入式硬件
hongmai6668883 小时前
ESP32-C61-WROOM-1-N8R2:Wi-Fi 6与RISC-V融合的中坚力量
人工智能·单片机·嵌入式硬件·物联网·智能家居·risc-v
炸膛坦客12 小时前
单片机/C/C++八股:(二十六)IIC 专题(I²C)---- 上集
c语言·c++·单片机
华清远见IT开放实验室17 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
AI的探索之旅19 小时前
AI辅助原理图评审:电源去耦、BOOT引脚、VCAP——19项逐一核查,遗漏?不存在的
人工智能·vscode·嵌入式硬件