1. 前言

  主要是参考了该代码的实现,不过原代码的RTOS用的是RTX5,这里把它移植到了FreeRTOS上,并额外的做了一层抽象方便后续的移植。硬件:STM32F407ZGT6,软件环境: FreeRTOS,ubuntu18, keil5(ARMCC编译器)
原项目链接:
   STM32_PTPD
也非常感谢这个博主的博文:
  STM32F407移植1588V2
本项目链接:
FreeRTOS_PTPD

2. 项目的代码框架

  在把原来的项目中的ptpd部分整合出来后,主要是以下几个文件

ethernetif.c
ethernetif.h
ethptp.c
ethptp.h
network.c
network.h
ptpd.h
ptpd_arch.c
ptpd_arch.h
ptpd_arith.c
ptpd_bmc.c
ptpd_constants.h
ptpd_datatypes.h
ptpd_main.c
ptpd_msg.c
ptpd_net.c
ptpd_protocol.c
ptpd_servo.c
ptpd_time.c
ptpd_timer.c
  • ethernetif:链接lwip和以太网驱动的核心。在这里把以太网驱动也放到了这个文件中,以及和ptpd协议底层状态记录的相关函数
  • ethptp: 主要是操作底层ETH外设用来设定时间戳和得到时间戳
  • network: 更上层的实现,包括LWIP协议栈初始化的封装和ptpd协议初始化的封装,同时定义了一些需要传给LWIP初始化的参数,包括IP地址,MAC地址等
    • lwip初始化
    • ptpd协议相关的初始化
    • mdns协议 / ping协议等辅助的初始化(mdns相关的文件在lwip/apps中)
  • ptpd.h: 定义了一些DBG调试的宏
  • ptpd_arch: 对ptpd协议栈用到了进程间通信的IPC的抽象,包括事件标志组,软件定时器等,方便后续适配不同的RTOS平台(个人新增)
  • ptpd协议栈的代码:在4中进一步的介绍

3. PTPD的核心—时间戳

整个ptpd协议的核心是如何获取到报文发送的精确时间戳,这个不仅仅是协议层的,更需要硬件层提供支持

3.1 硬件层

  这里时间戳依赖于硬件的MAC控制器,好在STM32F4底层的MAC控制器支持获取时间戳相关的参数。所以只需要我们在初始化的时候,做好对相关外设的初始化就可以通过ETH_DMA描述来获取时间戳或者修改时间戳了

typedef struct  
{
  __IO uint32_t   Status;           /*!< Status */
  
  uint32_t   ControlBufferSize;     /*!< Control and Buffer1, Buffer2 lengths */
  
  uint32_t   Buffer1Addr;           /*!< Buffer1 address pointer */
  
  uint32_t   Buffer2NextDescAddr;   /*!< Buffer2 or next descriptor address pointer */
  
  /*!< Enhanced ETHERNET DMA PTP Descriptors */
  uint32_t   ExtendedStatus;        /*!< Extended status for PTP receive descriptor */
  
  uint32_t   Reserved1;             /*!< Reserved */
  
  uint32_t   TimeStampLow;          /*!< Time Stamp Low value for transmit and receive */
  
  uint32_t   TimeStampHigh;         /*!< Time Stamp High value for transmit and receive */

} ETH_DMADescTypeDef;
void ETH_EnablePTPTimeStampUpdate(void)
{
  uint32_t tmpreg;

  /* Enable the PTP system time update with the Time Stamp Update register value */
  ETH->PTPTSCR |= ETH_PTPTSCR_TSSTU;

  /* Wait until the write operation will be taken into account :
   at least four TX_CLK/RX_CLK clock cycles */
  tmpreg = ETH->PTPTSCR;
  delay_us(ETH_REG_WRITE_DELAY);
  ETH->PTPTSCR = tmpreg;
}

/**
  * @brief  Initialize the PTP Time Stamp
  * @param  None
  * @retval None
  */
