The IEEE1588/PTP protocol is very important for real-time communication. I plan to test it on STM32H750 art PI, but unfortunately RT-Thread OS does not support the IEEE1588/PTP protocol. There are many introductions to the IEEE1588 PTP protocol online, but there is very little information on its actual implementation on an MCU. So, I decided to port it myself, only to find out that it is not simple.
A few years ago, I remember conducting Ethernet layer time synchronization tests on STM32F207 using Ethernet MAC supporting IEEE588. I thought it would be more convenient since subsequent STM32F4 and H7 also support MAC IEEE1588. However, I found that not only does RT-Thread OS not support IEEE1588/PTP protocol, but even the eth.c in STM32F4_HAL library and STM32H7XX_HAL library clearly state that IEEE1588/PTP is currently not supported. Information on the internet still remains at STM32F107 with no new content.
There is a project for STM32F4 on Github (https://github.com/mpthompson/stm32_ptpd). However, it is implemented on Nucleo STM32F429. It uses CMSIS-RTOS, and modifying it to RT-Thread H7 involves significant changes. After trying for several days, I almost gave up.
Discovered this project in utter helplessness
It is based on the project of mpthompson, and the advantage of this project is
1 Directly access the underlying hardware registers of STM32H7's PTP bottom layer driver without using the STM32H7xxx_HAL library to avoid HAL compatibility issues.
2 The software is implemented in the form of a function library, avoiding incompatibility with OS threads.
3 Support lwIP 2.0, use UDP multicast (IGMP protocol) to implement PTPD protocol.
4 It seems that the tick timer and TIM2 timer adjustment have not been implemented. However, this also has its advantages, avoiding compatibility issues with RT-Thread OS.
PTP Protocol
To transplant the PTP protocol, you still need to understand the program structure of the PTP protocol and PTPD.
PTP Message
PTP protocol defines 4 types of multicast messages and management messages
- Synchronous Message (Sync)
Sent from the master device to the slave device (sent every one to two seconds), the message can contain a Sync transmission time tag, which can also be included in subsequent Follow UP messages.
- Follow-up message
Sending time for delivering Sync messages
- Delay Request Message (Delay_Req)
- Delayed Response Message (Delay_Resp)
General message and event message
There are two types of messages: general messages and event messages. Follow-up messages and delayed response messages belong to general messages. General messages do not process timestamps themselves; they can carry accurate sending or receiving time value information of event messages. Synchronous messages and delayed request messages belong to event messages. Event messages are time-sensitive messages that require precise timestamps.
Message sending and receiving process
- The master clock periodically sends out a sync message and records the precise time t1 when the sync message leaves the master clock;
(The sync message sent here is sent periodically, and may or may not carry sending time information, because even if it does, it can only be an estimated sending timestamp originTimeStamp)
- The grandmaster clock encapsulates the precise time t1 into the Follow_up message and sends it to the slave clock;
(Since the sync message cannot carry the precise message departure time, we will encapsulate the precise sending timestamp t1 of the sync message in the subsequent Follow_up message and send it to the slave clock)
- Synchronize the arrival time t2 of the sync message recorded by the slave clock to the precise time of arrival at the slave clock;
- Send a delay_req message from the clock and record the precise transmission time t3;
- The master clock records the precise arrival time t4 of the delay_req message at the master clock;
- The grandmaster clock sends a delay_resp message carrying precise timestamp information t4 to the slave clock;
Several Methods to Implement IEEE1588
The basic idea of IEEE1588 is for a master clock device to periodically send timestamp, and for slave clock devices to adjust their local clocks based on the timestamps received from the master clock, in order to achieve time synchronization.
Specifically, you can use the following three methods
1 Software Method
Insert time stamps in the frame queue of Ethernet. Obviously, due to the uncertainty of software execution, the number of frames in the queue, network load, and other factors, the time stamps may not be accurate.
2 Implementing Inside the MAC Controller
When the PTP frame arrives at the MAC controller, a timestamp is inserted by the hardware. Obviously, there is a time counter inside the MAC controller. And it can filter PTP frames. In MCUs like STM32, indicating support for IEEE1588 means that the MAC controller has this functionality.
3 Implementing in the Ethernet physical layer
For example, the DP83640 chip from TI company is said to be the most accurate way. However, software access to the Phy device's serial management interface is required. Another advantage of the DP83640 is that it can generate hardware clock signals. It can directly generate hardware synchronization signals.
STM32MCU from STM32F107 to STM32H7 all support IEEE1588. Many years ago, the author tested the synchronization performance of IEEE1588 at the Ethernet layer. In fact, IEEE1588/PTP consists of two parts, one is the underlying hardware, and the other is the PTP protocol, which is a UDP-based protocol.
For some reason, ST company's support for IEEE1588 technology is not good, and even the HAL library does not support PTP. Some people online also say that ST's MAC IEEE1588 is not useful, and some suggest using external solutions like DP83640. However, this involves hardware design. Too troublesome. We decided to try the solution based on the MAC controller.
The process of transplantation and encountered problems
Downloading and Compiling Source Code
The documentation for this project is very simple. You have to figure out the implementation by yourself based on the simple documentation.
- The project is implemented on the art pi platform. The first step is to create a new art_pi_factory project. 2. Removed the wifi and bluetooth sections. 3. Download the project source code, copy the ptp directory to the Application directory of the RT-Thread OS project. Copy the constants_dep.h, ptpd_dep.c, and ptpd_dep.h files from the port/stm2H7 directory to ptp/dep, where constants_dep.h overwrites the existing file. Copy the constants.h file from the port directory to the ptp directory. 4. Compile the project, there will be a bunch of undefined errors, I added #include “stm32h7.h” in ptpd.h, and also removed some unused statements. The compilation passed.
Write main program
It is regrettable that the project does not include the main program. The documentation only mentions:
Software initialization is achieved by calling ptpd_init(), calling PTPD timers every 1ms, for example
// 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;
}
}
Continuously call ptpd_task()
Every 100ms call igmp_timer(), I don't know what this means, there is no such function in the lwIP source code. Let's put it aside for now.
According to his instructions, I made the following changes
1 Added an updateTime function in timer.c:
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;
}
}
}
The main program is modified to:
#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(¤tTime);
// printf("seconds:%d\n",currentTime.seconds);
rt_thread_mdelay(1);
}
return RT_EOK;
}
After running, currentTime will be printed. This clock should be read from the PTP time counter of the MAC controller. This indicates that the underlying hardware interface is correct.
How to set as Master Clock?
Under the slave clock mode, nothing is sent, and Wireshark doesn't show anything, so I decided to change it to Master Clock to see if it can send out Sync and Follow frames. However, I found that there is no clear explanation on how to configure it as Master Clock mode on the network. I had to track the source code myself to figure it out.
1 Some people say that as long as you change slave-only to FALSE, it will be fine. The PTP parameters are set in ptp.c in ptp_init(). The parameters are configured in constans.h. I made the following modifications
/* 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
It seems that there is no response, through analyzing the code, it was found that the state needs to be set to PTP_MASTER, so the ptpStartup function was modified.
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;
}
And found that toState must be followed by doState.
Found that E2E is empty in the judgment of delayMechanism in the toState function. Then traced to the end of ptp_init
rtOpts.delayMechanism = DEFAULT_DELAY_MECHANISM;
The DEFAULT_DELAY_MECHANISM here is E2E, so it was changed to
rtOpts.delayMechanism =P2P;// DEFAULT_DELAY_MECHANISM;
Of course, you can also change DEFAULT_DELAY_MECHANISM;
Once again using Wireshark for monitoring, Sync and Follow frames appeared, approximately every 1 second.
Sync frame
follow frame
Modify the transmission cycle of Sync and Follow frames
SYNC messages are sent periodically by the clock in the MASTER state, with an interval of 1(second) × (2^portDS.logSyncInterval).
Defined through DEFAULT_SYNC_INTERVAL in constans.hz
#define DEFAULT_SYNC_INTERVAL 0 /* -7 in 802.1AS */
Above is 1 second, if changed to -1 it becomes 1 second, and so on.
Testing on ubuntu OS
Next, proceed with the testing of Ubuntu and RT-Thread Slave.
Here you need to use two tools
ethtool and linuxPTP
After compiling linuxPTP, the command line is ptp4l
The first issue encountered is that ethertools detects that my PC does not support hardware timestamping. My Ethernet controller chip is from Realtek, not Intel, so it seems it won't work. However, ptp4l seems to support software timestamping.
**ptp4l -i enp1s -m -S **
-S represents software time tags.
It seems that it cannot communicate with the Master Clock of RT-Thread when running! I don't know why these issues occur.
^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
Configuration of PTP4L
·To solve the problem, use the Wireshark software to compare the SYNC and Follow frames sent by the PTP4L master clock and RT-Thread, and it was found that the transportSpecific numbers were different. It was discovered that PTP4L needs to be configured. My configuration file is
[global]
priority1 248
priority2 248
clockClass 248
transportSpecific 0x08
slaveOnly 1
After the modification, ptp4l was able to send Delay Request frames, but RT-Thread did not respond with Delay_response.
RT-Thread default mode does not support multicast
Another issue has emerged, it was found that although RT-Thread can send multicast UDP, it cannot receive multicast UDP. It took almost a day to find this problem!
Found the problem, the solution is simple
I added a section in the function rt_stm32_eth_init in dvr_eth.c (Added by yao between Add By yao and 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;