当前位置: 首页 > 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描述来获取时间戳或者修改时间戳了

typedefstruct{__IOuint32_tStatus;/*!< Status */uint32_tControlBufferSize;/*!< Control and Buffer1, Buffer2 lengths */uint32_tBuffer1Addr;/*!< Buffer1 address pointer */uint32_tBuffer2NextDescAddr;/*!< Buffer2 or next descriptor address pointer *//*!< Enhanced ETHERNET DMA PTP Descriptors */uint32_tExtendedStatus;/*!< Extended status for PTP receive descriptor */uint32_tReserved1;/*!< Reserved */uint32_tTimeStampLow;/*!< Time Stamp Low value for transmit and receive */uint32_tTimeStampHigh;/*!< Time Stamp High value for transmit and receive */}ETH_DMADescTypeDef;
voidETH_EnablePTPTimeStampUpdate(void){uint32_ttmpreg;/* 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 */voidETH_InitializePTPTimeStamp(void){uint32_ttmpreg;/* 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.voidethernetif_get_tx_timestamp(structpbuf*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_tindex=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_tretry_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.voidHAL_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真正接收数据

      /*从对应队列取出数据并处理*/staticssize_tptpd_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){caseANNOUNCE:handle_announce(ptp_clock,is_from_self);break;caseSYNC:handle_sync(ptp_clock,&time,is_from_self);break;caseFOLLOW_UP:handle_follow_up(ptp_clock,is_from_self);break;caseDELAY_REQ:handle_delay_req(ptp_clock,&time,is_from_self);break;casePDELAY_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 returned44handle:unpacked message type0handle_sync:received in state PTP_LISTENING handle_sync:disreguard handle:something handle:ptpd_net_recv_event returned0handle:ptpd_net_recv_general returned44handle:unpacked message type8handle_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 returned0handle:ptpd_net_recv_general returned64handle:unpacked message type11handle_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 returned0handle:ptpd_net_recv_general returned44handle:unpacked message type8handle_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 returned44handle:unpacked message type0handle_sync:received in state PTP_UNCALIBRATED handle:something handle:ptpd_net_recv_event returned0handle:ptpd_net_recv_general returned44handle:unpacked message type8handle_followup:received in state PTP_UNCALIBRATED ptpd_servo_update_offset ptpd_servo_update_offset:offset-1759720361seconds-340254876nanoseconds ptpd_servo_update_offset:cannot filter seconds PTPD:ptpd_servo_update_clock offset-1759720361sec340254876nsec ptpd_servo_init_clock PTPD:ptpd_servo_update_clock:one-way delayaveraged(E2E):0sec0nsec PTPD:ptpd_servo_update_clock:offset from master:-1759720361sec-340254876nsec PTPD:ptpd_servo_update_clock:observed drift:0handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:something handle:ptpd_net_recv_event returned0handle:ptpd_net_recv_general returned64handle:unpacked message type11handle_announce:received in state PTP_UNCALIBRATED event STATE_DECISION_EVENT recommending state PTP_SLAVE handle:something handle:ptpd_net_recv_event returned44handle:unpacked message type0handle_sync:received in state PTP_UNCALIBRATED handle:something handle:ptpd_net_recv_event returned0handle:ptpd_net_recv_general returned44handle:unpacked message type8handle_followup:received in state PTP_UNCALIBRATED ptpd_servo_update_offset ptpd_servo_update_offset:offset0seconds-76817nanoseconds PTPD:filter:-76817->-76817(0)PTPD:ptpd_servo_update_clock offset0sec76817nsec PTPD:ptpd_servo_update_clock:one-way delayaveraged(E2E):0sec0nsec PTPD:ptpd_servo_update_clock:offset from master:0sec-76817nsec PTPD:ptpd_servo_update_clock:observed drift:-4801event MASTER_CLOCK_SELECTED leaving state PTP_UNCALIBRATED entering state PTP_SLAVE PTPD:entering SLAVE statehandle:something handle:ptpd_net_recv_event returned0handle:ptpd_net_recv_general returned0handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:something handle:ptpd_net_recv_event returned44handle:unpacked message type0handle_sync:received in state PTP_SLAVE handle:something handle:ptpd_net_recv_event returned0handle:ptpd_net_recv_general returned44handle:unpacked message type8handle_followup:received in state PTP_SLAVE ptpd_servo_update_offset ptpd_servo_update_offset:offset0seconds-44237nanoseconds PTPD:filter:-44237->-60527(1)PTPD:ptpd_servo_update_clock offset0sec60527nsec PTPD:ptpd_servo_update_clock:one-way delayaveraged(E2E):0sec0nsec PTPD:ptpd_servo_update_clock:offset from master:0sec-60527nsec PTPD:ptpd_servo_update_clock:observed drift:-8583handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:nothing handle:something

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

#defineDEFAULT_CALIBRATED_OFFSET_NS10000// Offset from master < 10us -> calibrated#defineDEFAULT_UNCALIBRATED_OFFSET_NS100000// Offset from master > 100us -> uncalibrated#defineMAX_ADJ_OFFSET_NS100000000// Max offset to try to adjust it < 100ms
http://www.cnnetsun.cn/news/2066634.html

相关文章:

  • SLAM实战:如何为你的ZED 2i生成精准的imu.yaml和camchain.yaml配置文件?
  • 浏览器端3D模型查看器的技术架构与实战应用指南
  • 拉格朗日乘数法:数学优化与机器学习核心工具
  • 除了CFPS,还有哪些宝藏微观调查数据?CHFS、CHARLS等国内数据库横向对比
  • React-hn最佳实践:5个性能优化技巧让你的应用更流畅
  • 微信小程序二维码生成实战:3种高效实现方案深度解析
  • 从YOLOv2的Anchor Boxes到Darknet-19:手把手教你复现论文里的关键改进点
  • 如何快速掌握LayerDivider:图像智能分层的终极指南
  • 从网银U盾到微信支付:聊聊PKI公钥基础设施在我们日常生活中的‘隐形守护’
  • PsychoPy 2025.1.0:告别代码恐惧,用可视化构建你的心理学实验王国
  • 五一假期四场建模赛撞车,我为什么建议新手优先选C题(附空气质量预测模型保姆级清单)
  • 如何在15分钟内为Obsidian打造个人专属知识管理中心?终极指南
  • 华为ENSP实战:5分钟搞定OSPF基础配置,再聊聊DR/BDR选举那些‘坑’
  • 逆向百例——某备案号官网反爬
  • 从零开始:利用Overpass Turbo可视化OpenStreetMap数据
  • PowerToys中文汉化终极指南:三步实现微软效率工具完全中文化
  • 避坑指南:搞定TI DCA1000EVM数据采集卡与mmWave Studio连接(解决FPGA连接失败)
  • 3分钟搞定!在Windows电脑上玩转安卓应用的终极指南
  • 数据安全优先:企业级智能体私有化部署完整方案与最佳实践
  • 如何用d2s-editor让你的暗黑破坏神2角色瞬间变身超级英雄?
  • 避坑指南:STM32外部中断控制LED时,你的按键消抖真的做对了吗?
  • 【云端部署】2026年OpenClaw/Hermes Agent6分钟保姆级安装流程
  • Hadoop实战初步学习
  • 音乐自由之路:3分钟搞定加密音频格式转换
  • 手把手复现YOLOv2的Darknet-19:从ImageNet分类到COCO检测的完整训练流程
  • CUDA 13配置踩坑实录:92%开发者忽略的5个关键依赖链校验点,3分钟定位nvcc/cuDNN/PyTorch版本地狱
  • 收藏 | 传统产品经理转型AI产品,你需要掌握的核心能力与学习路径
  • 避开这些坑!用Stata做实证分析时,描述性统计、相关性矩阵和稳健性检验的5个常见错误
  • 告别数据拷贝:用CXL协议让GPU/加速器像CPU一样高效访问内存
  • 给新人的半导体ATE测试扫盲:DFT向量、MBIST、IDDQ到底在测什么?