qptp 时间同步流程

网络层级

流程说明:

目前 gPTP 同步到 Android 有 vhal 同步和 ptpClock 同步两种方式,平台化考虑 Tier1 自行决定 采用哪种方案。

  1. 时间同步优先级:gPTP > NTP > RTC。

  2. 开机时安卓侧请求 RTC 时间,此时没有 gPTP 时间,则用 RTC 时间。

  3. Tbox 部署 gPTP master,发起时间同步消息,QNX 作为 gPTP slave 接收消息,并同步系统时间( Android 和 QNX 时间同步更新 ),同步周期暂定为 1-10 秒之间。

  4. QNX 系统 gPTP 更新时间后,时间同步服务校准 RTC 时间,并通知 Android TimesyncService 校准Android 系统时间,同时通知 CarTimeSync 已同步 gPTP 时间。

  5. 如果未收到 CarTimeSync 已同步 gPTP 时间的通知,并且已经获取到 NTP 时间,由 CarTimeSync 通知QNX 侧同步 NTP 时间,同时更新 Android 侧时间。

  6. 如果后面 gPTP 时间同步成功,直接使用gPTP时间覆盖,同时更新 QNX 和 Android 侧时间。

1.2时间同步的必要条件

时间基准: 选取一个设备的时钟作为主时钟(类似于电视台或电包台的时钟),Grandmaster

周期同步: 主时钟定期发布同步信息(类似于电视台或电台整点报时),如周期1s更新

同步校准: 其他设备根据同步信息校正自己的本地时钟(相位同步,频率同步)

1.3.3基于gPTP协议的时间同步方案

基于以太网总线,使用时间同步协议实现时间同步

PTP: (Precision Time Protocol,精确时间协议),IEEE 15883

gPTP: (General Precise Time Protocol,通用精确时间协议), IEEE 802.1AS

gPTP(IEEE 802.1AS), 基于PTP (IEEE 1588v2)协议进行了一系列优化,形成了更具有针对性的时间同步机

制, gPTP的目标是确保局域网里的所有设备的时间完全一致(同步精度在±500ns内),车载领域常用gPTP

  • 主端口发送 Sync 报文,报文离开主端口 MAC 层时,触发主端口记录此时的时间戳 T1。
  • 从端口接收 Sync 报文,报文到达从端口 MAC 层时,触发从端口记录此时的时间戳 T2。
  • 主端口再次发送 Sync 报文,报文离开主端口 MAC 层时,触发主端口记录此时的时间戳 T3。
  • 从端口接收 Sync 报文,报文到达从端口 MAC 层时,触发从端口记录此时的时间戳 T4。通过两组 Sync 报文和两组 Follow - UP 可以计算出各个节点之间的频率偏差。

  • 主时钟在 t1 时刻发送 Sync 命令,从时钟在 t2 时刻收到同步指令。
  • 主时钟发送一个 Follow_Up 命令,该命令中携带 t1 的值,让从时钟知道 Sync 指令的发送时间。
  • 从时钟在 t3 时刻发送一个 Delay_Req 命令。
  • 主时钟在 t4 时刻收到该命令后发送一个 Delay_Resp 响应,携带 t4 的值,让从时钟知道主时钟收到 Delay_Req 命令的时间。
  • 从时钟根据 t1、t2、t3、t4 四个值计算路径传输延时和自己与主时钟的偏差,进而调整自己的时间

GPTP(Generalized Precision Time Protocol,通用精确时间协议)时间戳采样方式的 MAC 层实现,需要从 GPTP 时间戳采样方式、MAC 层功能以及两者结合的意义和实现细节等方面来理解,以下为你展开介绍:

GPTP 时间戳采样方式基础

GPTP 是 IEEE 802.1AS 标准的一部分,用于在以太网网络中实现高精度的时钟同步。时间戳采样方式是 GPTP 协议中用于记录数据包收发时间的方法,有硬件时间戳、软件时间戳和混合时间戳三种常见方式。这些时间戳是计算时钟偏移和网络延迟的关键依据,对于实现准确的时钟同步至关重要。

MAC 层的功能和作用

