bnjmnp / pysoem

Cython wrapper for the Simple Open EtherCAT Master Library
MIT License
96 stars 37 forks source link

[Need Help] DC mode confusion? #61

Closed tom9672 closed 2 years ago

tom9672 commented 2 years ago

@bnjmnp Good day, This is my first time to use a dc sync device, after add slave.dc_sync() and master.config_dc(), it works, but not totally works.

DC mode csp csp work as expected (but it seems unable to set velocity at csp)

                '''
                    if I give a bigger target position,
                    the driver will show me an error [Position Increment Increment Order times too large],
                    it seems that the motor jump to 607A, ignore the 6081.
                '''
                output_data.Mode_Operation = 8 
                for cmd in [6,7,15]:
                    output_data.Control_Word = cmd
                    self.slave1.output = bytes(output_data)
                    time.sleep(0.02)
                    print('status_word',cmd,self._convert_input_data(self.slave1.input).status_word)

                tp = 0
                output_data.Target_Position = tp
                self.slave1.output = bytes(output_data)

                while self._convert_input_data(self.slave1.input).actual_position not in range(tp-300,tp+300):
                    print('\ncurr_pos:'+str(self._convert_input_data(self.slave1.input).actual_position)+' \ curr_speed:'+str(self._convert_input_data(self.slave1.input).actual_speed)+' \ status_word:'+str(self._convert_input_data(self.slave1.input).status_word),end='')
                    time.sleep(0.1)

                tp = 10000000
                output_data.Target_Position = tp
                self.slave1.output = bytes(output_data)

                while self._convert_input_data(self.slave1.input).actual_position not in range(tp-300,tp+300):
                    print('\ncurr_pos:'+str(self._convert_input_data(self.slave1.input).actual_position)+' \ curr_speed:'+str(self._convert_input_data(self.slave1.input).actual_speed)+' \ status_word:'+str(self._convert_input_data(self.slave1.input).status_word),end='')
                    time.sleep(0.1)

DC mdoe pp not move at all

                output_data.Mode_Operation = 1
                for cmd in [6,7,15]:
                    output_data.Control_Word = cmd
                    self.slave1.output = bytes(output_data)
                    time.sleep(0.02)
                    print('status_word',cmd,self._convert_input_data(self.slave1.input).status_word)

                tp = 0
                output_data.Target_Position = tp
                output_data.target_speed_pp = 3000

                for cmd in [31]:
                    output_data.Control_Word = cmd
                    self.slave1.output = bytes(output_data)
                    time.sleep(0.02)
                    print('status_word',cmd,self._convert_input_data(self.slave1.input).status_word)

                while self._convert_input_data(self.slave1.input).actual_position not in range(tp-300,tp+300):
                    print('\ncurr_pos:'+str(self._convert_input_data(self.slave1.input).actual_position)+' \ curr_speed:'+str(self._convert_input_data(self.slave1.input).actual_speed)+' \ status_word:'+str(self._convert_input_data(self.slave1.input).status_word),end='')
                    time.sleep(0.1)

Full code here: https://github.com/tom9672/ethercat

I checked set dc in SOEM, seems I have the same issue about how to correctly setting DC. Still not find a way to solve, In SOEM, two functions to set dc ec_dcsync0 and ec_dcsync01, first is to active sync0, second is to active both sync0 and sync1. In PYSOEM, the fourth parameter of dy_sync will configure the sync1, not sure is it necessary to set sync1. I follow the basic example and not set it, will try to add it later on.

'not correctly config the dc', is it the reason lead to the device not work as expected? and need suggestions on how to correctly configure the dc in PYSOEM. It runs really weird now, at a super high velocity runs to target position, ignores 0x 6081.

Your help is very much appreciated!

tom9672 commented 2 years ago

Add self.slave1.dc_sync(1, 10000000,0,10000000), lead to raise HandleError('not all slaves reached SAFEOP state') Do I need to change pdo process gap after add sync1?

If I use self.slave1.dc_sync(1, 10000000), it can in OP, and I read 0x1C32:01 is 2, as the hand book, it means in 'DC SYNC 0 Mode' I checked other subindex of 0x1c32:

