# 时间同步调试指南

## 概述

时间同步是计算机系统中确保多个设备或组件时钟一致性的关键技术。根据精度、场景和实现方式的不同，存在多种时间同步方案，以下是常见方案的对比：

| 方案       | 主要目的                                                                 | 典型场景                     |
|------------|--------------------------------------------------------------------------|------------------------------|
| NTP        | 通过网络协议同步系统时间，精度在毫秒级。                                      | 通用服务器、个人计算机的时间同步 |
| PTP        | 提供微秒级甚至纳秒级的高精度时间同步，依赖硬件支持。                              | 工业控制、高频交易、 5G 网络等     |
| RTC        | 维持硬件时钟的持久化（即使断电），提供系统启动时的初始时间。                          | 所有计算机设备的基础时间源       |
| KVM 时钟同步 | 解决虚拟机与宿主机之间的时钟漂移问题。                                            | 虚拟化环境（如云计算）           |
| TSC        | 利用 CPU 内部计数器实现高精度计时（纳秒级），用于性能敏感操作。                              | 内核调度、性能分析、实时任务处理 |
| HPET       | 提供高精度硬件定时器，替代传统的 PIT（ Programmable Interval Timer）。                        | 需要精确事件触发的场景（如多媒体播放） |
| Adjtimex   | 调整系统时钟的频率和偏移，供 NTP 等工具微调时间。                                        | 时间同步算法的底层支持           |
| Tickless   | 动态调整时钟中断，减少无任务时的功耗。                                              | 移动设备、节能场景               |

本指南重点针对结合 PPS（ Pulse Per Second）的高精度时间同步方案进行说明。

PPS（ Pulse Per Second，每秒脉冲）是一种基于硬件信号的高精度时间同步方案，通过每秒一次的精确脉冲信号（通常由 GPS、原子钟等高精度时钟源提供）校准系统时间。在 Linux 系统中， PPS 与内核时间子系统（如 NTP、 PTP）协同工作，可实现微秒级甚至纳秒级的时间同步精度。

## 特点
高精度：依赖硬件脉冲信号，同步误差通常小于 1 微秒。

硬件依赖性：需要支持 PPS 信号输入的设备（如 GPS 接收器、 PPS 专用接口）。

低抖动：硬件信号直接触发中断，绕过软件延迟。

多协议支持：可与 NTP、 PTP 等协议结合使用。

## 功能描述

### 典型应用

通信基站： 5G 基站间的严格时间同步（如 TDD 时隙对齐）。

高频交易：金融订单时间戳需要纳秒级校准。

科学实验：分布式传感器数据的精确时间对齐。

电力系统：电网相位测量单元（ PMU）的同步采样。

### 功能原理

- 硬件信号生成：

GPS 接收器、原子钟等设备每秒产生一次精确脉冲（上升沿严格对齐 UTC 秒边界）。

- 内核捕获与记录：

PPS 信号通过 GPIO、串口或专用硬件接口传入主机。

Linux 内核的 PPS 子系统（ CONFIG_PPS）注册中断处理函数，记录脉冲到达的精确时间戳（基于 TSC 或 HPET 时钟源）。

- 用户态时间修正：

时间同步守护进程（如 chronyd、 ptp4l）通过 /dev/ppsX 设备读取时间戳，计算系统时钟偏差，并通过 adjtimex 系统调用调整内核时间参数（如频率补偿 mult 和偏移量 offset）。

### 工作方式

#### 硬件层
- 信号源：
GPS 模块（如 U-Blox）、 PPS 发生器、原子钟等。

- 接口类型：

    - GPIO 引脚：直接连接主板的 GPIO 接口（需电平匹配）。

    - 串口：通过 RS-232 的 DCD 或 RI 信号线传输 PPS。

    - 专用接口卡：如 Endace DAG 卡、 Intel i210 NIC 的 PPS 支持。

#### 内核层
- PPS 子系统：

驱动路径： drivers/pps/（核心框架）、 drivers/pps/clients/（设备驱动）。

设备节点：/dev/pps0 、/dev/pps1 （每个 PPS 设备对应一个节点）。

- 时间戳记录：

PPS 触发中断后，内核调用 pps_event() 记录时间戳，存储于 struct pps_device 的 assert_tu 字段。

