PPS - 每秒脉冲信号

版权所有 (C) 2007 Rodolfo Giometti <giometti@enneenne.com>

本程序是自由软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款重新发布和/或修改它;无论是许可证的第 2 版,还是(由您选择)任何更高版本。

分发本程序是希望它有用,但不作任何担保;甚至没有对适销性或针对特定用途的适用性的暗示担保。有关更多详细信息,请参阅 GNU 通用公共许可证。

概述

LinuxPPS 提供了一个编程接口 (API),用于在系统中定义多个 PPS 源。

PPS 代表“每秒脉冲信号”,PPS 源只是一个设备,它每秒提供一个高精度信号,以便应用程序可以使用它来调整系统时钟时间。

PPS 源可以连接到串行端口(通常连接到数据载波检测引脚)或并行端口(ACK 引脚)或特殊的 CPU GPIO(这是嵌入式系统中的常见情况),但在每种情况下,当新的脉冲到达时,系统必须对其应用时间戳并将其记录到用户空间。

常见的用法是将 NTPD 作为用户空间程序,与 GPS 接收器作为 PPS 源结合使用,以获得与 UTC 具有亚毫秒级同步的挂钟时间。

RFC 注意事项

在实现 RFC 2783 定义的 PPS API 并使用嵌入式 CPU GPIO 引脚作为信号的物理链路时,我遇到了一个更深层次的问题

在启动时,它需要一个文件描述符作为函数 time_pps_create() 的参数。

这意味着源具有 /dev/... 条目。 对于串行和并行端口,此假设是正确的,除了收集时间戳(这是 PPS API 的核心任务)之外,您还可以做一些有用的事情。 但是对于单用途 GPIO 线,此假设不适用。 在这种情况下,即使是基本的文件相关功能(如 read() 和 write())也毫无意义,并且不应成为使用 PPS API 的前提条件。

如果您认为 PPS 源并不总是与 GPS 数据源连接,则可以简单地解决此问题。

因此,您的程序应检查 GPS 数据源(例如,串行端口)是否也是 PPS 源,如果不是,它们应提供打开另一个设备作为 PPS 源的可能性。

在 LinuxPPS 中,PPS 源只是通常映射到文件 /dev/pps0、/dev/pps1 等的字符设备。

带 USB 到串行设备的 PPS

可以从 USB 到串行设备获取 PPS。 但是,您应该考虑到 USB 堆栈引入的延迟和抖动。 用户报告说,通过 USB 与 PPS 同步时,时钟不稳定在 +-1ms 左右。 使用 USB 2.0,抖动可能会降低到 125 微秒的量级。

由于其欠采样和算法,这可能适用于与 NTP 的时间服务器同步。

如果您的设备没有报告 PPS,您可以检查其驱动程序是否支持该功能。 大多数情况下,您只需要在检查 DCD 状态后添加对 usb_serial_handle_dcd_change 的调用(请参阅 ch341 和 pl2303 示例)。

编码示例

要在内核中注册 PPS 源,您应按如下所示定义 struct pps_source_info

static struct pps_source_info pps_ktimer_info = {
        .name         = "ktimer",
        .path         = "",
        .mode         = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
                        PPS_ECHOASSERT |
                        PPS_CANWAIT | PPS_TSFMT_TSPEC,
        .echo         = pps_ktimer_echo,
        .owner        = THIS_MODULE,
};

然后在您的初始化例程中调用函数 pps_register_source(),如下所示

source = pps_register_source(&pps_ktimer_info,
                    PPS_CAPTUREASSERT | PPS_OFFSETASSERT);

pps_register_source() 原型是

int pps_register_source(struct pps_source_info *info, int default_params)

其中“info”是指向描述特定 PPS 源的结构的指针,“default_params”告诉系统设备的初始默认参数应该是什么(很明显,这些参数必须是描述驱动程序功能的 struct pps_source_info 中定义的参数的子集)。

一旦您在系统中注册了一个新的 PPS 源,您就可以使用以下方式发出断言事件(例如在中断处理程序例程中)

pps_event(source, &ts, PPS_CAPTUREASSERT, ptr)

其中“ts”是事件的时间戳。

如果用户要求,同一函数也可以运行定义的 echo 函数 (pps_ktimer_echo(),将“ptr”指针传递给它)...等等。

请参阅文件 drivers/pps/clients/pps-ktimer.c 以获取示例代码。

SYSFS 支持

如果在内核中启用了 SYSFS 文件系统,它将提供一个新类

$ ls /sys/class/pps/
pps0/  pps1/  pps2/

每个目录都是系统中定义的 PPS 源的 ID,在其中您会找到几个文件

$ ls -F /sys/class/pps/pps0/
assert     dev        mode       path       subsystem@
clear      echo       name       power/     uevent

在每个“assert”和“clear”文件中,您都可以找到时间戳和序列号

$ cat /sys/class/pps/pps0/assert
1170026870.983207967#8

在“#”之前是时间戳(以秒为单位);之后是序列号。 其他文件是

  • echo:报告 PPS 源是否具有 echo 函数;

  • mode:报告可用的 PPS 功能模式;

  • name:报告 PPS 源的名称;

  • path:报告 PPS 源的设备路径,即 PPS 源连接到的设备(如果存在)。

