当前位置: 首页 > news >正文

FreeRTOS实现微秒级时间同步(基于1588V2)

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
http://www.dtcms.com/a/449475.html

相关文章:

  • 网站基本要素网站建设 主要学是么
  • Java包的命名,常见的包类,如何导入包
  • 滑动窗口题目:替换后的最长重复字符
  • 谷歌seo建站成都不能去的建筑设计公司
  • 贵阳网站制作企业陕西省建设网三类人员证书打印
  • 【小沐学WebGIS】基于Three.JS绘制飞行轨迹Flight Tracker(Three.JS/ vue / react / WebGL)
  • 告别 v-model 焦虑:在 React 中优雅地处理『双向绑定』
  • vue生态都有哪些?
  • C++之类与对象
  • 东莞长安做网站wordpress创意主题店铺
  • 百度地图收藏地址提取与格式转换工具 说明文档
  • 操作系统应用开发(二十三)RustDesk ng反向代理—东方仙盟筑基期
  • 河北省建设厅网站查询中心郑州建站系统费用
  • 分布式系统设计模式:从理论到实践
  • 新零售升维体验商业模式创新研究:基于开源AI大模型、AI智能名片与S2B2C商城小程序的融合实践
  • 【Linux】系统部分——线程安全与线程的单例模式
  • 关键词查询爱站网石家庄新闻头条新闻最新今天
  • HarmonyOS 应用开发深度解析:ArkTS 语法精要与现代化状态管理实践
  • wordpress站点图片多大合适做房产网站不备案可以吗
  • 【深入理解计算机网络04】通信基础核心知识全解析:从信号原理到物理层设备
  • 探索大语言模型(LLM):一文读懂通用大模型的定义、特点与分类
  • 江西网站建设价格东莞做网站 动点官网
  • 金融大数据处理与分析
  • 【Linux】基础开发工具---yum / apt
  • 做的好看的网站中国免费最好用建站cms
  • 项目---网络通信组件JsonRpc
  • 【数字孪生】03-交通数字孪生的架构和关键技术(1)
  • 构建AI智能体:五十五、混合式智能投资顾问:融合快速响应与深度推理的自适应架构
  • 网站建设需求填表你认为优酷该网站哪些地方可以做的更好_为什么?
  • 好用的软件下载网站网站恶意点击软件