返回首页

Raspberry Pi 上使用 chrony 和 GPS 的 Stratum 1 NTP

本文描述了在 Raspberry Pi 上使用 chrony、libgpiod v2、DCF77 和 GPS NEO-M8T 创建自主 Stratum 1 NTP 服务器的过程。实现了 42 ns 的精度。从 ntpd 和 GPIO API 迁移的问题得到解决。

RPi 上的精确 NTP Stratum 1:GPS PPS 实现 42 ns
Advertisement 728x90

在树莓派上搭建独立Stratum 1 NTP服务器:2026年使用chrony配合DCF77和GPS PPS

在树莓派OS Bookworm/Trixie上,旧的NTP服务器教程因libgpiod v2、转向chrony以及GPIO芯片变化而失效。一个C++20实现使用GPIO 17上的DCF77接收器RC8000和GPIO 18上带PPS的ublox NEO-M8T GPS模块。该服务器无需互联网即可实现<1微秒精度,并具备独立时间源验证。

过时实现的问题

libgpiod v2破坏GPIO代码

libgpiod v2移除了v1 API且无兼容性。使用gpiod_chip_open_by_namegpiod_chip_get_line的旧代码无法编译:

// libgpiod v1 — 与v2不兼容,无法编译
struct gpiod_chip *chip = gpiod_chip_open_by_name("gpiochip0");
struct gpiod_line *line = gpiod_chip_get_line(chip, 17);
gpiod_line_request_both_edges_events(line, "dcf77");

新API要求使用gpiod_line_settings_new()gpiod_line_config_new()

Google AdInline article slot
// libgpiod v2 — 正确方法
const SettingsPtr settings(gpiod_line_settings_new());
gpiod_line_settings_set_edge_detection(settings.get(), GPIOD_LINE_EDGE_BOTH);
gpiod_line_settings_set_event_clock(settings.get(), GPIOD_LINE_CLOCK_REALTIME);

const LineCfgPtr line_cfg(gpiod_line_config_new());
gpiod_line_config_add_line_settings(line_cfg.get(), &offset, 1, settings.get());

const RequestPtr request(gpiod_chip_request_lines(chip.get(), req_cfg.get(), line_cfg.get()));

一个关键优势是GPIOD_LINE_CLOCK_REALTIME:事件在中断时从内核接收时间戳,将抖动从40毫秒减少到<5毫秒。

从ntpd过渡到chrony

在Bookworm中,ntpd已被chrony取代。对比:

| 参数 | ntpd | chrony |

Google AdInline article slot

|-----------|------|--------|

| 收敛时间 | 分钟级 | 秒级(makestep 0.5 3) |

| 信号丢失 | 性能下降 | 保持模式(local stratum 10) |

Google AdInline article slot

| PPS支持 | 需补丁 | 原生refclock PPS |

| PPS精度 | ~100微秒 | <1微秒 |

两者都支持通过共享内存进行数据交换的NTP SHM。

RPi 5上的GPIO芯片

gpiochip0已过时。在RPi 5/Trixie上,使用gpiochip4(用gpiodetect检查)。

系统架构

两个时间源:

  • GPS NEO-M8T:PPS(/dev/pps0)+ 通过gpsd的NMEA(NTP SHM #0)
  • DCF77 RC8000:GPIO 17 → 解码器 → NTP SHM #2
  • chrony:Stratum 1,优先PPS
  • Web监控:chronyc + journald在:8080端口

chrony配置:

refclock PPS /dev/pps0 refid PPS precision 1e-9 poll 0 dpoll -2 lock NMEA prefer
refclock SHM 0 refid NMEA precision 1e-3 poll 0 dpoll -2 offset 0.0 delay 0.2 noselect
refclock SHM 2 refid DCF7 precision 1e-2 offset 0.0 delay 0.008 poll 6 dpoll 5
local stratum 10

DCF77作为备用和GPS欺骗检测器(差异>几秒时)。

DCF77解码器

内核时间戳

GPIOD_LINE_CLOCK_REALTIME提供中断时间戳,而非用户空间。SHM中的标准差:<5毫秒。

用于64位的NTP SHM

带填充的结构:

struct NtpShmTime {
    int32_t mode;
    int32_t count;
    int64_t clockTimeStampSec;
    int32_t clockTimeStampUSec;
    int32_t _pad;
    int64_t receiveTimeStampSec;
    int32_t receiveTimeStampUSec;
    int32_t leap;
    int32_t precision;
    int32_t nsamples;
    int32_t valid;
};
volatile NtpShmTime *shm_ = nullptr;

DCF77协议

59位/分钟,BCD编码带奇偶校验:

  • 100毫秒高电平 → 位0
  • 200毫秒高电平 → 位1
  • 暂停>1.5秒 → 分钟标记

验证:3个奇偶校验组 + timegm()。引导:2个帧,间隔60秒。

NEO-M8T配置

  • TIM固件:RAIM,低PPS抖动
  • dynModel=Stationary:抖动5–10纳秒
  • antCableDelay=50纳秒:电缆补偿
  • 4个星座:20–30颗卫星
  • lpMode=Continuous:无节能模式

Web监控和部署

dcf77_web:HTTP :8080,chronyc输出JSON,UI为二进制。API:/api/status/api/prefer(仅限本地主机)。

使用vcpkg + CMake构建:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

使用Ansible部署,配合systemd加固。

结果

chronyc tracking

  • Stratum:1
  • 系统时间:+42纳秒
  • RMS偏移:51纳秒
  • 根离散度:15纳秒

DCF77:约3毫秒。

关键要点

  • libgpiod v2需要带GPIOD_LINE_CLOCK_REALTIME的新对象模型
  • chrony在PPS精度(<1微秒)和保持模式上优于ntpd
  • 用于64位的NTP SHM需要为int64_t显式填充
  • NEO-M8T TIM + Stationary:PPS抖动5–10纳秒
  • DCF77验证GPS防欺骗

— Editorial Team

Advertisement 728x90

继续阅读