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

Posted 姚家湾

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RT-Thread : IEEE1588/PTP 协议的实现相关的知识,希望对你有一定的参考价值。

        在前一篇博文中我们已经介绍了在RT-Thread OS 下实现了IEEE1599/PTP 协议。但是这只是第一步,如何在控制系统中使用这一同步时钟呢?我们来讨论一下这个问题

同步时钟源

        实现了本机语主时钟设备同步以后,PTP 硬件将提供一个同步信号出来,它就是PPS(pulse-per-second),提醒的是,不要被它的名称所混淆。它并非一定是个秒信号,它的频率是可编程的。基于Phy 的1588硬件可以输出多路高速(10M)的PPS。但是对于STM32 而言,PPS 速率的最高频率只能是32.768KHz.

 硬件PPS

通过PPS 控制寄存器 (ETH_MACPPSCR)设置。最高为

0001:二进制翻转为 2 Hz,数字翻转为 1 Hz。

0010:二进制翻转为 4 Hz,数字翻转为 2 Hz。

0011:二进制翻转为 8 Hz,数字翻转为 4 Hz。

0100:二进制翻转为 16 Hz,数字翻转为 8 Hz。

 .. 1111:二进制翻转为 32.768 KHz,数字翻转为 16.384 KHz

PPS 是芯片的一个GPIO 输出信号,要使用STM32Cube来配置,STM32H7 是PG8 或者PB5.

TIM 触发ITR4

        对于应用程序而言,可以通过一个TIM 对PPS 计数,实现内部时钟。STM 在芯片的内部实现了这种机制。 PPS 可以产生ITR4触发信号触发TIM2 或者TIM3(STM32H7)计数。

软件同步方案

软件方案如下图所示:

        设置ETH_MAC 产生一个最高频率为32.768的ITR4,TIM2 设置为从模式。在应用程序中通过TIM2 的中断产生一个周期性的中断。最高频率做到30.5us有这个中断程序维持一个软时钟来实现软件组件之间的时间同步。

虽然30us 的延时有点长,但是对于一般的控制系统来讲也能够满足要求了.

        如果使用DDP83640外部Phy,它包括一个高度可配置的时钟输出信号,其与内置的IEEE 1588时钟谐振。注意到谐振意味着频率相同而相位则不必相同。这个时钟的标称频率是250 MHz的整除结果,例如250 MHz/N,其中N为从2到255的整数。如果e PTP_COC 中写入0x8019 这里的N=0x19 =25,那么可以输出10MHz的同步时钟!当然这需要带有DP83640 PHY 的开发板。只能暂时放一边,

RT-Thread 实现

        在RT-Thread 中,实现TIM2 计数器。具体步骤如下

1 开通HWTIMER ,在RT-Thread Studio 在配置。如果没有实现添加,需要在RT-Thread.h 中添加

#define BSP_USING_HWTIMER

2 在Board.h 中添加

#define BSP_USING_TIM

#ifdef BSP_USING_TIM

 #define BSP_USING_TIM2

3 使用STM32 配置TIM2PPS 。并且在stm32h7xx_hal_conf.h 中去掉

#define HAL_TIM_MODULE_ENABLED

的括号

4 Drivers/include/config/time_comfig.h 修改为

#define TIM2_CONFIG                                        \\

    {                                                       \\

       .tim_handle.Instance     = TIM2,                    \\

       .tim_irqn                = TIM2_IRQn,  \\

       .name                    = "timer2",                \\

}

5 art pi 中的 drv_hwtimer.c 替换原来的drv_hwtimer.c

6 修改drv_hwtimer.c 程序中的init,使TIM2 支持从模式的配置

