[etherlab-users] Distributed Clocks
Bilko AS, Oguz Dilmac
odilmac at bilko-automation.com
Mon Feb 22 14:23:12 CET 2016
Hi,
I didn't check the codes. But is it OK to have the first device in the
bus a EK1100?
I can't find it now, but a message in this list was telling me EK1100
does not support DC fully.
The fist node on the bus should be a drive or some other device which
can support DC fully.
Best regards,
Oguz.
22.2.2016 11:52 tarihinde Richard Hacker yazdı:
> I do not have the patience to look into your code in detail to find
> out what you're doing wrong, but here is an excerpt of my code I use
> in etherlab Simulink Coder (ecrt_support.c):
>
> void
> ecs_send()
> {
> //...
>
> list_for_each(master, &ecat_data.master_list, list) {
> list_for_each(domain, &master->domain_list, list) {
> // ...
>
> ecrt_domain_queue(domain->handle);
> }
>
> if (/* send frames */) {
> struct timespec tp;
>
> clock_gettime(CLOCK_MONOTONIC, &tp);
> ecrt_master_application_time(master->handle,
> EC_TIMESPEC2NANO(tp));
>
> if (/* sync_clocks */)) {
> ecrt_master_sync_reference_clock(master->handle);
> //...
> }
>
> ecrt_master_sync_slave_clocks(master->handle);
> ecrt_master_send(master->handle);
> }
> }
> }
>
> void
> ecs_receive()
> {
> //...
>
> list_for_each(master, &ecat_data.master_list, list) {
>
> if (/* receive frames */) {
> ecrt_master_receive(master->handle);
> }
>
> list_for_each(domain, &master->domain_list, list) {
>
> ecrt_domain_process(domain->handle);
> ecrt_domain_state(domain->handle, &domain->state);
>
> }
> }
> }
>
> void
> calculate()
> {
> while (forever) {
> ecs_receive();
> // do calculations
> ecs_send();
> sleep();
> }
> }
>
> Some of this is pseudo code, but I trust in your programming
> capabilities to be able to adapt this to your code ;)
>
> It is very important to call clock_gettime() and
> ecrt_master_application_time() as close to ecrt_master_send() as
> possible.
>
> As you can see, it is not necessary to call
> ecrt_master_sync_reference_clock() with every ecrt_master_send(). In
> fact, ~100Hz of syncing should be enough, provided that your call to
> clock_gettime() is accurate. On the other hand, if clock_gettime() is
> inaccurate, you're in trouble when using DC anyway!
>
> - Richard
>
> Am 2016-02-22 um 04:23 schrieb Thomas Bitsky Jr:
>> Ok, after following all your advice, plus the clock adjustment from
>> Mattieu, I’m pretty close. I enable the DC on the drive, get everything
>> into op, the domains all come up and it would appear that the clocks are
>> syncing. Except for one thing:
>>
>> [10248.633619] EtherCAT 0: Domain 0: Working counter changed to 1/17.
>> [10249.535776] EtherCAT WARNING 0: 3989 datagrams UNMATCHED!
>> [10249.539861] EtherCAT WARNING: Datagram f3bf2d0c (domain0-0-main) was
>> SKIPPED 1 time.
>> [10249.539887] EtherCAT WARNING: Datagram f3bf2a8c (domain2-120-main)
>> was SKIPPED 1 time.
>> [10249.539918] EtherCAT WARNING: Datagram f3bf2e8c (domain1-114-main)
>> was SKIPPED 1 time.
>> [10249.635934] EtherCAT 0: Domain 0: 9 working counter changes - now
>> 17/17.
>> [10249.919163] EtherCAT 0: Domain 2: Working counter changed to 1/1.
>> [10250.018245] EtherCAT 0: Domain 1: Working counter changed to 1/1.
>> [10250.040259] EtherCAT 0: Slave states on main device: OP.
>> [10250.536654] EtherCAT WARNING 0: 4001 datagrams UNMATCHED!
>> [10251.537469] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!
>> [10252.538282] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!
>> [10252.538296] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!
>> [10253.539097] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!
>> [10254.539909] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!
>> [10255.540723] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!
>> [10256.541537] EtherCAT WARNING 0: 4000 datagrams UNMATCHED!<— This
>> continues indefinitely
>>
>>
>> Everything else seems okay:
>>
>> user at core:~/dist$ ethercat slaves
>> 0 0:0 OP + EK1100 EtherCAT-Koppler (2A E-Bus)
>> 1 0:1 OP + EL1104 4K. Dig. Eingang 24V, 3ms, Sensorversorgung
>> 2 0:2 OP + EL1104 4K. Dig. Eingang 24V, 3ms, Sensorversorgung
>> 3 0:3 OP + EL5101 1K. Inc. Encoder 5V
>> 4 0:4 OP + EL7041 1K. Schrittmotor-Endstufe (50V, 5A)
>> 5 0:5 OP + EL7041 1K. Schrittmotor-Endstufe (50V, 5A)
>> 6 0:6 OP + EL2004 4K. Dig. Ausgang 24V, 0.5A
>> 7 0:7 OP + EL2004 4K. Dig. Ausgang 24V, 0.5A
>> 8 0:8 OP + EL4001 1K. Ana. Ausgang 0-10V, 12bit
>> 9 1:0 OP + 0x00000539:0x02200001
>> user at core:~/dist$ ethercat master
>> Master0
>> Phase: Operation
>> Active: yes
>> Slaves: 10
>> Ethernet devices:
>> Main: 00:30:18:c8:d4:92 (attached)
>> Link: UP
>> Tx frames: 5204542
>> Tx bytes: 733318966
>> Rx frames: 5204539
>> Rx bytes: 733318636
>> Tx errors: 0
>> Tx frame rate [1/s]: 4000 3944 2090
>> Tx rate [KByte/s]: 823.7 811.9 412.9
>> Rx frame rate [1/s]: 4000 3944 2090
>> Rx rate [KByte/s]: 823.7 811.9 412.9
>> Common:
>> Tx frames: 5204542
>> Tx bytes: 733318966
>> Rx frames: 5204539
>> Rx bytes: 733318636
>> Lost frames: 3
>> Tx frame rate [1/s]: 4000 3944 2090
>> Tx rate [KByte/s]: 823.7 811.9 412.9
>> Rx frame rate [1/s]: 4000 3944 2090
>> Rx rate [KByte/s]: 823.7 811.9 412.9
>> Loss rate [1/s]: 0 0 0
>> Frame loss [%]: 0.0 0.0 0.0
>> Distributed clocks:
>> Reference clock: Slave 0
>> Application time: 509424243322706771
>> 2016-02-22 02:44:03.322706771
>>
>>
>>
>>
>>
>>
>> I must have something out of sync. Is there something more I’m supposed
>> to do to adjust the clock? I can make the number of dropped packets
>> worse by simplifying the time adjustment, but I can’t seem to get it any
>> better. I think I’m not understanding something about the clock
>> adjustment. Can you see what I’m doing wrong?
>>
>> Thanks!
>>
>> My cyclic task:
>>
>>
>> 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_);
>> }
>>
>> 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)
>> {
>> // output first diff
>> PRINT("First master diff: %d.\n", dc_diff_ns);
>>
>> // record the time of this initial cycle
>> dc_start_time_ns = dc_time_ns;
>> }
>> }
>>
>> }
>>
>>
>> int
>>
>> ecatMain_process(void* lp)
>>
>> {
>>
>>
>> sync_distributed_clocks();
>>
>> update_master_clock();
>>
>>
>> ecrt_master_receive(master_);
>>
>> ecrt_domain_process(lrwDomainMgr_.domain);
>>
>> ecrt_domain_process(noLrwWriteDomainMgr_.domain);
>>
>> ecrt_domain_process(noLrwReadDomainMgr_.domain);
>>
>> … // handle my business
>>
>>
>> // send process data
>>
>> ecrt_domain_queue(lrwDomainMgr_.domain);
>>
>> ecrt_domain_queue(noLrwWriteDomainMgr_.domain);
>>
>> ecrt_domain_queue(noLrwReadDomainMgr_.domain);
>>
>> // send EtherCAT data
>>
>> ecrt_master_send(master_);
>>
>> return 1;
>>
>> }
>>
>>
>>
>>
>> I made my sync0 time half of the can rate, as you prescribed:
>>
>> void
>> ecatModule_registerDcClock( Slave* slave, uint16_t addr )
>> {
>> struct timespec cur_time;
>> clock_gettime(CLOCK_REALTIME, &cur_time);
>> uint16_t sync0time = (uint16_t)((globalScanRate_us * 1000) / 2);
>> ecrt_slave_config_dc(
>> slave->sc,
>> addr,
>> (globalScanRate_us * 1000),
>> sync0time,
>> 0,
>> 0);
>> }
>>
>>
>>
>>
>>
>> From: Graeme Foot <Graeme.Foot at touchcut.com
>> <mailto:Graeme.Foot at touchcut.com>>
>> Date: Sunday, February 21, 2016 at 4:07 PM
>> To: Thomas Bitsky <tbj at automateddesign.com
>> <mailto:tbj at automateddesign.com>>
>> Cc: "etherlab-users at etherlab.org <mailto:etherlab-users at etherlab.org>"
>> <etherlab-users at etherlab.org <mailto:etherlab-users at etherlab.org>>
>> Subject: RE: Distributed Clocks
>>
>> Hi,
>>
>> I build and patch against revision 2526, so don’t know what patches /
>> fixes have made it through to the latest release. However for my
>> revision I need fixes for reference clock selections and dc
>> synchronization issues.
>>
>> I’ve attached the dc related patches I use, but these are applied after
>> some other patches so you may get some conflicts or offsetting.
>>
>> *01 - Distributed Clock fixes and helpers.patch*
>>
>> This sorts out some ref slave issues, allowing a user selected ref
>> slave. It also adds some helper functions:
>>
>> - ecrt_master_setup_domain_memory() : this allows me to set up the
>> domain memory and do stuff with it before calling ecrt_master_activate()
>> (for user space apps)
>>
>> - ecrt_master_deactivate_slaves() : this lets me deactivate the slaves
>> while still in realtime to avoid the slaves getting some shutdown sync
>> errors
>>
>> *02 - Distributed Clock fixes from Jun Yuan - dc sync issues.patch*
>>
>> This sorts out some timing issues to do with slave dc syncing. Without
>> it they can start syncing from one cycle out causing a large syncing
>> time overhead, one slave at a time.
>>
>> Regards,
>>
>> Graeme.
>>
>> *From:*Thomas Bitsky Jr [mailto:tbj at automateddesign.com]
>> *Sent:* Sunday, 21 February 2016 10:27 a.m.
>> *To:* Graeme Foot
>> *Cc:* etherlab-users at etherlab.org <mailto:etherlab-users at etherlab.org>
>> *Subject:* Re: Distributed Clocks
>>
>> Graeme,
>>
>> Thank you so much for the detailed response. I'm away from my computer
>> right now, so I can't try out your advice, but I noticed you asked about
>> patches. I am not running any patches; which should I get?
>>
>> Thanks!
>> Thomas Bitsky Jr
>>
>> On Feb 20, 2016, at 3:04 PM, Graeme Foot <Graeme.Foot at touchcut.com
>> <mailto:Graeme.Foot at touchcut.com>> wrote:
>>
>> Hi,
>>
>> The slave clocks get synced via the distributed clock system using
>> the master methods: ecrt_master_reference_clock_time(),
>> ecrt_master_sync_slave_clocks(), ecrt_master_application_time(),
>> ecrt_master_sync_reference_clock().
>>
>> However each individual slave can choose (if it is capable of it)
>> whether to synchronize its reading and writing of data to a
>> particular point in time within the dc cycle. If that slave does
>> not choose to do so then the reading and writing of the data occurs
>> at the time the EtherCAT frame goes past, resulting in a progressive
>> update and application of data as the frame progresses through all
>> of the slaves.
>>
>> If a slave is registered to use the dc clock then it caches the
>> frame data until the sync0 interrupt so in theory all dc slaves
>> apply the data at the same time. It also means you don’t have to
>> worry about jitter as to the time the frame is sent over the wire.
>> The only thing is to choose a good sync0 time to ensure your frames
>> are always sent out and have reached all of the slaves before the
>> sync0 time occurs, and that the next frame is not sent out before
>> the previous sync0 time occurs.
>>
>> In my application my cycle time is 1000us. I choose a sync0 time of
>> 500us. I also send my frame as close as possible to the beginning
>> of the cycle. This gives the frame up to half the cycle time to
>> reach all of the slaves and then the other half of the cycle for the
>> frame to return in time for the master receive call. I could choose
>> a sync0 time later than 500us but I want it processed as soon as
>> possible after the frame is received while still allowing for a bus
>> with a large number of terminals.
>>
>> So below where you are calculating loop_shift based on the current
>> time is wrong. Just choose a time within the dc cycle and use that
>> value for all slaves. Note: the beginning of the dc cycle is in
>> phase with the first call to ecrt_master_application_time(), so all
>> of your realtime looping should also be in phase with that
>> initial time.
>>
>> Note, when selecting a slave to be the DC reference slave you should
>> generally choose the first slave on your bus, regardless of whether
>> it will be (or can be) registered to use the dc synchronization. At
>> the very least it must be before, or be the, first slave that will
>> be registered as a dc slave.
>>
>> Also note that some slaves clocks are not very stable and shouldn’t
>> be used as the DC reference slave. My original testing was on a
>> Beckhoff CX1020 with a CX1100-0004, this could not be used as a
>> reference slave as its clock was not stable.
>>
>> I see you are configuring the slaves via ecrt_slave_config_*()then
>> calling ecrt_slave_config_pdos()at the end. If you call
>> ecrt_slave_config_pdos() at the beginning you don’t need all the
>> other config calls. However I hear AKD drives and some other slaves
>> prefer explicit slave config calls but most slaves are happy with
>> just the ecrt_slave_config_reg_pdo_entry()methods.
>>
>> This also leads to another issue. One of the configured PDO items
>> is the “mode of operation” value (0x6060 0x00). You are also
>> configuring this with: ecrt_slave_config_sdo8( sc, 0x6060, 0, 8 ).
>> This value should be instead be set via the PDO value. Use
>> ecrt_slave_config_reg_pdo_entry()to get the offset to the value and
>> set the value there.
>>
>> Sorry if that was a bit long but DC’s is not an easy topic to get
>> your head around. Here’s a bit of a summary:
>>
>> - You can choose which slaves get registered with
>> ecrt_slave_config_dc(). But each slave you want synced must get its
>> own call to ecrt_slave_config_dc().
>>
>> - If your yaskawa drives get to OP without
>> ecrt_slave_config_dc()then they should get to OP with
>> ecrt_slave_config_dc().
>>
>> - The yaskawa drives require a very stable reference slave time,
>> which is why we sync the EtherCAT master to the reference slave
>> rather than the other way around.
>>
>> And some other comments:
>>
>> - Never use anEL9010 endcap module. These break the distributed
>> clock calculations. I don’t think they are available anymore
>> though.
>>
>> - There are some patches out there that fix various DC clock issues,
>> are you using any of these?
>>
>> Regards,
>>
>> Graeme.
>>
>> *From:*Thomas Bitsky Jr [mailto:tbj at automateddesign.com]
>> *Sent:* Sunday, 21 February 2016 7:15 a.m.
>> *To:* Graeme Foot; etherlab-users at etherlab.org
>> <mailto:etherlab-users at etherlab.org>
>> *Subject:* Re: Distributed Clocks
>>
>> [snip]
>>
>> I’ve never been able to get the EL7041 stepper modules to work in dc
>> mode.
>>
>> [/snip]
>>
>> Is it all or nothing? I need the servo drives, the LVDT and the
>> EL3356 tied to a distributed clock. The EL7041 is optional for me.
>>
>> [snip]
>>
>> I don’t see in your code calls to ecrt_slave_config_dc().
>>
>> For the yaskawa drive, during the config stage, I use the following
>> calls…
>>
>> [/snip]
>>
>> Forgot to put that part; my bad. This is what I had for the
>> Yaskawa/AKD, although I was only doing it to one of the drives. I
>> thought I was supposed to set up one distributed clock, and it
>> became the master and handled the rest. Am I supposed to do this for
>> all the cards, or do I select?
>>
>> Yaskawa(AKD drive code is pretty much the same):
>>
>> if (!(sc = ecrt_master_slave_config(
>>
>> master,
>>
>> slavePosDomain,
>>
>> slavePosIndex,
>>
>> vendorId, productCode)))
>>
>> {
>>
>> return FALSE;
>>
>> }
>>
>> ecrt_slave_config_sdo8( sc, 0x1C12, 0, 0 ); /* clear sm pdo
>> 0x1c12 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1C13, 0, 0 ); /* clear sm pdo
>> 0x1c12 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A00, 0, 0 ); /* clear TxPDO0 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A01, 0, 0 ); /* clear TxPDO1 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A02, 0, 0 ); /* clear TxPDO2 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A03, 0, 0 ); /* clear TxPDO3 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1600, 0, 0 ); /* number of var in this
>> PDO */
>>
>> ecrt_slave_config_sdo8( sc, 0x1601, 0, 0 ); /* clear RxPdo 0x1601 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1602, 0, 0 ); /* clear RxPdo
>> 0x1602 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1603, 0, 0 ); /* clear RxPdo
>> 0x1603 */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A00, 0, 0 ); /* clear TxPDO0 */
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 1, 0x60410010 ); // Status word
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 2,0x60640020 );// Position
>> actual value, per encoder
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 3,0x60770010 );// Torque,
>> actual value
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 4,0x60F40020 );// Following
>> error, actual value
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 5,0x60610008 );// Modes of
>> operation display
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 6,0x00000008 );// GAP
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 7,0x60B90010 );// Touch probe
>> status
>>
>> ecrt_slave_config_sdo32( sc, 0x1A00, 8, 0x60BA0020 ); // Touch probe
>> 1 position
>>
>> ecrt_slave_config_sdo8( sc, 0x1A00, 0, 8 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A01, 0, 0 ); /* clear TxPDO1 */
>>
>> ecrt_slave_config_sdo32( sc, 0x1A01,1,0x60410010 ); // Status word
>>
>> ecrt_slave_config_sdo32( sc, 0x1A01,2,0x60640020 );// Position
>> actual value, per encoder
>>
>> ecrt_slave_config_sdo8( sc, 0x1A01, 0, 2 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A02, 0, 0 ); /* clear TxPDO2 */
>>
>> ecrt_slave_config_sdo32( sc, 0x1A02,1,0x60410010 ); // Status word
>>
>> ecrt_slave_config_sdo32( sc, 0x1A02,2,0x60640020 );// Position
>> actual value, per encoder
>>
>> ecrt_slave_config_sdo8( sc, 0x1A02, 0, 2 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1A03, 0, 0 ); /* clear TxPDO2 */
>>
>> ecrt_slave_config_sdo32( sc, 0x1A03,1,0x60410010 ); // Status word
>>
>> ecrt_slave_config_sdo32( sc, 0x1A03,2,0x60640020 );// Position
>> actual value, per encoder
>>
>> ecrt_slave_config_sdo32( sc, 0x1A03,3,0x60770010 );// Torque, actual
>> value
>>
>> ecrt_slave_config_sdo8( sc, 0x1A03, 0, 3 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1600, 0, 0 ); /* clear entries */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 1, 0x60400010 ); /* control
>> word */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 2, 0x607A0020 ); /* target
>> position */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 3, 0x60FF0020 ); /* target
>> velocity */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 4, 0x60710010 ); /* target
>> torque */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 5, 0x60720010 ); /* max
>> torque */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 6, 0x60600008 ); /* modes of
>> operation */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 7, 0x00000008 ); /* gap */
>>
>> ecrt_slave_config_sdo32( sc, 0x1600, 8, 0x60B80010 ); /* touch
>> probe function */
>>
>> ecrt_slave_config_sdo8(sc, 0x1600, 0, 8 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1601, 0, 0 ); /* clear entries */
>>
>> ecrt_slave_config_sdo32( sc, 0x1601, 1, 0x60400010 ); /* control
>> word */
>>
>> ecrt_slave_config_sdo32( sc, 0x1601, 2, 0x607A0020 ); /* target
>> position */
>>
>> ecrt_slave_config_sdo8( sc, 0x1601, 0, 2 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1602, 0, 0 ); /* clear entries */
>>
>> ecrt_slave_config_sdo32( sc, 0x1602, 1, 0x60400010 ); /* control
>> word */
>>
>> ecrt_slave_config_sdo32( sc, 0x1602, 2, 0x60FF0020 ); /* target
>> position */
>>
>> ecrt_slave_config_sdo8( sc, 0x1602, 0, 2 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo8( sc, 0x1603, 0, 0 ); /* clear entries */
>>
>> ecrt_slave_config_sdo32( sc, 0x1603, 1, 0x60400010 ); /* control
>> word */
>>
>> ecrt_slave_config_sdo32( sc, 0x1603, 2, 0x60710020 ); /* target
>> position */
>>
>> ecrt_slave_config_sdo8( sc, 0x1603, 0, 2 ); /* pdo entries */
>>
>> ecrt_slave_config_sdo16( sc, 0x1C12, 1, 0x1601 ); /* download pdo
>> 1C12 index */
>>
>> ecrt_slave_config_sdo8( sc, 0x1C12, 0, 1 ); /* set number of
>> RxPDO */
>>
>> ecrt_slave_config_sdo16( sc, 0x1C13, 1, 0x1A01 ); /* download pdo
>> 1C13 index */
>>
>> ecrt_slave_config_sdo8( sc, 0x1C13, 0, 1 ); /* set number of
>> TxPDO */
>>
>> // OPMODE
>>
>> // Yaskawa recommends 8
>>
>> ecrt_slave_config_sdo8( sc, 0x6060, 0, 8 );
>>
>> unsigned char interpolationTime = 0xFF;
>>
>> // 250
>>
>> unsigned char cycleExponent = 0xFA;
>>
>> // microseconds
>>
>> // globalSCanRate_us equals either 250 or 125.
>>
>> unsigned int us = globalScanRate_us;
>>
>> size_t i;
>>
>> for ( i=0;i<6, us > 0xFF;++i )
>>
>> {
>>
>> us /= 10;
>>
>> cycleExponent += 1;
>>
>> }
>>
>> interpolationTime = us;
>>
>> ecrt_slave_config_sdo8( akd->sc_akd, 0x60C2, 1, interpolationTime );
>> /* Interpolation time */
>>
>> ecrt_slave_config_sdo8( akd->sc_akd, 0x60C2, 2, cycleExponent ); /*
>> Cycle exponent */
>>
>> PRINT("Configuring PDOs...\n");
>>
>> if (ecrt_slave_config_pdos(sc, EC_END, slave_syncs))
>>
>> {
>>
>> PRINT("Failed to configure Yaskawa Sigma PDOs.\n");
>>
>> return FALSE;
>>
>> }
>>
>> *struct timespec cur_time;*
>>
>> *clock_gettime(CLOCK_REALTIME, &cur_time);*
>>
>> *size_t loop_period = globalScanRate_us * 1000;*
>>
>> *if ( loop_period == 0 ) loop_period = 1;*
>>
>> *size_t loop_shift *
>>
>> *= loop_period - (cur_time.tv_nsec % loop_period);*
>>
>> *ecrt_slave_config_dc(*
>>
>> *sc, *
>>
>> *0x0300, *
>>
>> *loop_period, *
>>
>> *loop_shift, *
>>
>> *0, *
>>
>> *0);*
>>
>> For the EL3356, would I then?
>>
>> KL3356StrainGauge* sg = (KL3356StrainGauge*)slave->instance;
>>
>> printf( "Begin kl3356_ecConfigure...\n");
>>
>> //
>>
>> // Create the slave configuration
>>
>> //
>>
>> if (!(sg->sc = ecrt_master_slave_config(
>>
>> master,
>>
>> slavePosDomain, slavePosIndex, // Bus position
>>
>> vendorId, productCode
>>
>> // Slave type
>>
>> )))
>>
>> {
>>
>> printf(
>>
>> "kl3356_ecConfigure -- Failed to get slave configuration.\n");
>>
>> return FALSE;
>>
>> }
>>
>> //
>>
>> // Register startup configuration for the hardware
>>
>> //
>>
>> ecrt_slave_config_sdo8( sg->sc, 0x1C12, 0, 0 ); /* clear sm pdo
>> 0x1c12 */
>>
>> ecrt_slave_config_sdo8( sg->sc, 0x1C13, 0, 0 ); /* clear sm pdo
>> 0x1c12 */
>>
>> ecrt_slave_config_sdo16( sg->sc, 0x1C12, 1, 0x1600 ); /* download
>> pdo 1C12 index */
>>
>> ecrt_slave_config_sdo8( sg->sc, 0x1C12, 0, 1 ); /* set number of
>> RxPDO */
>>
>> ecrt_slave_config_sdo16( sg->sc, 0x1C13, 1, 0x1A00 ); /* download
>> pdo 1C13 index */
>>
>> ecrt_slave_config_sdo16( sg->sc, 0x1C13, 2, 0x1A02 ); /* download
>> pdo 1C13 index */
>>
>> ecrt_slave_config_sdo8( sg->sc, 0x1C13, 0, 2 ); /* set number of
>> TxPDO */
>>
>> //
>>
>> // Configure the hardware's PDOs
>>
>> //
>>
>> if (ecrt_slave_config_pdos(sg->sc, EC_END, kl3356_syncs))
>>
>> {
>>
>> printf(
>>
>> "kl3356_ecConfigure -- Failed to configure PDOs.\n");
>>
>> return FALSE;
>>
>> }
>>
>> *struct timespec cur_time;*
>>
>> *clock_gettime(CLOCK_REALTIME, &cur_time);*
>>
>> *size_t loop_period = globalScanRate_us * 1000;*
>>
>> *if ( loop_period == 0 ) loop_period = 1;*
>>
>> *size_t loop_shift *
>>
>> *= loop_period - (cur_time.tv_nsec % loop_period);*
>>
>> *ecrt_slave_config_dc(*
>>
>> *s**g->sc, *
>>
>> *0x0300, *
>>
>> *loop_period, *
>>
>> *loop_shift, *
>>
>> *0, *
>>
>> *0);*
>>
>> 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>
>>
>> *From: *Graeme Foot <Graeme.Foot at touchcut.com
>> <mailto:Graeme.Foot at touchcut.com>>
>> *Date: *Friday, February 19, 2016 at 7:24 PM
>> *To: *Thomas Bitsky <tbj at automateddesign.com
>> <mailto:tbj at automateddesign.com>>, "etherlab-users at etherlab.org
>> <mailto:etherlab-users at etherlab.org>" <etherlab-users at etherlab.org
>> <mailto:etherlab-users at etherlab.org>>
>> *Subject: *RE: Distributed Clocks
>>
>> Hi,
>>
>> I don’t see in your code calls to ecrt_slave_config_dc().
>>
>> For the yaskawa drive, during the config stage, I use the following
>> calls:
>>
>> // set interpolation time period (free run mode)
>>
>> // where 0x60C2 is time in seconds = (0x60C2, 0x01) x
>> 10^(0x60C2, 0x02)
>>
>> // eg period of 1ms:
>>
>> // (0x60C2, 0x01) = 1
>>
>> // (0x60C2, 0x02) = -3
>>
>> // => 1 x 10^(-3) = 0.001s
>>
>> ecrt_slave_config_sdo8(dev->slaveConfig, 0x60C2, 0x01,
>> (uint8_t)g_app.scanTimeMS);
>>
>> ecrt_slave_config_sdo8(dev->slaveConfig, 0x60C2, 0x02,
>> (int8_t)(-3));
>>
>> // set up the distributed clock
>>
>> // 0x0000 = free run, 0x0300 = dc
>>
>> // (Supported DC cycle: 125us to 4ms (every 125us cycle))
>>
>> ecrt_slave_config_dc(dev->slaveConfig, 0x0300,
>> g_app.scanTimeNS, 500000, 0, 0);
>>
>> 0x60C2 shouldn’t be necessary for dc mode, but I used it before I
>> had dc mode working and have never tried it without and it doesn’t
>> harm anything having it in.
>>
>> The second value that is being passed to the
>> ecrt_slave_config_dcmethod is a value that is written to the ESC
>> register 0x980. The Yaskawa SGDV doco says this value should be
>> 0x0000 for free run mode and 0x0300 for dc mode. Other ESC’s may
>> required different values.
>>
>> I’ve never been able to get the EL7041 stepper modules to work in dc
>> mode.
>>
>> Graeme.
>>
>> *From:*etherlab-users [mailto:etherlab-users-bounces at etherlab.org]
>> *On Behalf Of *Thomas Bitsky Jr
>> *Sent:* Saturday, 20 February 2016 1:09 p.m.
>> *To:* etherlab-users at etherlab.org
>> <mailto:etherlab-users at etherlab.org>
>> *Subject:* [etherlab-users] Distributed Clocks
>>
>> 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 at etherlab.org
>> http://lists.etherlab.org/mailman/listinfo/etherlab-users
>>
> _______________________________________________
> etherlab-users mailing list
> etherlab-users at etherlab.org
> http://lists.etherlab.org/mailman/listinfo/etherlab-users
>
>
--
Oguz Dilmac
ARGE Bolumu
Bilko AS, R&D Department
====================================
Perpa Ticaret Merkezi B Blok Kat 13 Nr. 2568
TR-34384 Okmeydani Istanbul Turkey
Tel : +90 212 220 07 40 Fax : +90 212 210 47 01
e-mail : odilmac at bilko-automation.com
web site : http://www.bilko-automation.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.etherlab.org/pipermail/etherlab-users/attachments/20160222/dad3548e/attachment-0003.htm>
More information about the Etherlab-users
mailing list