测试 PPS 支持

为了在没有特定硬件的情况下测试 PPS 支持,您可以使用 pps-ktimer 驱动程序(请参阅 PPS 配置菜单中的客户端子部分)和您的发行版 pps-tools 包中提供的用户空间工具,http://linuxpps.org ,或 https://github.com/redlab-i/pps-tools

一旦您启用了 pps-ktimer 的编译,只需 modprobe 它(如果不是静态编译)

# modprobe pps-ktimer

然后按如下所示运行 ppstest

$ ./ppstest /dev/pps1
trying PPS source "/dev/pps1"
found PPS source "/dev/pps1"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1186592699.388832443, sequence: 364 - clear  0.000000000, sequence: 0
source 0 - assert 1186592700.388931295, sequence: 365 - clear  0.000000000, sequence: 0
source 0 - assert 1186592701.389032765, sequence: 366 - clear  0.000000000, sequence: 0

请注意,要编译用户空间程序,您需要文件 timepps.h。 这在上面提到的 pps-tools 存储库中可用。

发生器

有时,人们不仅需要能够捕获 PPS 信号,还需要能够生成它们。 例如,运行一个分布式模拟,这要求计算机的时钟非常紧密地同步。

为此,添加了类 pps-gen。 可以通过定义一个 struct pps_gen_source_info 来在内核中注册 PPS 生成器,如下所示

static const struct pps_gen_source_info pps_gen_dummy_info = {
        .use_system_clock       = true,
        .get_time               = pps_gen_dummy_get_time,
        .enable                 = pps_gen_dummy_enable,
};

其中 use_system_clock 说明生成器是否使用系统时钟来生成其脉冲,或者它们是否来自外围设备时钟。 方法 get_time() 用于查询存储在生成器时钟中的时间,而方法 enable() 用于启用或禁用 PPS 脉冲生成。

然后在您的初始化例程中调用函数 pps_gen_register_source(),如下所示,以在系统中创建一个新的生成器

pps_gen = pps_gen_register_source(&pps_gen_dummy_info);

生成器 SYSFS 支持

如果在内核中启用了 SYSFS 文件系统,它将提供一个新类

$ ls /sys/class/pps-gen/
pps-gen0/  pps-gen1/  pps-gen2/

每个目录都是系统中定义的 PPS 生成器的 ID,在其中您会找到几个文件

$ ls -F /sys/class/pps-gen/pps-gen0/
dev  enable  name  power/  subsystem@  system  time  uevent

要启用 PPS 信号生成,您可以使用以下命令

$ echo 1 > /sys/class/pps-gen/pps-gen0/enable

并行端口生成器

一种方法是发明一些复杂的硬件解决方案,但它可能既没有必要也没有负担得起。 廉价的方法是在其中一台计算机(主计算机)上加载 PPS 生成器,在其他计算机(从计算机)上加载 PPS 客户端,并使用非常简单的电缆通过并行端口传递信号,例如。

并行端口电缆引脚排列

pin     name    master      slave
1       STROBE    *------     *
2       D0        *     |     *
3       D1        *     |     *
4       D2        *     |     *
5       D3        *     |     *
6       D4        *     |     *
7       D5        *     |     *
8       D6        *     |     *
9       D7        *     |     *
10      ACK       *     ------*
11      BUSY      *           *
12      PE        *           *
13      SEL       *           *
14      AUTOFD    *           *
15      ERROR     *           *
16      INIT      *           *
17      SELIN     *           *
18-25   GND       *-----------*

请注意,并行端口中断仅在高 -> 低转换时发生,因此它用于 PPS 断言边沿。 只能通过在中断处理程序中轮询来确定 PPS 清除边沿,这实际上可以更精确地完成,因为中断处理延迟可能非常大且随机。 因此,当前的 parport PPS 生成器实现(pps_gen_parport 模块)倾向于使用清除边沿进行时间同步。

清除边沿轮询是在禁用中断的情况下完成的,因此最好选择断言和清除边沿之间的延迟尽可能小,以减少系统延迟。 但如果它太小,从机将无法捕获清除边沿转换。 在大多数情况下,30us 的默认值应该足够好。 可以使用“delay”pps_gen_parport 模块参数选择延迟。

英特尔定时 I/O PPS 信号发生器

英特尔定时 I/O 是一种高精度设备,存在于 2019 年及更新的英特尔 CPU 上,可以生成 PPS 信号。

定时 I/O 和系统时间都由同一硬件时钟驱动。 信号的生成精度约为 20 纳秒。 生成的 PPS 信号用于将外部设备与系统时钟同步。 例如,它可用于与接收由定时 I/O 设备生成的 PPS 信号的设备共享您的时钟。 有专用的定时 I/O 引脚可将 PPS 信号传递到外部设备。

使用英特尔定时 I/O 作为 PPS 生成器

开始生成 PPS 信号

$echo 1 > /sys/class/pps-gen/pps-genx/enable

停止生成 PPS 信号

$echo 0 > /sys/class/pps-gen/pps-genx/enable