造一个微型的 GNSS 授时时钟服务器

  • ~5.55K 字
  • 次阅读
  • 条评论
  1. 1. 方案与模块选择
  2. 2. 硬件连接
  3. 3. 系统构建
    1. 3.1. 安装相关软件包
    2. 3.2. 准备内核驱动
    3. 3.3. 调整设备树
  4. 4. 调试
  5. 5. 总结

作为一个力求做到准时的人,我总是希望能获得较为精准的时间。

通常来说,许多设备都内置有 NTP 客户端和默认的服务器池,使用这些公开的服务就能获得相对较为精确的时间;但这依赖于可靠的互联网连接,且需要这些服务器能够正常工作。作为一个家里有数据中心的人,我希望能拥有一个离网也可用的授时服务器,以在尽可能获得更精确的时间的同时,能多一道在不可抗力出现时依然能获得精准时间的备用方案。

方案与模块选择

GNSS 卫星授时方案作为一种常见的离网授时方案,它具有时钟精度高、覆盖范围大、容灾能力强的特点,这使得它被广泛应用于各种需要精确时间的设备中。但一个全新的高精度卫星授时模块的价格并不便宜,加上其他周边电路设计以及宿主服务器的价格就更为高昂了,也因此市面上廉价的成品 GNSS 授时服务器并不常见,我的计划也一直处于被搁置的状态。直到前几天我在网上随意冲浪时,一个视频的评论引起了我的注意。原评论的内容是使用 LEA-M8T 模块板配合香橙派的开发板搭建了一个授时服务器,并附上了一张图片作为参考。

给了我实现启发的评论

参考附带的图片配合着关键词搜索,很快我就找到了 WD22UGRC 这个模块板,以及对应的一些资料:它似乎是从华为的 BBU UMPT 设备上拆下来的授时模块,连接用的排针是 2mm 间距的小杜邦(不确定它的规范名称是什么),天线端使用的则是 SMB 接头(可转接成常见的 SMA )。它的原理图如下:

WD22UGRC

+3.3VGnd 是芯片电源, V ant 是天线电源(和芯片共地), PPS 是锁定后输出秒脉冲的接口, TxDRxD 则是使用 UART 协议进行通信的数据端。

我还找到了一篇使用这个模块搭建授时服务器的博客: Revisiting Microsecond Accurate NTP for Raspberry Pi with GPS PPS in 2025

博客中使用的开发板是 树莓派 5 。对于授时服务器来说它的性能有些过剩,并且最近连续几次的价格飞涨让它称不上是一个合适的选择。在已知香橙派可用的情况下,一个便宜但又恰好够用的开发板选择是什么呢?

我选择的是 LicheeRV-Nano (E) ,它有网口,有排针,这就足够连接到 WD22UGRC 模块板了。它有一个官方支持的基于 buildroot 的构建仓库,并且有比较清晰详细的说明文档,这使得基于它进行项目构建成为了可能。它的排针规格是标准的 2.54mm ,使用标准的杜邦线头就可以连接。

找齐了资料之后,把需要的物品都下单,稍微等几天的快递就全部都送到了。

模块与开发板

卫星天线

另外,由于这块开发板本身不带有存储空间,所以还需要一张至少 2GB 的 MicroSD 存储卡用于存储构建完成的系统镜像。

硬件连接

硬件的连接非常简单,只需要参考对应的图示将需要的脚位连接在一起就行。

在 LicheeRV-Nano (E) 的默认设置中:

  • GPIOA 28 & 29 工作在 UART 模式下,默认是 UART 1 (/dev/ttyS1) ;
  • GPIOA 14 是 GPIO ,高电平时会点亮 用户 LED。

我使用的是 3V-5V 供电电压范围的有源卫星天线,所以可以直接并联使用 3.3V 主供电。

因此,依照示意图这样连接可以尽可能减少需要改动的部分。当然如果您有其他的需要(例如连接多个 GNSS 模块,或是接收多路 PPS 输入,或是使用不同工作电压的天线),也完全可以依据您实际的需要进行对应的调整。