void ETH_InitializePTPTimeStamp(void)
{
  uint32_t tmpreg;

  /* Initialize the PTP Time Stamp */
  ETH->PTPTSCR |= ETH_PTPTSCR_TSSTI;

  /* Wait until the write operation will be taken into account :
   at least four TX_CLK/RX_CLK clock cycles */
  tmpreg = ETH->PTPTSCR;
  delay_us(ETH_REG_WRITE_DELAY);
  ETH->PTPTSCR = tmpreg;
}

另外,PTPD协议栈的实现依赖与mdns服务,所以我们需要配置底层的MAC控制器让它支持mdns协议

 // Initialize custom MAC parameters.
  // NOTE: the MulticastFramesFilter is set to none for support of MDNS packets.
ETH_MACInitTypeDef mac_init;
  memset(&mac_init, 0, sizeof(mac_init));
  mac_init.Watchdog = ETH_WATCHDOG_ENABLE;
  mac_init.Jabber = ETH_JABBER_ENABLE;
  mac_init.InterFrameGap = ETH_INTERFRAMEGAP_96BIT;
  mac_init.CarrierSense = ETH_CARRIERSENCE_ENABLE;
  mac_init.ReceiveOwn = ETH_RECEIVEOWN_ENABLE;
  mac_init.LoopbackMode = ETH_LOOPBACKMODE_DISABLE;
  mac_init.ChecksumOffload = ETH_CHECKSUMOFFLAOD_ENABLE;
  mac_init.RetryTransmission = ETH_RETRYTRANSMISSION_DISABLE;
  mac_init.AutomaticPadCRCStrip = ETH_AUTOMATICPADCRCSTRIP_DISABLE;
  mac_init.BackOffLimit = ETH_BACKOFFLIMIT_10;
  mac_init.DeferralCheck = ETH_DEFFERRALCHECK_DISABLE;
  mac_init.ReceiveAll = ETH_RECEIVEAll_DISABLE;
  mac_init.SourceAddrFilter = ETH_SOURCEADDRFILTER_DISABLE;
  mac_init.PassControlFrames = ETH_PASSCONTROLFRAMES_BLOCKALL;
  mac_init.BroadcastFramesReception = ETH_BROADCASTFRAMESRECEPTION_ENABLE;
  mac_init.DestinationAddrFilter = ETH_DESTINATIONADDRFILTER_NORMAL;
  mac_init.PromiscuousMode = ETH_PROMISCUOUS_MODE_DISABLE;
  mac_init.MulticastFramesFilter = ETH_MULTICASTFRAMESFILTER_NONE;
  mac_init.UnicastFramesFilter = ETH_UNICASTFRAMESFILTER_PERFECT;
  mac_init.HashTableHigh = 0x0U;
  mac_init.HashTableLow = 0x0U;
  mac_init.PauseTime = 0x0U;
  mac_init.ZeroQuantaPause = ETH_ZEROQUANTAPAUSE_DISABLE;
  mac_init.PauseLowThreshold = ETH_PAUSELOWTHRESHOLD_MINUS4;
  mac_init.UnicastPauseFrameDetect = ETH_UNICASTPAUSEFRAMEDETECT_DISABLE;
  mac_init.ReceiveFlowControl = ETH_RECEIVEFLOWCONTROL_DISABLE;
  mac_init.TransmitFlowControl = ETH_TRANSMITFLOWCONTROL_DISABLE;
  mac_init.VLANTagComparison = ETH_VLANTAGCOMPARISON_16BIT;
  mac_init.VLANTagIdentifier = 0x0U;
  HAL_ETH_ConfigMAC(&ethernetif_handle, &mac_init);

3.2 协议层

  通过函数ethernetif_get_tx_timestamp可以得到具体的时间戳

