stm32实现1588协议

发布者:中和子最新更新时间:2019-08-06 来源: eefocus关键字:stm32  1588协议 手机看文章 扫描二维码
随时随地手机看文章

1.引言

本文章基于stm32官方例程实现,详细代码可下载例程。


在RT-thread系统上实现1588协议,网络驱动上需要做较多的修改。 


2.代码修改


2.1驱动分析

当Accumulator register,溢出之后,会增加subsecond register一个步长,这个步长为constant value;每个时钟周期 Accumulator register会加上 addend register的值,这样做的目的是调整 subsecond register 增加的周期。

为Accumulator register提供clock的位系统时钟 HCLK,一般为72MHz,而 subsecond register 增加的频率需要为50MHz。

subsecond register的最大值为2^31,把2^31作为1秒钟,那么每20ns subsecond register需要增加 20ns  * (2^31) / 10^9ns = 43 ,即constant value 为43.。

Accumulator register 溢出时间为 49 *10^9 / (2^31) ~= 20.023ns

 Addend / 2^32 = (1 / 20ns) /72mhz    换算单位之后 Addend = 2979125334.90

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) //接收出错

        {

            ERROR("handle: failed to poll socketsn");

            toState(ptpClock, PTP_FAULTY);

            return;

        }

        else if (!ret) //没有收到数据,直接返回

        {

          //  DBGVV("handle: nothingn");

            return;

        }

    }