MAC(Media Access Control,介质访问控制)层是数据链路层的下半部分,主要负责控制与连接物理层的物理介质。它的主要功能包括:

  • 帧封装与解封装:将上层协议数据单元封装成 MAC 帧,以及将接收到的 MAC 帧解封装为上层数据。

  • 介质访问控制:决定节点何时可以访问共享介质,避免数据冲突。

  • 物理地址识别:使用 MAC 地址来识别网络中的不同设备。

GPTP 时间戳采样方式在 MAC 层实现的含义

时间戳生成位置

在 MAC 层实现 GPTP 时间戳采样,意味着时间戳的生成是在 MAC 层完成的。当数据包在 MAC 层进行封装或解封装时,记录下精确的时间信息。例如,在发送数据包时,MAC 层在帧即将离开物理层接口的时刻生成发送时间戳;在接收数据包时,MAC 层在帧刚进入物理层接口的时刻生成接收时间戳。

高精度时间同步

MAC 层实现时间戳采样能够提供更精确的时间信息,因为它更接近物理层,减少了上层软件处理带来的延迟和不确定性。这对于需要高精度时钟同步的应用场景(如工业自动化、电力系统中的分布式测量和控制)非常重要。

MAC 层实现的具体方式

硬件支持

许多网络接口控制器(NIC)或交换机芯片都提供了在 MAC 层实现时间戳采样的硬件支持。这些硬件可以在 MAC 层的特定事件(如帧发送开始、帧接收完成)发生时,自动记录时间戳。硬件实现的优点是速度快、精度高,能够达到纳秒级别的时间精度。

软件辅助

虽然硬件实现是主要方式,但软件也可以在 MAC 层实现时间戳采样中发挥辅助作用。例如,软件可以对硬件生成的时间戳进行校准和修正,以补偿硬件时钟的漂移和误差。此外,软件还可以处理一些复杂的时间戳计算和同步算法。

实现步骤和流程

硬件配置

  • 首先需要对支持 MAC 层时间戳采样的硬件设备进行配置,包括设置时间戳生成的触发条件(如帧发送、帧接收)和时间戳的格式。

  • 配置硬件时钟源,确保其具有足够的精度和稳定性。

软件编程

  • 在操作系统或驱动程序中编写代码,以读取和处理 MAC 层生成的时间戳。

  • 实现 GPTP 协议的消息交换和同步算法,根据时间戳计算时钟偏移和网络延迟,并调整本地时钟。

同步过程

  • 主时钟周期性地发送 Sync 消息,在 MAC 层记录发送时间戳。

  • 从时钟在 MAC 层接收到 Sync 消息时记录接收时间戳,并发送 Delay_Req 消息,同样在 MAC 层记录发送和接收时间戳。

  • 主时钟和从时钟通过交换这些时间戳信息,计算网络延迟和时钟偏移,实现时钟同步。

验证:/* gPTP LPM modes */

#define GPTP_LPM_MODE_OFF 1

#define GPTP_LPM_MODE_ON 2

qgptp emac1 -V & -l 1 // 2

SYNC_MESSAGE = 0, DELAY_REQ_MESSAGE = 1, PATH_DELAY_REQ_MESSAGE = 2, PATH_DELAY_RESP_MESSAGE = 3,