// Get the TX time associated with the packet buffer.
void ethernetif_get_tx_timestamp(struct pbuf *p)
{
  // Lock the Ethernet mutex.
   sys_mutex_lock(&ethernetif_mutex_id);

  // Start without a DMA TX descriptor.
  __IO ETH_DMADescTypeDef *dma_tx_desc = NULL;

  // Find the DMA TX descriptor assocated with this packet buffer.
  // This is a onetime function to prevent issues with accidently
  // pointing to a recycled DMA TX descriptor.
  uint32_t index = ethernet_tx_tail;
  while (index != ethernet_tx_head)
  {
    // Is this the DMA TX descriptor we are interested in?
    if (p->time_nsec == ethernet_tx_entries[index].id)
    {
      // Get the DMA TX descriptor for use below.
      dma_tx_desc = ethernet_tx_entries[index].tx_desc;

      // This is a one time operation so clear the entry.
      ethernet_tx_entries[index].id = 0;
      ethernet_tx_entries[index].tx_desc = NULL;

      // We found the DMA TX descriptor.
      break;
    }

    // Increment to the next TX buffer.
    index = (index + 1) == ETH_TXBUFNB ? 0 : index + 1;
  }

  // Release the Ethernet mutex.
  sys_mutex_unlock(&ethernetif_mutex_id);

  // Fill in the default values.
  p->time_sec = 0;
  p->time_nsec = 0;

  // Did we find the dma tx descriptor?
  if (dma_tx_desc)
  {
    // Wait up to 20 millisecond for the DMA transfer to complete.
    for (uint32_t retry_count = 10; (retry_count > 0) && ((dma_tx_desc->Status & ETH_DMATXDESC_TTSS) != ETH_DMATXDESC_TTSS); --retry_count)
    {
      // Wait up to two milliseconds for a transfer to complete.
      EventBits_t xReceivedBits;
      xReceivedBits = sys_eventgroup_wait_bits(&ethernetif_event_id, ETHERNETIF_EVENT_TRANSMIT, 2);
      if (xReceivedBits & ETHERNETIF_EVENT_TRANSMIT)
      {
		break;
      }
    }

    // Make sure after waiting we have the timestamp information.
    if (dma_tx_desc->Status & ETH_DMATXDESC_TTSS )
    {
      // Fill in the timestamp information.
      p->time_sec = dma_tx_desc->TimeStampHigh;
      p->time_nsec = subsecond_to_nanosecond(dma_tx_desc->TimeStampLow);
    }
    else
    {
      // Report timeount.
      printf("ETHERNETIF: tx timestamp timeout\n");
    }
  }
}

得到时间戳的过程可以大概简化为:

  • 底层调用netif->linkouput发送数据包,如果数据包是PTPD协议相关的,就记录以太网的描述符和pbuf之间的映射关系(这里对pbuf做了一定的修改,新增了时间戳变量成员)
  • 当以太网发送完成后,会调用对应的回调函数,此时回调函数会对事件标志组进行置位操作
// Ethernet Tx transfer complete callback from the HAL.
void HAL_ETH_TxCpltCallback(ETH_HandleTypeDef *eth_handle)
{
  UNUSED(eth_handle);
	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  // Notify the Ethernet thread of the outgoing packet complete.
  xHigherPriorityTaskWoken = sys_eventgroup_set_bits_isr(&ethernetif_event_id, ETHERNETIF_EVENT_TRANSMIT); 
	if( xHigherPriorityTaskWoken != pdFALSE )
  {
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  }
}
  • ethernetif_get_tx_timestamp就可以等到这个标志位置位,从而从对应的以太网描述符中取出时间进行记录