//**********************************************************************

  //  DBGVV("handle: somethingn");

 

    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 socketn");

        toState(ptpClock, PTP_FAULTY);

        return;

    }

    else if (!ptpClock->msgIbufLength)

    {                                 //读取数据,接收时MAC层加入的时间戳保存在time中;数据保存在msgIbuf中

[1] [2] [3]
关键字:stm32  1588协议 引用地址:stm32实现1588协议

上一篇:STM32 IO寄存器操作
下一篇:STM32中如何使用底层的地址来控制IO口的电平

推荐阅读最新更新时间:2024-11-12 13:30

按键操作(CubeMX加HAL库学STM32系列)
这一篇是补充第一篇的按键操作, 因为第一篇很多地方很详细了, 写了很大的篇幅, 所以单独写一篇用CubeMX配置STM32使用自带按键的操作 提示 : 仍然按照先Cube配置再IAR或者Keil编程, CubeMX里面关于时钟的配置跟我上一篇写的一样, RCC和SYS设置以及时钟树(Clock Configuration)都按照第一篇配置即可, 没有什么特别的需求, 是从始至终不需改变这个地方的配置的。 1-2. 使用核心板自带按键 操作简介 :    通过板子上的两个按钮控制LED灯的亮灭 WK_UP按键按下则LED0闪烁, 松开停止闪烁并熄灭, 即WK_UP按键长按LED0才会持续闪烁    KEY0按下再松手, LE
[单片机]
按键操作(CubeMX加HAL库学<font color='red'>STM32</font>系列)
stm32_FSMC注意事项
关于FSMC总线的介绍前面已经略有介绍,在此不作赘述。只是简单说一下其配置过程既需要注意的几点问题: 一、 FSMC内部结构和映射地址空间 FSMC包含AHB接口、NOR Flash和PSRAM控制器、NANDflash和PC卡控制器、外部设备接口4个主要模块。在ST吗内部,FSMC的一端通过内部高速总线AHB连接内核,另一端则是面向扩展存储器的外部总线。内核对外部存储器的访问信号发送到AHB总线后,经过FSMC转换为符合外部存储器规约的信号,送到外部存储器响应的管脚,视线内河鱼 数据交换。FSMC起到了桥梁的作用,既能够进行信号类型的转换,有能够进行信号宽度和时序的调整,屏蔽掉不同存储器之间的差异。 FSMC内部包含NOR Fl
[单片机]
利用STM32 ST-LINK Utility查看内核运行状态
本文围绕STM32 ST-LINK Utility讲几点主要功能及相关拓展知识: 1.STM32编程下载; 2.利用该编程工具查看内核运行状态; 3.Option Bytes选项字配置; 1STM32编程下载 STM32 ST-LINK Utility一个最重要的功能就是对STM32进行编程。支持常见的hex、bin文件,还有早期摩托罗拉定义的srec和s19格式的文件(说实话,我都不了解这两种格式的文件)。 这里主要想提示一下初学者:hex带有地址,而bin文件不带地址,下载时需要填写起始地址。 下面看两张在STM32 ST-LINK Utility中下载选择文件的图大家就明白了。 1.选择hex下载,地址不
[单片机]
利用<font color='red'>STM32</font> ST-LINK Utility查看内核运行状态
STM32双缓冲机制初始化(使用STM32CubeMX)
1.使用STM32CubeMX配置的串口引脚设置和dma的设置会生成在usart.c。 1)如果DMA接收想采用循环缓冲区的方式,可以直接将RX-DMA设置成Circle方式,然后数据就会硬件上自动实现环形缓冲区的功能,省了不少时间。 2)DMA在采用Normal模式的时候,当一次任务完成后,DMA- DMA_BufferSize自动清零,并且DMA自动停止。如果想再次设置DMA的BufferSize的话,必须要进行如下操作: step1:DMA_CMD(DMAx_Channely,DISABLE); step2: 设置DMA_BufferLen step3:DMA_CMD(DMAx_Channely
[单片机]
STM32 通用定时器做微秒延时函数(STM32CubeMX版本)
环境: 开发板:STM32F4探索者(正点原子) 1.配置定时器时钟 选择时钟源 这里选择的是内部时钟,来自 RCC 的TIMxCLK,在通用定时器框图中我们可以看到如下: 而我们可以在 STM32F4xx中文参考手册中找到,TIM2 在外设总线1(APB1上),因此其时钟为 84MHz,如下图所示: 2.计数器时钟频率及计数模式 除了配置定时器的时钟,还需要配置计数器时钟频率,我们要实现微秒延时,因此计数器时钟频率应该是1MHz, 而要实现还需要以下3个参数: 预分频系数 根据STM32F4xx中文参考手册中的时钟频率计算,如下图所示: 其中fCK_PSC就是通用定时器框图中的CK_PSC, 即值为8
[单片机]
用 <font color='red'>STM32</font> 通用定时器做微秒延时函数(STM32CubeMX版本)
在使用STM32的FSMC的一些体会。。
刚开始接触感觉似乎很难,真的是云里雾里。但是看了百度百科FMSC介绍以及芯嵌stm32入门视频《基于stm32的FSMC控制LCD的理解》视频终于对FMSC有自己的理解。虽然还有些细节还没有完全吃透,但是学东西就是需要在摸索中前进,停下来就是浪费时间。然后马上拿起板子,下载SRAM的例程运行。好正常运行。下面我们来看看相关的代码吧。 /*神舟SRAM读写程序相关定义*/ #define BUFFER_SIZE 0x400 /*定义读写BUFFER大小*/ #define WRITE_READ_ADDR 0x8000 /*定义SRAM读写的地址*/ ErrorStatus HSEStartUpSt
[单片机]
STM32 驱动例程
1、IO输出 led.c #include #include led.h //初始输出口.并使能这两个口的时钟 //LED IO初始化 void LED_Init(void) { RCC- APB2ENR|=1 2; //使能PORTA时钟 RCC- APB2ENR|=1 3; //使能PORTB时钟 GPIOA- CRL|=0X00000300;//PA2 推挽输出 GPIOA- ODR|=1 2; //PA2 输出高 GPIOA- CRL|=0X00003000;//PA3 推挽输出 GPIOA- ODR|=1 3; //PA3 输出高 GPIOB-
[单片机]
STM32-异常与中断
在使用单片机的时候我们常用到的中断,但是但是我们常说的中断都是由(内核的)外部事件引起的、正常的紧急事件。而异常与我们所说的中断相似,但也有不同之处。 异常(内核中断)和外部中断 异常是CPU内部产生的中断,即在CPU执行特定指令的时候出现的非法情况,如除数为0等等,所以不可能在执行指令期间发生异常,只会在执行一条指令后有可能发生,所以也称同步中断。而中断则是一种异步的,它与特定的进程是无关的,又称为异步中断。 CM3 内核支持 256 个中断,其中包含了 16 个内核中断(主要用于系统异常)和 240 个外部中断,并且具有 3 个固定的高优先级和256级(8位)的可编程中断设置。因为芯片设计者可以修改 CM3 的硬件描述
[单片机]
STM32-异常与中断
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved