LinuxCNC with EtherCAT

LinuxCNC is a free, open-source Linux software system that implements computer numerical control capability using general purpose computers to control CNC machines. It is typically bundled as an ISO file with a modified version of Debian Linux which provides the required real-time kernel. However, we can simply build LinuxCNC from source and deploy leveraging Linux distribution and real-time kernel provided by ECI. We can also leverage Ethercat master stack from ECI to control physical slave device with LinuxCNC.

For more information, refer to LinuxCNC website.

Install LinuxCNC with EtherCAT master stack

Do the following to prepare real-time environment, set up EtherCAT master stack and install LinuxCNC:

  1. Follow Get Started Steps to prepare a real-time Linux environment. PREEMPT_RT kernel is recommended real-time kernel.

  2. Install and set up IgH EtherCAT Master Stack.

  3. Execute following step to fetch and compile LinuxCNC

    # INSTALL THE BUILD PACKAGES
    $ sudo apt install git build-essential
    # CLONE THE LINUXCNC SOURCE CODE
    $ git clone github.com/LinuxCNC/linuxcnc.git linuxcnc-dev
    # MOVE TO LINUXCNC-DEV FOLDER
    $ cd linuxcnc-dev
    # CHANGE TO VERSION YOU WANT TO BUILD (FOR ME 2.9= QTDRAGON_HD)
    $ git checkout 2.9
    # CHANGE TO DEBIAN FOLDER
    $ cd debian
    # THEN CONFIGURE
    $ ./configure uspace
    # THEN MOVE BACK TO THE LINUXCNC-DEV FOLDER
    $ cd ..
    # CHECK FOR ANY MISSING BUILD DEPENDENCIES (THER WILL BE SERVERAL PACKAGES TO INSTALL. JUST COPY SELECTION AND PASTE TO SAVE ALL THAT TYPING, OTHERWISE JUST TAKE TIME AND TYPE THEM ALL OUT)
    $ dpkg-checkbuilddeps
    # COPY/PASTE THE LIST OF BUILD DEPPENDENCIES THEN INSTALL WITH
    $ sudo apt-get install "LIST OF DEPS COPIED FROM DPKG-CHECKBUILDDEPS"
    # INSTALL ALL OF THE PACKAGES NEEDED, THEN CHECK DEPS AGAIN
    $ dpkg-checkbuilddeps
    # ONCE ALL THE DEPENDENCIES HAVE BEEN INSTALLED THEN MOVE TO THE SOURCE FOLDER
    $ cd src
    # THEN AFTER GETTING INTO SRC FOLDER
    $ ./autogen.sh
    # THEN CONFIGURE
    $ ./configure --with-realtime=uspace
    # THEN MAKE
    $ make
    # ALLOW ACCESS TO HARDWARE
    $ sudo make setuid
    # SETUP RIP ENVIRONMENT
    $ . ../scripts/rip-environment
    # START UP LINUXCNC
    $ linuxcnc
    
  4. Change the permission of ethercat device so LinuxCNC can manipulate ethercat without root permission.

    # CHANGE ETHERCAT DEVICE PERMISSION
    $ sudo chmod 666 /dev/EtherCAT0
    # OPEN IN VIM TO GIVE ETHERCAT PORT STARTUP PERMISSION
    $ sudo vim /etc/udev/rules.d/99-ethercat.rules
    # ONCE THE FILE IS OPEN, ADD THE FOLLOWING:
    KERNEL=="EtherCAT[0-9]", MODE="0777"
    # THEN SAVE AND EXIT VIM WITH ":wq".
    # ONCE BACK ON THE COMMAND LINE, THEN RELOAD THE RULES
    $ sudo udevadm control --reload-rules
    
  5. Setup LinuxCNC EtherCAT Driver

    Clone the LinuxCNC EtherCAT driver repository.

    $ cd ~/
    $ git clone github.com/sittner/linuxcnc-ethercat.git linuxcnc-ethercat
    $ cd /linuxcnc-ethercat/src
    $ vim realtime.mk
    

    Now overwrite the realtime.mk file with following code.

    include ../config.mk
    include Kbuild
    cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \
                > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)
    .PHONY: all clean install
    ifeq ($(BUILDSYS),kbuild)
    module = $(patsubst %.o,%.ko,$(obj-m))
    ifeq (,$(findstring -Wframe-larger-than=,$(EXTRA_CFLAGS)))
    EXTRA_CFLAGS += $(call cc-option,-Wframe-larger-than=2560)
    endif
    $(module):
          $(MAKE) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" KBUILD_EXTRA_SYMBOLS="$(RTLIBDIR)/Module.symvers $(RTAIDIR)/modules/ethercat/Module.symvers" -C $(KERNELDIR) SUBDIRS=`pwd` CC=$(CC) V=0 modules
    clean::
          rm -f $(obj-m)
          rm -f *.mod.c .*.cmd
          rm -f modules.order Module.symvers
          rm -rf .tmp_versions
    else
    module = $(patsubst %.o,%.so,$(obj-m))
    EXTRA_CFLAGS := $(filter-out -Wframe-larger-than=%,$(EXTRA_CFLAGS))
    $(module): $(lcec-objs)
          $(CC) -shared -o $@ $(lcec-objs) -Wl,-rpath,$(LIBDIR) -L$(LIBDIR) -llinuxcnchal -lethercat -lrt
    %.o: %.c
          $(CC) -o $@ $(EXTRA_CFLAGS) -Os -c $<
    endif
    all: $(module)
    clean::
          rm -f $(module)
          rm -f $(lcec-objs)
    install: $(module)
          mkdir -p $(DESTDIR)$(RTLIBDIR)
          cp $(module) $(DESTDIR)$(RTLIBDIR)/
    

    After saving, move to the LinuxCNC-EtherCAT folder, apply the following patch to source code.

    diff --git a/src/lcec_generic.c b/src/lcec_generic.c
    index dfddf73..3fdc44a 100644
    --- a/src/lcec_generic.c
    +++ b/src/lcec_generic.c
    @@ -27,6 +27,14 @@ hal_u32_t lcec_generic_read_u32(uint8_t *pd, lcec_generic_pin_t *hal_data);
    void lcec_generic_write_s32(uint8_t *pd, lcec_generic_pin_t *hal_data, hal_s32_t sval);
    void lcec_generic_write_u32(uint8_t *pd, lcec_generic_pin_t *hal_data, hal_u32_t uval);
    
    +float ecrt_read_real(const void *data)
    +{
    +    uint32_t raw = EC_READ_U32(data);
    +    return *(float *) (const void *) &raw;
    +}
    +
    +#define EC_READ_REAL(DATA) ecrt_read_real(DATA)
    +
    int lcec_generic_init(int comp_id, struct lcec_slave *slave, ec_pdo_entry_reg_t *pdo_entry_regs) {
      lcec_master_t *master = slave->master;
      lcec_generic_pin_t *hal_data = (lcec_generic_pin_t *) slave->hal_data;
    

    Then make and install.

    $ cd ~/linuxcnc-ethercat
    $ make clean && make
    # IF YOU HAVE FAILED ON THE MAKE, GO BACK TO THE "linuxcnc-dev/src" FOLDER
    # AND DO THE ". ../scripts/rip-environment" AGAIN TO SETUP ENVIRONMENT VARIABLE, THEN RE-EXECUTE
    $ make install
    

    Then add text /usr/local/lib below the include line in /etc/ld.so.conf file.

    include /etc/ld.so.conf.d/*.conf
    /usr/local/lib
    

    After saving, change to root and load the library.

    $ sudo su
    $ ldconfig -v
    $ exit
    

    Install CiA402 HAL component. CiA402 is CANopen device profile for drives and motion control. HAL is hardware abstract layer for LinuxCNC.

    $ cd ~/
    $ git clone https://github.com/dbraun1981/hal-cia402
    $ cd hal-cia402
    $ halcompile --install cia402.comp
    # IF YOU HAVE FAILED ON "halcompile" COMMAND, GO BACK TO THE "linuxcnc-dev/src" FOLDER
    # AND DO THE ". ../scripts/rip-environment" AGAIN TO SETUP ENVIRONMENT VARIABLE, THEN RE-EXECUTE
    

    Now installation of all components is finished.

LinuxCNC Demo with Physical Axes

Now we will link five physical axes to the joints in LinuxCNC simulation demo by LinuxCNC-EtherCAT driver. Here we choose table rotary tilting as a standard 5-axis configuration to demonstrate the steps. Here is 3D model simulator for table rotary tilting in LinuxCNC.

../../_images/xyzbc-trt.png

All configuration files for table rotary tilting demo are under linuxcnc-dev/configs/sim/axis/vismach/5axis/table-rotary-tilting/

  1. First we need to prepare an xml file which describes the property of servo drives as ethercat slave device. Here we use Inovance IS620N as example of five slave devices. Put below content in a new generated xml file and rename it, for example, ethercat-conf.xml.

    <masters>
       <master idx="0" appTimePeriod="1000000" refClockSyncCycles="1">
          <slave idx="0" type="generic" vid="00100000" pid="000C0108" configPdos="true">
             <dcConf assignActivate="300" sync0Cycle="*1" sync0Shift="0"/>
             <syncManager idx="2" dir="out">
             <pdo idx="1600">
                <pdoEntry idx="6040" subIdx="00" bitLen="16" halPin="cia-controlword" halType="u32"/>
                <pdoEntry idx="6060" subIdx="00" bitLen="8" halPin="opmode" halType="s32"/>
                <pdoEntry idx="607A" subIdx="00" bitLen="32" halPin="target-position" halType="s32"/>
                <pdoEntry idx="60FF" subIdx="00" bitLen="32" halPin="target-velocity" halType="s32"/>
             </pdo>
             </syncManager>
             <syncManager idx="3" dir="in">
             <pdo idx="1a00">
                <pdoEntry idx="6041" subIdx="00" bitLen="16" halPin="cia-statusword" halType="u32"/>
                <pdoEntry idx="6061" subIdx="00" bitLen="8" halPin="opmode-display" halType="s32"/>
                <pdoEntry idx="6064" subIdx="00" bitLen="32" halPin="actual-position" halType="s32"/>
                <pdoEntry idx="606C" subIdx="00" bitLen="32" halPin="actual-velocity" halType="s32"/>
             </pdo>
             </syncManager>
          </slave>
          <slave idx="1" type="generic" vid="00100000" pid="000C0108" configPdos="true">
             <dcConf assignActivate="300" sync0Cycle="*1" sync0Shift="0"/>
             <syncManager idx="2" dir="out">
             <pdo idx="1600">
                <pdoEntry idx="6040" subIdx="00" bitLen="16" halPin="cia-controlword" halType="u32"/>
                <pdoEntry idx="6060" subIdx="00" bitLen="8" halPin="opmode" halType="s32"/>
                <pdoEntry idx="607A" subIdx="00" bitLen="32" halPin="target-position" halType="s32"/>
                <pdoEntry idx="60FF" subIdx="00" bitLen="32" halPin="target-velocity" halType="s32"/>
             </pdo>
             </syncManager>
             <syncManager idx="3" dir="in">
             <pdo idx="1a00">
                <pdoEntry idx="6041" subIdx="00" bitLen="16" halPin="cia-statusword" halType="u32"/>
                <pdoEntry idx="6061" subIdx="00" bitLen="8" halPin="opmode-display" halType="s32"/>
                <pdoEntry idx="6064" subIdx="00" bitLen="32" halPin="actual-position" halType="s32"/>
                <pdoEntry idx="606C" subIdx="00" bitLen="32" halPin="actual-velocity" halType="s32"/>
             </pdo>
             </syncManager>
          </slave>
          <slave idx="2" type="generic" vid="00100000" pid="000C0108" configPdos="true">
             <dcConf assignActivate="300" sync0Cycle="*1" sync0Shift="0"/>
             <syncManager idx="2" dir="out">
             <pdo idx="1600">
                <pdoEntry idx="6040" subIdx="00" bitLen="16" halPin="cia-controlword" halType="u32"/>
                <pdoEntry idx="6060" subIdx="00" bitLen="8" halPin="opmode" halType="s32"/>
                <pdoEntry idx="607A" subIdx="00" bitLen="32" halPin="target-position" halType="s32"/>
                <pdoEntry idx="60FF" subIdx="00" bitLen="32" halPin="target-velocity" halType="s32"/>
             </pdo>
             </syncManager>
             <syncManager idx="3" dir="in">
             <pdo idx="1a00">
                <pdoEntry idx="6041" subIdx="00" bitLen="16" halPin="cia-statusword" halType="u32"/>
                <pdoEntry idx="6061" subIdx="00" bitLen="8" halPin="opmode-display" halType="s32"/>
                <pdoEntry idx="6064" subIdx="00" bitLen="32" halPin="actual-position" halType="s32"/>
                <pdoEntry idx="606C" subIdx="00" bitLen="32" halPin="actual-velocity" halType="s32"/>
             </pdo>
             </syncManager>
          </slave>
          <slave idx="3" type="generic" vid="00100000" pid="000C0108" configPdos="true">
             <dcConf assignActivate="300" sync0Cycle="*1" sync0Shift="0"/>
             <syncManager idx="2" dir="out">
             <pdo idx="1600">
                <pdoEntry idx="6040" subIdx="00" bitLen="16" halPin="cia-controlword" halType="u32"/>
                <pdoEntry idx="6060" subIdx="00" bitLen="8" halPin="opmode" halType="s32"/>
                <pdoEntry idx="607A" subIdx="00" bitLen="32" halPin="target-position" halType="s32"/>
                <pdoEntry idx="60FF" subIdx="00" bitLen="32" halPin="target-velocity" halType="s32"/>
             </pdo>
             </syncManager>
             <syncManager idx="3" dir="in">
             <pdo idx="1a00">
                <pdoEntry idx="6041" subIdx="00" bitLen="16" halPin="cia-statusword" halType="u32"/>
                <pdoEntry idx="6061" subIdx="00" bitLen="8" halPin="opmode-display" halType="s32"/>
                <pdoEntry idx="6064" subIdx="00" bitLen="32" halPin="actual-position" halType="s32"/>
                <pdoEntry idx="606C" subIdx="00" bitLen="32" halPin="actual-velocity" halType="s32"/>
             </pdo>
             </syncManager>
          </slave>
          <slave idx="4" type="generic" vid="00100000" pid="000C0108" configPdos="true">
             <dcConf assignActivate="300" sync0Cycle="*1" sync0Shift="0"/>
             <syncManager idx="2" dir="out">
             <pdo idx="1600">
                <pdoEntry idx="6040" subIdx="00" bitLen="16" halPin="cia-controlword" halType="u32"/>
                <pdoEntry idx="6060" subIdx="00" bitLen="8" halPin="opmode" halType="s32"/>
                <pdoEntry idx="607A" subIdx="00" bitLen="32" halPin="target-position" halType="s32"/>
                <pdoEntry idx="60FF" subIdx="00" bitLen="32" halPin="target-velocity" halType="s32"/>
             </pdo>
             </syncManager>
             <syncManager idx="3" dir="in">
             <pdo idx="1a00">
                <pdoEntry idx="6041" subIdx="00" bitLen="16" halPin="cia-statusword" halType="u32"/>
                <pdoEntry idx="6061" subIdx="00" bitLen="8" halPin="opmode-display" halType="s32"/>
                <pdoEntry idx="6064" subIdx="00" bitLen="32" halPin="actual-position" halType="s32"/>
                <pdoEntry idx="606C" subIdx="00" bitLen="32" halPin="actual-velocity" halType="s32"/>
             </pdo>
             </syncManager>
          </slave>
       </master>
    </masters>
    

    If there is servo drive of other model, change vid and pid accordingly, which represents Vendor ID and Product ID of servo drive.

  2. Add following code to .hal file of demo. HAL file contains the HAL commands executed on LinuxCNC initialization, including HAL components loading and HAL pins connection. For table rotary tilting xyzbc-trt demo, xyzbc-trt_cmds.hal will be generated automatically when demo first startup. Change HALFILE = LIB:basic_sim.tcl to HALFILE = ./xyzbc-trt_cmds.hal in xyzbc-trt.ini and add following code to xyzbc-trt_cmds.hal.

    #################################
    # Setup
    #################################
    
    loadusr -W lcec_conf ethercat-conf.xml
    loadrt lcec
    loadrt cia402 count=5
    
    setp cia402.0.csp-mode 1
    setp cia402.0.pos-scale 8388608
    setp cia402.1.csp-mode 1
    setp cia402.1.pos-scale 8388608
    setp cia402.2.csp-mode 1
    setp cia402.2.pos-scale 8388608
    setp cia402.3.csp-mode 1
    setp cia402.3.pos-scale 8388608
    setp cia402.4.csp-mode 1
    setp cia402.4.pos-scale 8388608
    
    #################################
    # Functions servo-thread
    #################################
    
    addf lcec.read-all servo-thread
    addf cia402.0.read-all servo-thread
    addf cia402.1.read-all servo-thread
    addf cia402.2.read-all servo-thread
    addf cia402.3.read-all servo-thread
    addf cia402.4.read-all servo-thread
    
    addf cia402.0.write-all servo-thread
    addf cia402.1.write-all servo-thread
    addf cia402.2.write-all servo-thread
    addf cia402.3.write-all servo-thread
    addf cia402.4.write-all servo-thread
    addf lcec.write-all servo-thread
    
    #########################################
    # Net pins
    #########################################
    
    net x-statusword     lcec.0.0.cia-statusword  => cia402.0.statusword
    net x-opmode-fb      lcec.0.0.opmode-display  => cia402.0.opmode-display
    net x-drv-act-pos    lcec.0.0.actual-position => cia402.0.drv-actual-position
    
    net x-controlword       cia402.0.controlword    => lcec.0.0.cia-controlword
    net x-opmode-cmd        cia402.0.opmode         => lcec.0.0.opmode
    net x-drv-target-pos    cia402.0.drv-target-position   => lcec.0.0.target-position
    
    net y-statusword     lcec.0.1.cia-statusword  => cia402.1.statusword
    net y-opmode-fb      lcec.0.1.opmode-display  => cia402.1.opmode-display
    net y-drv-act-pos    lcec.0.1.actual-position => cia402.1.drv-actual-position
    
    net y-controlword       cia402.1.controlword    => lcec.0.1.cia-controlword
    net y-opmode-cmd        cia402.1.opmode         => lcec.0.1.opmode
    net y-drv-target-pos    cia402.1.drv-target-position   => lcec.0.1.target-position
    
    net z-statusword     lcec.0.2.cia-statusword  => cia402.2.statusword
    net z-opmode-fb      lcec.0.2.opmode-display  => cia402.2.opmode-display
    net z-drv-act-pos    lcec.0.2.actual-position => cia402.2.drv-actual-position
    
    net z-controlword       cia402.2.controlword    => lcec.0.2.cia-controlword
    net z-opmode-cmd        cia402.2.opmode         => lcec.0.2.opmode
    net z-drv-target-pos    cia402.2.drv-target-position   => lcec.0.2.target-position
    
    net b-statusword     lcec.0.3.cia-statusword  => cia402.3.statusword
    net b-opmode-fb      lcec.0.3.opmode-display  => cia402.3.opmode-display
    net b-drv-act-pos    lcec.0.3.actual-position => cia402.3.drv-actual-position
    
    net b-controlword       cia402.3.controlword    => lcec.0.3.cia-controlword
    net b-opmode-cmd        cia402.3.opmode         => lcec.0.3.opmode
    net b-drv-target-pos    cia402.3.drv-target-position   => lcec.0.3.target-position
    
    net c-statusword     lcec.0.4.cia-statusword  => cia402.4.statusword
    net c-opmode-fb      lcec.0.4.opmode-display  => cia402.4.opmode-display
    net c-drv-act-pos    lcec.0.4.actual-position => cia402.4.drv-actual-position
    
    net c-controlword       cia402.4.controlword    => lcec.0.4.cia-controlword
    net c-opmode-cmd        cia402.4.opmode         => lcec.0.4.opmode
    net c-drv-target-pos    cia402.4.drv-target-position   => lcec.0.4.target-position
    

    By now we have finished the initialization of lcec (LinuxCNC-EtherCAT driver) and cia402 as HAL components and connected their pins accordingly. The final step is connecting cia402 pins of each physical axis to joint pins correctly so that motion component can send their target position to physical axis each control cycle other than the simulator and fetch actual physical axis position as feedback.

    Also in xyzbc-trt_cmds.hal file, find below lines:

    net J0:enable joint.0.amp-enable-out => J0_pid.enable
    net J0:pos-cmd joint.0.motor-pos-cmd => J0_pid.command
    net J0:pos-fb J0_mux.out => J0_mux.in0 J0_switch.cur-pos J0_vel.in joint.0.motor-pos-fb
    
    net J1:enable joint.1.amp-enable-out => J1_pid.enable
    net J1:pos-cmd joint.1.motor-pos-cmd => J1_pid.command
    net J1:pos-fb J1_mux.out => J1_mux.in0 J1_switch.cur-pos J1_vel.in joint.1.motor-pos-fb
    
    net J2:enable joint.2.amp-enable-out => J2_pid.enable
    net J2:pos-cmd joint.2.motor-pos-cmd => J2_pid.command
    net J2:pos-fb J2_mux.out => J2_mux.in0 J2_switch.cur-pos J2_vel.in joint.2.motor-pos-fb
    
    net J3:enable joint.3.amp-enable-out => J3_pid.enable
    net J3:pos-cmd joint.3.motor-pos-cmd => J3_pid.command
    net J3:pos-fb J3_mux.out => J3_mux.in0 J3_switch.cur-pos J3_vel.in joint.3.motor-pos-fb
    
    net J4:enable joint.4.amp-enable-out => J4_pid.enable
    net J4:pos-cmd joint.4.motor-pos-cmd => J4_pid.command
    net J4:pos-fb J4_mux.out => J4_mux.in0 J4_switch.cur-pos J4_vel.in joint.4.motor-pos-fb
    

    And change them to:

    net J0:enable joint.0.amp-enable-out => J0_pid.enable cia402.0.enabl
    net J0:pos-cmd joint.0.motor-pos-cmd => J0_pid.command cia402.0.pos-cmd
    net J0:pos-fb cia402.0.pos-fb => J0_mux.in0 J0_switch.cur-pos J0_vel.in joint.0.motor-pos-fb
    
    net J1:enable joint.1.amp-enable-out => J1_pid.enable cia402.1.enable
    net J1:pos-cmd joint.1.motor-pos-cmd => J1_pid.command cia402.1.pos-cmd
    net J1:pos-fb cia402.1.pos-fb => J1_mux.in0 J1_switch.cur-pos J1_vel.in joint.1.motor-pos-fb
    
    net J2:enable joint.2.amp-enable-out => J2_pid.enable cia402.2.enable
    net J2:pos-cmd joint.2.motor-pos-cmd => J2_pid.command cia402.2.pos-cmd
    net J2:pos-fb cia402.2.pos-fb => J2_mux.in0 J2_switch.cur-pos J2_vel.in joint.2.motor-pos-fb
    
    net J3:enable joint.3.amp-enable-out => J3_pid.enable cia402.3.enable
    net J3:pos-cmd joint.3.motor-pos-cmd => J3_pid.command cia402.3.pos-cmd
    net J3:pos-fb cia402.3.pos-fb => J3_mux.in0 J3_switch.cur-pos J3_vel.in joint.3.motor-pos-fb
    
    net J4:enable joint.4.amp-enable-out => J4_pid.enable cia402.4.enable
    net J4:pos-cmd joint.4.motor-pos-cmd => J4_pid.command cia402.4.pos-cmd
    net J4:pos-fb cia402.4.pos-fb => J4_mux.in0 J4_switch.cur-pos J4_vel.in joint.4.motor-pos-fb
    

    Now five joints J0-J5 in motion are connected to five physical axes. Restart LinuxCNC and execute table rotary tilting xyzbc-trt demo, then we will see that physical axes run following motion in simulator.