简单来说,使用DC的目的是为了使不同的从站在同一时刻产生sync0信号,为此,Etherlab及应用程序需要完成以下工作:
(1) 计算从站之间的传输延时
(2) 计算从站本地时钟和系统时钟的初始偏移量
(3) 设置DC周期时间
(4) 设置sync0启动时间
(5) 使能DC
(6) 时钟同步
1、计算传输延时
在检测到总线拓扑发生变化后,发送一个广播写命令,写所有从站端口0的接收时间寄存器0x0900:
sm_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 { ...... }
根据主站时间、从站本地时间及偏差,计算从站时间和主站时间的初始偏移量:
sm_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; } ...... }
4、计算sync0启动时间
除了将从站间的系统时间同步,还需要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周期时间。
5、使能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; }
6、时钟同步
从站间时钟同步:
ecrt_master_sync_slave_clocks(master);
主从时钟同步:
ecrt_master_sync_reference_clock(master); //将主站时间master->app_time写入参考时钟从站。
同步报文: