http://www.verydemo.com/demo_c161_i272139.html
本文章基于stm32官方例程实现,详细代码可下载例程。
在RT-thread系统上实现1 5 8 8协议,网络驱动上需要做较多的修改。
2.代码修改
2.1驱动分析
2.2驱动层的修改
原代码的DMA描述符如下:
- static ETH_DMADESCTypeDef DMARxDscrTab[ETH_RXBUFNB], DMATxDscrTab[ETH_TXBUFNB];
- //STM32的MAC加入时间戳时,会将描述符的地址覆盖,
- //所以需要将地址提前保存起来,MAC的时间戳读出来之后,再将地址写回去。
- //再创建两个描述符用于保存地址
- ETH_DMADESCTypeDef DMAPTPRxDscrTab[ETH_RXBUFNB], DMAPTPTxDscrTab[ETH_TXBUFNB];/* Ethernet Rx & Tx PTP Helper Descriptors */
2.3发送函数的更改
- rt_err_t rt_stm32_eth_tx( rt_device_t dev, struct pbuf* p)
- {
- struct pbuf* q;
- rt_uint32_t offset = 0;
- unsigned char * buffer;
- /* get free tx buffer */
- {
- rt_err_t result;
- result = rt_sem_take(&tx_buf_free, 2);
- if (result != RT_EOK) return -RT_ERROR;
- }
- #if LWIP_PTP
- unsigned int timeout = 0;
- struct ptptime_t timestamp;
- DMATxDescToSet->Buffer1Addr = DMAPTPTxDescToSet->Buffer1Addr; //保存地址
- DMATxDescToSet->Buffer2NextDescAddr = DMAPTPTxDescToSet->Buffer2NextDescAddr;
- #endif
- buffer = (unsigned char *)DMATxDescToSet->Buffer1Addr;
- for (q = p; q != NULL; q = q->next)
- {
- rt_uint8_t* ptr;
- rt_uint32_t len;
- len = q->len;
- ptr = q->payload;
- /**Copy the frame to be sent into memory pointed by the current ETHERNET DMA Tx descriptor*/
- memcpy((void *)(&buffer[offset] ), ptr, len);
- offset += len;
- }
- #ifdef ETH_TX_DUMP
- .......
- #endif
- /* Setting the Frame Length: bits[12:0] */
- DMATxDescToSet->ControlBufferSize = (p->tot_len & ETH_DMATxDesc_TBS1);
- /* Setting the last segment and first segment bits (in this case a frame is transmitted in one descriptor) */
- DMATxDescToSet->Status |= ETH_DMATxDesc_LS | ETH_DMATxDesc_FS;
- /* Enable TX Completion Interrupt */
- DMATxDescToSet->Status |= ETH_DMATxDesc_IC;
- #ifdef CHECKSUM_BY_HARDWARE
- ........
- #endif
- /* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
- DMATxDescToSet->Status |= ETH_DMATxDesc_OWN;
- /* When Tx Buffer unavailable flag is set: clear it and resume transmission */
- if ((ETH->DMASR & ETH_DMASR_TBUS) != (uint32_t)RESET)
- {
- /* Clear TBUS ETHERNET DMA flag */
- ETH->DMASR = ETH_DMASR_TBUS;
- /* Transmit Poll Demand to resume DMA transmission*/
- ETH->DMATPDR = 0;
- }
- #if LWIP_PTP
- /* Wait for ETH_DMATxDesc_TTSS flag to be set */
- do
- {
- timeout++;
- } while (!(DMATxDescToSet->Status & ETH_DMATxDesc_TTSS) && (timeout < PHY_READ_TO)); //等待加入时间戳
- /* Return ERROR in case of timeout */
- if(timeout == PHY_READ_TO)
- {
- return ETH_ERROR;
- }
- timestamp.tv_nsec = ETH_PTPSubSecond2NanoSecond(DMATxDescToSet->Buffer1Addr);
- timestamp.tv_sec = DMATxDescToSet->Buffer2NextDescAddr;
- /* Clear the DMATxDescToSet status register TTSS flag */
- DMATxDescToSet->Status &= ~ETH_DMATxDesc_TTSS;
- DMATxDescToSet->Buffer1Addr = DMAPTPTxDescToSet->Buffer1Addr;
- DMATxDescToSet->Buffer2NextDescAddr = DMAPTPTxDescToSet->Buffer2NextDescAddr; //还原地址
- /* Update the ETHERNET DMA global Tx descriptor with next Tx decriptor */
- /* Chained Mode */
- /* Selects the next DMA Tx descriptor list for next buffer to send */
- DMATxDescToSet = (ETH_DMADESCTypeDef*) (DMATxDescToSet->Buffer2NextDescAddr);
- if(DMAPTPTxDescToSet->Status != 0)
- {
- DMAPTPTxDescToSet = (ETH_DMADESCTypeDef*) (DMAPTPTxDescToSet->Status);
- }
- else
- {
- DMAPTPTxDescToSet++;
- }
- #else
- /* Update the ETHERNET DMA global Tx descriptor with next Tx decriptor */
- /* Chained Mode */
- /* Selects the next DMA Tx descriptor list for next buffer to send */
- DMATxDescToSet = (ETH_DMADESCTypeDef*) (DMATxDescToSet->Buffer2NextDescAddr);
- #endif
- #if LWIP_PTP
- p->time_sec = timestamp.tv_sec;
- p->time_nsec = timestamp.tv_nsec;
- #endif
- /* Return SUCCESS */
- return RT_EOK;
- }
2.4其他
接收函数也做类似的更改,还有中断函数直接参照例程修改就可以。
3.协议分析
1588协议中,定义了两种报文,事件报文和通用报文;
事件报文时间概念报文,进出设备端口时打上精确的时间戳,PTP根据事件报文携带的时间戳,计算链路延迟。事件报文包含以下4种:Sync、Delay_Req、Pdelay_Req和Pdelay_Resp。
通用报文:非时间概念报文,进出设备不会产生时间戳,用于主从关系的建立、时间信息的请求和通告。通用报文包含以下6种:Announce、Follow_Up、Delay_Resp、Pdelay_Resp_Follow_Up、Management和Signaling,目前设备不支持Management、Signaling报文。
时钟同步的实现主要包括3个步骤:
- 建立主从关系,选取最优时钟、协商端口主从状态等。
- 频率同步,实现从节点频率与主节点同步。
- 时间同步,实现从节点时间与主节点同步。
协议初始化之后,开始监听网络,master会主动发送sync、Announce包,slave收到Announce后,添加主机(addForeign函数实现),并对其进行最佳主时钟算法(BMC)比较。
3.1建立主从关系
主从关系建立步骤
PTP是通过端口接收到和发送Announce报文,实现端口数据集和端口状态机信息的交互。BMC(Best Master Clock)算法通过比较端口数据集和端口状态机,实现时钟主从跟踪关系。一般按照下面几个步骤来建立:
-
接收和处理来自对端设备端口的Announce报文。
-
利用BMC算法决策出最优时钟和端口的推荐状态,包括Master、Slave或者Passive状态。
-
根据端口推荐状态,更新端口数据集合。
-
按照推荐状态和状态决策事件,根据端口状态机决定端口的实际状态,实现时钟同步网络的建立。状态决策事件包括Announce报文的接收事件和接收Announce报文的超时时间结束事件,当接口接收Announce报文的时间间隔大于超时时间间隔时,将此PTP接口状态置为Master。
BMC算法
(bmc函数实现),简单的来说就是依次比较每个主机的参数,这几个参数为:gPriority1、clockClass、clockAccuracy、offsetScaledLogVariance、Priority2。这里做一些比较,得出最佳主时钟。
- Priority1:时钟优先级1,支持用户配置,取值范围是0~255,取值越小优先级越高。
- ClockClass:时钟级别,定义时钟的时间或频率的国际原子时TAI(International Atomic Time)跟踪能力。
- ClockAccuracy:时钟精度,取值越低精确度越高。
- OffsetScaledLogVariance:时钟稳定性。
- Priority2:时钟优先级2,支持用户配置,取值范围是0~255,取值越小优先级越高。
最优时钟可以通过手工配置静态指定,也可以通过最佳主时钟BMC(Best Master Clock)算法动态选举。
3.2 PTP频率同步
在主从关系建立后,即可以进行频率同步和时间同步。PTP本来只是用户设备之间的高精度时间同步,但也可以被用来进行设备之间的频率同步。
PTP通过记录主从设备之间事件报文交换时产生的时间戳,计算出主从设备之间的路径延迟和时间偏移,实现主从设备之间的时间和频率同步,设备支持两种携带时间戳的模式,分别为:
- 单步时钟模式(One step),指事件报文Sync和Pdelay_Resp带有本报文发送时刻的时间戳,报文发送和接收的同时也完成了时间信息的通告。
- 两步时钟模式(Two step),指事件报文Sync和Pdelay_Resp不带有本报文发送时刻的时间戳,而分别由后续的通用报文Follow_Up和Pdelay_Resp_Follow_Up带上该Sync和PDelay_Resp报文的发送时间信息。两步时钟模式中,时间信息的产生和通告分两步完成,这样可以兼容一些不支持给事件报文打时间戳的设备。
3.3 PTP时间同步
PTP时间同步有两种不同的同步方式:Delay方式和Pdelay方式,如此划分主要是由于PTP计算路径延时有两种机制。
- 延时请求-请求响应机制E2E(End to End):根据主从时钟之间的整体路径延时时间计算时间差。
- 对端延时机制P2P(Peer to Peer):根据主从时钟之间的每一条链路延时时间计算时间差。
fg
对端延时机制P2P(Peer to Peer)
P2P机制是利用延时请求Pdelay_Req报文、延时回答Pdelay_Resp报文和可能的Pdelay_Resp_Follow_Up报文,计算两个支持P2P机制的通信端口之间测量端口到端口的传播时间,也就是路径延时。与延时请求-响应机制相比,路径延时测量原理并无不同,只是路径延时测量在每段链路之间进行,主从节点间每段链路的链路延时累计在Pdelay_Resp或Pdelay_Resp_Follow_Up报文中,向下游传递,同时传递信息还包括同步报文在透明时钟TC上的驻留时间。从节点每段链路的链路延时和在透明时钟TC上的驻留时间,计算主从节点的平均路径延时。在对端延时机制中,延时测量和端口的主从属性无关,在支持Pdelay机制的两个相连端口之间进行。
Pdelay机制原理
时间戳t1和t2是Pdelay_Req消息发送时间戳和接收时间戳,时间戳t3和t4是Pdelay_Resp消息的发送时间戳和接收时间戳。计算单段链路延时的公式如下所示:
单段链路延时=[(t2-t1) + (t4-t3)]/2 = [(t2-t3) + (t4-t1)]/2。
4.协议代码分析
4.1 handle(PtpClock *ptpClock)函数:
- /* check and handle received messages */
- static void handle(PtpClock *ptpClock)
- {
- int ret;
- Boolean isFromSelf;
- TimeInternal time = { 0, 0 };
- //**********************************************************************检查是否收到数据
- if (FALSE == ptpClock->messageActivity)
- {
- ret = netSelect(&ptpClock->netPath, 0);
- if (ret < 0)<span> </span>//接收出错
- {
- ERROR("handle: failed to poll sockets\n");
- toState(ptpClock, PTP_FAULTY);
- return;
- }
- else if (!ret)<span> </span>//没有收到数据,直接返回
- {
- // DBGVV("handle: nothing\n");
- return;
- }
- }
- //**********************************************************************
- // DBGVV("handle: something\n");
- ptpClock->msgIbufLength = netRecvEvent(&ptpClock->netPath, ptpClock->msgIbuf, &time);
- /* local time is not UTC, we can calculate UTC on demand, otherwise UTC time is not used */
- /* time.seconds += ptpClock->timePropertiesDS.currentUtcOffset; */
- if (ptpClock->msgIbufLength < 0)
- {
- ERROR("handle: failed to receive on the event socket\n");
- toState(ptpClock, PTP_FAULTY);
- return;
- }
- else if (!ptpClock->msgIbufLength)
- { <span>//读取数据,接收时MAC层加入的时间戳保存在time中;数据保存在msgIbuf中</span>
- ptpClock->msgIbufLength = netRecvGeneral(&ptpClock->netPath, ptpClock->msgIbuf, &time);
- if (ptpClock->msgIbufLength < 0)
- {
- ERROR("handle: failed to receive on the general socket\n");
- toState(ptpClock, PTP_FAULTY);
- return;
- }
- else if (!ptpClock->msgIbufLength)
- return;
- }
- ptpClock->messageActivity = TRUE;
- if (ptpClock->msgIbufLength < HEADER_LENGTH)
- {
- ERROR("handle: message shorter than header length\n");
- toState(ptpClock, PTP_FAULTY);
- return;
- }
- msgUnpackHeader(ptpClock->msgIbuf, &ptpClock->msgTmpHeader);//将数据包解析后放在<span>ptpClock-></span><span>msgTmpHeader中。</span>
- if (ptpClock->msgTmpHeader.versionPTP != ptpClock->portDS.versionNumber)
- {
- DBGV("handle: ignore version %d message\n", ptpClock->msgTmpHeader.versionPTP);
- return;
- }
- if (ptpClock->msgTmpHeader.domainNumber != ptpClock->defaultDS.domainNumber)
- {
- DBGV("handle: ignore message from domainNumber %d\n", ptpClock->msgTmpHeader.domainNumber);
- return;
- }
- /*Spec 9.5.2.2*/
- isFromSelf = isSamePortIdentity(
- &ptpClock->portDS.portIdentity,
- &ptpClock->msgTmpHeader.sourcePortIdentity); //判断是否是同一个主机
- /* subtract the inbound latency adjustment if it is not a loop back and the
- time stamp seems reasonable */
- if (!isFromSelf && time.seconds > 0)
- subTime(&time, &time, &ptpClock->inboundLatency);
- switch (ptpClock->msgTmpHeader.messageType)
- {
- case ANNOUNCE:
- handleAnnounce(ptpClock, isFromSelf);
- break;
- case SYNC:
- handleSync(ptpClock, &time, isFromSelf);
- break;
- case FOLLOW_UP:
- handleFollowUp(ptpClock, isFromSelf);
- break;
- case DELAY_REQ:
- handleDelayReq(ptpClock, &time, isFromSelf);
- break;
- case PDELAY_REQ:
- handlePDelayReq(ptpClock, &time, isFromSelf);
- break;
- case DELAY_RESP:
- handleDelayResp(ptpClock, isFromSelf);
- break;
- case PDELAY_RESP:
- handlePDelayResp(ptpClock, &time, isFromSelf);
- break;
- case PDELAY_RESP_FOLLOW_UP:
- handlePDelayRespFollowUp(ptpClock, isFromSelf);
- break;
- case MANAGEMENT:
- handleManagement(ptpClock, isFromSelf);
- break;
- case SIGNALING:
- handleSignaling(ptpClock, isFromSelf);
- break;
- default:
- DBG("handle: unrecognized message %d\n", ptpClock->msgTmpHeader.messageType);
- break;
- }
- }
4.2 handleSync
- static void handleSync(PtpClock *ptpClock, TimeInternal *time, Boolean isFromSelf)
- {
- TimeInternal originTimestamp;
- TimeInternal correctionField;
- Boolean isFromCurrentParent = FALSE;
- DBGV("handleSync: received\n");
- if (ptpClock->msgIbufLength < SYNC_LENGTH)
- {
- ERROR("handleSync: short message\n");
- toState(ptpClock, PTP_FAULTY);
- return;
- }
- switch (ptpClock->portDS.portState)
- {
- case PTP_INITIALIZING:
- case PTP_FAULTY:
- case PTP_DISABLED:
- DBGV("handleSync: disreguard\n");
- break;
- case PTP_UNCALIBRATED:
- case PTP_SLAVE:
- if (isFromSelf)
- {
- DBGV("handleSync: ignore from self\n");
- break;
- }
- isFromCurrentParent = isSamePortIdentity(
- &ptpClock->parentDS.parentPortIdentity,
- &ptpClock->msgTmpHeader.sourcePortIdentity);
- if (!isFromCurrentParent)
- {
- DBGV("handleSync: ignore from another master\n");
- break;
- }
- ptpClock->timestamp_syncRecieve = *time; //保存接收sync报文时,本机的时间戳
- scaledNanosecondsToInternalTime(&ptpClock->msgTmpHeader.correctionfield, &correctionField); //保存sync报文的修正时间
- if (getFlag(ptpClock->msgTmpHeader.flagField[0], FLAG0_TWO_STEP)) //采用TWO_STEP方式,也就是说发送sync报文主机的时间,通过follow_up报文发送过来而不是包含在sync报文中。
- {
- ptpClock->waitingForFollowUp = TRUE;
- ptpClock->recvSyncSequenceId = ptpClock->msgTmpHeader.sequenceId; //用于识别
- /* Save correctionField of Sync message for future use */
- ptpClock->correctionField_sync = correctionField;
- }
- else
- {
- msgUnpackSync(ptpClock->msgIbuf, &ptpClock->msgTmp.sync);
- ptpClock->waitingForFollowUp = FALSE;
- /* Synchronize local clock */
- toInternalTime(&originTimestamp, &ptpClock->msgTmp.sync.originTimestamp);
- /* use correctionField of Sync message for future use */
- updateOffset(ptpClock, &ptpClock->timestamp_syncRecieve, &originTimestamp, &correctionField);
- updateClock(ptpClock);
- issueDelayReqTimerExpired(ptpClock);
- }
- break;
- case PTP_MASTER: //如果本机是主机
- if (!isFromSelf)
- {
- DBGV("handleSync: from another master\n");
- break;
- }
- else
- {
- DBGV("handleSync: ignore from self\n");
- break;
- }
- // if waitingForLoopback && TWO_STEP_FLAG
- // {
- // /*Add latency*/
- // addTime(time, time, &rtOpts->outboundLatency);
- //
- // issueFollowup(ptpClock, time);
- // break;
- // }
- case PTP_PASSIVE:
- issueDelayReqTimerExpired(ptpClock);
- DBGV("handleSync: disreguard\n");
- break;
- default:
- DBGV("handleSync: disreguard\n");
- break;
- }
- }
4.3 handleFollowUp
- static void handleFollowUp(PtpClock *ptpClock, Boolean isFromSelf)
- {
- TimeInternal preciseOriginTimestamp;
- TimeInternal correctionField;
- Boolean isFromCurrentParent = FALSE;
- DBGV("handleFollowup: received\n");
- if (ptpClock->msgIbufLength < FOLLOW_UP_LENGTH)
- {
- ERROR("handleFollowup: short message\n");
- toState(ptpClock, PTP_FAULTY);
- return;
- }
- if (isFromSelf)
- {
- DBGV("handleFollowup: ignore from self\n");
- return;
- }
- switch (ptpClock->portDS.portState)
- {
- case PTP_INITIALIZING:
- case PTP_FAULTY:
- case PTP_DISABLED:
- case PTP_LISTENING:
- DBGV("handleFollowup: disreguard\n");
- break;
- case PTP_UNCALIBRATED:
- case PTP_SLAVE:
- isFromCurrentParent = isSamePortIdentity(
- &ptpClock->parentDS.parentPortIdentity,
- &ptpClock->msgTmpHeader.sourcePortIdentity);
- if (!ptpClock->waitingForFollowUp)
- {
- DBGV("handleFollowup: not waiting a message\n");
- break;
- }
- if (!isFromCurrentParent)
- {
- DBGV("handleFollowup: not from current parent\n");
- break;
- }
- if (ptpClock->recvSyncSequenceId != ptpClock->msgTmpHeader.sequenceId)
- {
- DBGV("handleFollowup: SequenceID doesn't match with last Sync message\n");
- break;
- }
- msgUnpackFollowUp(ptpClock->msgIbuf, &ptpClock->msgTmp.follow); //从msgIbuf中 34 36 40位置,获取时间戳,也就是sync的master时间。参考<strong>协议</strong>
- ptpClock->waitingForFollowUp = FALSE;
- /* synchronize local clock */
- toInternalTime(&preciseOriginTimestamp, &ptpClock->msgTmp.follow.preciseOriginTimestamp);//
- scaledNanosecondsToInternalTime(&ptpClock->msgTmpHeader.correctionfield, &correctionField);//handle中赋值msgTmpHeader.correctionfield。这里是follow_up 的修正值
- addTime(&correctionField, &correctionField, &ptpClock->correctionField_sync);//handleSync中赋值。这里是sync的修正值。两个修正值叠加
- updateOffset(ptpClock, &ptpClock->timestamp_syncRecieve, &preciseOriginTimestamp, &correctionField);//sync接收到时间timestamp_syncRecieve,主机发送sync时间preciseOriginTimestamp,修正时间correctionField
- updateClock(ptpClock);
- issueDelayReqTimerExpired(ptpClock);
- break;
- case PTP_MASTER:
- DBGV("handleFollowup: from another master\n");
- break;
- case PTP_PASSIVE:
- issueDelayReqTimerExpired(ptpClock);
- DBGV("handleFollowup: disreguard\n");
- break;
- default:
- DBG("handleFollowup: unrecognized state\n");
- break;
- }//Switch on (port_state)
- }