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 配置TIM2,PPS 。并且在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 协议的实现的主要内容,如果未能解决你的问题,请参考以下文章