EtherCAT使用分布式時鐘(DC)達到精準同步
===
###### tags: `OverSampling`,`EtherCAT`,`Driver`,`DC`
[toc]
## 文章目的
1. 對EtherCAT的分布式時鐘技術(Distributed Clocks, DC)有基礎了解
2. 了解使用 EtherCAT DC 設計的設備如何使用戶受益。
## 何謂分布式時鐘
+ DC意指在在EtherCAT系統的同步的邏輯網路中,分布的本地時鐘
+ 使用DC於EtherCAT,可以使同步誤差低於100ns
+ 控制器使用一般的網卡即可
## 為什麼要同步網路?
## 功能與原裡
EtherCAT 利用幾個重要的操作原則,允許高效且優雅地實施DC方法
+ 操作"One the Fly"(即時, 当进程正在运行时这个改变就生效了)
+ 在硬體中處理通訊協定
+ 在給定的拓樸中固定住所有幀(frame)的幀率
+ 鎖存slave端口以及邏輯處理單元的接收時間
+ 易於分配時間和偏移量的指令集
+ DC單元內置於EtherCAT Slave Controller(ESC), 大大促進了硬體功能
+ DC單元包含外部接口
## 即時乙太網之功能與原理

+ Process Data(PD)被即時地提取與插入
+ PD的編譯方法在各個循環中可以不一樣。例如: 超短周期的Cycle用來控制軸,長些的控制I/O更新
+ 外加的異步、事件觸發通訊功能
+ 最多連接65535台設備的EtherCAT網路
## EtherCAT系統上的幀處理順序

## 重要記憶點
+ EtherCAT中只有Master能夠生成frame
+ Slave只能修改frame
+ Frame並不會主動被發送到指定節點,而是遍歷整個網路上的所有節點,就算是Frame中沒有提及的節點也是。
+ 一個frame就可以服務整個網路
+ 如果Frame大小超過1500bytes,可以透過**連續(back-to-back)**發送**多幀(Multiple frames)** 達成目標
## 分布式時鐘單元(Distributed Clocks Unit)

### DC的特色
+ 系統時間的定義
+ 開始於2000/01/01
+ 基礎時間單位為1ns
+ 以64位表示(可表示超過500年)
```4.294秒 * 65535 * 65535(64Bits) / 60(秒) / 60(分) / 24(時) / 365.25(日) = 584.39 年```
+ 低 32 位的數字換算到時間約4.29秒會溢位(通常足以用於通信和時間戳)
+ 參考時鐘的定義
+ 其中一個EtherCAT Slave會被用來當作參考時鐘
+ 參考時鐘會循環地分配其時鐘
+ 參考時鐘可從“全局”參考時鐘調整 – IEEE 1588
### 測量傳播延遲 (Propagation Delay Measurement)

> EtherCAT 節點測量離開和返回幀之間的時間差
0. ESC繼存器:
```
Registers:
– Receive Time Port 0 (ADO: 0x0900:0x0903)
– Receive Time Port 1 (ADO: 0x0904:0x0907)
– Receive Time Port 2 (ADO: 0x0908:0x090B)
– Receive Time Port 3 (ADO: 0x090C:0x090F)
– System Time Delay (ADO: 0x0928:0x092B)
```
1. 執行寫訪問至Receive Time Port 0 以激活鎖存器(latch)
+ 鎖存 SOF(幀開始, Start of Frame) 的本地時間
+ 在EOF(幀結束, End of Frame)時,SOF 時間被複製到Receive Time 端口 X
2. Receive Time Port X 以本地時鐘單位表示(受控的)
3. 所有frame的SOF時間都被內部鎖存於所有的Port(端口)上
4. Master讀取所有時間戳並計算相對於拓樸的延遲時間
5. 個別的延遲時間被寫入於System Time Delay繼存器中

> EtherCAT 節點測量離開和返回幀之間的時間差

> 參考時鐘與各DC Slave在端口中之時間差就是傳播延遲,謂之System Time Delay
> 該值是由Master分配,並儲存在Salve上,以便之後可以用於偏移補償的計算
## 將參考時鐘綁定到 RTC(Real-time Clock)
+ 繼存器
```
- System Time Offset
(ADO: 0x0920:0x927, small systems 0x0920:0x0923)
```
+ Master RTC和參考時鐘的時間差由Master計算
+ 該時間差只會被寫在參考時鐘的System Time Offset繼存器中

> Master將參考時鐘設置為RTC (或其他來源)
## 偏移時間補償(Offset Compensation)
+ 繼存器
```
– System Time Offset
(ADO: 0x0920:0x927, small systems 0x0920:0x0923)
```
+ Master會計算參考時鐘與所有slave的裝置時鐘的時間差,謂之偏移時間
+ 該邊偏移時間由master計算
+ 各slave可以透過本地時間加上偏移量,來計算本地複本的系統時間:
$$
t_{Local Copy Of System Time} = t_{Local Time} + t_{Offset}
$$$$
t_{系統時間的本地副本} = t_{本地時間} + t_{偏移量}
$$
### 設置個別Slave為參考時鐘