#### 用户态层
- 同步工具：

    - Chrony：通过 refclock PPS 指令绑定 PPS 设备，优先使用其时间戳。

    - PTP： ptp4l 可配置 PPS 为时间参考源，结合 PTP 协议实现多节点同步。

    - NTP：通过 pps 驱动类型配置，与 NTP 服务器时间融合校准。

## 驱动代码

该功能的主要代码都在 kernel 和应用层
检测 pps 信号这里有两种硬件方案，一种是 GPIO 中断，一种配置引脚 Time_Sync 功能。\
我们首先介绍 GPIO 中断的配置方式。

**dts 部分**

(1)  {project}/kernel/arch/arm64/boot/dts/hobot/pinmux-func.dtsi 中添加 gps_pps 的引脚定义，方便 hobot-pps 标签使用

```
&lsio_iomuxc {

        ......

        gps_pps_func: gps_pps_func {
        horizon,pins = <
                LSIO_I2C1_SDA        LSIO_PINMUX_2        BIT_OFFSET22        MUX_ALT1        &pconf_input_en_3v3
        >; /*因为复用的 I2C1_SDA pin 的地址 , 所以定义该 pin 的时候，只需要更改 MUX_ALT1即可*/
        };
};

```
(2) {project}/kernel/arch/arm64/boot/dts/hobot/x5-evb.dtsi 中检查 &hobot_pps 已经打开了：
```
&hobot_pps {
        status = "okay";
};
```
(3) {project}/kernel/arch/arm64/boot/dts/hobot/x5.dtsi 对 hobot_pps 进行描述和完善中断配置
```
    hobot_pps: hobot_pps {
            pinctrl-names = "default";
            pinctrl-0 = <&gps_pps_func &lsio_gpio1_10>;/* 指定引脚控制器状态 "default" 对应的引脚配置，引用了一个名为 "gps_pps_func" 和 "lsio_gpio1_10"的引脚配置*/
            interrupt-parent = <&ls_gpio1_porta>;/*指定该设备的中断父节点为 "ls_gpio1_porta"，即该设备的中*/
            interrupts = <10 IRQ_TYPE_EDGE_RISING>; /*指定中断号和中断触发类型，中断号为 10，触发类型为上升沿触发（IRQ_TYPE_EDGE_RISING）*/
            compatible = "hobot-pps"; /*指定设备的兼容性字符串，用于匹配驱动程序。*/
            status = "disabled"; /*指定设备的状态为 "disabled"，表示该设备默认情况下是禁用的，不会在系统中启用*/
    };
```

**驱动部分**
{project}/kernel/drivers/pps/clients/hobot-pps.c 中打开 REAL_PPS_ENABLE
```
#define REAL_PPS_ENABLE
// #define TIMER_INTERVAL 1
```

**deconfig 部分**
```
CONFIG_HOBOT_PPS_CLIENT=y
CONFIG_PPS_CLIENT_HOBOT_PPS=y
```

如果使用引脚 Time_Sync 功能，需要修改如下位置。

```
&lsio_iomuxc {

        ......
	gps_pps_func: gps_pps_func {
        horizon,pins = <
                LSIO_I2C1_SDA        LSIO_PINMUX_2        BIT_OFFSET22        MUX_ALT2        &pconf_input_en_3v3
        >;/*因为复用的 I2C1_SDA pin 的地址 , 所以定义该 pin 的时候，只需要更改为 MUX_ALT2*/
	};
};
```
驱动部分和 deconfig 部分可以保持不变。

## 功能使用

使用 ppstest 测试代码，来测试 pps 功能是否能正常收到信号

![hobot_pps_example](./_static/_images/45-Hobot-pps_Driver_Debug_Guide/hobot_pps_example.png)

测试工具可以直接使用开源的代码： https://github.com/redlab-i/pps-tools
编译方式也在该仓库的 readme。

## 常见问题
- Q : 如何确认 pps 信号是否真的产生。
- A : 可以使用示波器来观察，接上 pps 信号发生器设备之后，发现形如下面的信号，可以判断 pps 信号是否真实产生了。

![pps_oscilloscope_display](./_static/_images/45-Hobot-pps_Driver_Debug_Guide/pps_oscilloscope_display.png)

如果手上暂时没有 pps 设备，也可以使用 pwm 信号来模拟 pps 信号，进而观察 pps 测试是否正常。上图中的信号就是通过 pwm 来模拟的。
