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