硬件连线示意

模块板接线(左侧)

模块板接线(右侧)

开发板接线

整体接线

系统构建

根据参考博客,系统构建主要有三个重要工作:安装相关软件包、准备内核驱动、调整设备树。

为了方便复现及避免遗忘,我 fork 了官方的构建仓库并在 gps 分支调整了对应的内容,以及在仓库的 release 部分发布构建完成的镜像文件压缩包。如果您要从仓库开始构建,那么仅需将 defconfig sg2002_licheervnano_sd 改为 defconfig sg2002_licheervnano_sd_gps 即可。

安装相关软件包

软件包的构建系统基于 buildroot ,所以需要先进入项目仓库的 buildroot 目录。

主要用到的软件有这些:

  • gpsd 用于与 GNSS 模块通讯,获得日期时间数据(需要启用 UBX 协议)
  • pps-tools 用于测试 PPS 脉冲是否成功产生并被正确接收
  • chrony 用于校准系统时钟,并提供 NTP 服务
  • linuxptp 用于锁定网卡时钟,以提供 PTP 服务

make menuconfig 打开配置页面,去相关的位置调整各个软件包的选项。

同时为了精简构建出来的镜像,我去掉了一些不被需要的软件包,例如 Python 3 ,以及 ntp (但不确定 ntp 会被什么依赖重新启用,似乎在构建完成的镜像中它依然存在)。

配置文件如果不保存,那么下一次 defconfig 时它就会被覆盖。所以需要将调整后的配置内容保存为 configs/cvitek_SG200X_musl_riscv64_defconfig 这个文件。

除了配置软件包外,还需要准备给这些软件使用的配置文件。我在 board/cvitek/SG200X/overlay/etc 里创建了这些文件:

chrony.conf
1
2
3
4
5
6
7
8
9
10
11
driftfile /var/lib/chrony/chrony.drift
ntsdumpdir /var/lib/chrony
logdir /var/log/chrony
maxupdateskew 100.0
rtcsync
makestep 1 3

refclock SHM 0 refid NMEA offset 0.000 precision 1e-3 poll 0 filter 3
refclock PPS /dev/pps0 refid PPS lock NMEA offset 0.0 poll 3 trust

allow all
linuxptp.cfg
1
2
3
4
5
6
7
8
[global]
time_stamping hardware
serverOnly 1
priority1 120
clockClass 6
timeSource 0x20

[eth0]

同时,由于这个仓库的 GPSD 默认的启动配置不会包含 -n 选项(并且 buildroot 没法配置它),从而导致数据无法发送到 SHM 导致 chrony 无法读取当前时间,所以需要使用特调过的文件替换掉它(这个文件需要可执行权限,也就是 chmod +x ):

init.d/S50gpsd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/sh
#
# Starts the gps daemon.
#

NAME=gpsd
DAEMON=/usr/sbin/$NAME
DEVICES="/dev/ttyS1 /dev/pps0"
PIDFILE=/var/run/$NAME.pid