static void timer_init(struct rt_hwtimer_device *timer, rt_uint32_t state)
{
    uint32_t prescaler_value = 0;
    uint32_t pclk1_doubler, pclk2_doubler;
    TIM_HandleTypeDef *tim = RT_NULL;
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_SlaveConfigTypeDef sSlaveConfig = {0};
    struct stm32_hwtimer *tim_device = RT_NULL;

    RT_ASSERT(timer != RT_NULL);
    if (state)
    {
        tim = (TIM_HandleTypeDef *)timer->parent.user_data;
        tim_device = (struct stm32_hwtimer *)timer;
        pclkx_doubler_get(&pclk1_doubler, &pclk2_doubler);
        if (tim->Instance == TIM2){
            printf("tim2\\n");
        tim->Init.Prescaler = 0;
        tim->Init.CounterMode = TIM_COUNTERMODE_DOWN;
        tim->Init.Period = 8;
        tim->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        tim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
        if (HAL_TIM_Base_Init(tim) != HAL_OK)
        {
          Error_Handler();
        }

        sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
        sSlaveConfig.InputTrigger = TIM_TS_ITR4;
        sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
          sSlaveConfig.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;
          sSlaveConfig.TriggerFilter = 0x0;                         // 滤波:本例中不需要任何滤波
        if (HAL_TIM_SlaveConfigSynchro(tim, &sSlaveConfig) != HAL_OK)
        {
          Error_Handler();
        }

        /* set the TIMx priority */
        HAL_NVIC_SetPriority(tim_device->tim_irqn, 3, 0);
        /* enable the TIMx global Interrupt */
         HAL_NVIC_EnableIRQ(tim_device->tim_irqn);
         /* clear update flag */
         __HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE);
       /* enable update request source */
      __HAL_TIM_URS_ENABLE(tim);
        } else{

        /* time init */
        prescaler_value = (uint32_t)(HAL_RCC_GetPCLK1Freq() * pclk1_doubler / 10000) - 1;
        tim->Init.Period            = 10000 - 1;
        tim->Init.Prescaler         = prescaler_value;
        tim->Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;
        if (timer->info->cntmode == HWTIMER_CNTMODE_UP)
        {
            tim->Init.CounterMode   = TIM_COUNTERMODE_UP;
        }
        else
        {
            tim->Init.CounterMode   = TIM_COUNTERMODE_DOWN;
        }
        tim->Init.RepetitionCounter = 0;
        tim->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
        if (HAL_TIM_Base_Init(tim) != HAL_OK)
        {
            LOG_E("%s init failed", tim_device->name);
            return;
        }
        else
        {

            /* set the TIMx priority */
            HAL_NVIC_SetPriority(tim_device->tim_irqn, 3, 0);

            /* enable the TIMx global Interrupt */
            HAL_NVIC_EnableIRQ(tim_device->tim_irqn);

            /* clear update flag */
            __HAL_TIM_CLEAR_FLAG(tim, TIM_FLAG_UPDATE);
            /* enable update request source */
            __HAL_TIM_URS_ENABLE(tim);
            LOG_D("%s init success", tim_device->name);
        }
        }
    }
}

另一处的修改,TIM2 不需要修改prescale ,让它保持为0

static rt_err_t timer_ctrl(rt_hwtimer_t *timer, rt_uint32_t cmd, void *arg)
{
    TIM_HandleTypeDef *tim = RT_NULL;
    rt_err_t result = RT_EOK;
    uint32_t pclk1_doubler, pclk2_doubler;

    RT_ASSERT(timer != RT_NULL);
    RT_ASSERT(arg != RT_NULL);

    tim = (TIM_HandleTypeDef *)timer->parent.user_data;

    switch (cmd)
    {
    case HWTIMER_CTRL_FREQ_SET:
    {
        rt_uint32_t freq;
        rt_uint16_t val;

        /* set timer frequence */
        freq = *((rt_uint32_t *)arg);

        pclkx_doubler_get(&pclk1_doubler, &pclk2_doubler);
        if (tim->Instance != TIM2 ){  //By yao
          val = HAL_RCC_GetPCLK1Freq() * pclk1_doubler / freq;
          __HAL_TIM_SET_PRESCALER(tim, val - 1);
        /* Update frequency value */
        tim->Instance->EGR |= TIM_EVENTSOURCE_UPDATE;
        }
    }
    break;
    default:
    {
        result = -RT_ENOSYS;
    }
    break;
    }

    return result;
}

