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