4.PTPD其它文件的简介

  具体的实现我看的也不深,权当是做了个了解

  • 4.1 ptpd_net.c : 在应用层与LWIP协议栈交互
    • ptpd_net_init函数
      初始化两个消息队列

    • 创建了两个udp控制块

      • 事件端口的控制块 eventPcb: 319端口 控制事件消息(Sync同步 Delay_seq(延迟请求))
        需要加时间戳的消息
      • 通用端口控制块 generalPcb: 320端口 辅助同步的消息
        不需要加时间戳的消息
    • 事件端口的接收回调 : ptpd_net_event_callback
      把pbuf放入事件队列eventQ并唤醒处理线程

    • 通用端口的接收回调 : ptpd_net_general_callback
      把pbuf放入通用事件队列generalQ,并唤醒处理线程(ptpd_main当中)

    • ptpd发送数据 ptpd_net_send函数:
      通过对应的udp控制块发送数据 然后如果需要返回时间戳的会把具体发送时刻通过指针返回

      if (time != NULL)
      {
          // We have special call back into the Ethernet interface to fill the timestamp
          // of the buffer just transmitted. This call will block for up to a certain amount
          // of time before it may fail if a timestamp was not obtained.
          ethernetif_get_tx_timestamp(p);
      }
      // Get the timestamp of the sent buffer.  We avoid overwriting 
      // the time if it looks to be an invalid zero value.
      if ((time != NULL) && (p->time_sec != 0))
      {
          time->seconds = p->time_sec;
          time->nanoseconds = p->time_nsec;
          DBGV("PTPD: %d sec %d nsec\n", time->seconds, time->nanoseconds);
      }
      
    • ptpd真正接收数据

      /*从对应队列取出数据并处理*/
      static ssize_t ptpd_net_recv(octet_t *buf, TimeInternal *time, BufQueue *queue)
      

      把数据从queue中取出来 数据信息放在buf里 时间信息放在TimeInternal, 时间信息来自以太网的DMA描述符

  • 4.2 ptpd_protocol.c: 协议实现的核心
    • ptpd_protocol_to_state : 状态机状态切换
    • ptpd_protocol_do_state: 处理事件 调用 do_state进行状态切换
    • handle: 协议事件处理的核心
      • step1: 把数据从消息队列取出来
      • step2: switch - case 处理 : 根据不同报文的类型去做处理
         switch (ptp_clock->msgTmpHeader.messageType)
        {
            case ANNOUNCE:
            handle_announce(ptp_clock, is_from_self);
            break;
        
            case SYNC:
            handle_sync(ptp_clock, &time, is_from_self);
            break;
        
            case FOLLOW_UP:
            handle_follow_up(ptp_clock, is_from_self);
            break;
        
            case DELAY_REQ:
            handle_delay_req(ptp_clock, &time, is_from_self);
            break;
        
            case PDELAY_REQ:
            handle_peer_delay_req(ptp_clock, &time, is_from_self);
            ...
        }
        
  • 4.3 ptpd_sero
    ptpd的算法部分 包括如何计算同步时间 还有一些滤波操作在这里面
    • ptpd_aritch.c: 辅助数学函数
    • ptpd_bmc.c : 最佳主时钟(BMC)算法核心 作为丛机用不到
  • 4.4 ptpd_time 与 ethptp: ptpd算法得到时间后 对于底层的修正
    修正以太网的ETH寄存器相关的标志位
    ptpd_get_rand无用 正好也没随机数寄存器 删了就行
  • 4.5 ptpd_msg
    中间层 就是涉及到某些帧具体是怎么封装了(delay_req / Sync / follow_up) 需要适配的话得改这里—根据不同的实现进行修改
  • 4.6 ptpd_timer.c:
    涉及到一些ptpd协议中用到的软件定时器,是为了辅助前面提到的ptpd_protocol,进行一些超时的处理
  • 4.7 ptpd_main.c:
    没啥好讲的 就是调用封装的prptocol的API 以及提供一些对外的接口来得到ptpd协议栈的信息
  • 为什么底层用udp不用tcp
    可能是因为udp的mudp多播吧 把自己报文广播给特定的主机
    包括udp的传输效率更高

5 使用与测试

  首先要保证你的ubutnu和你的开发板在一个子网下
ubutun上要安装一个ptpd4l,然后启动协议,我这里是ens33网卡,根据具体情况进行修改

sudo ptp4l -E -4 -S -i ens33 -m  

最终的结果为

