Ethernet/IP mapping to struct

2022-04-04
2022-04-06
  • yannickasselin - 2022-04-04

    Hello,

    Is there a way to map an Ethernet/ip adapter device directly to a struct instead of mapping every byte one by one?

    When using Modbus TCP I can map the modbus array directly to a struct of the right size but when using ethernet/IP, the inputs and outputs are not part of an array so I cannot find a way to map them directly to a struct. The only way I can think of is by using a MEMCPY with the address of the first input or output.

     
  • imdatatas - 2022-04-05

    Same need also exist for mapping to struct when used EtherCAT and CanOpen too.
    A common and convenient answer will help for all cases.

     
  • dFx

    dFx - 2022-04-05

    Using %I %Q addressing, you can also map by program using POINTER TO BYTE (and as you know the right size, get all the data to a formated struct or whatever you needs).

     
  • imdatatas - 2022-04-05

    Can you explain it with a simple example ?
    For instance; In above case, How to get %IB393 object value from slave device into an application variable without using "AT" physical address decleration and not using device IO mapping editor ?

     
  • dFx

    dFx - 2022-04-05

    For instance, imagine you have an FB to correctly map your com data to a struct, and this FB takes as inputs :

        pInData : POINTER TO ARRAY [0..5] OF BYTE;
        pOutData : POINTER TO ARRAY [0..5] OF BYTE;
    

    Then to call your FB, you could use :

        MyFB(pInData:= ADR(%IB0), pOutData:= ADR(%QB0));
    

    Then, inside your FB :

        var1 := pInData^;
        var2 := pInData^[1];
        var3.0 := pInData^[2].0;
    
     

    Related

    Talk.ru: 1
    Talk.ru: 2

  • fajean - 2022-04-05

    I initially thought about using the pointer method above, but found out it had a few shortcomings. It requires that the address be valid at the time the function block is called, and if I remember correctly, this may not be the case if the data comes from a fieldbus that is in error. It also makes it hard to have nested components with I/O, because there must be an initialization method that provides each component with the required address(es). It can be done with methods, but the number of method quickly grows. Finally, it does not work in TwinCAT as far as I know, because there is no (or we have not found) a way to access the requires addresses. Here is how I do it.

    Suppose I have a structure S1 that I want to use in I/O.

    TYPE S1 :
    STRUCT
       var1: BOOL;
       var2: UINT;
    END_STRUCT
    END_TYPE
    

    Suppose I want to create a reusable component that will be in a library that takes that structure as both an input and output (uncommon, but i am keeping things simple) :

    FUNCTION_BLOCK FB1
    VAR
      a AT %I*: S1;
      b AT %Q*: S1;
    END_VAR
    

    I can declare this in my project, like this :

    PROGRAM MAIN_71110ea90b
    VAR
      fb: FB1;
    END_VAR
    

    The last thing that needs to be done is to create a VAR_CONFIG to map I/O addresses to my function block.

    VAR_CONFIG  
        MAIN_71110ea90b.fb.A AT %MB0: S1;
        MAIN_71110ea90b.fb.B AT %MB0: S1;
    END_VAR
    

    I am using %MB0 here as a placeholder, your true I/O addresses would go there, and you should lock them in your I/O devices so that they do not shift.

    While I have not done it here for the sake of (relative) brevity, the function block instance could be nested at any depth and it would work the same, just with a deeper path in the VAR_CONFIG, all with no code to write.

    TwinCAT (as far as we know) does not offer single-binding, whole-structure mapping, but it can work with variable-by-variable mapping using the same code.

    CODESYS warns of unmapped * addresses, which is a big bonus as far as I am concerned, especially if you have a large number of mappings (forgetting one is so easy). With the pointer method, this is a benefit that would require more code and complexity.

     
    • dFx

      dFx - 2022-04-05

      I don't get the point on nested FBs and pointers. Just get the address you need on top level FB and forward it.
      For instance, if you have a car FB, with an accelerator pedal FB inside, reading the analog value of the pedal, just forward the address from car FB to pedal FB.
      You can also place the address inside a struct, from a first POU and then use the address provided by the struct.

      The point with dealing with pointers is that your adress may change with online change, so if you don't set the pointer address before using it, it may lead to some unexpected behavior (like CPU STOP). But if you just do ADR(%IB0) (or whatever address, even fieldbus) at every FB call using your pointer, there are no risks.

      Just don't do math with pointers, this is generally a bad idea if you use different plateforms.

      About your code, it is not portable on different plateforms, because of byte alignments in struct and also due to the external code supplying internal values of a FB. But it may be better resolved by cross references.

       

      Last edit: dFx 2022-04-05
      • fajean - 2022-04-05

        The systems we design look more like 15 cars of different models with a variable (but high) number of accelerator and brake pedals each, and they are modular so we can never hard-code a single memory address anywhere in reusable function blocks. Cascading addresses in the (modular) chain of FB instances is not a realistic solution in the scenarios I deal with.

        I assume you can use ADR(%IB0) and survive fieldbus issues (not something I have experience with), but this is also a property of mapping the structure itself with an %* address.

         
  • nothinrandom - 2022-04-06

    In order to prevent the address from changing when you update code, you'd need to map the first byte parameter to a variable to keep it static. For example, let's say your first byte of the first EtherNet/IP device is mapped to IB%01, then just map that to a global variable so that it won't change.

    Ideally, we should be able to access the parameters directly via interface. For example, let's say you have an EtherNet/IP scanner (master) that speaks to a few adapters (slaves) called MyDevice1, MyDevice2, MyDevice3, etc. I would be nice if we could just say MyDevice1.dataAddress (or something similar) that would return the pointer to the first parameter value. This way, it wouldn't matter at all even if the memory address might change after download / memory rebase.

     

Log in to post a comment.