0x1C32:01 2 # Synchronization Type, 2 stand for DC SYNC 0 Mode
0x1C32:02 10000000 # Cycle Time
0x1C32:04 6 #Synchronization Types Supported,4 stand for DC SYNC 0 Mode
0x1C32:06 0 #Minmum Cycle Time
0x1C32:09 0 # Dealy time 
0x1C32:20 b'\x00'  # True: active sync and no error, False: not active with error

Got same result by using overlap or not using it.

This is twcat COE-onine: tc

The handbook: 1c32_20

But it can run as expected on twincat, althrough the 1C32:20 is FALSE.

As 20h, seems not active dc sync. Need some help about it. Thank you!

tom9672 commented 2 years ago

Status word, while output cmd 15, it shold make the status word become 567, bit3 1, but not. Does that mean not successfully enable the devcice?

status_word 6 561
status_word 7 563
status_word 15 563

curr_pos:9769568 \ curr_speed:0 \ status_word:563
curr_pos:9769570 \ curr_speed:-16000 \ status_word:1587
curr_pos:9769571 \ curr_speed:16000 \ status_word:1587
curr_pos:9769571 \ curr_speed:0 \ status_word:1591
curr_pos:9769569 \ curr_speed:-32000 \ status_word:1591
curr_pos:9769570 \ curr_speed:-16000 \ status_word:1591
tom9672 commented 2 years ago

BOOM, it seems solved, the issue may not caused by dc_sync. It seems caused by pdo not successfully received by device.

tp = 20000000
output_data.Target_Position = tp 
#output_data.target_speed_pp = 3000000 # pdo output writing velocity not works, 
device.sdo_write(0x6081,0x0,bytes(ctypes.c_uint32(3000000 ))) # sdo writing velocity works, so weird...
time.sleep(0.3) # After add this, it works, If I use 0.2 or less, it will not work, seems causing by the delay of my computer..

This stitutaion not happened while I not use the dc op mode.

Any suggestions? thanks in advance. Your help is very much appreciated!

tom9672 commented 2 years ago

Hope this could help beginner, tested on an 23-bit encoder servo device with DC OP mode and runs as expected. an example of moving a servo motor

import pysoem
from INOV_PDO import InputPdo
from INOV_PDO import OutputPdo
import ctypes
import time
import threading