start() {
printf "Starting $NAME: "
start-stop-daemon -S -q -p $PIDFILE --exec $DAEMON -- -P $PIDFILE $DEVICES -n && echo "OK" || echo "Failed"
}
stop() {
printf "Stopping $NAME: "
start-stop-daemon -K -q -p $PIDFILE && echo "OK" || echo "Failed"
rm -f $PIDFILE
}
restart() {
stop
start
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac

exit $?

对于 PTP 服务器来说,还需要调整一下网卡的配置,以尽可能减小延迟(这个文件也需要可执行权限):

init.d/S99ptp-nic-coalesce
1
2
3
#!/bin/sh

/usr/sbin/ethtool -C eth0 tx-usecs 0 rx-usecs 0

准备内核驱动

与一般的 buildroot 项目不同的是,这个仓库的内核是在项目仓库目录的 linux_5.10 目录中单独被构建的。它的配置文件是 build/boards/sg200x/sg2002_licheervnano_sd 目录中的 linux 目录中的 sg2002_licheervnano_sd_defconfig

内核部分的改动主要是增加 PPS 的驱动以及 PTP 相关的驱动:

  • PPS 用于接收来自 GNSS 模块的稳定秒脉冲,使用来自 GPIO 的 PPS 信号输入。
  • PTP 用于向局域网内的设备提供高精度的时间服务。

因为项目现有的内核配置改动似乎有一些超出控制,所以不能把它挪到 linux 目录中执行 make menuconfig ,否则会破坏掉现有的配置选项导致构建失败。因此直接在文件末尾追加这些内容:

1
2
3
4
5
6
7
# PPS
CONFIG_PPS=y
CONFIG_PPS_CLIENT_GPIO=y

# PTP
CONFIG_PTP_1588_CLOCK=y
CONFIG_NETWORK_PHY_TIMESTAMPING=y

调整设备树

因为使用了开发板的默认设置,所以设备树上只需要添加一个 pps-gpio 的绑定就行。参考 PPS-GPIO 的定义,在 build/boards/sg200x/sg2002_licheervnano_sd 目录中的 dts_riscv 目录中的 sg2002_licheervnano_sd.dts 文件中添加所需的内容:

1
2
3
4
5
pps {
gpios = <&porta 14 GPIO_ACTIVE_HIGH>;

compatible = "pps-gpio";
};

调整完这些配置后,就可以使用 defconfig 加载新的配置,然后使用 build_all 命令来构建完整的系统镜像文件了。

构建完成的是 .img 格式的文件,可以直接用 balena etcher 之类的工具将它写入存储卡中。如果您下载的是 .tar.xz 格式的压缩文件,则可以将其中的 .img 文件解压出来后执行一样的写入步骤。

调试

其实到这一步构建出的镜像就可以直接用于提供 NTP 服务了(只要确保连线正确)。但为了对可能需要调整的部分进行进一步的测试,系统保留了 SSH 的功能,您可以使用 用户名 root 与 密码 candinya 连接到开发板执行相关的工作。例如:

  • 使用 ppstest /dev/pps0 检查 PPS 信号是否正常产生
  • 使用 cgps 检查 GNSS 模块的状态
  • 使用 chronyc sourceschronyc tracking 检查 chrony 的时间锁定状态

您可以使用 vi 编辑相关程序的配置文件,以确认不同参数对其功能的影响。

当 GNSS 模块完成卫星锁定后,您应当可以看见每当 PPS 脉冲产生时,开发板的 用户 LED 都会闪烁一次:

PPS 信号点亮 用户 LED

一切正确工作后,您也可以在局域网设备上使用相关的工具测试 NTP 服务。

总结

至此,一个微型的 GNSS 授时时钟服务器已经被成功建立,它提供 NTP 服务用以让网络上的设备可以获得较为精准的时间。

这块开发板的网卡理论上拥有对 PTP 协议的硬件支持,但截至发稿时,我依然没能成功解决网卡时钟异常跳动和过滤器无法启动的问题,所以您可以先将文中的 PTP 相关部分直接忽略。我创建了一个 issue 用于跟踪相关的进展,如果您有任何进展或是补充,欢迎随时在此处跟进。

总的来说,这次只是一个简单的组装测试,没有专门针对性能进行调优,并且 GNSS 模块的 PPS 误差相比起 OCXO 和 原子钟 来说依然显得较为显著,我也没有针对 CPU 进行负载配置,这使得当前的实现依然具有一定的优化与提升空间。下一步的开发计划主要集中在修复 PTP 协议支持的问题、提升时间的稳定度和想办法连接小屏幕来显示实时时间上,如果有较为可观的进展,我会发一篇新的文章用于跟进。

非常感谢您的阅读。时值新春佳节,预祝您在新的一年里身体健康,诸事顺遂。

分享这一刻
让朋友们也来瞅瞅!