RT-Thread : IEEE1588/PTP 协议的实现

内容

           IEEE1588/PTP协议对于实时通信非常重要。打算在STM32H750 art PI 上测试一下,可惜RT-Thread OS 没有支持IEEE1588/PTP 协议,网络上介绍IEEE1588 PTP 协议的很多,但是真正在一个MCU 上实现的资料却很少。  于是自己动手移植一个,结果发现并不简单。

       记得几年前曾经在STM32F207 上使用以太网MAC 支持IEEE588 功能做过以太网层的时间同步测试。以为既然后续的STM32F4,H7 都支持MAC IEEEE1588 .大概会比较方便了。结果发现,不仅RT-Thread OS 没有支持IEEE1588/PTP 协议,就连STM32F4_HAL 库以及STM32H7XX_HAL库中的eth.c 都明确指出目前不支持IEEE1588/PTP.网络上的资料依然停留在STM32F107 没有新的内容。

    Github 上有一个STM32F4 的项目(https://github.com/mpthompson/stm32_ptpd)。不过它是在nucleo stm32F429 上实现的。使用的是CMSIS-RTOS,将其修改到RT-Thread H7 ,改动非常大,尝试了几天几乎要放弃了。

     在万般无奈之际,发现了这个项目

  它是基于mpthompson 的项目基础上做的项目,这个项目的优点是

1 将STM32H7 的PTP 底层驱动直接地通过访问底层硬件寄存器完成,没有使用STM32H7xxx_HAL 库。避免了HAL 兼容性的问题。

2 软件以函数库的形式实现,避免了与OS 线程之间的不兼容性。

3 支持lwIP 2.0 ,使用UDP 组播方式(IGMP 协议) 实现PTPD 协议。

4 好像没有实现tick 定时器和TIM2 定时器调整。但是也有好处,避免了与RT-Thread OS 的兼容性问题。

PTP 协议

移植PTP 协议还是需要了解PTP 协议和PTPD 的程序结构。

PTP 报文

PTP协议定义了4种多点传送的报文类型和管理报文

-同步报文(Sync)

     由主设备发送给从设备(一到两秒发一次) , 消息中可以包含 Sync 发送时间标签 , 也可以在后续的Follow UP 消息中包含

-跟随报文(Follow_up)

用于传送Sync 消息的发送时间 

-延迟请求报文(Delay_Req)

-延迟应答报文(Delay_Resp)

一般报文和事件报文
      报文有一般报文和事件报文两种类型。跟随报文和延迟应答报文属于一般报文,一般报文本身不进行时戳处理,它可以携带事件报文的准确发送或接收时刻值信息。同步报文和延迟请求报文属于事件报文,事件报文是时间敏感消息,需要加盖精确的时间戳。

报文收发流程

1. 主时钟周期性的发出 sync 报文,并记录下 sync 报文离开主时钟的精确发送时间 t1;

(此处 sync 报文是周期性发出,可以携带或者不携带发送时间信息,因为就算携带也只能是预估发送时间戳 originTimeStamp)

2. 主时钟将精确发送时间 t1 封装到 Follow_up 报文中,发送给从时钟;

(由于 sync 报文不可能携带精确的报文离开时间,所以我们在之后的 Follow_up 报文中,将 sync 报文精确的发送时间戳  t1 封装起来,发给从时钟)

3. 从时钟记录 sync 报文到达从时钟的精确时到达时间 t2;

4. 从时钟发出 delay_req 报文并且记录下精确发送时间 t3;

5. 主时钟记录下 delay_req 报文到达主时钟的精确到达时间 t4;

6. 主时钟发出携带精确时间戳信息 t4 的 delay_resp 报文给从时钟;

 实现IEEE1588 的几种方法

IEEE1588 的基本思想是由一个主时钟设备周期性地发送时间标签,从时钟设备根据主时钟发来的时间标签来调整本地时钟,达到时间同步的目的。

  具体实现时,可以使用下面三种方式

1 软件方式

    在以太网的帧队列中插入时间标签.显然,由于软件执行的不确定性和队列中帧数量,网络负载等因素,会造成时间标签的不精准。

2 在MAC 控制器内实现

  当PTP 帧到达MAC 控制器时,由硬件插入一个时间标签。显然在MAC 控制器内部,有一个时间计数器。并且能够过滤PTP 帧。在STM32 等MCU 中表明支持IEEE1588 就是指MAC 控制器中具备这种功能。

3 在以太网物理层实现

      例如TI 公司的DP83640 芯片,据说这是精度最高的方式。但是需要软件访问Phy 器件的串行管理接口。DP83640 的另一个优点是它能够产生硬件时钟信号。可以直接产生硬件同步信号。

     STM32MCU 从STM32F107 到STM32H7 都支持IEEE1588 。许多年前,笔者曾经在Ethernet 底层测试果IEEE1588 的同步性能。事实上IEEE1588 /PTP 包括了两部分,一部分是底层硬件,另一部分是PTP 协议,它是一个基于UDP 的协议。

    不知道什么原因,ST 公司对IEEE1588 的技术支持并不好,连HAL 库中都没有支持PTP 。网络上也有人表示ST 的MAC IEEE1588 没什么用,有一些人主张使用外部DP83640 之类的方案。但是涉及到需要硬件设计。太麻烦了。我们决定还是试试基于MAC 控制器的方案。

移植的过程和遇到的问题

下载和编译源码

   该项目的文档非常简单。只能根据简单的文档自己摸索实现

  1. 项目在art pi 平台上实现。第一步新建一个art_pi_factory 项目
  2. 删除了wifi ,蓝牙部分
  3. 下载项目源代码,将其中ptp 目录拷贝到RT-Thread OS项目的Application 目录下。将port/stm2H7目录中的constants_dep.h,ptpd_dep.c和ptpd_dep.h三个文件拷贝到ptp/dep 中,其中constants_dep.h 覆盖了原来已有的文件。将port 目录中的congstants.h 拷贝到ptp目录中。
  4. 编译项目,会出现一堆没定义的,我在ptpd.h 中添加了#include “stm32h7.h”,另外删除了一些不用的语句。编译通过了。

编写主程序

令人遗憾的是该项目没有包含主程序。文档中只是提到:

软件初始化通过调用ptpd_init()实现,每1ms 调用PTPD timers ,比如

// ptpd timers
for (uint8_t i=0; i < TIMER_ARRAY_SIZE; i++)
{
    switch (ptpdTimersCounter[i])
    {
        case 0:
        break;
        case 1:
        ptpdTimersExpired[i] = TRUE;
        ptpdTimersCounter[i] = ptpdTimers[i];
        break;
        default :
        ptpdTimersCounter[i]--;
        break;
    }
}

连续地调用  ptpd_task()

每隔100ms 调用igmp_timer() ,我不知道这是什么意思,lwip 源代码中并没有这个函数。暂时放一放再说。

根据他的说明,我做了如下改动

1 在 timer.c 中添加了一个updateTime 函数:

void updateTimer(void){
    for (uint8_t i=0; i < TIMER_ARRAY_SIZE; i++)
      {
          switch (ptpdTimersCounter[i])
          {
              case 0:
              break;
              case 1:
              ptpdTimersExpired[i] = TRUE;
              ptpdTimersCounter[i] = ptpdTimers[i];
              break;
              default :
              ptpdTimersCounter[i]--;
              break;
          }
      }
}

主程序修改为:

#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"
#include <netdev_ipaddr.h>
#include <netdev.h>
#include <dfs_fs.h>
typedef struct
{
        int32_t seconds;
        int32_t nanoseconds;
} TimeInternal;
void ptpd_init();
void ptpd_task();
void updateTimer(void);
void getTime(TimeInternal *time);
#define DBG_COLOR
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#define PTPD_DBG
#define LWIP_PTP 1
#include <rtdbg.h>
int main(void)
{
    TimeInternal  currentTime;
    printf("IEEE1588/PTP Test\n");
     ptpd_init();
    while(1){
       ptpd_task();
        updateTimer();
    //    getTime(&currentTime);
    //    printf("seconds:%d\n",currentTime.seconds);
        rt_thread_mdelay(1);
    }
    return RT_EOK;
}
#include "stm32h7xx.h"
static int vtor_config(void)
{
    /* Vector Table Relocation in Internal QSPI_FLASH */
    SCB->VTOR = QSPI_BASE;
    return 0;
}
INIT_BOARD_EXPORT(vtor_config);

    运行后,会打印currentTime 。这个时钟应该是MAC 控制器的PTP 时间计数器中读出来的。说明底层的硬件接口是对的。

如何设置为Master Clock?

          slave clock 模式下什么都不发送,wirshark 看不出什么名堂,于是打算改成Master Clock,看看是否能够发出Sync 和 Follow 帧来。结果发现,网络上居然没有说明白如何配置成为Master Clock 模式。只能自己跟踪源代码,看看如何搞。

1   有人说只要将slave-only 修改为FALSE,就可以了。PTP 的参数设置是在ptp.c 的ptp_init()完成的。参数在constans.h 中配置。我做了下面的修改

/* features, only change to refelect changes in implementation */
#define NUMBER_PORTS      1
#define VERSION_PTP       2
#define BOUNDARY_CLOCK    TRUE
#define SLAVE_ONLY        FALSE
#define NO_ADJUST         FALSE

好像没有什么反应,通过分析代码发现要将state 设置为 PTP_MASTER,于是修改了ptpStartup 函数

int16_t ptpdStartup(PtpClock * ptpClock, RunTimeOpts *rtOpts, ForeignMasterRecord* foreign)
{
	ptpClock->rtOpts = rtOpts;
	ptpClock->foreignMasterDS.records = foreign;
	/* 9.2.2 */
	if (rtOpts->slaveOnly) rtOpts->clockQuality.clockClass = DEFAULT_CLOCK_CLASS_SLAVE_ONLY;
	/* No negative or zero attenuation */
	if (rtOpts->servo.ap < 1) rtOpts->servo.ap = 1;
	if (rtOpts->servo.ai < 1) rtOpts->servo.ai = 1;
	printf("event POWER UP\n");
	ETH_PTPStart(ETH_PTP_FineUpdate);
 	toState(ptpClock, PTP_INITIALIZING);
    doState(ptpClock);
 	toState(ptpClock, PTP_MASTER);
 	doState(ptpClock);
	return 0;
}

而且发现toState 后面要跟doState 才行。

又发现在 toState 函数中 delayMechanism 的判断中 E2E 是空的。再追踪到ptp_init 的最后

rtOpts.delayMechanism = DEFAULT_DELAY_MECHANISM;

这里的DEFAULT_DELAY_MECHANISM是E2E,于是改成了

rtOpts.delayMechanism =P2P;// DEFAULT_DELAY_MECHANISM;

当然,你也可以改DEFAULT_DELAY_MECHANISM;

再一次使用wirshark 监控,出现了Sync和Follow 帧,大约是每隔1秒出现一次。

Sync 帧

 follow 帧

修改 Sync和follow 帧的发送周期

SYNC报文由处于MASTER状态的时钟周期性的发送,间隔时间为1(秒) ×(2^portDS.logSyncInterval)。

通过constans.hz中的DEFAULT_SYNC_INTERVAL定义

#define DEFAULT_SYNC_INTERVAL         0 /* -7 in 802.1AS */ 

上面为 1 秒,如果改为 -1 便是1 秒,以此类推。

ubuntu OS 下的测试

下一步进行Ubuntu 与RT-Thread Slave 的测试。

这里要使用两个工具

ethtool和linuxPTP

linuxPTP 编译后 命令行是ptp4l

      首先遇到的问题是ethertools 检测我的PC 不支持硬件时间标签。我的以太网控制芯片是螃蟹,不是Intel 的,看来不行。不过ptp4l 好像支持软件时间标签。

**ptp4l -i enp1s -m -S **

-S 表示软件时间标签。

不过好像运行起来与RT-Thread 的Master Clock 无法联通!不知道为啥出现这些

^C(base) yao@minipc:~$ sudo ptp4l -i enp1s0 -S -s -m
ptp4l[95.547]: port 1: INITIALIZING to LISTENING on INIT_COMPLETE
ptp4l[95.547]: port 0: INITIALIZING to LISTENING on INIT_COMPLETE
ptp4l[103.048]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[109.373]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[115.664]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[122.787]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[129.956]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[137.784]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[144.257]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[150.536]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[158.146]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[165.345]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[172.264]: selected local clock 00e44f.fffe.6808ad as best master
ptp4l[179.509]: selected local clock 00e44f.fffe.6808ad as best master

PTP4L 的配置

·为了解决问题,使用wireshark 软件对PTP4L master clock 和RT-Thread 发出来的SYNC和Follw帧做了对比,发现transportSpecific 的号不一样。结果发现 PTP4L 是需要配置的。我的配置文件为

[global]
priority1		248
priority2		248
clockClass		248
transportSpecific	0x08
slaveOnly		1

修改之后,ptp4l能够发出Delay Request 帧了,但是RT-Thread 没有相应 Delay_response

RT-Thread 缺省模式不支持组播 

        又一个问题出现了,发现虽然RT-Thread 能够发送多播UDP ,但是无法接收到多播UDP。尝试了几乎一天时间才找到是这个问题!

发现了问题,解决方法就简单了

我在dvr_eth.c 的rt_stm32_eth_init 的函数中添加了一段(Add By yao 到 end yao之间)

/* EMAC initialization function */ static rt_err_t rt_stm32_eth_init(rt_device_t dev) {     ETH_MACConfigTypeDef MACConf;     uint32_t regvalue = 0;

    uint8_t  status = RT_EOK;

总结
文章介绍了在STM32H750 art PI上测试IEEE1588/PTP协议的过程。作者发现RT-Thread OS不支持该协议,尝试移植却遇到困难。最终在GitHub上找到一个基于STM32H7的项目,避免了HAL库兼容性问题,支持lwIP 2.0,使用UDP组播方式实现PTPD协议。文章还详细介绍了PTP协议的报文类型、报文收发流程以及实现IEEE1588的三种方法。最后描述了移植过程中的下载、编译源码等步骤。