7 编译时发现找不到下面这两个文件

rt_err_t rt_device_hwtimer_register(rt_hwtimer_t *timer, const char *name, void *user_data);

void rt_device_hwtimer_isr(rt_hwtimer_t *timer);

        在这里卡壳了蛮长的时间,结果发现,在rtthread->components->drivers中没有hwtimer目录。而在art pi 中却是存在的。可是目录中明明有hwtimer 的目录。也许是因为一开始没有选配HWTIMER,然后再RT-Thread.h添加后,不会导入hwtimer 目录了(我觉得这是RT-Thread Studio 的一个BUG! 如果你再后来去点击第一个蓝色的RT-thread Settings 的图标去设置的话,会导致你添加再Application 目录下的应用程序目录被隐藏,删掉了也无法从新添加,烦死人!!)网络上建议点击项目资源管理器边框右上方的向下小箭头中的“过滤器和定制”设置可以解决这个问题。

8 主程序添加一个hwtimer_sample

#define HWTIMER_DEV_NAME   "timer2"
int SoftTimer=4096;
rt_tick_t last_time,current_time;
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{  SoftTimer--;
if (SoftTimer==0){
   current_time= rt_tick_get();
    rt_kprintf("tick is :%d(ms)!\\n", current_time-last_time);
    last_time=current_time;
    SoftTimer=4096;
};
    return 0;
}

static int hwtimer_sample(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    rt_hwtimerval_t timeout_s;
    rt_device_t hw_dev = RT_NULL;
    rt_hwtimer_mode_t mode;


    hw_dev = rt_device_find(HWTIMER_DEV_NAME);
    if (hw_dev == RT_NULL)
    {
        rt_kprintf("hwtimer sample run failed! can't find %s device!\\n", HWTIMER_DEV_NAME);
        return RT_ERROR;
    }


    ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
    if (ret != RT_EOK)
    {
        rt_kprintf("open %s device failed!\\n", HWTIMER_DEV_NAME);
        return ret;
    }


    rt_device_set_rx_indicate(hw_dev, timeout_cb);


    mode = HWTIMER_MODE_PERIOD;
    ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
    if (ret != RT_EOK)
    {
        rt_kprintf("set mode failed! ret is :%d\\n", ret);
        return ret;
    }


    timeout_s.sec = 0;
    timeout_s.usec = 8;

   if (rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
    {
        rt_kprintf("set timeout value failed\\n");
        return RT_ERROR;
    }


    rt_thread_mdelay(3500);


    rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s));
    rt_kprintf("Read: Sec = %d, Usec = %d\\n", timeout_s.sec, timeout_s.usec);

    return ret;
}

MSH_CMD_EXPORT(hwtimer_sample, hwtimer sample);

运行 hwtimer_sample 命令时,输出

 显然达到目的了

实现时间触发网络协议

有了同步的软件时钟,实现时间触发网络就接近一步了。可以考虑使用PTP SYNC 帧作为时间触发网络的同步帧。我们另文深入探讨

结束语

      STM32H 能够实现IEEE1588/PTP 协议,唯一的缺点是PPS频率太低了。如果能够输出一个高频信号,比如10MHz 就完美了。

    

以上是关于RT-Thread : IEEE1588/PTP 协议的实现的主要内容,如果未能解决你的问题,请参考以下文章

IEEE 1588-PTP简介

ZYNQ 系统的IEEE1588 实现方法

如何实现IEEE1588 高精度时间同步

基于1588PTP精密时钟同步(时间同步服务)技术介绍

1588PTP网络时钟服务器(时间同步)技术应用方案

基于1588PTP精密时钟同步(卫星校时钟)技术介绍