> 偏移時間統一由Master計算與發佈並寫入至各Slave,為的是調整所有裝置到一個相同的確切時間
### 偏移補償 – 分布式時鐘控制
+ RMW(read – multiple write)命令允許Master讀取參考時鐘的系統時間並將其寫入單個frame內的所有使用相同幀路由的slave時鐘,因此傳播延遲與初始測量相同。
+ DC控制
+ 寫訪問System Time以用本地時間比較接收到的時間,公式如下:
$$
\Delta t = (t_{本地時間} + t_{偏移時間} - t_{傳播延遲}) - t_{收到的系統時間}
$$
+ 如果 **∆t > 0**,將本地時鐘**減速**(每個刻度視為更短的時間)
+ 如果 **∆t < 0**,將本地時鐘**加速**(每個刻度視為更長的時間)
+ Master命令參考時鐘偶爾(定期)將其本地時間分配給所有節點
+ 發出RMW命令的頻率,決定了系統時鐘裡允許的漂移量
+ 基於以下原因,我們不需要有無抖動的frame來擁有一個無抖動的系統!
+ 每當RMW命令被呼叫時,RMW命令會去(重新)分配參考時鐘的時間
+ 傳播延遲時間不變
+ 因此,Master不需要是特殊的網卡,即使對於要求同步的應用程序,主設備也可以是軟件堆棧。

> 兩個獨立設備的長期範圍視圖(之間有300個節點,120m電纜長度)
## EtherCAT DC 的示例特性
+ EtherCAT Master與Slave間的時鐘同步
+ 每個Slave同步產生輸出信號 (同步信號)
+ 對輸入信號產生精確的時間戳 (鎖存訊號)
+ 產生對本地微處理器的同步中斷 (IRQ訊號, Interrupt Request)
### 基於指定時間的操作:同步 0/1
+ EtherCAT Slave Controller(ESC)中的分佈式時鐘單元通常具有2個可以觸發時間控制的引腳。 SYNC0 和 SYNC1。

+ 在下述情況下,ESC中的比較單元將處於激活狀態:如果本地分佈式時鐘時間與用戶定義的致能時間匹配,則 ESC 觸發相關的同步引腳。
+ 這個行為可以被設為單次執行或循環執行的,以及是否需要確認
### 對外部信號的反應: 閂鎖 0/1
+ 如果相應地配置了ESC, 它可以儲存外部事件發生當時的本地時間。意即它可以使用(Capture)捕獲單元將其立即放入緩衝區中。

+ 可以被設定為上緣或下緣觸發、單一事件觸發或連續鎖存
+ 此類外部事件的包含
+ 邊緣觸發在ESC(鎖存器 0/1)的專用引腳上
+ EtherCAT 幀的到達
+ EtherCAT 幀的結束
+ 連接的微控制器的通信
以及各種其他項目

> 閂鎖和同步使用示例
### 連接到外部邏輯: IRQ
+ ESC不只可以被用作獨立設播,它也擁有可以溝通其他電子單元的接口,例如微控制器、驅動電路。
+ 透過IRQ接口的通信也可以通過分布式時鐘去控制以確保同步、高精準度的輸入參數採樣,或基於多個**基礎掃描速率**的循環中斷
+ 這種用途的示例包括連接到控制動力驅動的微處理器、電子軸編碼器分析儀、或用於狀態監測的數據採集從站。

> IRQ與微控制器一起使用的示例: 過採樣(Oversampling)
## 總結
1. 無需使用特殊的現場總線卡即可在 EtherCAT 從站和主站之間實現緊密的時鐘同步
2. 設備的分布式時鐘是由 EtherCAT 獨特的通信原理和ESC的內置特性共同實現的。
3. 一些常見的內置行為包含:
+ 同步讀取輸入信號
+ 輸入信號的精確時間戳(鎖存信號)
+ 生成本地微處理器的同步中斷(IRQ信號)
---
:::info
以下章節來自Backoff文件
並實作於5051T
:::
## 各裝置ESC繼存器中儲存之相關參數

### 從ESC Memory讀到DC System Time

---

