Quellcode durchsuchen

feat: add ec_htimer_update for dc sync with dc_ref_clock

Signed-off-by: sakumisu <1203593632@qq.com>
sakumisu vor 2 Wochen
Ursprung
Commit
943c74bd7b

+ 0 - 1
CMakeLists.txt

@@ -14,7 +14,6 @@ if(CONFIG_CHERRYECAT)
         ${CMAKE_CURRENT_LIST_DIR}/src/ec_mailbox.c
         ${CMAKE_CURRENT_LIST_DIR}/src/ec_master.c
         ${CMAKE_CURRENT_LIST_DIR}/src/ec_netdev.c
-        ${CMAKE_CURRENT_LIST_DIR}/src/ec_perf.c
         ${CMAKE_CURRENT_LIST_DIR}/src/ec_sii.c
         ${CMAKE_CURRENT_LIST_DIR}/src/ec_slave.c
         ${CMAKE_CURRENT_LIST_DIR}/src/ec_timestamp.c

+ 1 - 0
README.md

@@ -60,6 +60,7 @@ The pic shows dc jitter < 3us (hpm6800evk with flash_xip):
 ![ethercat](docs/assets/ethercat7.png)
 ![ethercat](docs/assets/ethercat8.png)
 ![ethercat](docs/assets/ethercat9.png)
+![ethercat](docs/assets/ethercat10.png)
 
 ## Tool
 

+ 1 - 0
README_zh.md

@@ -60,6 +60,7 @@ CherryECAT 是一个小而美的、高实时性、低抖动的 EtherCAT 主机
 ![ethercat](docs/assets/ethercat7.png)
 ![ethercat](docs/assets/ethercat8.png)
 ![ethercat](docs/assets/ethercat9.png)
+![ethercat](docs/assets/ethercat10.png)
 
 ## 工具
 

+ 0 - 1
SConscript

@@ -17,7 +17,6 @@ src += Glob('src/ec_foe.c')
 src += Glob('src/ec_mailbox.c')
 src += Glob('src/ec_master.c')
 src += Glob('src/ec_netdev.c')
-src += Glob('src/ec_perf.c')
 src += Glob('src/ec_sii.c')
 src += Glob('src/ec_slave.c')
 src += Glob('src/ec_timestamp.c')

+ 0 - 1
cherryecat_config_template.h

@@ -61,7 +61,6 @@
 #define CONFIG_EC_PER_PDO_MAX_PDO_ENTRIES 8
 #endif
 
-#define CONFIG_EC_PERF_ENABLE
 #define CONFIG_EC_CMD_ENABLE
 // #define CONFIG_EC_TIMESTAMP_CUSTOM
 // #define CONFIG_EC_PHY_CUSTOM

+ 0 - 1
demo/hpmicro/inc/ec_config.h

@@ -61,7 +61,6 @@
 #define CONFIG_EC_PER_PDO_MAX_PDO_ENTRIES 8
 #endif
 
-#define CONFIG_EC_PERF_ENABLE
 #define CONFIG_EC_CMD_ENABLE
 // #define CONFIG_EC_TIMESTAMP_CUSTOM
 // #define CONFIG_EC_PHY_CUSTOM

+ 4 - 1
demo/hpmicro/main.c

@@ -262,7 +262,10 @@ int ec_start(int argc, const char **argv)
         }
     }
 
-    ec_master_start(&g_ec_master, atoi(argv[1]));
+    g_ec_master.cycle_time = atoi(argv[1]) * 1000;       // cycle time in ns
+    g_ec_master.shift_time = atoi(argv[1]) * 1000 * 0.2; // 20% shift time in ns
+    g_ec_master.dc_sync_with_dc_ref_enable = true;       // enable DC sync with dc reference clock
+    ec_master_start(&g_ec_master);
     return 0;
 }
 CSH_CMD_EXPORT(ec_start, );

BIN
docs/assets/ethercat10.png


+ 1 - 3
docs/source/api.rst

@@ -41,7 +41,7 @@ ec_master_start
 .. code-block:: c
    :linenos:
 
-    int ec_master_start(ec_master_t *master, uint32_t period_us);
+    int ec_master_start(ec_master_t *master);
 
 .. list-table::
     :widths: 10 10
