简单来说,使用DC的目的是为了使不同的从站在同一时刻产生sync0信号,为此,Etherlab及应用程序需要完成以下工作:
(1) 计算从站之间的传输延时
(2) 计算从站本地时钟和系统时钟的初始偏移量
(3) 设置DC周期时间
(4) 设置sync0启动时间
(5) 使能DC
(6) 时钟同步
在检测到总线拓扑发生变化后,发送一个广播写命令,写所有从站端口0的接收时间寄存器0x0900:
fsm_master.c
void ec_fsm_master_state_clear_addresses(
ec_fsm_master_t *fsm /**< Master state machine. */
)
{
......
EC_MASTER_DBG(master, 1, "Sending broadcast-write"
" to measure transmission delays on %s link.\n",
ec_device_names[fsm->dev_idx != 0]);
ec_datagram_bwr(datagram, 0x0900, 1);//广播写0x0900寄存器
ec_datagram_zero(datagram);
fsm->datagram->device_index = fsm->dev_idx;
fsm->retries = EC_FSM_RETRIES; //
fsm->state = ec_fsm_master_state_dc_measure_delays;
}
完成从站扫描后开始计算总线拓扑和传输延时:
fsm_master.c -> ec_fsm_master_state_scan_slave() -> ec_master_calc_dc(master)
/** Distributed-clocks calculations.
*/
void ec_master_calc_dc(
ec_master_t *master /**< EtherCAT master. */
)
{
// find DC reference clock
ec_master_find_dc_ref_clock(master); //把第一个支持DC的从站作为参考时钟
// calculate bus topology
ec_master_calc_topology(master); //根据数据链路状态寄存器0x0110-0x0111,计算总线拓扑
ec_master_calc_transmission_delays(master); //计算传输延时
}
读取从站本地系统时间,以及与参考时钟的偏差:
fsm_master.c
void ec_fsm_master_enter_write_system_times(
ec_fsm_master_t *fsm /**< Master state machine. */
)
{
ec_master_t *master = fsm->master;
if (master->has_app_time) {
while (fsm->slave < master->slaves + master->slave_count) {
if (!fsm->slave->base_dc_supported
|| !fsm->slave->has_dc_system_time) {
fsm->slave++;
continue;
}
EC_SLAVE_DBG(fsm->slave, 1, "Checking system time offset.\n");
// read DC system time (0x0910, 64 bit) //本地系统时间
// gap (64 bit) //gap是数据帧处理单元接收时间
// and time offset (0x0920, 64 bit) //本地时间和系统时间的偏差
ec_datagram_fprd(fsm->datagram, fsm->slave->station_address,
0x0910, 24);
......
}
} else {
......
}
根据主站时间、从站本地时间及偏差,计算从站时间和主站时间的初始偏移量:
fsm_master.c
void ec_fsm_master_state_dc_read_offset(
ec_fsm_master_t *fsm /**< Master state machine. */
)
{
......
system_time = EC_READ_U64(datagram->data); // 0x0910, 本地系统时间
old_offset = EC_READ_U64(datagram->data + 16); // 0x0920,本地时间和系统时间的偏差
jiffies_since_read = jiffies - datagram->jiffies_sent;
//计算从站本地时间和主站时间(master->app_time)的初始偏移量
if (slave->base_dc_range == EC_DC_32) {
new_offset = ec_fsm_master_dc_offset32(fsm,
system_time, old_offset, jiffies_since_read);
} else {
new_offset = ec_fsm_master_dc_offset64(fsm,
system_time, old_offset, jiffies_since_read);
}
// set DC system time offset and transmission delay
ec_datagram_fpwr(datagram, slave->station_address, 0x0920, 12);
EC_WRITE_U64(datagram->data, new_offset);
EC_WRITE_U32(datagram->data + 8, slave->transmission_delay); //0x0928-0x092B,从站和参考时钟的传输延时
fsm->datagram->device_index = slave->device_index;
fsm->retries = EC_FSM_RETRIES;
fsm->state = ec_fsm_master_state_dc_write_offset;
}
应用程序设置DC周期、偏移量、使能控制字:
// configure SYNC signals for this slave
ecrt_slave_config_dc(sc, 0x0300, PERIOD_NS, 440000, 0, 0); //使能sync0, 周期1ms,偏移量440us
将DC周期写入0x09A0寄存器:
fsm_slave_config.c
void ec_fsm_slave_config_enter_dc_cycle(
ec_fsm_slave_config_t *fsm /**< slave state machine */
)
{
......
if (config->dc_assign_activate) {
......
// set DC cycle times
ec_datagram_fpwr(datagram, slave->station_address, 0x09A0, 8);
EC_WRITE_U32(datagram->data, config->dc_sync[0].cycle_time);
EC_WRITE_U32(datagram->data + 4, config->dc_sync[1].cycle_time);
fsm->retries = EC_FSM_RETRIES;
fsm->state = ec_fsm_slave_config_state_dc_cycle;
}
......
}
除了将从站间的系统时间同步,还需要sync0产生的相位一致,从站间的sync0才能保持同步。
计算sync0启动时间在Fsm_slave_config.c文件的ec_fsm_slave_config_state_dc_sync_check()函数中,
每个从站配置时都会执行一次:
void ec_fsm_slave_config_state_dc_sync_check(
ec_fsm_slave_config_t *fsm /**< slave state machine */
)
{
......
// set DC start time
start_time = master->app_time + EC_DC_START_OFFSET; // now + X ns
// FIXME use slave's local system time here?
if (sync0->cycle_time) {
// find correct phase
if (master->has_app_time) {
u64 diff, start;
u32 remainder;
diff = start_time - master->app_start_time;
remainder = do_div(diff, sync0->cycle_time); //取余数
start = start_time +
sync0->cycle_time - remainder + sync0->shift_time; //相位补偿
......
start_time = start; //补偿相位以后的启动时间
} else {
EC_SLAVE_WARN(slave, "No application time supplied."
" Cyclic start time will not be in phase.\n");
}
}
ec_datagram_fpwr(datagram, slave->station_address, 0x0990, 8); //将sync0启动时间写入从站寄存器
EC_WRITE_U64(datagram->data, start_time);
fsm->retries = EC_FSM_RETRIES;
fsm->state = ec_fsm_slave_config_state_dc_start;
}
其中,启动时间往后延时EC_DC_START_OFFSET,是为了避免使能DC后,从站的本地系统时间已经超过sync0启动时间,sync0将不会产生.
启动时间往前推移remainder是为了不同从站的sync0启动时间相差整数倍DC周期时间。
写0x0981寄存器,激活SYNC0信号:
fsm_slave_config.c
void ec_fsm_slave_config_state_dc_start(
ec_fsm_slave_config_t *fsm /**< slave state machine */
)
{
......
EC_SLAVE_DBG(slave, 1, "Setting DC AssignActivate to 0x%04x.\n",
config->dc_assign_activate);
// assign sync unit to EtherCAT or PDI
ec_datagram_fpwr(datagram, slave->station_address, 0x0980, 2);
EC_WRITE_U16(datagram->data, config->dc_assign_activate);
fsm->retries = EC_FSM_RETRIES;
fsm->state = ec_fsm_slave_config_state_dc_assign;
}
从站间时钟同步:
ecrt_master_sync_slave_clocks(master);
主从时钟同步:
ecrt_master_sync_reference_clock(master); //将主站时间master->app_time写入参考时钟从站。