handle: ptpd_net_recv_event returned 44
handle: unpacked message type 0
handle_sync: received in state PTP_LISTENING
handle_sync: disreguard
handle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 44
handle: unpacked message type 8
handle_followup: received in state PTP_LISTENING
handle_followup: disreguard
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 64
handle: unpacked message type 11
handle_announce: received in state PTP_LISTENING
handle_announce: from another foreign master
event STATE_DECISION_EVENT
recommending state PTP_SLAVE
leaving state PTP_LISTENING
ptpd_servo_init_clock
entering state PTP_UNCALIBRATED
PTPD: entering UNCALIBRATED statehandle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 44
handle: unpacked message type 8
handle_followup: received in state PTP_UNCALIBRATED
handle_followup: not waiting a message
event MASTER_CLOCK_CHANGED
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: something
handle: ptpd_net_recv_event returned 44
handle: unpacked message type 0
handle_sync: received in state PTP_UNCALIBRATED
handle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 44
handle: unpacked message type 8
handle_followup: received in state PTP_UNCALIBRATED
ptpd_servo_update_offset
ptpd_servo_update_offset: offset -1759720361 seconds -340254876 nanoseconds
ptpd_servo_update_offset: cannot filter seconds
PTPD: ptpd_servo_update_clock offset -1759720361 sec 340254876 nsec
ptpd_servo_init_clock
PTPD: ptpd_servo_update_clock: one-way delay averaged (E2E): 0 sec 0 nsec
PTPD: ptpd_servo_update_clock: offset from master: -1759720361 sec -340254876 nsec
PTPD: ptpd_servo_update_clock: observed drift: 0
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 64
handle: unpacked message type 11
handle_announce: received in state PTP_UNCALIBRATED
event STATE_DECISION_EVENT
recommending state PTP_SLAVE
handle: something
handle: ptpd_net_recv_event returned 44
handle: unpacked message type 0
handle_sync: received in state PTP_UNCALIBRATED
handle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 44
handle: unpacked message type 8
handle_followup: received in state PTP_UNCALIBRATED
ptpd_servo_update_offset
ptpd_servo_update_offset: offset 0 seconds -76817 nanoseconds
PTPD: filter: -76817 -> -76817 (0)
PTPD: ptpd_servo_update_clock offset 0 sec 76817 nsec
PTPD: ptpd_servo_update_clock: one-way delay averaged (E2E): 0 sec 0 nsec
PTPD: ptpd_servo_update_clock: offset from master: 0 sec -76817 nsec
PTPD: ptpd_servo_update_clock: observed drift: -4801
event MASTER_CLOCK_SELECTED
leaving state PTP_UNCALIBRATED
entering state PTP_SLAVE
PTPD: entering SLAVE statehandle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 0
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: something
handle: ptpd_net_recv_event returned 44
handle: unpacked message type 0
handle_sync: received in state PTP_SLAVE
handle: something
handle: ptpd_net_recv_event returned 0
handle: ptpd_net_recv_general returned 44
handle: unpacked message type 8
handle_followup: received in state PTP_SLAVE
ptpd_servo_update_offset
ptpd_servo_update_offset: offset 0 seconds -44237 nanoseconds
PTPD: filter: -44237 -> -60527 (1)
PTPD: ptpd_servo_update_clock offset 0 sec 60527 nsec
PTPD: ptpd_servo_update_clock: one-way delay averaged (E2E): 0 sec 0 nsec
PTPD: ptpd_servo_update_clock: offset from master: 0 sec -60527 nsec
PTPD: ptpd_servo_update_clock: observed drift: -8583
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: nothing
handle: something

从结果来看,大概是几十微妙,emmm客观来说这个结果算不上太优秀,想要更请准的结果可以修改对应的宏

#define DEFAULT_CALIBRATED_OFFSET_NS    10000    // Offset from master < 10us -> calibrated
#define DEFAULT_UNCALIBRATED_OFFSET_NS  100000   // Offset from master > 100us -> uncalibrated
#define MAX_ADJ_OFFSET_NS               100000000  // Max offset to try to adjust it < 100ms

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