pysoem icon indicating copy to clipboard operation
pysoem copied to clipboard

Failed to go to OP STATE with MR-J4-70TM-LD servo amplifier

Open EulaliaVives opened this issue 3 years ago • 8 comments

Hello @bnjmnp We are trying to set up 4 Mitsubishi HG-KR73 motors controlled by SMC drivers (model LECSN2-T9, AC servo MR-J4 -70TM-LD), equipped with EtherCAT cards (https://www.accs.cz/Files/FA/SERVO/MR-J4-TM_Instr_Manual_EtherCAT.pdf , this is the manual we are using)

We have installed Pysoem on a Raspberry Pi4 and we have tried many different codes. We are able to write and read on the drivers and to put them in Safe Op state, but we never succeeded in movig the motors, neither we have reached the OP state.

For instance, we are trying to run the following code, which we adapted from your one in Issue #30

import pysoem
import ctypes
import time

master = pysoem.Master()
master.open('eth0')

def config_func():
    global device
    #software position limit
    device.sdo_write(0x607D, 1, bytes(ctypes.c_int32(0)))
    device.sdo_write(0x607D, 2, bytes(ctypes.c_int32(1000)))

    # gear ratio
    device.sdo_write(0x6091, 1, bytes(ctypes.c_uint32(1000000)))

    #polarity
    device.sdo_write(0x607E, 0, bytes(ctypes.c_uint8(192)))


if master.config_init() > 0:
    device = master.slaves[0]
    device.config_func=config_func()
    master.config_map()
    
    if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
        master.state = pysoem.OP_STATE
        master.write_state()
        master.state_check(pysoem.OP_STATE, 5_000_000)
        
        
        
        if master.state == pysoem.OP_STATE:
            device.sdo_write(0x6060, 0, bytes(ctypes.c_int8(1)))
            device.sdo_write(0x607A, 0, bytes(ctypes.c_int32(100))) #target position

            
            for control_cmd in [6, 7, 15]:
                device.sdo_write(0x6040, 0, bytes(ctypes.c_uint16(control_cmd)))
                master.send_processdata()
                master.receive_processdata(1_000)
                time.sleep(0.01)
            
            try:
                while 1:
                    master.send_processdata()
                    master.receive_processdata(1_000)
                    time.sleep(0.01)
            except KeyboardInterrupt:
                print('stopped')
            # zero everything
            device.output = bytes(len(device.output))
            master.send_processdata()
            master.receive_processdata(1_000)
                    
        else:
            print('failed to got to OP_STATE')
        
    else:
        print('failed to got to safeop state')
    master.state = pysoem.PREOP_STATE
    master.write_state()
else:
    print('no device found')


master.close()

The answer we get is 'failed to got to OP_STATE'

Any help is welcomed.

EulaliaVives avatar Aug 10 '21 08:08 EulaliaVives

Hi @EulaliaVives,

I'm sure you already know it but as long as the servo drive dosen't reach OP state, the motor will not move. So first of all you need to figure out why you don't get into OP state. For this you may try to print the "al status code", in order to get more information on why OP state is not reached.

Taken your script just add this print line in the else of the == pysoem.OP_STATE.

    if master.state == pysoem.OP_STATE:
        ...
                
    else:
        print('al status code {} ({})'.format(hex(device.al_status), pysoem.al_status_code_to_string(device.al_status)))
        print('failed to got to OP_STATE')

Other than that: Some EtherCAT device need a running process data loop prior to kicking the device into OP state. This is the case for the EL1259. In the basic_example.py is an example on how to do this unsing threading. There the _processdata_thread() is started right before master.write_state() which tells the all devices to go to OP state.

bnjmnp avatar Aug 12 '21 17:08 bnjmnp

Hello @bnjmnp

Thank you very much for your answer. We appreciate you rhelp since we are stuck in an apparently silly question. We bought 4 rather expensive motors and we are not able to run them.

We have included the line you suggested

print('al status code {} ({})'.format(hex(device.al_status), pysoem.al_status_code_to_string(device.al_status))) Once we run the code, the answer we get is :

al status code 0x0(No error)
Failed to got to OP_STATE

So we don't understand what is hapening.

Besides, we have also tried to add the data loop using threading, but we got sintax errors. We are not very good at Python, and we do not understand the way the variable self works. We copied the definition

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

and we included the lines

check_thread = threading.Thread(target=self._check_thread)
        check_thread.start()
        proc_thread = threading.Thread(target=self._processdata_thread)
        proc_thread.start()
        
        # send one valid process data to make outputs in slaves happy
        self._master.send_processdata()
        self._master.receive_processdata(2000)

and we got

NameError: 'self' is not defined.

Could you please suggest how to write those lines without the self syntax.

EulaliaVives avatar Aug 24 '21 06:08 EulaliaVives

Coming from you original code, it could be solved like this. But I was not able to test it. Also check my comments.

import pysoem
import ctypes
import time
import threading

pd_thread_stop_event = threading.Event()
master = pysoem.Master()
master.open('eth0')
actual_wkc = 0

def config_func():
    global device
    # software position limit
    device.sdo_write(0x607D, 1, bytes(ctypes.c_int32(0)))
    device.sdo_write(0x607D, 2, bytes(ctypes.c_int32(1000)))
    # gear ratio
    device.sdo_write(0x6091, 1, bytes(ctypes.c_uint32(1000000)))
    # polarity
    device.sdo_write(0x607E, 0, bytes(ctypes.c_uint8(192)))

def processdata_thread():
    global master  # not sure if this is necessary
    global pd_thread_stop_event  # not sure if this is necessary
    global actual_wkc  # not sure if this is necessary
    while not pd_thread_stop_event.is_set():
        master.send_processdata()
        actual_wkc = master.receive_processdata(10000)
        if not actual_wkc == master.expected_wkc:
            print('incorrect wkc')
        time.sleep(0.01)

if master.config_init() > 0:
    device = master.slaves[0]
    device.config_func=config_func()
    master.config_map()
    
    if master.state_check(pysoem.SAFEOP_STATE, 50_000) == pysoem.SAFEOP_STATE:
        master.state = pysoem.OP_STATE

        proc_thread = threading.Thread(target=processdata_thread)
        proc_thread.start()

        master.send_processdata()  # this is actually done in the "processdata_thread" - maybe it is not needed here
        master.receive_processdata(2000)  # this is actually done in the "processdata_thread" - maybe it is not needed here

        master.write_state()
        master.state_check(pysoem.OP_STATE, 5_000_000)
        
        if master.state == pysoem.OP_STATE:
            device.sdo_write(0x6060, 0, bytes(ctypes.c_int8(1)))  # Modes of operation - Profile position mode (pp)
            device.sdo_write(0x607A, 0, bytes(ctypes.c_int32(100)))  # Target position

            for control_cmd in [6, 7, 15, 31]:  # the 31 is necessary to update the "Target position", search you manual for "New set-point" - only required in pp mode
                device.sdo_write(0x6040, 0, bytes(ctypes.c_uint16(control_cmd)))  # update the controlword
                time.sleep(0.2)  # you may need to increase this sleep
            try:
                while 1:
                    time.sleep(0.01)
            except KeyboardInterrupt:
                print('stopped')
            # zero everything
            device.output = bytes(len(device.output))
            master.send_processdata()
            master.receive_processdata(1_000)
            
            pd_thread_stop_event.set()
            proc_thread.join()
                    
        else:
            print('failed to got to OP_STATE')
        
    else:
        print('failed to got to safeop state')
    master.state = pysoem.PREOP_STATE
    master.write_state()
else:
    print('no device found')

master.close()

bnjmnp avatar Aug 25 '21 05:08 bnjmnp

Hello @bnjmnp Thank you very much for your answer and for taking your time to help us.

We have tried your code. The first time we run it (after resetting the motors) gives an error. The second time it does not crash but the motor is not moving. This is the screen output:

root@raspberrypi:/home/pi/Desktop/PYTHON# python3 resposta2-bnjmp25-8.py
Traceback (most recent call last):
  File "resposta2-bnjmp25-8.py", line 54, in <module>
    device.sdo_write(0x6040, 0, bytes(ctypes.c_uint16(control_cmd)))  # update the controlword
  File "pysoem\pysoem.pyx", line 608, in pysoem.pysoem.CdefSlave.sdo_write
  File "pysoem\pysoem.pyx", line 735, in pysoem.pysoem.CdefSlave._raise_exception
Exception: unexpected error, Etype: 1
^CException ignored in: <module 'threading' from '/usr/lib/python3.7/threading.py'>
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 1281, in _shutdown
    t.join()
  File "/usr/lib/python3.7/threading.py", line 1032, in join
    self._wait_for_tstate_lock()
  File "/usr/lib/python3.7/threading.py", line 1048, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt
root@raspberrypi:/home/pi/Desktop/PYTHON# python3 resposta2-bnjmp25-8.py
^Cstopped
root@raspberrypi:/home/pi/Desktop/PYTHON# 

EulaliaVives avatar Aug 25 '21 07:08 EulaliaVives

don't no if it is related to this but the last control_cmd must be a 31 instead of 32, I'll fix my code from above

bnjmnp avatar Aug 25 '21 07:08 bnjmnp

The change to 31 does not work.

Maybe, the problem is related to other control_cmd values.

Usually when the motor is ready to start it gets powered by voltage and makes a characteristic noise, even if it is not moving. In this case, this is not happening

The driver has a display that, in all the cases ends in a message alternating n00 and 86.1. According to the manual this message might mean many different things: network module disconnected, network cable disconnected,error exist in controller setting, etc,,,

One good thing about your code is that it seems that we are truly in OP_State. There is a green led in the driver that before was always blinking and now, with your code, while we are in the "try" loop, is not blinking anymore.

EulaliaVives avatar Aug 25 '21 10:08 EulaliaVives

Dear @bnjmnp

Our motors are still not moving. We have tried many things:

  1. Increasing the time.sleep (0.2) in the control_cmd loop does not help

  2. We have checked that, strictly speaking, the code is never able to write the control_cmd=7

  3. It is true that it suceeds in writting control_cmd=6, but if we include a time.sleep, in any place before the loop, then it also fails to write control_cmd=6

  4. Supressing the two lines master.send_processdata and master.receive_processdata does not make any difference

  5. Independently, in all casses, an error message appears in the small display of the driver. And, the error seems to occur after the master.state_check(pysoem.OP_STATE, 5_000_000) command.

Any idea?

Is there a way to communicate with you exchanging our screen or even using a camera so that you can see what is actually happening in the motors? Do you know how can we obtain help (pysoem+raspberry+mitsubishi motors)? We are willing to spend some money for technical consulting.

Thanks a lot.

EulaliaVives avatar Sep 03 '21 07:09 EulaliaVives

I don't have any idea for now. In the for control_cmd in [6, 7, 15, 32]: loop you could add a print of the Statusword:

print(hex(int.from_bytes(device.sdo_read(0x6041, 0), byteorder='little', signed=False)))

So instead of the 0.2 sleep, you could print the Statusword 10 times in a loop plus wait 0.02 each cycle. Maybe this gives you a hint on what is wrong.

Did you actuall try to get this working using TwinCAT?

You can reach out to me on gitter: https://gitter.im/bnjmnp just using your GitHub login.

The options I see were you could get further help:

  • Use TwinCAT and optionally ask the Mitsubishi support for help (I guess all device vendors should be able help you if you use TwinCAT) and then apply that knowledge to your Python script.
  • No sure about that, but I think the maintainers of SOEM are somehow related to https://rt-labs.com, there you could get help in exchange for money. But they might only give you C code that uses SOEM.
  • Try to ask the ETG for help, they might provide you with alternative technical consulting companies.
  • Or write C code for SOEM yourself and create an issue SOEM GitHub, where you ask for help.

bnjmnp avatar Sep 03 '21 18:09 bnjmnp

Closed due to inactivity of the OP.

bnjmnp avatar Jan 03 '23 19:01 bnjmnp