bnjmnp / pysoem

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

Profile position mode #73

Closed Phiraos closed 2 years ago

Phiraos commented 2 years ago

Hi,

I follow up on my project (using a C1250-DS-XC-1S with standard ASIC ET1100 and CiA402 Protocol). Now that I can realize a home, I would like to move the actuator according to my input.

I try a simple single target but nothing happen.

Here is the code :

import time
import pysoem
import ctypes

class InputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('statusword', ctypes.c_uint16),
        ('position_actual_value', ctypes.c_int32),
        ('demand_position', ctypes.c_int32),
        ('demand_current', ctypes.c_int32),
        ('modes_of_operation_display', ctypes.c_int8),
        ('byte_padding', ctypes.c_int8),
    ]

class OutputPdo(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('controlword', ctypes.c_uint16),
        ('target_position', ctypes.c_int32),
        ('target_velocity', ctypes.c_int32),
        ('modes_of_operation', ctypes.c_int8),
        ('byte_padding', ctypes.c_int8),
    ]

modes_of_operation = {
    'No mode': 0,
    'Profile position mode': 1,
    'Profile velocity mode': 3,
    'Homing mode': 6,
    'Cyclic synchronous position mode': 8,
    'Cyclic synchronous velocity mode': 9,
    'Cyclic synchronous torque mode': 10,
}

master = pysoem.Master()
output_data = OutputPdo()

print('opening connection')

master.open('eno1')

if master.config_init() > 0:
    master.read_state()
    for device in master.slaves:
        print('Found Device: {},'.format(device.name))
        print('Manufacturer id: {}'.format(hex(device.man)))
        print('Id: {}'.format((hex(device.id))))
        print('state: {}'.format(hex(device.state)))

    print('Transition system to SAFEOP_STATE')
    io_map_size = master.config_map()
    print('IOMap Size: {}'.format(io_map_size))

    if master.state_check(pysoem.SAFEOP_STATE, 50000) != pysoem.SAFEOP_STATE:
        master.read_state()
        if not device.state == pysoem.SAFEOP_STATE:
                print('{} did not reach SAFEOP state'.format(device.name))
                print('al status code {} ({})'.format(hex(device.al_status), pysoem.al_status_code_to_string(device.al_status))
                )

        raise Exception('not all slaves reached SAFEOP state')
    else:
            print('Device in SAFEOP state: {}'.format(hex(device.state)))

# Send and receive process data to have valid data at all outputs before transistioning to OP_STATE
    print('Send and receive process data to have valid data at all outputs before transitioning to OP_STATE')
    master.send_processdata()
    actual_wkc = master.receive_processdata(2000)
    if not actual_wkc == master.expected_wkc:
        print('incorrect wkc')

# Transition MASTER to OP_STATE (Slave should follow)
    print('Transitioning system to OP_STATE')
    master.state = pysoem.OP_STATE
    master.write_state()

    if master.state_check(pysoem.OP_STATE, 50000) != pysoem.OP_STATE:
        master.read_state()
        if not device.state == pysoem.OP_STATE:
                print('{} did not reach OP state'.format(device.name))
                print('al status code {} ({})'.format(hex(device.al_status), pysoem.al_status_code_to_string(device.al_status)))
        raise Exception('Not all slaves reached OP state')

        # Read state of all slaves at start-up
    master.read_state()
    print('Device in OP state: {}'.format(hex(device.state)))
    """output_data.modes_of_operation = modes_of_operation['Homing mode']
    for control_cmd in [6,7,15,0x1F]:
        print('{}'.format(control_cmd))
        output_data.controlword = control_cmd
        device.output = bytes(output_data)
        master.send_processdata()
        master.receive_processdata(1000)
        time.sleep(0.05)
        print('{}'.format(device.output))
    time.sleep(5) """

    output_data.modes_of_operation = modes_of_operation['Profile position mode']
    output_data.target_position = 200
    output_data.target_velocity = 1
    for control_cmd in [6,7,15,0x3F]:
        print('{}'.format(control_cmd))
        output_data.controlword = control_cmd
        device.output = bytes(output_data)
        master.send_processdata()
        master.receive_processdata(1000)
        time.sleep(1)
        print('{}'.format(device.output))
    time.sleep(5)
else:
    print('no device found')
master.slaves[0].output = bytes(len(master.slaves[0].output))
master.send_processdata()
master.receive_processdata(1000)

master.close()
Phiraos commented 2 years ago

I solved my problem.

I use the cyclic synchronous position mode (mode 8) instead of the profile position (mode 1).

And, most important, something I miss or not really explicit, the PDO streaming has to be stricly cyclic. If not, the buffer suffers an underflow and motor stop working.

So, as in the basic_example.py, I implement a thread for processdata in order to satisfy the cyclic condition.

DHavron commented 2 years ago

tell me how you changed mode 8 to mode 1. Can I see the correct code?

Phiraos commented 2 years ago

tell me how you changed mode 8 to mode 1. Can I see the correct code?

Aye @DHavron,

I changed the mode 1 to mode 8.

To do this, I juste changed the parameter in this line

    output_data.modes_of_operation = modes_of_operation['Profile position mode']

I use now "Cyclic synchronous position mode".