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.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 生成器,在其他计算机(从计算机)上加载 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 模块参数可用于选择延迟。