在树莓派上搭建独立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_name和gpiod_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():
// 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 |
|-----------|------|--------|
| 收敛时间 | 分钟级 | 秒级(makestep 0.5 3) |
| 信号丢失 | 性能下降 | 保持模式(local stratum 10) |
| 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
暂无评论。