## 抓抓封包
1. 執行寫訪問至Receive Time Port 0 以激活鎖存器(latch
2. 主站应在测量传输延时和偏移补偿
3. 在独立的数据帧中发送很多ARMW/FRMW报文,完成分布式时钟初始化。
4. 随后在周期运行阶段,可以随着过程数据周期性发送ARMV/FRAM报文命令读取参考时钟,动态补偿时钟漂移。主站使用命令FRMW读取第一个DC从站的时间并写入到后续DC从站,达到时间同步的目的。(发送时钟同步数据帧的周期时间必须满足从机时钟漂移量小于控制应用所规定的最大漂移量的限制。)
## SOEM中關於DC的設定code
``` c=
/// configure DC 在AmaxEcatMasterReconnectSlaves(Rescan)階段
for (slave = 1; slave <= *(FdoData->ECatContext.slavecount); slave++) {
ecx_dcsync0(&FdoData->ECatContext, slave, FALSE, 0, 0);
}
ecx_configdc(&FdoData->ECatContext);
/**
* Set DC of slave to fire sync0 at CyclTime interval with CyclShift offset.
*
* @param[in] context = context struct
* @param [in] slave Slave number.
* @param [in] act TRUE = active, FALSE = deactivated
* @param [in] CyclTime Cycltime in ns.
* @param [in] CyclShift CyclShift in ns.
*/
void ecx_dcsync0(ecx_contextt *context, uint16 slave, boolean act, uint32 CyclTime, uint32 CyclShift)
{
uint8 h, RA;
uint16 slaveh;
int64 t, t1;
int32 tc;
slaveh = context->slavelist[slave].configadr;
RA = 0;
/* stop cyclic operation, ready for next trigger */
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSYNCACT, sizeof(RA), &RA, EC_TIMEOUTRET);
if (act)
{
RA = 1 + 2; /* act cyclic operation and sync0, sync1 deactivated */
}
h = 0;
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCCUC, sizeof(h), &h, EC_TIMEOUTRET); /* write access to ethercat */
t1 = 0;
(void)ecx_FPRD(context->port, slaveh, ECT_REG_DCSYSTIME, sizeof(t1), &t1, EC_TIMEOUTRET); /* read local time of slave */
t1 = etohll(t1);
/* Calculate first trigger time, always a whole multiple of CyclTime rounded up plus the shifttime (can be negative)
This insures best sychronisation between slaves, slaves with the same CyclTime
will sync at the same moment (you can use CyclShift to shift the sync) */
if (CyclTime > 0)
{
t = ((t1 + SyncDelay) / CyclTime) * CyclTime + CyclTime + CyclShift;
}
else
{
t = t1 + SyncDelay + CyclShift;
/* first trigger at T1 + CyclTime + SyncDelay + CyclShift in ns */
}
t = htoell(t);
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSTART0, sizeof(t), &t, EC_TIMEOUTRET); /* SYNC0 start time */
tc = htoel(CyclTime);
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCCYCLE0, sizeof(tc), &tc, EC_TIMEOUTRET); /* SYNC0 cycle time */
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSYNCACT, sizeof(RA), &RA, EC_TIMEOUTRET); /* activate cyclic operation */
}
/**
* Set DC of slave to fire sync0 and sync1 at CyclTime interval with CyclShift offset.
*
* @param[in] context = context struct
* @param [in] slave Slave number.
* @param [in] act TRUE = active, FALSE = deactivated
* @param [in] CyclTime0 Cycltime SYNC0 in ns.
* @param [in] CyclTime1 Cycltime SYNC1 in ns. This time is a delta time in relation to
the SYNC0 fire. If CylcTime1 = 0 then SYNC1 fires a the same time
as SYNC0.
* @param [in] CyclShift CyclShift in ns.
*/
void ecx_dcsync01(ecx_contextt *context, uint16 slave, boolean act, uint32 CyclTime0, uint32 CyclTime1, uint32 CyclShift)
{
uint8 h, RA;
uint16 slaveh;
int64 t, t1;
int32 tc;
uint32 TrueCyclTime;
/* Sync1 can be used as a multiple of Sync0, use true cycle time */
TrueCyclTime = ((CyclTime1 / CyclTime0) + 1) * CyclTime0;
slaveh = context->slavelist[slave].configadr;
RA = 0;
/* stop cyclic operation, ready for next trigger */
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSYNCACT, sizeof(RA), &RA, EC_TIMEOUTRET);
if (act)
{
RA = 1 + 2 + 4; /* act cyclic operation and sync0 + sync1 */
}
h = 0;
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCCUC, sizeof(h), &h, EC_TIMEOUTRET); /* write access to ethercat */
t1 = 0;
(void)ecx_FPRD(context->port, slaveh, ECT_REG_DCSYSTIME, sizeof(t1), &t1, EC_TIMEOUTRET); /* read local time of slave */
t1 = etohll(t1);
/* Calculate first trigger time, always a whole multiple of TrueCyclTime rounded up
plus the shifttime (can be negative)
This insures best sychronisation between slaves, slaves with the same CyclTime
will sync at the same moment (you can use CyclShift to shift the sync) */
if (CyclTime0 > 0)
{
t = ((t1 + SyncDelay) / TrueCyclTime) * TrueCyclTime + TrueCyclTime + CyclShift;
}
else
{
t = t1 + SyncDelay + CyclShift;
/* first trigger at T1 + CyclTime + SyncDelay + CyclShift in ns */
}
t = htoell(t);
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSTART0, sizeof(t), &t, EC_TIMEOUTRET); /* SYNC0 start time */
tc = htoel(CyclTime0);
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCCYCLE0, sizeof(tc), &tc, EC_TIMEOUTRET); /* SYNC0 cycle time */
tc = htoel(CyclTime1);
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCCYCLE1, sizeof(tc), &tc, EC_TIMEOUTRET); /* SYNC1 cycle time */
(void)ecx_FPWR(context->port, slaveh, ECT_REG_DCSYNCACT, sizeof(RA), &RA, EC_TIMEOUTRET); /* activate cyclic operation */
}
```
## 參考
https://blog.csdn.net/pwl999/article/details/114329076
https://blog.csdn.net/Terrys0518/article/details/106312820