else if (strcmp(argv[i] + 1, "V") == 0) { portInit.automotive_profile = true; } sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM ); sigaddset(&set, SIGHUP); sigaddset(&set, SIGUSR2); if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) {


/** * @enum Event * IEEE 1588 event enumeration type * Defined at: IEEE 1588-2008 Clause 9.2.6 */

typedef enum { NULL_EVENT = 0, //!< Null Event. Used to initialize events. POWERUP = 5, //!< Power Up. Initialize state machines. INITIALIZE, //!< Same as POWERUP. LINKUP, //!< Triggered when link comes up. LINKDOWN,

//!< Triggered when link goes down. STATE_CHANGE_EVENT, //!< Signalizes that something has changed. Recalculates best master. SYNC_INTERVAL_TIMEOUT_EXPIRES, //!< Sync interval expired. Its time to send a sync message. PDELAY_INTERVAL_TIMEOUT_EXPIRES, //!< PDELAY interval expired. Its time to send pdelay_req message SYNC_RECEIPT_TIMEOUT_EXPIRES, //!< Sync receipt timeout. Restart timers and take actions based on port's state. QUALIFICATION_TIMEOUT_EXPIRES, //!< Qualification timeout. Event not currently used ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES, //!< Announce receipt timeout. Same as SYNC_RECEIPT_TIMEOUT_EXPIRES ANNOUNCE_INTERVAL_TIMEOUT_EXPIRES, //!< Announce interval timout. Its time to send an announce message if asCapable is true FAULT_DETECTED, //!< A fault was detected. PDELAY_DEFERRED_PROCESSING, //!< Defers pdelay processing PDELAY_RESP_RECEIPT_TIMEOUT_EXPIRES, //!< Pdelay response message timeout PDELAY_RESP_PEER_MISBEHAVING_TIMEOUT_EXPIRES, //!< Timeout for peer misbehaving. This even will re-enable the PDelay Requests SYNC_RATE_INTERVAL_TIMEOUT_EXPIRED, //!< Sync rate signal timeout for the Automotive Profile POWERDOWN //!< Power down. closing GPTP daemon, comes when ctrl + c is used

} Event;

int main(int argc, char **argv)

//daemon_cl.cpp pPort = new EtherPort(&portInit);

if (!pPort->init_port()) {

bool CommonPort::init_port( void )

{ return _init_port(); =====>> EtherPort::_init_port( void ) EtherPort::EtherPort( PortInit_t *portInit ) : CommonPort( portInit )

timestamper = new LinuxTimestamperGeneric(); portInit.timestamper = timestamper; //sequenceId and message type based int LinuxTimestamperGeneric::HWTimestamper_txtimestamp ( int32_t messageId, uint16_t sequenceId, Timestamp &timestamp, unsigned &clock_value, bool last ) PTPMessageCommon *buildPTPMessage void EtherPort::processMessage ( char *buf, int length, LinkLayerAddress *remote, uint32_t link_speed ) { GPTP_LOG_VERBOSE("Processing network buffer");

PTPMessageCommon *msg = buildPTPMessage( buf, (int)length, remote, this );

if (pm_state != 0) {

if (pm_state == GPTP_LPM_MODE_ON) {

qgptp_port->gPTP_lpm = true; qgptp_port->processEvent(LINKDOWN); }

void PTPMessageSignalling::processMessage( EtherPort *port )

bool PTPMessageFollowUp::sendPort bool CommonPort::processEvent( Event e ) //!< Sync interval expired. Its time to send a sync message. case SYNC_INTERVAL_TIMEOUT_EXPIRES: //发起同步msg GPTP_LOG_DEBUG("SYNC_INTERVAL_TIMEOUT_EXPIRES occured");

// If asCapable is true attempt some media specific action ret = true;

if( asCapable )

ret = _processEvent( e ); /* Do getDeviceTime() after transmitting sync frame causing an update to local/system timestamp */

{ Timestamp system_time; Timestamp mono_time; Timestamp device_time; uint32_t local_clock, nominal_clock_rate; FrequencyRatio local_system_freq_offset; FrequencyRatio local_mono_freq_offset = 0;

int64_t local_system_offset; int64_t local_mono_offset;

getDeviceTime ( system_time, mono_time, device_time, local_clock, nominal_clock_rate ); GPTP_LOG_VERBOSE ( "port::processEvent(): System time: %u,%u " "Device Time: %u,%u", system_time.seconds_ls, system_time.nanoseconds, device_time.seconds_ls, device_time.nanoseconds ); local_system_offset = TIMESTAMP_TO_NS(system_time) - TIMESTAMP_TO_NS(device_time); local_mono_offset = TIMESTAMP_TO_NS(mono_time) - TIMESTAMP_TO_NS(device_time); local_system_freq_offset = clock->calcLocalSystemClockRateDifference ( device_time, system_time, mono_time, &local_mono_freq_offset );

clock->setMasterOffset ( this, 0, device_time, 1.0, local_system_offset, system_time, local_system_freq_offset, local_mono_offset, mono_time, local_mono_freq_offset, getSyncCount(), pdelay_count, port_state, asCapable ); } // Call media specific action for completed sync syncDone();

bool EtherPort::_processEvent( Event e )

case SYNC_INTERVAL_TIMEOUT_EXPIRES: { /* Set offset from master to zero, update device vs system time offset */ // Send a sync message and then a followup to broadcast PTPMessageSync *sync = new PTPMessageSync(this); PortIdentity dest_id; bool tx_succeed;

getPortIdentity(dest_id);

sync->setPortIdentity(&dest_id); getTxLock();

tx_succeed = sync->sendPort(this, NULL);

GPTP_LOG_VERBOSE("Sent SYNC message");


bool PTPMessageSync::sendPort ( EtherPort *port, PortIdentity *destIdentity ) { uint8_t buf_t[256]; uint8_t *buf_ptr = buf_t + port->getPayloadOffset(); unsigned char tspec_msg_t = 0x0; Timestamp originTimestamp_BE; uint32_t link_speed; memset(buf_t, 0, 256); // Create packet in buf // Copy in common header messageLength = PTP_COMMON_HDR_LENGTH + PTP_SYNC_LENGTH; tspec_msg_t |= messageType & 0xF; buildCommonHeader(buf_ptr); // Get timestamp originTimestamp = port->getClock()->getTime(); originTimestamp_BE.seconds_ms = PLAT_htons(originTimestamp.seconds_ms); originTimestamp_BE.seconds_ls = PLAT_htonl(originTimestamp.seconds_ls); originTimestamp_BE.nanoseconds = PLAT_htonl(originTimestamp.nanoseconds); // Copy in v2 sync specific fields memcpy(buf_ptr + PTP_SYNC_SEC_MS(PTP_SYNC_OFFSET), &(originTimestamp_BE.seconds_ms), sizeof(originTimestamp.seconds_ms)); memcpy(buf_ptr + PTP_SYNC_SEC_LS(PTP_SYNC_OFFSET), &(originTimestamp_BE.seconds_ls), sizeof(originTimestamp.seconds_ls)); memcpy(buf_ptr + PTP_SYNC_NSEC(PTP_SYNC_OFFSET), &(originTimestamp_BE.nanoseconds), sizeof(originTimestamp.nanoseconds)); port->sendEventPort ( PTP_ETHERTYPE, buf_t, messageLength, MCAST_OTHER, destIdentity, &link_speed ); port->incCounter_ieee8021AsPortStatTxSyncCount(); return getTxTimestamp( port, link_speed ); }

  • 时间同步过程:主时钟周期性地发送包含其当前时间戳的同步消息(Sync Message),从时钟在接收到同步消息时记录本地时间戳。然后,从时钟通过与主时钟交换延迟请求(Delay_Req Message)和延迟响应(Delay_Resp Message)消息,测量主从时钟之间的消息传输延迟。根据这些时间戳和传输延迟信息,从时钟可以计算出与主时钟的时间偏差,并据此调整本地时钟,实现时间同步。


void IEEE1588Clock::setMasterOffset ( CommonPort *port, int64_t master_local_offset, #define INVALID_TIMESTAMP (Timestamp( 0xC0000000, 0, 0 )) /*!< Defines an invalid timestamp using a Timestamp instance and a fixed value*/ #define PDELAY_PENDING_TIMESTAMP (Timestamp( 0xC0000001, 0, 0 )) /*!< PDelay is pending timestamp */ PTPMessageCommon *buildPTPMessage ( char *buf, int size, LinkLayerAddress *remote, EtherPort *port ) { if (port == NULL) { return NULL; } OSTimer *timer = port->getTimerFactory()->createTimer(); PTPMessageCommon *msg = NULL; msg->_timestamp = timestamp; class PTPMessageSync : public PTPMessageCommon === 父类实现 void PTPMessageFollowUp::processMessage( EtherPort *port ) { GPTP_LOG_DEBUG("Processing a follow-up message"); PTPMessageSync *sync = port->getLastSync(); sync_arrival = sync->getTimestamp(); local_clock_adjustment = port->getClock()-> calcMasterLocalClockRateDifference ( preciseOriginTimestamp, sync_arrival ); scalar_offset = TIMESTAMP_TO_NS( sync_arrival ); scalar_offset -= TIMESTAMP_TO_NS( preciseOriginTimestamp ); port->getClock()->setMasterOffset ( port, scalar_offset, sync_arrival, local_clock_adjustment,

/* brief API to get rx time stamp value.

\\ details This function will read received packet's timestamp from

\\ the descriptor and pass it to stack and also perform some sanity checks.

\param[in] pdata - pointer to private data structure.

\param[in] pmbuf - pointer to mbuf structure.

\param[in] desc_data - pointer to wrapper receive descriptor structure.

\param[in] q_indx - Queue/Channel number.

*/

static unsigned char emac_get_rx_hwtstamp

hw_if->get_rx_tstamp = emac_get_rx_tstamp;

/*!

  • \brief This sequence is used get the 64-bit of the timestamp

  • captured by the device for the corresponding received packet

  • in nanosecond.

  • \param[in] rxdesc

  • \return (unsigned long long) on success

  • \retval ns

/*=======================================================*/

static ULONG_LONG emac_get_rx_tstamp( emac_rx_cntxt_desc_t *rxdesc)

{

ULONG_LONG ns;

ULONG varrdes1;

/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/

RX_CONTEXT_DESC_RDES0_Ml_Rd(rxdesc->RDES0, ns);

RX_CONTEXT_DESC_RDES1_Ml_Rd(rxdesc->RDES1, varrdes1);

ns = ns + (varrdes1 * 1000000000ull);

return ns;

} /* emac_get_rx_tstamp */

static unsigned char emac_get_rx_hwtstamp

pdata->xstats.rx_timestamp_captured_n++;

/* get valid tstamp */

ns = hw_if->get_rx_tstamp((struct emac_rx_cntxt_desc_s *)rx_context_desc);


int emac_ptp_is_eventmsg ( struct emac_prv_data_t *pdata, struct mbuf *m, ptpv2hdr_t **ph ) { int ret = EMAC_FALSE; int remain, pktlen; struct ether_header *eh; struct ip *iph; struct udphdr *udph; emac_dev_t *dev = pdata->parent; if (m == NULL) { DBGPR("%s: NULL mbuf", __func__); goto check_failed; } if (m->m_len < sizeof(struct ether_header) + sizeof(ptpv2hdr_t)) { goto check_failed; } eh = (struct ether_header *) m->m_data; switch (ntohs(eh->ether_type)) { case ETHERTYPE_PTP:


if (emac_ptp_is_eventmsg(pdata, pmbuf, &ph) == TRUE && ph != NULL) { if (pmbuf->m_len < sizeof(struct ether_header) + sizeof(ptpv2hdr_t)) { EMAC_MSG_ERROR("%s: length of packet is less than ether_header + ptpv2hdr : pmbuf->m_len:%d", __func__, pmbuf->m_len); return EMAC_PTP_PKT_INVAL; } pthread_mutex_lock(&pdata->ptp_rx_mutex); pdata->rx_ptp_event[pdata->ptp_event_rx_idx].seqID = ntohs(ph->sequenceId); pdata->rx_ptp_event[pdata->ptp_event_rx_idx].messageType = ph->messageId & 0x0F; pdata->rx_ptp_event[pdata->ptp_event_rx_idx].ts = ns; DBGPR("%s: PTP Rx msgType: %d seqId: %d ts %llu",__func__, pdata->rx_ptp_event[pdata->ptp_event_rx_idx].messageType, pdata->rx_ptp_event[pdata->ptp_event_rx_idx].seqID, pdata->rx_ptp_event[pdata->ptp_event_rx_idx].ts); pdata->ptp_event_rx_idx = (pdata->ptp_event_rx_idx + 1) % EMAC_PTP_EVENT_BUF_SIZE; pthread_mutex_unlock(&pdata->ptp_rx_mutex); }


switch (pulse.code) { case EMAC_RX_HANDLE_PULSE: emac_disable_all_ch_rx_interrpt(pdata); while (1 != pdata->threads_need_quiesce && 1 != pdata->rx_thread_need_quiesce) { received = emac_rx_poll_mq(emac_dev, weight); for (chInx = 1; chInx < EMAC_RX_QUEUE_CNT; chInx++) { per_q_received = pdata->clean_rx(emac_dev->pdata, per_q_budget, chInx); received += per_q_received; } else { pdata->rx_buffer_len = EMAC_ETH_FRAME_LEN; pdata->clean_rx = emac_clean_rx_irq; pdata->alloc_rx_buf = emac_alloc_rx_buf; EMAC_MSG_DEBUG1("alloc_rx_buf = emac_alloc_rx_buf"); } static int emac_clean_rx_irq /* get rx tstamp if available */ if (hw_if->rx_tstamp_available(rx_normal_desc)) { DBGPR("-->%s: RX timestamp is available q_indx = %u",__func__, q_indx); ret = emac_get_rx_hwtstamp(pdata, pmbuf, desc_data, q_indx); if (ret == 0)

上层调用的接口

o configure the gPTP clock manually as a timeTransmitter clock, run the following command: qgptp eth0 -V -GM & ■ To configure the gPTP clock manually as a timeReceiver clock, run the following command: qgptp eth0 -V &

你给出的内容是关于手动配置 gPTP(Generic Precision Time Protocol,通用精确时间协议)时钟的说明。下面为你详细解释这些命令以及 gPTP 时钟的相关概念。

gPTP 时钟简介

gPTP 是一种用于在以太网网络中实现高精度时间同步的协议。在一个 gPTP 网络里,时钟设备可以分为不同的角色,主要有时间发送者(timeTransmitter)和时间接收者(timeReceiver)。

  • 时间发送者(timeTransmitter):也被称作主时钟(Grandmaster Clock),它会产生精确的时间信号,并通过网络将这些时间信息发送给其他设备。

  • 时间接收者(timeReceiver):也叫从时钟(Slave Clock),它会接收主时钟发送的时间信号,并根据这些信号来调整自己的时钟,以实现与主时钟的时间同步。

命令解释
qgptp eth0 -V -GM &
  • qgptp:这是一个用于配置和管理 gPTP 时钟的工具。

  • eth0:指定要使用的网络接口。在这个例子中,使用的是名为 eth0 的以太网接口。

  • -V:通常是用于启用详细日志输出的选项。开启该选项后,qgptp 工具会输出更详细的调试信息,方便用户了解 gPTP 时钟的配置和运行情况。

  • -GM:表示将该设备配置为时间发送者(主时钟)。GM 是 Grandmaster 的缩写。

  • &:这是一个 shell 命令符号,用于将命令放到后台执行。这样在执行命令后,终端可以继续接受其他输入,而不必等待 qgptp 命令执行完毕。

qgptp eth0 -V &
  • 这个命令与上面的命令类似,区别在于没有 -GM 选项。在没有 -GM 选项的情况下,qgptp 工具会将该设备配置为时间接收者(从时钟)。因此,该命令的作用是将 eth0 接口对应的设备配置为 gPTP 时间接收者,并开启详细日志输出,命令在后台执行。

在广义精确时间同步协议(gPTP)中,时钟同步的 One - Step(一步法)和 Two - Step(两步法)是两种不同的时间戳传递方式,用于实现主从时钟之间的时间同步2。具体如下2:

  • One - Step(一步法):主时钟在同步消息中直接发送其当前的时间戳。从时钟接收到消息后,就会根据这个时间戳来更新自己的时钟。这种方法的优点是简单,处理速度快,但是因为处理时间和网络传输时间的不确定性,可能会影响到时间同步的精度。

  • Two - Step(两步法):主时钟首先发送一个同步消息,但这个消息中并不包含时间戳,而是标记为两步法的标志。然后,主时钟在发送完同步消息后,再发送一个关联的后续消息,这个消息中包含了发送同步消息时的精确时间戳。从时钟在接收到这两个消息后,就可以根据这个精确的时间戳来更新自己的时钟。两步法虽然比一步法复杂,需要发送和处理更多的消息,但是因为它能够提供更精确的时间戳,所以能够提供更高的时间同步精度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值