@@ -51,8 +51,6 @@ ec_master_start
       - description
     * - master
       - 主站对象指针
-    * - period_us
-      - 主站周期,单位微秒
 
 ec_master_stop
 ---------------------------

+ 47 - 10
docs/source/quickstart.rst

@@ -1,18 +1,55 @@
 快速入门
 ===========================
 
+本节主要介绍如何使用 CherryECAT。当前我们推荐在以下开发板中使用 CherryECAT。
 
-HPMicro Boards
-----------------------------
+- HPMicro 系列开发板
+- RT-Thread Titan、 etherkit、RuiQing 开发板
 
-RT-Thread Boards
-----------------------------
+ethercat 命令行
+-----------------
 
-Titan Board
-^^^^^^^^^^^^^^^^
+我们提供了一个简单的命令行工具 `ethercat` 用于测试 CherryECAT 功能。为了方便使用过 IgH 的 linux 用户过渡,格式部分参考的是 IgH。
 
-etherkit Board
-^^^^^^^^^^^^^^^^
+- ethercat master: 用于查看主站状态,数据统计等信息
+- ethercat rescan: 重新扫描总线
+- ethercat slaves: 查看从站信息,使用 `-p` 参数选择指定从站,使用 `-v` 参数查看详细信息
+- ethercat pdos: 查看 PDO 映射信息,使用 `-p` 参数选择指定从站
+- ethercat states: 请求从站状态转换, 使用 `-p` 参数选择指定从站,状态需要从 `ec_slave_state_t` 枚举中选择并以十六进制输入
+- ethercat coe_read: 通过 CoE 读取 SDO, 使用 `-p` 参数选择指定从站,后续参数依次为 index, subindex, 如果忽略 subindex 则使用 sdo complete 模式
+- ethercat coe_write: 通过 CoE 写入 SDO,使用 `-p` 参数选择指定从站,后续参数依次为 index, subindex, data,不使用 sdo complete 模式
+- ethercat foe_read: 通过 FoE 读取文件, 使用 `-p` 参数选择指定从站,后续参数依次为 filename, pwd 和 16进制数组,数组从低位到高位输入
+- ethercat foe_write: 通过 FoE 写入文件,使用 `-p` 参数选择指定从站,后续参数依次为 filename, pwd 和 16进制数组,数组从低位到高位输入
+- ethercat eoe_start: 启动 EoE 功能
+- ethercat pdo_read: 读取过程数据,使用 `-p` 参数选择指定从站
+- ethercat pdo_write: 写入过程数据,使用 `-p` 参数选择指定从站,后续参数依次为 offset 和 16进制数组,数组从低位到高位输入,offset 表示写入数据在 PDO 中的偏移位置
+- ethercat sii_read: 读取 SII 数据, 使用 `-p` 参数选择指定从站
+- ethercat sii_write: 写入 SII 数据, 使用 `-p` 参数选择指定从站
+- ethercat wc: 查看主站工作计数器
+- ethercat perf: 性能测试, 使用 `-s` 参数开始,使用 `-v` 参数查看测试结果,使用 `-d` 参数停止测试
+- ethercat timediff: 系统时间差监测,使用 `-s` 参数开始,使用 `-v` 参数查看测试结果,使用 `-d` 参数停止测试
 
-RuiQing Board
-^^^^^^^^^^^^^^^^
+典型使用流程
+-----------------
+
+ethercat 命令行中并没有提供启动周期性传输的命令,因为需要用户配置 slave 的一些参数后才能使用周期性传输功能。下面是一个典型的使用流程:
+
+- 调用 `ec_master_init` 初始化主站
+- 为每个 slave 配置 config 参数,包括 PDO 映射、DC 同步等, `ec_slave_config_t` 结构体定义如下:
+
+.. code-block:: c
+   :linenos:
+
+    ec_sync_info_t *sync;                           /**< Sync manager configuration. */
+    uint8_t sync_count;                             /**< Number of sync managers. */
+    ec_pdo_callback_t pdo_callback;                 /**< PDO process data callback. */
+    uint16_t dc_assign_activate;                    /**< dc assign control */
+    ec_sync_signal_t dc_sync[EC_SYNC_SIGNAL_COUNT]; /**< DC sync signals. */
+
+- 配置主站 master 相关参数,包括 `cycle_time`、 `shift_time`、`dc_sync_with_dc_ref_enable` 等
+
+`cycle_time` 代表主站定时器的周期,单位为纳秒。 `shift_time` 代表相对于 cycle_time 的滞后时间,单位为纳秒,通常设置为 20% 的 cycle_time。 `dc_sync_with_dc_ref_enable` 代表 DC 同步是与主站时钟同步还是和第一个带有 DC 的从站同步。
+
+- 最后调用 `ec_master_start` 启动周期性传输,如果从站没有扫描就调用,则会一直死等,直到所有从站扫描完成。
+
+- 对于时间敏感的 PDO 处理,可以在设置的 `pdo_callback` 中进行。对于不敏感的,异步的处理,则可以使用 `ec_master_get_slave_domain_*` 系列函数进行读写。