class MachineWork:
    def __init__(self,port):
        self._port = port
        self._master = pysoem.Master()
        self._master.in_op = False
        self._master.do_check_state = False
        self._pd_thread_stop_event = threading.Event()
        self._ch_thread_stop_event = threading.Event()

    def _device_config_func(self,slave_pos):
        device = self._master.slaves[slave_pos]
        device.sdo_write(0x2002,0x1,bytes(ctypes.c_uint16(9)))

        device.sdo_write(0x60C5,0x0,bytes(ctypes.c_uint32(174762667)))
        device.sdo_write(0x60C6,0x0,bytes(ctypes.c_uint32(174762667)))
        device.sdo_write(0x6083,0x0,bytes(ctypes.c_uint32(174762666)))
        device.sdo_write(0x6084,0x0,bytes(ctypes.c_uint32(174762666)))
        device.sdo_write(0x607F,0x0,bytes(ctypes.c_uint32(104857600)))
        device.sdo_write(0x6081,0x0,bytes(ctypes.c_uint32(1048576)))

        device.sdo_write(0x1C12,0x0,bytes(ctypes.c_uint8(0)))
        device.sdo_write(0x1C13,0x0,bytes(ctypes.c_uint8(0)))
        device.sdo_write(0x1600,0x0,bytes(ctypes.c_uint8(0)))

        device.sdo_write(0x1A00,0x0,bytes(ctypes.c_uint8(0)))

        device.sdo_write(0x1600,0x1,bytes(ctypes.c_uint32(1614807056)))#6040 c_word
        device.sdo_write(0x1600,0x2,bytes(ctypes.c_uint32(1616904200)))#6060 mode
        device.sdo_write(0x1600,0x3,bytes(ctypes.c_uint32(1618608160)))#607A t_pos
        device.sdo_write(0x1600,0x4,bytes(ctypes.c_uint32(1619066912)))#6081 t_speeed PP
        device.sdo_write(0x1600,0x4,bytes(ctypes.c_uint32(1627324448)))#60FF t_speeed PV

        device.sdo_write(0x1A00,0x1,bytes(ctypes.c_uint32(1614872592)))#6041 s_word
        device.sdo_write(0x1A00,0x2,bytes(ctypes.c_uint32(1617166368)))#6064 pos
        device.sdo_write(0x1A00,0x3,bytes(ctypes.c_uint32(1617690656)))#606C speed

        device.sdo_write(0x1600,0x0,bytes(ctypes.c_uint8(4)))
        device.sdo_write(0x1A00,0x0,bytes(ctypes.c_uint8(3)))

        device.sdo_write(0x1C12,0x1,bytes(ctypes.c_uint16(5632))) #1600
        device.sdo_write(0x1C13,0x1,bytes(ctypes.c_uint16(6656))) #1A00

        device.sdo_write(0x1C12,0x0,bytes(ctypes.c_uint8(1)))
        device.sdo_write(0x1C13,0x0,bytes(ctypes.c_uint8(1)))

        self.slave1.dc_sync(1, 10000000)

    def _convert_input_data(self,data):
        return InputPdo.from_buffer_copy(data)

    def setup(self):
        self._master.open(self._port)
        if not self._master.config_init() > 0:
            self._master.close()
            raise HandleError('no slave found')

        self.slave1 = self._master.slaves[0]
        self.slave1.config_func = self._device_config_func
        self._master.config_overlap_map()
        self._master.config_dc()

        if self._master.state_check(pysoem.SAFEOP_STATE, 50000) != pysoem.SAFEOP_STATE:
            self._master.close()
            raise HandleError('not all slaves reached SAFEOP state')

        self._master.state = pysoem.OP_STATE

        self.check_thread = threading.Thread(target=self._check_thread)
        self.check_thread.start()
        self.proc_thread = threading.Thread(target=self._processdata_thread)
        self.proc_thread.start()

        # send one valid process data to make outputs in slaves happy
        self._master.send_overlap_processdata()
        wkc = self._master.receive_processdata(2000)

        self._master.write_state()
        self.all_slaves_reached_op_state = False

        for i in range(40):
            self._master.state_check(pysoem.OP_STATE, 50000)
            if self._master.state == pysoem.OP_STATE:
                self.all_slaves_reached_op_state = True
                break

    def move(self):       
        if self.all_slaves_reached_op_state:
            output_data = OutputPdo()
            try:
                self.enable(output_data)
                print('enable')
                time.sleep(0.5)
                print('move start')
                self.absoluate_move_immediately(output_data,8388608,10485760)
                self.absoluate_move_immediately(output_data,0,10485760)
                self.relative_move_immediately(output_data,200000,1048576)
                self.relative_move_immediately(output_data,-200000,1048576)
                print('move over')
                print(self.check_curr_pos())
            except KeyboardInterrupt:
                pass

            self.cleanup()
            print('cleanup over')
        else:
            print('al status code {} ({})'.format(hex(self.slave1.al_status), pysoem.al_status_code_to_string(self.slave1.al_status)))
            print('failed to got to OP_STATE')

    def enable(self,output_data):

        output_data.Mode_Operation = 1

        for cmd in [6,7,15]:
            output_data.Control_Word = cmd
            self.slave1.output = bytes(output_data)
            time.sleep(0.02)
            print('status_word',cmd,self._convert_input_data(self.slave1.input).status_word)

    def cleanup(self):
        # zero everything
        self.slave1.output = bytes(len(self.slave1.output))
        time.sleep(1)

        # stop thread
        self._pd_thread_stop_event.set()
        self._ch_thread_stop_event.set()
        self.proc_thread.join()
        self.check_thread.join()

        # close master
        self._master.state = pysoem.INIT_STATE
        self._master.write_state()
        self._master.close() 

    def absoluate_move(self,target_pos,velocity):
        self.slave1.sdo_write(0x6081,0x0,bytes(ctypes.c_uint32(velocity)))

        output_data = OutputPdo()
        output_data.Target_Position = target_pos 

        for cmd in [15,31]:
            output_data.Control_Word = cmd
            self.slave1.output = bytes(output_data)
            time.sleep(0.02)

        while self._convert_input_data(self.slave1.input).actual_position not in range(target_pos-20000,target_pos+20000):
            time.sleep(0.01)

    def absoluate_move_immediately(self,output_data,target_pos,velocity):
        self.slave1.sdo_write(0x6081,0x0,bytes(ctypes.c_uint32(velocity)))

        output_data.Target_Position = target_pos 

        for cmd in [47,63]:
            output_data.Control_Word = cmd
            self.slave1.output = bytes(output_data)
            time.sleep(0.02)

        while self._convert_input_data(self.slave1.input).actual_position not in range(target_pos-20000,target_pos+20000):
            time.sleep(0.01)

    def relative_move(self,output_data,target_step,velocity):
        self.slave1.sdo_write(0x6081,0x0,bytes(ctypes.c_uint32(velocity)))

        output_data.Target_Position = target_step 
        curr_pos = self._convert_input_data(self.slave1.input).actual_position
        target_pos = curr_pos + target_step

        for cmd in [79,95]:
            output_data.Control_Word = cmd
            self.slave1.output = bytes(output_data)
            time.sleep(0.02)

        while self._convert_input_data(self.slave1.input).actual_position not in range(target_pos-20000,target_pos+20000):
            time.sleep(0.01)

    def relative_move_immediately(self,output_data,target_step,velocity):
        self.slave1.sdo_write(0x6081,0x0,bytes(ctypes.c_uint32(velocity)))

        curr_pos = self._convert_input_data(self.slave1.input).actual_position
        target_pos = curr_pos + target_step
        output_data.Target_Position = target_step 

        for cmd in [111,127]:
            output_data.Control_Word = cmd
            self.slave1.output = bytes(output_data)
            time.sleep(0.02)

        while self._convert_input_data(self.slave1.input).actual_position not in range(target_pos-20000,target_pos+20000):
            time.sleep(0.01)

    def check_curr_pos(self):
        return self._convert_input_data(self.slave1.input).actual_position

    def _check_thread(self):
        while not self._ch_thread_stop_event.is_set():
            if self._master.in_op and ((self._actual_wkc < self._master.expected_wkc) or self._master.do_check_state):
                print('checking')
                self._master.do_check_state = False
                self._master.read_state()
                for i, slave in enumerate(self._master.slaves):
                    if slave.state != pysoem.OP_STATE:
                        self._master.do_check_state = True
                        MachineWork._check_slave(slave, i)
                if not self._master.do_check_state:
                    print('OK : all slaves resumed OPERATIONAL.')
            time.sleep(0.01)

    def _processdata_thread(self):
        while not self._pd_thread_stop_event.is_set():
            self._master.send_overlap_processdata()
            self._actual_wkc = self._master.receive_processdata(10000)
            if not self._actual_wkc == self._master.expected_wkc:
                print('incorrect wkc',self._actual_wkc,self._master.expected_wkc)
            time.sleep(0.01)

    @staticmethod
    def _check_slave(slave, pos):
        if slave.state == (pysoem.SAFEOP_STATE + pysoem.STATE_ERROR):
            print(
                'ERROR : slave {} is in SAFE_OP + ERROR, attempting ack.'.format(pos))
            slave.state = pysoem.SAFEOP_STATE + pysoem.STATE_ACK
            slave.write_state()
        elif slave.state == pysoem.SAFEOP_STATE:
            print(
                'WARNING : slave {} is in SAFE_OP, try change to OPERATIONAL.'.format(pos))
            slave.state = pysoem.OP_STATE
            slave.write_state()
        elif slave.state > pysoem.NONE_STATE:
            if slave.reconfig():
                slave.is_lost = False
                print('MESSAGE : slave {} reconfigured'.format(pos))
        elif not slave.is_lost:
            slave.state_check(pysoem.OP_STATE)
            if slave.state == pysoem.NONE_STATE:
                slave.is_lost = True
                print('ERROR : slave {} lost'.format(pos))
        if slave.is_lost:
            if slave.state == pysoem.NONE_STATE:
                if slave.recover():
                    slave.is_lost = False
                    print(
                        'MESSAGE : slave {} recovered'.format(pos))
            else:
                slave.is_lost = False
                print('MESSAGE : slave {} found'.format(pos))

class HandleError(Exception):
    def __init__(self, message):
        super(HandleError, self).__init__(message)
        self.message = message

port = 'YOUR EHTH PORT'
machine = MachineWork(port)
machine.setup()
machine.move()

BTW, I not sure about the overlap, It works with or without overlap..