文章目录[隐藏]
本文记录RT-Thread OS 下,使用DP83640 Phy 芯片实现IEEE1588 协议的过程。
硬件
为了测试软件,我们专门布了一块板,SOC 采用了STM32f429 实现。外接了DP83640 PHY 和GPS 模块。
要点:
- DP83640 外接50M 晶振。
- OUT_CLK 连接到STM3 的TIM1_ETR 作为外部时钟计数
- 接口使用RMII 接口
DP83640 与普通PHY 芯片的主要差别是几个GPIO 引脚
上面的GPIO 信号中,GPIO12 (CLK_OUT )是一个比较重要的信号,相当于PPS 输出信号。以内部250MHz 为基础,可以分频为10M 输出。将它连接到TIM4 的TIM1 ETR(PF2) 输入。用作内部时钟的节拍。
参考代码
DP83640 的驱动代码
IT 原始的代码: snlc036
我参考了这个项目的代码
Github 上的代码https://github.com/j123b567/ti-epl 是为STM32Fxx7 上移植的代码
PTP 协议栈
我参考了这个项目的代码
GitHub - hasseb/stm32h7_atsame70_ptpd: IEEE 1588 PTP daemon for STM32H7 and ATSAME70
软件实现要点
步骤
1 实现Ethernet 通信。
主要修改drv_eth中的phy_monitor_thread_entry 程序
尽管我们将原来的LAN8742 改成了DP83640 但是由于PHY_ID1_REG,PHY_BASIC_CONTROL_REG等寄存器的地址是相同的,所以不需要什么修改。
2 实现 PTP 协议
MDIO 的接口驱动
STM32F 的MAC 控制器支持MDIO接口。使用下面的函数实现:
HAL_StatusTypeDef HAL_ETH_ReadPHYRegister(ETH_HandleTypeDef *heth, uint16_t PHYReg, uint32_t *RegValue);
HAL_StatusTypeDef HAL_ETH_WritePHYRegister(ETH_HandleTypeDef *heth, uint16_t PHYReg, uint32_t RegValue);
DP83640 的地址
在RT-Thread 的drv_eth.c中,Phy addrerss是自动扫描的,找到的地址为 0x19 (可能与上电Strap 有关)。
DP83640 底层驱动
DP83640 通过MDIO 接口访问,DP83640 的底层接口改为:
1 在 drv_eth 中添加了:
uint32_t ReadPHYRegister( uint16_t PHYReg){
uint32_t data;
HAL_ETH_ReadPHYRegister(&EthHandle,PHYReg, &data);
return data;
}
void WritePHYRegister( uint16_t PHYReg, uint32_t RegValue){
HAL_ETH_WritePHYRegister(&EthHandle,PHYReg,RegValue);
}
2 在epl_core.c 中,替换了IntWriteReg和EPLReadReg中的读写子程序。
LED 灯
按照DP83640 的数据手册,LED应该为mode1 .但是绿灯却不闪烁。 在drv_eth.c 中添加了
HAL_ETH_WritePHYRegister(&EthHandle, PHY_PHYCTRL, 0x8019);
绿灯闪了, 这却是mode2!
DP83640 的初始化
Pdp_dep.c 中添加了
void PHY_PTPStart(void) {
RX_CFG_ITEMS rx_cfg_items = {
.ptpVersion = 0x02,
.ptpFirstByteMask = 0x00,
.ptpFirstByteData = 0x00,
.ipAddrData = 0,
.tsMinIFG = 0x00,
.srcIdHash = 0,
.ptpDomain = 0,
.tsSecLen = 0, //0; // DRs option
.rxTsNanoSecOffset = 0, //0x24; // DRs option
.rxTsSecondsOffset = 0, //0x21; // DRs option
};
PTPEnable(pEPL_HANDLE, TRUE);
PTPSetClockConfig(pEPL_HANDLE,0x0007,0x19,0,0);//OUT_CLK=10M 0x19
PTPSetTransmitConfig(pEPL_HANDLE, TXOPT_TS_EN | TXOPT_IPV4_EN, 2, 0, 0);
PTPSetReceiveConfig(pEPL_HANDLE, RXOPT_RX_TS_EN | RXOPT_RX_IPV4_EN, &rx_cfg_items);
};
PTPSetClockConfig 是我添加的,为了在OUT_CLK 输出 10M 时钟。
0x19=25,
250M/25=10MHz.
DP83640 读写的互斥保护
如果多个线程访问DP83640 寄存器的话,可能引起出错。需要使用操作系统的互斥机制
Mutex 。这是与OS 有关的,需要在epl_oai.c 程序中修改。
允许组播
在STM32f4xx_eth.c 中的ETH_MACDMAConfig程序中。改为:
macinit.MulticastFramesFilter = ETH_MULTICASTFRAMESFILTER_NONE;
这有点简单粗暴,如果改为过滤方式,网络通信更有效。
Sys_time.c 的修改
时钟都在DP83640 芯片中,所以PTP 协议主要修改这部分程序:
void getTime(TimeInternal *time)
{
NS_UINT32 seconds,nanoseconds;
// ETH_PTPTime_GetTime(×tamp);
PTPClockReadCurrent( pEPL_HANDLE, &seconds, &nanoseconds);
time->seconds = (int32_t)seconds;
time->nanoseconds = (int32_t) nanoseconds;
}
void setTime(const TimeInternal *time)
{
PTPClockSet(pEPL_HANDLE, time->seconds, time->nanoseconds);
DBG("resetting system clock to %d sec %d nsec\n", time->seconds, time->nanoseconds);
}
void updateTime(const TimeInternal *time)
{
struct ptptime_t timeoffset;
DBGV("updateTime: %d sec %d nsec\n", time->seconds, time->nanoseconds);
timeoffset.tv_sec = -time->seconds;
timeoffset.tv_nsec = -time->nanoseconds;
/* Coarse update method */
ETH_PTPTime_UpdateOffset(&timeoffset);
DBGV("updateTime: updated\n");
}
uint32_t getRand(uint32_t randMax)
{
return rand() % randMax;
}
bool adjFreq(int32_t adj1)
{
if (adj1!=0){
int64_t adj=adj1-16;
if (adj > ADJ_FREQ_MAX)
adj = ADJ_FREQ_MAX;
else if (adj < -ADJ_FREQ_MAX)
adj = -ADJ_FREQ_MAX;
int32_t seconds=(int)(adj /0x1000000000);
int32_t nanoseconds=(int)(adj %1000000000);
bool negAdj = TRUE;
if ( adj > 0)
negAdj = FALSE;
PTPClockStepAdjustment (pEPL_HANDLE,abs(seconds),abs(nanoseconds),negAdj);
}
return TRUE;
}
drv_hwtimer.c 的修改
平心而论,RT-Thread 的dvr_timer 的实现是比较不完整的。需要自己修改。实现要点
1 使用TIM1 External1 模式
2 Prescale 为 10,分频为 1MHz。
static void timer_init(struct rt_hwtimer_device *timer, rt_uint32_t state)
{
TIM_HandleTypeDef *tim = RT_NULL;
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;
tim->Init.Period = 10000-1;
tim->Init.Prescaler =9;//div 10 1MHz
tim->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
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;
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_ETRF;
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_NONINVERTED;
sSlaveConfig.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;
sSlaveConfig.TriggerFilter = 0;
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);
LOG_D("%s init success", tim_device->name);
}
}
结果
目前代码已经调试出来了。从ppm 看,好像还有点问题。周期性地(每隔50秒左右)会突跳到100ppm 。不知道什么原因,也可能是MASTER 的问题。下面要进一步调试。
将Master PTP 的SYNC INTERVAL 改小,有明显改善。
#define DEFAULT_SYNC_INTERVAL -7/* -7 in 802.1AS */
MASTER 使用了STM32F429 MAC 1588 的程序。下一步要使用DP83640 的版本也测试一下。
版权声明:本文为CSDN博主「姚家湾」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yaojiawan/article/details/122434783
本文记录RT-Thread OS 下,使用DP83640 Phy 芯片实现IEEE1588 协议的过程。
硬件
为了测试软件,我们专门布了一块板,SOC 采用了STM32f429 实现。外接了DP83640 PHY 和GPS 模块。
要点:
- DP83640 外接50M 晶振。
- OUT_CLK 连接到STM3 的TIM1_ETR 作为外部时钟计数
- 接口使用RMII 接口
DP83640 与普通PHY 芯片的主要差别是几个GPIO 引脚
上面的GPIO 信号中,GPIO12 (CLK_OUT )是一个比较重要的信号,相当于PPS 输出信号。以内部250MHz 为基础,可以分频为10M 输出。将它连接到TIM4 的TIM1 ETR(PF2) 输入。用作内部时钟的节拍。
参考代码
DP83640 的驱动代码
IT 原始的代码: snlc036
我参考了这个项目的代码
Github 上的代码https://github.com/j123b567/ti-epl 是为STM32Fxx7 上移植的代码
PTP 协议栈
我参考了这个项目的代码
GitHub - hasseb/stm32h7_atsame70_ptpd: IEEE 1588 PTP daemon for STM32H7 and ATSAME70
软件实现要点
步骤
1 实现Ethernet 通信。
主要修改drv_eth中的phy_monitor_thread_entry 程序
尽管我们将原来的LAN8742 改成了DP83640 但是由于PHY_ID1_REG,PHY_BASIC_CONTROL_REG等寄存器的地址是相同的,所以不需要什么修改。
2 实现 PTP 协议
MDIO 的接口驱动
STM32F 的MAC 控制器支持MDIO接口。使用下面的函数实现:
HAL_StatusTypeDef HAL_ETH_ReadPHYRegister(ETH_HandleTypeDef *heth, uint16_t PHYReg, uint32_t *RegValue);
HAL_StatusTypeDef HAL_ETH_WritePHYRegister(ETH_HandleTypeDef *heth, uint16_t PHYReg, uint32_t RegValue);
DP83640 的地址
在RT-Thread 的drv_eth.c中,Phy addrerss是自动扫描的,找到的地址为 0x19 (可能与上电Strap 有关)。
DP83640 底层驱动
DP83640 通过MDIO 接口访问,DP83640 的底层接口改为:
1 在 drv_eth 中添加了:
uint32_t ReadPHYRegister( uint16_t PHYReg){
uint32_t data;
HAL_ETH_ReadPHYRegister(&EthHandle,PHYReg, &data);
return data;
}
void WritePHYRegister( uint16_t PHYReg, uint32_t RegValue){
HAL_ETH_WritePHYRegister(&EthHandle,PHYReg,RegValue);
}
2 在epl_core.c 中,替换了IntWriteReg和EPLReadReg中的读写子程序。
LED 灯
按照DP83640 的数据手册,LED应该为mode1 .但是绿灯却不闪烁。 在drv_eth.c 中添加了
HAL_ETH_WritePHYRegister(&EthHandle, PHY_PHYCTRL, 0x8019);
绿灯闪了, 这却是mode2!
DP83640 的初始化
Pdp_dep.c 中添加了
void PHY_PTPStart(void) {
RX_CFG_ITEMS rx_cfg_items = {
.ptpVersion = 0x02,
.ptpFirstByteMask = 0x00,
.ptpFirstByteData = 0x00,
.ipAddrData = 0,
.tsMinIFG = 0x00,
.srcIdHash = 0,
.ptpDomain = 0,
.tsSecLen = 0, //0; // DRs option
.rxTsNanoSecOffset = 0, //0x24; // DRs option
.rxTsSecondsOffset = 0, //0x21; // DRs option
};
PTPEnable(pEPL_HANDLE, TRUE);
PTPSetClockConfig(pEPL_HANDLE,0x0007,0x19,0,0);//OUT_CLK=10M 0x19
PTPSetTransmitConfig(pEPL_HANDLE, TXOPT_TS_EN | TXOPT_IPV4_EN, 2, 0, 0);
PTPSetReceiveConfig(pEPL_HANDLE, RXOPT_RX_TS_EN | RXOPT_RX_IPV4_EN, &rx_cfg_items);
};
PTPSetClockConfig 是我添加的,为了在OUT_CLK 输出 10M 时钟。
0x19=25,
250M/25=10MHz.
DP83640 读写的互斥保护
如果多个线程访问DP83640 寄存器的话,可能引起出错。需要使用操作系统的互斥机制
Mutex 。这是与OS 有关的,需要在epl_oai.c 程序中修改。
允许组播
在STM32f4xx_eth.c 中的ETH_MACDMAConfig程序中。改为:
macinit.MulticastFramesFilter = ETH_MULTICASTFRAMESFILTER_NONE;
这有点简单粗暴,如果改为过滤方式,网络通信更有效。
Sys_time.c 的修改
时钟都在DP83640 芯片中,所以PTP 协议主要修改这部分程序:
void getTime(TimeInternal *time)
{
NS_UINT32 seconds,nanoseconds;
// ETH_PTPTime_GetTime(×tamp);
PTPClockReadCurrent( pEPL_HANDLE, &seconds, &nanoseconds);
time->seconds = (int32_t)seconds;
time->nanoseconds = (int32_t) nanoseconds;
}
void setTime(const TimeInternal *time)
{
PTPClockSet(pEPL_HANDLE, time->seconds, time->nanoseconds);
DBG("resetting system clock to %d sec %d nsec\n", time->seconds, time->nanoseconds);
}
void updateTime(const TimeInternal *time)
{
struct ptptime_t timeoffset;
DBGV("updateTime: %d sec %d nsec\n", time->seconds, time->nanoseconds);
timeoffset.tv_sec = -time->seconds;
timeoffset.tv_nsec = -time->nanoseconds;
/* Coarse update method */
ETH_PTPTime_UpdateOffset(&timeoffset);
DBGV("updateTime: updated\n");
}
uint32_t getRand(uint32_t randMax)
{
return rand() % randMax;
}
bool adjFreq(int32_t adj1)
{
if (adj1!=0){
int64_t adj=adj1-16;
if (adj > ADJ_FREQ_MAX)
adj = ADJ_FREQ_MAX;
else if (adj < -ADJ_FREQ_MAX)
adj = -ADJ_FREQ_MAX;
int32_t seconds=(int)(adj /0x1000000000);
int32_t nanoseconds=(int)(adj %1000000000);
bool negAdj = TRUE;
if ( adj > 0)
negAdj = FALSE;
PTPClockStepAdjustment (pEPL_HANDLE,abs(seconds),abs(nanoseconds),negAdj);
}
return TRUE;
}
drv_hwtimer.c 的修改
平心而论,RT-Thread 的dvr_timer 的实现是比较不完整的。需要自己修改。实现要点
1 使用TIM1 External1 模式
2 Prescale 为 10,分频为 1MHz。
static void timer_init(struct rt_hwtimer_device *timer, rt_uint32_t state)
{
TIM_HandleTypeDef *tim = RT_NULL;
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;
tim->Init.Period = 10000-1;
tim->Init.Prescaler =9;//div 10 1MHz
tim->Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
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;
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_ETRF;
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_NONINVERTED;
sSlaveConfig.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;
sSlaveConfig.TriggerFilter = 0;
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);
LOG_D("%s init success", tim_device->name);
}
}
结果
目前代码已经调试出来了。从ppm 看,好像还有点问题。周期性地(每隔50秒左右)会突跳到100ppm 。不知道什么原因,也可能是MASTER 的问题。下面要进一步调试。
将Master PTP 的SYNC INTERVAL 改小,有明显改善。
#define DEFAULT_SYNC_INTERVAL -7/* -7 in 802.1AS */
MASTER 使用了STM32F429 MAC 1588 的程序。下一步要使用DP83640 的版本也测试一下。
版权声明:本文为CSDN博主「姚家湾」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yaojiawan/article/details/122434783
暂无评论