Direct Pointers in IOMapping for EtherCAT with IoDrvEthercatLib.ETCSlave_Dia

mondinmr
2024-02-12
2024-02-13
  • mondinmr

    mondinmr - 2024-02-12

    Using SDO, I can read the EtherCAT mappings and offsets from various registers like 0x1c12, 0x1c13, etc. When I obtain registers mapped in the PDOs and various offsets, I could technically access directly to the statusword, controlword, etc., if they are mapped. I have noticed that on IoDrvEthercatLib.ETCSlave_Diag I can find pointers to the input and output buffers. However, although the input buffer can be easily read by referring to what has been obtained from the SDOs, it is not possible to write to the output buffer, as it is overwritten in each cycle by the data from the IOMapping task. Is there a way, knowing an instance of IoDrvEthercatLib.ETCSlave_Diag, to obtain the pointer to the first data in the IOMapping? The offsets are identical to those of the PDOs, but obviously the data is a copy. For me, the cleanest thing would be to access the pointers on IOMapping, for DS402a devices to retrieve the key data and point them to internal references.

    I need to know this as, having developed our own motion library, I would like to simplify the initial setup by eliminating unnecessary and nonsensical mappings that can lead users to unnecessary errors. In the current version that we have been using for years, we have to map everything manually. I would like to pass only the slave reference to the FB_init constructors and eliminate the mapping.

     
  • rmaas - 2024-02-13

    Hi,

    Im not sure if this is what you mean, but i do succesfully use the IoDrvEthercatLib.ETCSlave.InputData and IoDrvEthercatLib.ETCSlave.OutputData to read from and write to beckhoff DI and DO cards. Very convenient as you can make an FB with an VAR_IN_OUT of type IoDrvEthercatLib.ETCSlave and only have to pass the slave reference... So i am not using .ETCSlave_Diag but .ETCSlave...

     
  • dkugler - 2024-02-13

    rmaas: how do you get the reference of the slave?

     
    • mondinmr

      mondinmr - 2024-02-13

      Thank you for the response!
      It's a path I had already considered, however, while it has always worked for me on inputs, it doesn't on outputs, because if I enable the option to update all IOs at every scan, the IO tasks overwrite each other.

      However, I have found something very interesting which I am posting below.

       
    • mondinmr

      mondinmr - 2024-02-13
       

      Last edit: mondinmr 2024-02-13
    • rmaas - 2024-02-13

      The reference used is the ethercat slave name.

      For example in this screenshot the reference would be: EL1809_1

       
  • mondinmr

    mondinmr - 2024-02-13

    I have found a very interesting solution using:

    IoConfigTaskMap
    IoConfigConnectorMap
    IoConfigChannelMap

    The first is the list of IO tasks.
    The second is the connector for each IO module in the IOMap.
    The third is the individual input or output on the IOMap.

    One of the properties of the connector is another pointer to a connector, which corresponds with the connector of the EtherCAT slave.
    Through this information, it is possible to understand to which EtherCAT slave an IO connectormap corresponds.

    I am attaching an FB that allows for the construction of an IO map and finding the pointer to the actual IOs in the IOMap based on the bitoffset.

    FUNCTION_BLOCK IOExplorer
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
        inputChannels: COL.LinkedList;
        outputChannels: COL.LinkedList;
        ulintFactory: COL.UlintElementFactory;
    END_VAR
    
    METHOD inputAtBitOffsetOfConnector : POINTER TO BYTE
    VAR_INPUT
        conn: POINTER TO IoConfigConnectorMap;
        bitOffset: UDINT;
    END_VAR
    VAR
        it: COL.LinkedListIterator;
        itf: COL.IElement;
        elem: COL.iUlintElement;
        channelInfo: POINTER TO ADVChannelInfo;
        bitOffsetR: UDINT;
    END_VAR
        inputChannels.ElementIterator(it);
        WHILE it.HasNext() DO
            it.Next(itfElement => itf);
            __QUERYINTERFACE(itf, elem);
            {warning disable C0033}
            channelInfo := TO___UXINT(elem.UlintValue);
            {warning restire C0033}
            IF channelInfo^.connectorField = conn THEN
                IF bitOffsetR = bitOffset THEN
                    inputAtBitOffsetOfConnector := channelInfo^.addr;
                    RETURN;
                END_IF
                bitOffsetR := bitOffsetR + channelInfo^.size;
            ELSE
                bitOffsetR := 0;
            END_IF
        END_WHILE
        inputAtBitOffsetOfConnector := 0;
    END_METHOD
    
    METHOD outputAtBitOffsetOfConnector : POINTER TO BYTE
    VAR_INPUT
        conn: POINTER TO IoConfigConnectorMap;
        bitOffset: UDINT;
    END_VAR
    VAR
        it: COL.LinkedListIterator;
        itf: COL.IElement;
        elem: COL.iUlintElement;
        channelInfo: POINTER TO ADVChannelInfo;
        bitOffsetR: UDINT;
    END_VAR
        outputChannels.ElementIterator(it);
        WHILE it.HasNext() DO
            it.Next(itfElement => itf);
            __QUERYINTERFACE(itf, elem);
            {warning disable C0033}
            channelInfo := TO___UXINT(elem.UlintValue);
            {warning restire C0033}
            IF channelInfo^.connectorField = conn THEN
                IF bitOffsetR = bitOffset THEN
                    outputAtBitOffsetOfConnector := channelInfo^.addr;
                    RETURN;
                END_IF
                bitOffsetR := bitOffsetR + channelInfo^.size;
            ELSE
                bitOffsetR := 0;
            END_IF
        END_WHILE
        outputAtBitOffsetOfConnector := 0;
    END_METHOD
    
    METHOD scanIO
    VAR_INPUT
    END_VAR
    VAR
        numTasks: DINT := IoConfig_Globals.nIoConfigTaskMapCount;
        tType: WORD;
        ioTask: POINTER TO IoConfigTaskMap;
        numCon: WORD;
        connector: POINTER TO IoConfigConnectorMap;    
        numCh: DWORD;
        channelInfo: POINTER TO ADVChannelInfo;
        iTsk: DINT;
        iCon: WORD;
        iCh: DWORD;
        i: DINT;
        _tmpConnList: COL.IList;
        elem: COL.IUlintElement;
        itf: COL.IElement;
        tmpCh: POINTER TO ADVChannelInfo;
        lastE: DINT;
        e: COL.COLLECTION_ERROR;
        e1: Error;
    END_VAR
    VAR_INST
        lF: COL.ListFactory;
    END_VAR
        IF outputChannels.CountElements() > 0 OR inputChannels.CountElements() > 0 THEN
            RETURN;
        END_IF
    
        _tmpConnList := lF.CreateDynamicList(16, 16);
    
        //Iterate through all IO tasks
        FOR iTsk := 0 TO numTasks - 1 DO
            ioTask := ADR(IoConfig_Globals.pIoConfigTaskMap[iTsk]);
            //Store the type of the task (Input or Output)
            tType := ioTask^.wType;
            numCon := ioTask^.wNumOfConnectorMap;
            //Iterate through all connectors of the task
            FOR iCon := 0 TO numCon - 1 DO
                connector := ADR(ioTask^.pConnectorMapList[iCon]);
                numCh := connector^.dwNumOfChannels;
                //Iterate through all channels of the connector
                FOR iCh := 0 TO numCh - 1 DO
                    //Create a new channel info object and fill it with the address, connector and size of the channel
                    //Connectors is address of field connector in this case like EtherCAT slave
                    //Address is the address of the IOMap
                    //Size is the size of channel data in bits in IOMap
                    channelInfo := __NEW(ADVChannelInfo);
                    channelInfo^.addr := connector^.pChannelMapList[iCh].pbyIecAddress;
                    channelInfo^.connectorField := connector^.pConnector;
                    channelInfo^.size := connector^.pChannelMapList[iCh].wSize;
    
                    //We put the channel info a temporary ordered list
                    //Order is based on the address of IOMap
                    lastE := TO_DINT(_tmpConnList.CountElements()) - 1;
                    FOR i := 0 TO lastE DO
                        _tmpConnList.GetElementAt(udiPosition := TO_UDINT(i), itfElement => itf);
                        __QUERYINTERFACE(itf, elem);
                        {warning disable C0033}
                        tmpCh := TO___UXINT(elem.UlintValue);
                        {warning restire C0033}
                        //If the address of the channel is smaller than the address of the channel in the list
                        IF tmpCh^.addr > channelInfo^.addr THEN
                           //Insert the channel in the list at the current position
                           _tmpConnList.InsertElementAt(TO_UDINT(i), ulintFactory.Create(TO_ULINT(channelInfo)));
                           //Clear the channel info pointer
                           channelInfo := 0;
                           //Exit the loop
                           i := lastE + 1;
                        END_IF
                    END_FOR
                    //If the channel info is not 0, it means that the channel was not inserted in the list
                    IF channelInfo <> 0 THEN
                        //Add the channel to the end of the list
                        elem := ulintFactory.Create(TO_ULINT(channelInfo));
                        _tmpConnList.AddElement(elem);
                    END_IF
                END_FOR
                //Iterate temporary list and add the channels to the input or output list
                lastE := TO_DINT(_tmpConnList.CountElements()) - 1;
                FOR i := 0 TO lastE DO
                    _tmpConnList.GetElementAt(udiPosition := TO_UDINT(i), itfElement => itf);
                    __QUERYINTERFACE(itf, elem);
                    {warning disable C0033}
                    tmpCh := TO___UXINT(elem.UlintValue);
                    {warning restire C0033}
                    //If type is input, add the channel to the input list
                    IF tType = TaskMapTypes.TMT_INPUTS THEN
                        e := inputChannels.AddElement(ulintFactory.Create(TO_ULINT(tmpCh)));
                    //If type is output, add the channel to the output list
                    ELSIF tType = TaskMapTypes.TMT_OUTPUTS THEN
                        e := outputChannels.AddElement(ulintFactory.Create(TO_ULINT(tmpCh)));
                    ELSE
                        __DELETE(tmpCh);
                    END_IF
                END_FOR
                //Clear the temporary list
                _tmpConnList.RemoveAllElements();
            END_FOR
        END_FOR
    END_METHOD
    
     
    • rmaas - 2024-02-13

      Wow, this is next level for me, very impressive!
      glad you found a solution, thanks for sharing!

       

Log in to post a comment.