No, this was my TIMESPEC2NS: #define TIMESPEC2NS(T) ((uint64_t) (T).tv_sec * NANOS_PER_SEC + (T).tv_nsec)
So, I’ll update it to: #define TIMESPEC2NSEPOCH2000(T) ((uint64_t) (((T).tv_sec - 946684800ULL) * 1000000000ULL) + (T).tv_nsec) I hadn’t realized I needed to adjust for EtherCAT time EPOCH at all. Thanks! Thanks! Thomas C. Bitsky Jr. | Lead Developer ADC | automateddesign.com <http://automateddesign.com/> P: 630-783-1150 F: 630-783-1159 M: 630-632-6679 Follow ADC news and media: Facebook <https://facebook.com/automateddesigncorp> | Twitter <https://twitter.com/ADCSportsLogic> | YouTube <https://www.youtube.com/user/ADCSportsLogic> On 2/19/16, 7:25 PM, "Matthieu Bec" <m...@gmto.org> wrote: >Hello Thomas, > >Just a wild guess, are you adjusting for EtherCAT time EPOCH? I have something >like: > >static inline uint64_t TIMESPEC2NSEPOCH2000(struct timespec ts) >{ > return (ts.tv_sec - 946684800ULL) * 1000000000ULL + ts.tv_nsec; >} > >Regards, >Matthieu > > > > > > > > >On 2/19/16, 4:08 PM, "etherlab-users on behalf of Thomas Bitsky Jr" ><etherlab-users-boun...@etherlab.org on behalf of t...@automateddesign.com> >wrote: > >Hello. > > >I’ve been using the EtherCAT master for years to great success, but I’m stuck >on a problem I can’t figure out that I think several people here are doing >successfully. I can’t implement distributed clocks in my application. > > >I am having the same problem on two systems I have up and running: > > > > > > >SYSTEM ONE: > > >EtherLAB Master 1.52, E1000E Driver, Scan Rate 4Khz, Ubuntu Server 14.04LTS, >RT-PREEMPT 3.12.50-rt68 > > >alias=0, position=0, device=EK1100 >alias=0, position=1, device=EL1104 >alias=0, position=2, device=EL2004 >alias=0, position=3, device=EL9510 >alias=0, position=4, device=EL3356 >alias=0, position=5, device=Kollmorgen AKD >alias=0, position=6, device=MTS LVDT > > > > > >SYSTEM TWO: > > >EtherLAB Master 1.52, E1000E Driver, Scan Rate 8Khz, Ubuntu Server 14.04LTS, >RT-PREEMPT 3.12.50-rt68 > > >alias=0, position=0, device=EK1100 >alias=0, position=1, device=EL3001 >alias=0, position=2, device=EL1104 >alias=0, position=3, device=EL1104 >alias=0, position=4, device=EL1104 >alias=0, position=5, device=EL2004 >alias=0, position=6, device=EL2004 >alias=0, position=7, device=EL9505 >alias=0, position=8, device=EL7041 >alias=0, position=9, device=EL7041 >alias=0, position=10, device=EL7041 >alias=0, position=11, device=EL7041 >alias=0, position=12, device=EL7041 >alias=0, position=13, device=EL7041 >alias=0, position=14, device=EK1110 >alias=1, position=0, device=SIGMA5-05 >alias=2, position=0, device=Yaskawa SIGMA5-05 >alias=3, position=0, device=Yaskawa SIGMA5-05 > > > > > >Both of the system are fully operational. However, for various reasons, I need >to implement distributed clocks on these systems. I’ve never been able to get >this to work. > > > > >What follows is the code I used for both systems to try this. I read through >examples on the mailing list, plus the examples, but I’m not seeing what I’m >doing wrong. The result is always the same: all the fieldbus cards go into >operation, but the servo > drives won’t because of “bad configuration.” Take out the distributed clock > code, and they work fine. I’m getting away with it for now, but I do need > better clock resolution. > > >The systems have an LRW domain, then a separate read domain and write domain >for the servo drive(s) for a total of three domains (LRW, read, write). The >yaskawa drives necessitate this. The scan rate is usually 4Khz, but I have >tried it at both 1Khz and > 8Khz and gotten the same results. Everything about the implementation is > fairly straight-forward. There’s just something fundamental about the DC > configuration that I’m not understanding. > > >Almost all the code below is taken right from the examples or the message >boards (thanks, everybody!). If anyone could tell me what I’m going wrong or >offer any insights, it’s greatly appreciated. For brevity, I tried to narrow >it down to relevant parts, > but please let me know any additional information or code I can provide. > > >Thank you in advance, >Tom > > > > >********************************************************** > > >// EtherCAT distributed clock variables > > >#define DC_FILTER_CNT 1024 >#define SYNC_MASTER_TO_REF 1 > > >static uint64_t dc_start_time_ns = 0LL; >static uint64_t dc_time_ns = 0; >static uint8_t dc_started = 0; >static int32_t dc_diff_ns = 0; >static int32_t prev_dc_diff_ns = 0; >static int64_t dc_diff_total_ns = 0LL; >static int64_t dc_delta_total_ns = 0LL; >static int dc_filter_idx = 0; >static int64_t dc_adjust_ns; >static int64_t system_time_base = 0LL; >static uint64_t wakeup_time = 0LL; >static uint64_t overruns = 0LL; > > > > > > >/** Get the time in ns for the current cpu, adjusted by system_time_base. > * > * \attention Rather than calling rt_get_time_ns() directly, all application > * time calls should use this method instead. > * > * \ret The time in ns. > */ >uint64_t system_time_ns(void) >{ >struct timespec ts; >clock_gettime(GLOBAL_CLOCK_TO_USE, &ts); >return TIMESPEC2NS(ts); >} > > > > > > >static >void sync_distributed_clocks(void) >{ > uint32_t ref_time = 0; > uint64_t prev_app_time = dc_time_ns; > > > dc_time_ns = system_time_ns(); > > > // set master time in nano-seconds > ecrt_master_application_time(master_, dc_time_ns); > > > // get reference clock time to synchronize master cycle > ecrt_master_reference_clock_time(master_, &ref_time); > dc_diff_ns = (uint32_t) prev_app_time - ref_time; > > > // call to sync slaves to ref slave > ecrt_master_sync_slave_clocks(master_); >} > > > > >/** Return the sign of a number > * > * ie -1 for -ve value, 0 for 0, +1 for +ve value > * > * \retval the sign of the value > */ >#define sign(val) \ > ({ typeof (val) _val = (val); \ > ((_val > 0) - (_val < 0)); }) > > > > >/*****************************************************************************/ > > >/** Update the master time based on ref slaves time diff > * > * called after the ethercat frame is sent to avoid time jitter in > * sync_distributed_clocks() > */ > >static unsigned int cycle_ns = 1000000; // 1 millisecond > > >void update_master_clock(void) >{ > > > // calc drift (via un-normalised time diff) > int32_t delta = dc_diff_ns - prev_dc_diff_ns; > prev_dc_diff_ns = dc_diff_ns; > > > // normalise the time diff > dc_diff_ns = > ((dc_diff_ns + (cycle_ns / 2)) % cycle_ns) - (cycle_ns / 2); > > > // only update if primary master > if (dc_started) { > > > // add to totals > dc_diff_total_ns += dc_diff_ns; > dc_delta_total_ns += delta; > dc_filter_idx++; > > > if (dc_filter_idx >= DC_FILTER_CNT) { > // add rounded delta average > dc_adjust_ns += > ((dc_delta_total_ns + (DC_FILTER_CNT / 2)) / DC_FILTER_CNT); > > > // and add adjustment for general diff (to pull in drift) > dc_adjust_ns += sign(dc_diff_total_ns / DC_FILTER_CNT); > > > // limit crazy numbers (0.1% of std cycle time) > if (dc_adjust_ns < -1000) { > dc_adjust_ns = -1000; > } > if (dc_adjust_ns > 1000) { > dc_adjust_ns = 1000; > } > > > // reset > dc_diff_total_ns = 0LL; > dc_delta_total_ns = 0LL; > dc_filter_idx = 0; > } > > > // add cycles adjustment to time base (including a spot adjustment) > system_time_base += dc_adjust_ns + sign(dc_diff_ns); > } > else { > dc_started = (dc_diff_ns != 0); > > > if (dc_started) >{ > // record the time of this initial cycle > dc_start_time_ns = dc_time_ns; > } > } > > >} > > > > > > >struct timespec dcTime_; > > > > > >int >ecatMain_process(void* lp) >{ > >ecrt_master_receive(master_); > >clock_gettime(CLOCK_REALTIME, &dcTime_); >ecrt_master_application_time(master_, TIMESPEC2NS(dcTime_)); > > >ecrt_master_sync_reference_clock(master_); >ecrt_master_sync_slave_clocks(master_); > > > > >ecrt_domain_process(lrwDomainMgr_.domain); >ecrt_domain_process(noLrwWriteDomainMgr_.domain); >ecrt_domain_process(noLrwReadDomainMgr_.domain); > > >… // handle my business > > >// write application time to master >clock_gettime(CLOCK_REALTIME, &dcTime_); >ecrt_master_application_time(master_, TIMESPEC2NS(dcTime_)); > > > > >if (sync_ref_counter_) >{ >sync_ref_counter_--; >} >else >{ >sync_ref_counter_ = 1; // sync every cycle >ecrt_master_sync_reference_clock(master_); >} > > >// send process data >ecrt_domain_queue(lrwDomainMgr_.domain); > >ecrt_domain_queue(noLrwWriteDomainMgr_.domain); >ecrt_domain_queue(noLrwReadDomainMgr_.domain); > > > >// sync distributed clock just before master_send to set >// most accurate master clock time >sync_distributed_clocks(); > > >// send EtherCAT data >ecrt_master_send(master_); > > >// update the master clock >// Note: called after ecrt_master_send() to reduce time >// jitter in the sync_distributed_clocks() call >update_master_clock(); > >return 1; >} > > > > > > > >int >ecatMain_start(void* lp) >{ > >// >// domain regs must end in a null entry >// > >lrwDomainMgr_.domainRegs = realloc( >lrwDomainMgr_.domainRegs, >sizeof(ec_pdo_entry_reg_t) * (lrwDomainMgr_.size + 1) ); > >memset( >&(lrwDomainMgr_.domainRegs[lrwDomainMgr_.size]), >0, >sizeof(ec_pdo_entry_reg_t) ); > > >noLrwReadDomainMgr_.domainRegs = realloc( >noLrwReadDomainMgr_.domainRegs, >sizeof(ec_pdo_entry_reg_t) * (noLrwReadDomainMgr_.size + 1) ); > >memset( >&(noLrwReadDomainMgr_.domainRegs[noLrwReadDomainMgr_.size]), >0, >sizeof(ec_pdo_entry_reg_t) ); > > > > >noLrwWriteDomainMgr_.domainRegs = realloc( >noLrwWriteDomainMgr_.domainRegs, >sizeof(ec_pdo_entry_reg_t) * (noLrwWriteDomainMgr_.size + 1) ); > >memset( >&(noLrwWriteDomainMgr_.domainRegs[noLrwWriteDomainMgr_.size]), >0, >sizeof(ec_pdo_entry_reg_t) ); > > > > > > > > >// >// NOTE: the Output Domain must be registered with >// ecrt_domain_reg_pdo_entry_list before the Input Domain otherwise you >// will not have any data exchanged even though the drive goes into OP >// mode. >// > > >PRINT("\nAttempting to register PDOs on WRITE ONLY domain...\n"); >if (ecrt_domain_reg_pdo_entry_list( >noLrwWriteDomainMgr_.domain, noLrwWriteDomainMgr_.domainRegs)) >{ >PRINT("WRITE ONLY PDO entry registration failed!\n"); >return FALSE; > } > >PRINT("\nAttempting to register PDOs on READ ONLY domain...\n"); >if (ecrt_domain_reg_pdo_entry_list( >noLrwReadDomainMgr_.domain, noLrwReadDomainMgr_.domainRegs)) >{ >PRINT("READ ONLY PDO entry registration failed!\n"); >return FALSE; > } > > > > >// >// And now we register the bi-directional domain. >// >PRINT("\nAttempting to register PDOs on LRW domain...\n"); >if (ecrt_domain_reg_pdo_entry_list( >lrwDomainMgr_.domain, lrwDomainMgr_.domainRegs)) >{ >PRINT("LRW PDO entry registration failed!\n"); >return FALSE; > } > > > >/* >* Finishes the configuration phase and prepares for cyclic operation. >* This function tells the master that the configuration phase >* is finished and the realtime operation will begin. >* The function allocates internal memory for the domains and calculates >* the logical FMMU addresses for domain members. >* It tells the master state machine that the bus configuration is >* now to be applied >*/ >PRINT("\nAttempting to activate ECAT master...\n"); >if (ecrt_master_activate(master_)) >{ >PRINT( >"%s Failed to activate master!\n", >__FUNCTION__ ); >return FALSE; >} > >/* >* Returns the domain's process data. >*/ >PRINT( "%s getting LRW process data from master.\n", __FUNCTION__ ); >if (!(lrwDomainMgr_.processData >= ecrt_domain_data(lrwDomainMgr_.domain))) >{ >PRINT( >"%s set ecProcessData -- domain data is NULL!\n", >__FUNCTION__ ); > >return FALSE; > } > > >if (!(noLrwReadDomainMgr_.processData >= ecrt_domain_data(noLrwReadDomainMgr_.domain))) >{ >PRINT( >"%s set read ProcessData -- domain data is NULL!\n", >__FUNCTION__ ); > >return FALSE; > } > >if (!(noLrwWriteDomainMgr_.processData >= ecrt_domain_data(noLrwWriteDomainMgr_.domain))) >{ >PRINT( >"%s set write ProcessData -- domain data is NULL!\n", >__FUNCTION__ ); > >return FALSE; > } > > > >… // blah blah blah > > > >doScan_ = TRUE; > > > >PRINT( "%s completed successfully.\n", __FUNCTION__ ); >return TRUE; >} > > > > > > _______________________________________________ etherlab-users mailing list etherlab-users@etherlab.org http://lists.etherlab.org/mailman/listinfo/etherlab-users