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 同步时,时钟不稳定约为 +-1 毫秒。使用 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 源是否有回显功能;

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

  • name:报告 PPS 源的名称;

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

测试 PPS 支持

为了测试 PPS 支持,即使没有特定的硬件,您也可以使用 pps-ktimer 驱动程序(请参阅 PPS 配置菜单中的客户端子部分)和您的发行版的 pps-tools 软件包中提供的用户空间工具,http://linuxpps.orghttps://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 生成器,在其他计算机(从计算机)上加载 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 模块)旨在将清除边沿用于时间同步。

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