+ 18 - 5
include/ec_master.h

@@ -20,7 +20,6 @@
 #include "ec_def.h"
 #include "ec_osal.h"
 #include "ec_port.h"
-#include "ec_perf.h"
 #include "ec_timestamp.h"
 #include "ec_version.h"
 #include "ec_datagram.h"
@@ -109,14 +108,28 @@ typedef struct ec_master {
     uint64_t total_systime_diff;
     bool systime_diff_enable;
 
+    bool dc_sync_with_dc_ref_enable; /**< true: Sync the reference clock by dc ref clock, false: by master */
+    uint32_t cycle_time;             /**< Cycle time [ns]. */
+    int32_t shift_time;              /**< Shift time [ns]. */
+    int64_t dc_sync_integral;        /**< DC integral value. */
+
     uint64_t interval;
 
     ec_slave_t *slaves;
     uint32_t slave_count;
 
-#ifdef CONFIG_EC_PERF_ENABLE
-    ec_perf_t perf;
-#endif
+    bool perf_enable;
+    uint64_t last_start_time;
+    uint32_t min_period_ns;
+    uint32_t max_period_ns;
+    uint64_t total_period_ns;
+    uint64_t period_count;
+    uint32_t min_exec_ns;
+    uint32_t max_exec_ns;
+    uint64_t total_exec_ns;
+    uint64_t exec_count;
+    int32_t min_offset_ns;
+    int32_t max_offset_ns;
 
     ec_osal_mutex_t scan_lock;
     ec_osal_thread_t scan_thread;
@@ -133,7 +146,7 @@ typedef struct ec_master {
 
 int ec_master_init(ec_master_t *master, uint8_t master_index);
 void ec_master_deinit(ec_master_t *master);
-int ec_master_start(ec_master_t *master, uint32_t period_us);
+int ec_master_start(ec_master_t *master);
 int ec_master_stop(ec_master_t *master);
 int ec_master_queue_ext_datagram(ec_master_t *master, ec_datagram_t *datagram, bool wakep_poll, bool waiter);
 uint8_t *ec_master_get_slave_domain(ec_master_t *master, uint32_t slave_index);

+ 0 - 30
include/ec_perf.h

@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2025, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#ifndef EC_PERF_H
-#define EC_PERF_H
-
-typedef struct {
-    bool enable;    // Enable performance measurement
-    uint64_t count; // Current measurement count
-
-    uint64_t min_interval;    // Minimum interval
-    uint64_t max_interval;    // Maximum interval
-    int64_t min_jitter;       // Minimum jitter
-    int64_t max_jitter;       // Maximum jitter
-    uint64_t total_interval;  // Total interval time
-    int64_t total_jitter;     // Total jitter (for average calculation)
-
-    uint32_t ignore_count;      // Number of ignored measurements
-    uint64_t last_timestamp;    // Last interrupt timestamp
-    uint64_t expected_interval; // Expected interrupt interval
-} ec_perf_t;
-
-void ec_perf_init(ec_perf_t *perf, uint64_t expected_interval_us);
-void ec_perf_polling(ec_perf_t *perf);
-bool ec_perf_is_complete(ec_perf_t *perf);
-void ec_perf_print_statistics(ec_perf_t *perf);
-
-#endif

+ 1 - 1
include/ec_port.h

@@ -18,13 +18,13 @@ void ec_netdev_low_level_poll_link_state(ec_netdev_t *netdev);
 #endif
 uint8_t *ec_netdev_low_level_get_txbuf(ec_netdev_t *netdev);
 int ec_netdev_low_level_output(ec_netdev_t *netdev, uint32_t size);
-int ec_netdev_low_level_input(ec_netdev_t *netdev);
 
 void ec_mdio_low_level_write(struct chry_phy_device *phydev, uint16_t phy_addr, uint16_t regnum, uint16_t val);
 uint16_t ec_mdio_low_level_read(struct chry_phy_device *phydev, uint16_t phy_addr, uint16_t regnum);
 
 void ec_htimer_start(uint32_t us, ec_htimer_cb cb, void *arg);
 void ec_htimer_stop(void);
+void ec_htimer_update(uint32_t us);
 
 uint32_t ec_get_cpu_frequency(void);
 

+ 12 - 3
port/netdev_hpmicro.c

@@ -421,8 +421,9 @@ SDK_DECLARE_EXT_ISR_M(IRQn_ENET1, isr_enet1)
 #define EC_HTIMER_IRQ      BOARD_GPTMR_IRQ
 #define EC_HTIMER_CLK_NAME BOARD_GPTMR_CLK_NAME
 
-ec_htimer_cb g_ec_htimer_cb = NULL;
-void *g_ec_htimer_arg = NULL;
+static ec_htimer_cb g_ec_htimer_cb = NULL;
+static void *g_ec_htimer_arg = NULL;
+static uint32_t g_timer_reload_us_div = 0;
 
 void ec_htimer_isr(void)
 {
@@ -445,8 +446,9 @@ void ec_htimer_start(uint32_t us, ec_htimer_cb cb, void *arg)
 
     clock_add_to_group(EC_HTIMER_CLK_NAME, 0);
     gptmr_freq = clock_get_frequency(EC_HTIMER_CLK_NAME);
+    g_timer_reload_us_div = gptmr_freq / 1000000;
 
-    config.reload = gptmr_freq / 1000000 * us;
+    config.reload = g_timer_reload_us_div * us;
     gptmr_stop_counter(EC_HTIMER, EC_HTIMER_CH);
     gptmr_channel_config(EC_HTIMER, EC_HTIMER_CH, &config, false);
     gptmr_enable_irq(EC_HTIMER, GPTMR_CH_RLD_IRQ_MASK(EC_HTIMER_CH));
@@ -462,7 +464,14 @@ void ec_htimer_stop(void)
     intc_m_disable_irq(EC_HTIMER_IRQ);
 }
 
+EC_FAST_CODE_SECTION void ec_htimer_update(uint32_t us)
+{
+    gptmr_channel_config_update_reload(EC_HTIMER, EC_HTIMER_CH, us * g_timer_reload_us_div);
+}
+
+#ifndef CONFIG_EC_TIMESTAMP_CUSTOM
 uint32_t ec_get_cpu_frequency(void)
 {
     return clock_get_frequency(clock_cpu0);
 }
+#endif

+ 11 - 2
port/netdev_renesas.c

@@ -331,6 +331,7 @@ EC_FAST_CODE_SECTION int ec_netdev_low_level_input(ec_netdev_t *netdev)
 
 static ec_htimer_cb g_ec_htimer_cb = NULL;
 static void *g_ec_htimer_arg = NULL;
+static uint32_t g_timer_reload_us_div = 0;
 
 void timer0_esc_callback(timer_callback_args_t *p_args)
 {
@@ -349,13 +350,14 @@ void ec_htimer_start(uint32_t us, ec_htimer_cb cb, void *arg)
     timer_info_t time_info;
 
     R_GPT_InfoGet(&g_timer0_ctrl, &time_info);
-    uint32_t count = us * (time_info.clock_frequency / 1000000);
+
+    g_timer_reload_us_div = time_info.clock_frequency / 1000000;
 
     g_ec_htimer_cb = cb;
     g_ec_htimer_arg = arg;
 
     fsp_err |= R_GPT_CounterSet(&g_timer0_ctrl, 0);
-    fsp_err |= R_GPT_PeriodSet(&g_timer0_ctrl, count);
+    fsp_err |= R_GPT_PeriodSet(&g_timer0_ctrl, us * g_timer_reload_us_div);
     fsp_err |= R_GPT_Start(&g_timer0_ctrl);
 
     if (fsp_err != FSP_SUCCESS) {
@@ -368,6 +370,11 @@ void ec_htimer_stop(void)
     R_GPT_Stop(&g_timer0_ctrl);
 }
 
+EC_FAST_CODE_SECTION void ec_htimer_update(uint32_t us)
+{
+    R_GPT_PeriodSet(&g_timer0_ctrl, us * g_timer_reload_us_div);
+}
+
 void user_ether0_callback(ether_callback_args_t *p_args)
 {
     rt_interrupt_enter();
@@ -400,10 +407,12 @@ void user_ether0_callback(ether_callback_args_t *p_args)
     rt_interrupt_leave();
 }
 
+#ifndef CONFIG_EC_TIMESTAMP_CUSTOM
 uint32_t ec_get_cpu_frequency(void)
 {
     return SystemCoreClock;
 }
+#endif
 
 #if defined(SOC_SERIES_R9A07G0)
 volatile uint64_t mtu3_overflow_count = 0;

+ 46 - 11
src/ec_cmd.c

@@ -132,10 +132,9 @@ static void ec_master_cmd_show_help(void)
     EC_LOG_RAW("  sii_read -p [idx]                              Read SII\n");
     EC_LOG_RAW("  sii_write -p [idx]                             Write SII\n");
     EC_LOG_RAW("  wc                                             Show master working counter\n");
-#ifdef CONFIG_EC_PERF_ENABLE
-    EC_LOG_RAW("  perf -s <time>                                 Start performance test\n");
+    EC_LOG_RAW("  perf -s                                        Start performance test\n");
+    EC_LOG_RAW("  perf -d                                        Stop performance test\n");
     EC_LOG_RAW("  perf -v                                        Show performance statistics\n");
-#endif
     EC_LOG_RAW("  timediff -s                                    Enable system time diff monitor\n");
     EC_LOG_RAW("  timediff -d                                    Disable system time diff monitor\n");
     EC_LOG_RAW("  timediff -v                                    Show system time diff statistics\n");
@@ -1068,25 +1067,61 @@ int ethercat(int argc, const char **argv)
             ec_osal_leave_critical_section(flags);
         } else if (strcmp(argv[2], "-v") == 0) {
             for (uint32_t i = 0; i < 10; i++) {
-                EC_LOG_RAW("System Time Diff curr = %d, min = %d, max = %d, avg = %d ns\n",
+                EC_LOG_RAW("System Time Diff curr = %u, min = %u, max = %u, avg = %u ns\n",
                            global_cmd_master->curr_systime_diff,
                            global_cmd_master->min_systime_diff,
                            global_cmd_master->max_systime_diff,
-                           global_cmd_master->total_systime_diff / global_cmd_master->systime_diff_count);
+                           (unsigned int)(global_cmd_master->total_systime_diff / global_cmd_master->systime_diff_count));
                 ec_osal_msleep(1000);
             }
         }
         return 0;
-#ifdef CONFIG_EC_PERF_ENABLE
     } else if (strcmp(argv[1], "perf") == 0) {
-        if (argc >= 4 && strcmp(argv[2], "-s") == 0) {
-            ec_perf_init(&global_cmd_master->perf, atoi(argv[3]));
+        if (strcmp(argv[2], "-s") == 0) {
+            uintptr_t flags;
+
+            flags = ec_osal_enter_critical_section();
+            global_cmd_master->perf_enable = true;
+            global_cmd_master->min_period_ns = 0xffffffff;
+            global_cmd_master->max_period_ns = 0;
+            global_cmd_master->total_period_ns = 0;
+            global_cmd_master->period_count = 0;
+
+            global_cmd_master->min_exec_ns = 0xffffffff;
+            global_cmd_master->max_exec_ns = 0;
+            global_cmd_master->total_exec_ns = 0;
+            global_cmd_master->exec_count = 0;
+
+            global_cmd_master->min_offset_ns = 0xffffffff;
+            global_cmd_master->max_offset_ns = 0;
+            ec_osal_leave_critical_section(flags);
+            return 0;
+        } else if (strcmp(argv[2], "-d") == 0) {
+            uintptr_t flags;
+
+            flags = ec_osal_enter_critical_section();
+            global_cmd_master->perf_enable = false;
+            ec_osal_leave_critical_section(flags);
             return 0;
-        } else if (argc >= 3 && strcmp(argv[2], "-v") == 0) {
-            ec_perf_print_statistics(&global_cmd_master->perf);
+        } else if (strcmp(argv[2], "-v") == 0) {
+            for (uint32_t i = 0; i < 10; i++) {
+                EC_LOG_RAW("Period min = %10u, max = %10u, avg = %10u ns\n",
+                           global_cmd_master->min_period_ns,
+                           global_cmd_master->max_period_ns,
+                           (unsigned int)(global_cmd_master->total_period_ns / global_cmd_master->period_count));
+                EC_LOG_RAW("Exec   min = %10u, max = %10u, avg = %10u ns\n",
+                           global_cmd_master->min_exec_ns,
+                           global_cmd_master->max_exec_ns,
+                           (unsigned int)(global_cmd_master->total_exec_ns / global_cmd_master->exec_count));
+
+                EC_LOG_RAW("Offset min = %10d, max = %10d ns\n",
+                           global_cmd_master->min_offset_ns,
+                           global_cmd_master->max_offset_ns);
+
+                ec_osal_msleep(1000);
+            }
             return 0;
         }
-#endif
     } else {
     }
 

+ 91 - 26
src/ec_master.c

@@ -493,7 +493,7 @@ void ec_master_deinit(ec_master_t *master)
 {
 }
 
-int ec_master_start(ec_master_t *master, uint32_t period_us)
+int ec_master_start(ec_master_t *master)
 {
     ec_slave_t *slave;
     uint32_t bitlen;
@@ -501,6 +501,9 @@ int ec_master_start(ec_master_t *master, uint32_t period_us)
     ec_netdev_index_t netdev_idx;
     uint8_t sm_idx;
 
+    EC_ASSERT_MSG(master->cycle_time >= (40 * 1000), "Cycle time %u ns is too small. Minimum is 40000 ns.\n", master->cycle_time);
+    EC_ASSERT_MSG(master->cycle_time >= master->shift_time, "Shift time %u ns is larger than cycle time %u ns.\n", master->shift_time, master->cycle_time);
+
     if (master->active) {
         return 0;
     }
@@ -518,6 +521,7 @@ int ec_master_start(ec_master_t *master, uint32_t period_us)
     master->nonperiod_suspend = true;
     master->interval = 0;
     master->systime_diff_enable = false;
+    master->dc_sync_integral = 0;
 
     // wait for non-periodic thread to suspend
     while (master->nonperiod_suspend) {
@@ -625,7 +629,7 @@ int ec_master_start(ec_master_t *master, uint32_t period_us)
         ec_dlist_add_tail(&master->pdo_datagram_queue, &pdo_datagram->queue);
     }
 
-    ec_htimer_start(period_us, ec_master_period_process, master);
+    ec_htimer_start(master->cycle_time / 1000, ec_master_period_process, master);
 
     for (uint32_t i = 0; i < master->slave_count; i++) {
         master->slaves[i].requested_state = EC_SLAVE_STATE_OP;
@@ -812,37 +816,64 @@ uint32_t ec_master_get_slave_domain_isize(ec_master_t *master, uint32_t slave_in
     return slave->idata_size;
 }
 
+EC_FAST_CODE_SECTION static void ec_master_dc_sync_with_pi(ec_master_t *master, uint64_t dc_ref_time, int32_t *offsettime)
+{
+    int64_t delta;
+
+    delta = (dc_ref_time - master->shift_time) % master->cycle_time;
+    if (delta > (master->cycle_time / 2)) {
+        delta = delta - master->cycle_time;
+    }
+    if (delta > 0) {
+        master->dc_sync_integral++;
+    }
+    if (delta < 0) {
+        master->dc_sync_integral--;
+    }
+    *offsettime = -(delta / 100) - (master->dc_sync_integral / 20); // Kp = 0.1f, Ki = 0.05f
+}
+
 EC_FAST_CODE_SECTION static void ec_master_period_process(void *arg)
 {
     ec_master_t *master = (ec_master_t *)arg;
     ec_pdo_datagram_t *pdo_datagram, *n;
     ec_netdev_index_t netdev_idx;
+    uint64_t dc_ref_systime = 0;
+    int32_t offsettime = 0;
+    uint32_t delta = 0;
+    uint64_t start_time;
+    uint32_t period_ns;
+    uint32_t exec_ns;
 
     if (master->phase != EC_OPERATION) {
         return;
     }
 
-#ifdef CONFIG_EC_PERF_ENABLE
-    ec_perf_polling(&master->perf);
-#endif
-    if (master->systime_diff_enable) {
-        if (master->systime_diff_mon_datagram.state == EC_DATAGRAM_RECEIVED) {
-            master->curr_systime_diff = EC_READ_U32(master->systime_diff_mon_datagram.data) & 0x7fffffff;
+    start_time = ec_timestamp_get_time_ns();
 
-            if (master->curr_systime_diff < master->min_systime_diff) {
-                master->min_systime_diff = master->curr_systime_diff;
-            }
+    if (master->dc_ref_clock) {
+        if (master->dc_sync_with_dc_ref_enable) {
+            if (master->dc_all_sync_datagram.state == EC_DATAGRAM_RECEIVED) {
+                dc_ref_systime = EC_READ_U64(master->dc_all_sync_datagram.data) - master->dc_ref_clock->transmission_delay;
 
-            if (master->curr_systime_diff > master->max_systime_diff) {
-                master->max_systime_diff = master->curr_systime_diff;
-            }
-            master->systime_diff_count++;
-            master->total_systime_diff += master->curr_systime_diff;
-        }
+                ec_master_dc_sync_with_pi(master, dc_ref_systime, &offsettime);
 
-        if ((master->interval % 10) == 0) {
-            ec_datagram_zero(&master->systime_diff_mon_datagram);
-            ec_master_queue_datagram(master, &master->systime_diff_mon_datagram);
+                if (offsettime > 0) {
+                    delta = offsettime;
+                } else {
+                    delta = -offsettime;
+                }
+
+                if (delta > 0) {
+                    ec_htimer_update((master->cycle_time + offsettime) / 1000);
+                }
+            }
+        } else {
+            EC_WRITE_U32(master->dc_ref_sync_datagram.data, ec_timestamp_get_time_ns() & 0xffffffff);
+            if (master->dc_ref_clock->base_dc_range == EC_DC_64) {
+                EC_WRITE_U32(master->dc_ref_sync_datagram.data + 4, (uint32_t)(ec_timestamp_get_time_ns() >> 32));
+            }
+            ec_master_queue_datagram(master, &master->dc_ref_sync_datagram);
         }
     }
 
@@ -865,12 +896,6 @@ EC_FAST_CODE_SECTION static void ec_master_period_process(void *arg)
     }
 
     if (master->dc_ref_clock) {
-        EC_WRITE_U32(master->dc_ref_sync_datagram.data, ec_timestamp_get_time_ns() & 0xffffffff);
-        if (master->dc_ref_clock->base_dc_range == EC_DC_64) {
-            EC_WRITE_U32(master->dc_ref_sync_datagram.data + 4, (uint32_t)(ec_timestamp_get_time_ns() >> 32));
-        }
-        ec_master_queue_datagram(master, &master->dc_ref_sync_datagram);
-
         ec_datagram_zero(&master->dc_all_sync_datagram);
         ec_master_queue_datagram(master, &master->dc_all_sync_datagram);
     }
@@ -880,7 +905,47 @@ EC_FAST_CODE_SECTION static void ec_master_period_process(void *arg)
         ec_master_queue_datagram(master, &pdo_datagram->datagrams[EC_NETDEV_MAIN]);
     }
 
+    if (master->systime_diff_enable) {
+        if (master->systime_diff_mon_datagram.state == EC_DATAGRAM_RECEIVED) {
+            master->curr_systime_diff = EC_READ_U32(master->systime_diff_mon_datagram.data) & 0x7fffffff;
+
+            if (master->curr_systime_diff < master->min_systime_diff) {
+                master->min_systime_diff = master->curr_systime_diff;
+            }
+
+            if (master->curr_systime_diff > master->max_systime_diff) {
+                master->max_systime_diff = master->curr_systime_diff;
+            }
+            master->systime_diff_count++;
+            master->total_systime_diff += master->curr_systime_diff;
+        }
+
+        if ((master->interval % 10) == 0) {
+            ec_datagram_zero(&master->systime_diff_mon_datagram);
+            ec_master_queue_datagram(master, &master->systime_diff_mon_datagram);
+        }
+    }
+
     ec_master_send(master);
 
+    period_ns = start_time - master->last_start_time;
+    exec_ns = ec_timestamp_get_time_ns() - start_time;
+    master->last_start_time = start_time;
+
+    if (master->perf_enable) {
+        master->min_period_ns = MIN(period_ns, master->min_period_ns);
+        master->max_period_ns = MAX(period_ns, master->max_period_ns);
+        master->total_period_ns += period_ns;
+        master->period_count++;
+
+        master->min_exec_ns = MIN(exec_ns, master->min_exec_ns);
+        master->max_exec_ns = MAX(exec_ns, master->max_exec_ns);
+        master->total_exec_ns += exec_ns;
+        master->exec_count++;
+
+        master->min_offset_ns = MIN(offsettime, master->min_offset_ns);
+        master->max_offset_ns = MAX(offsettime, master->max_offset_ns);
+    }
+
     master->interval++;
 }

+ 0 - 80
src/ec_perf.c

@@ -1,80 +0,0 @@
-/*
- * Copyright (c) 2025, sakumisu
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-#include "ec_master.h"
-
-#ifdef CONFIG_EC_PERF_ENABLE
-void ec_perf_init(ec_perf_t *perf, uint64_t expected_interval_us)
-{
-    memset(perf, 0, sizeof(ec_perf_t));
-
-    perf->enable = true;
-    perf->min_interval = UINT64_MAX;
-    perf->max_interval = 0;
-    perf->min_jitter = INT64_MAX;
-    perf->max_jitter = INT64_MIN;
-    perf->expected_interval = expected_interval_us;
-    perf->ignore_count = 5;
-
-    EC_LOG_RAW("Perf initialized\n");
-    EC_LOG_RAW("Expected interval: %llu us\n", expected_interval_us);
-}
-
-EC_FAST_CODE_SECTION void ec_perf_polling(ec_perf_t *perf)
-{
-    uint64_t current_timestamp = jiffies;
-
-    if (!perf->enable) {
-        return;
-    }
-
-    if (perf->ignore_count > 0) {
-        perf->ignore_count--;
-        perf->last_timestamp = current_timestamp;
-        return;
-    }
-
-    uint64_t interval = current_timestamp - perf->last_timestamp;
-    int64_t jitter = (int64_t)interval - (int64_t)perf->expected_interval;
-
-    if (interval < perf->min_interval)
-        perf->min_interval = interval;
-    if (interval > perf->max_interval)
-        perf->max_interval = interval;
-    if (jitter < perf->min_jitter)
-        perf->min_jitter = jitter;
-    if (jitter > perf->max_jitter)
-        perf->max_jitter = jitter;
-
-    perf->total_interval += interval;
-    perf->total_jitter += jitter;
-
-    perf->count++;
-    perf->last_timestamp = current_timestamp;
-}
-
-void ec_perf_print_statistics(ec_perf_t *perf)
-{
-    if (perf->count == 0)
-        return;
-
-    double avg_interval = (double)perf->total_interval / perf->count;
-    double avg_jitter = (double)perf->total_jitter / perf->count;
-
-    EC_LOG_RAW("\n========= Perf Statistics =========\n");
-    EC_LOG_RAW("Measurements: %lld\n", perf->count);
-    EC_LOG_RAW("\nInterval Statistics (us):\n");
-    EC_LOG_RAW("  Average:    %.2f\n", avg_interval);
-    EC_LOG_RAW("  Minimum:    %llu\n", perf->min_interval);
-    EC_LOG_RAW("  Maximum:    %llu\n", perf->max_interval);
-
-    EC_LOG_RAW("\nJitter Statistics (us):\n");
-    EC_LOG_RAW("  Average:        %.2f\n", avg_jitter);
-    EC_LOG_RAW("  Minimum:        %lld\n", perf->min_jitter);
-    EC_LOG_RAW("  Maximum:        %lld\n", perf->max_jitter);
-
-    EC_LOG_RAW("===================================\n");
-}
-#endif