Structure of booleans for IO module

2017-04-05
2017-04-20
  • tjarcoboerkoel - 2017-04-05

    Dear readers,

    I'm working with extension modules, and there are a lot of identical structures in a device. So I would like to have a single structure that I can reuse.
    Declaring this structure globally would make it accessible through the entire controller.

    //Making the structure with 16 booleans, resulting in a WORD width.
    TYPE t16bitDUT:
    STRUCT
    BIT00 : BOOL; //Boolean Type
    BIT01 : BOOL; //Boolean Type
    BIT02 : BOOL; //Boolean Type
    BIT03 : BOOL; //Boolean Type
    BIT04 : BOOL; //Boolean Type
    BIT05 : BOOL; //Boolean Type
    BIT06 : BOOL; //Boolean Type
    BIT07 : BOOL; //Boolean Type
    BIT08 : BOOL; //Boolean Type
    BIT09 : BOOL; //Boolean Type
    BIT0A : BOOL; //Boolean Type
    BIT0B : BOOL; //Boolean Type
    BIT0C : BOOL; //Boolean Type
    BIT0D : BOOL; //Boolean Type
    BIT0E : BOOL; //Boolean Type
    BIT0F : BOOL; //Boolean Type
    END_STRUCT
    END_TYPE

    //And create globally AT the address of an extension module.
    MY16bDUT AT %MW0 : t16bitDUT;

    But this does also not work.. Results in a type mismatch.
    So I tried :

    //Create an union from the structure and the an overlapping WORD and place the word AT the address of the extension module.

    UNION
    bits: t16bitDUT;
    all AT %MW0 : WORD;
    END_UNION

    But this doesn't work either..

    //Or maybe define the union first
    TYPE u16bitDUT
    UNION
    bits: t16bitDUT;
    all : WORD;
    END_UNION
    END_TYPE

    //And then create the variable globally
    MY16bDUT AT %MW0 : u16bitDUT;

    Also not working.

    Does anybody know how this can be done?
    A pointer type would also be acceptable.

    best regards,

    Tjarco

     
  • Anonymous - 2017-04-05

    Originally created by: scott_cunningham

    A BOOL uses a byte of memory. It is not bit sized. Your hope of 16 BOOLs being a WORD is not correct.

     
  • tjarcoboerkoel - 2017-04-10

    Hi,

    Thanks for your reply. Your comment is not correct, when using as STRUCT they ARE bitwise members. See documentation:

    Zitat:
    Bit Access in Structures
    The data type BIT is a special data type which can only be defined in structures. It consumes memory space of 1 bit and allows you to address single bits of a structure by name.
    TYPE <structurename>:
    STRUCT
    <bitname bit1=""> : BIT;
    <bitname bit2=""> : BIT;
    <bitname bit3=""> : BIT;
    ...
    <bitname bitn=""> : BIT;
    END_STRUCT
    END_TYPE
    You can gain access to the structure component BIT by using the following syntax:
    <structurename>.<bitname>
    NOTE: The usage of references and pointer on BIT variables is not possible. Furthermore, BIT variables are not allowed in </bitname></structurename></bitname></bitname></bitname></bitname></structurename>

     
  • jzhvymetal - 2017-04-10

    @tjarcoboerkoel

    Bit and BOOL are not the same. Your first post will work if you use BIT instead of BOOL. Also note BIT can only used in structures. When using BOOL codesys stores it as byte.

    BR

     
  • tjarcoboerkoel - 2017-04-11

    Are you serious?
    There are two 'bit' types where the first is bitwise and the second is byte oriented?
    Why on earth...?

    Well, thanks for the clarification. I'll try again.

    Indeed, the expansion module has also type BOOL, so that makes it confusing.
    I've created the STRUCT in a DUT, within TYPE declarations and I created the variable in a GVL. That works fine.
    Then I assigned the variable to the PARENT element of the digital interface and start compilation.

    Zitat:
    ------ Build started: Application: MyController.Sim.MyController.Application -------
    |ERROR| .... The name used in the interface is not identical with the object's Name
    Compile complete -- 1 errors, 0 warnings

    I get an error, but I don't really understand it. The 'name' is not the same.. Why should the name be the same..?

    /*DUT code*/
    TYPE FLOOR1 :
       STRUCT
          FloorValve2A:          BIT:=FALSE;   (* Hal + Gang begandegrond*)
          FloorValve3A:         BIT:=FALSE;   (* Woonkamer, verdeeld in 3 zones *)
          FloorValve3B:         BIT:=FALSE;   (* Woonkamer, verdeeld in 3 zones *)
          FloorValve3C:         BIT:=FALSE;   (* Woonkamer, verdeeld in 3 zones *)
          FloorValve4A:         BIT:=FALSE;   (* Keuken, verdeeld in 3 zones *)
          FloorValve4B:         BIT:=FALSE;   (* Keuken, verdeeld in 3 zones *)
          FloorValve4C:         BIT:=FALSE;   (* Keuken, verdeeld in 3 zones *)
          FloorValve6A:         BIT:=FALSE;   (* Berging, verdeeld in 2 zones *)
          FloorValve6B:         BIT:=FALSE;   (* Berging, verdeeld in 2 zones *)
          FloorValve7A:         BIT:=FALSE;   (* Badkamer + Toilet *)
          FloorValve8A:         BIT:=FALSE;   (* Werk / slaapkamer, verdeeld in 2 zones *)
          FloorValve8B:         BIT:=FALSE;   (* Werk / slaapkamer, verdeeld in 2 zones *)
          
          FloorValve10A:          BIT:=FALSE;   (* Overloop 1ste verdieping*)
          FloorValve11A:          BIT:=FALSE;   (* Slaapkamer 1ste verdieping*)
          FloorValve12A:          BIT:=FALSE;   (* Slaapkamer 1ste verdieping*)
          FloorValve13A:          BIT:=FALSE;   (* Slaapkamer 1ste verdieping*)
       END_STRUCT
    END_TYPE
    
    /*GVL code*/
    VAR_GLOBAL
          tester :FLOOR1;
          ...
    END_VAR
    

    /IO MAPPING/

    Even if I create a simple variable of type WORD, and assign this to the PARENT (testword:WORD;) then I get the same error.

    best regards

    IMG: DigitalOutput.jpg

     
  • tjarcoboerkoel - 2017-04-11

    Well,

    I've changed the DUT (file) name to be identical to the TYPE name. And this makes the error about the name disappear and now I'm able to build without errors.

    Thank you for the thoughts!

     
  • Anonymous - 2017-04-12

    Originally created by: scott_cunningham

    CoDeSys is a hardware independent software and I've learned one should never count on a particular data size or byte order (Motorola vs. Intel). Using c language bit-banging tricks (which were necessary when RAM and CPU power was low) can get you in trouble fast. As you just found out with BIT vs BOOL. I never use UNIONS (since I started with CoDeSys in 2005) and avoid bit-level manipulation when possible. Even when I have to deal with bit-coded control words for drives, I define a control word "object" (function block) and immediate generate BOOL outputs or "Properties" so I don't build in bit-order dependence in my code. When I switch from brand X to Y and the Enable bit moves from bit 0 to bit 3, I only change one line of code.

     
  • tjarcoboerkoel - 2017-04-12

    Hi Scott,

    Thank you for your reply. I agree that it's not the most "sustainable" is for cross platform solutions, but on the other hand you always have to change something.
    As you probably noticed, I've experience in C/C++ in bare-metal/embedded and linux environments. not much in this area (yet). I have some experience in Structured Text.

    Do you have a simple example or know where I can find a simple example to create this 'control word object'?
    Do you mean creating a function with "input" variable WORD and call the function from the ?

    Best regards,

     
  • Anonymous - 2017-04-13

    Originally created by: scott_cunningham

    I first came from c language and embedded systems, too. I have learned to toss out many things I used to do that were all about saving RAM, stack size and overhead. I now think: "I have unlimited RAM - don't care about it. I can transfer lots of variables - don't care about it. I have fast execute times - don't care about it." On some platforms, I had to revisit the execute time, but so rarely I still ignore it at the beginning of the project. I've never had to deal with "out of memory" issues (how that makes life so much easier compared to the old on-chip days).

    Regarding the example - I create a FB for the control word encoding (or anywhere I have to encode/decode bits). Only in this FB, will "evil" bit coding occur...

    The "traditional" solution would use VAR_INPUTS and VAR_OUTPUTS and the FB would have to be called to calculate the CW:

    FUNCTION_BLOCK CwEncode
    VAR_INPUT
       Enable : BOOL;
       Run    : BOOL;
       EStop  : BOOL;
    END_VAR
    VAR_OUTPUT
       CtrlWd : INT;
    END_VAR
    CtrlWd.0 := Enable;
    CtrlWd.1 := Run;
    CtrlWd.2 := EStop;
    END_FUNCTION_BLOCK
    PROGRAM PLC_PRG
    VAR
       Drive1CW       : CtrlWdEncode;
       Drive2CW       : CtrlWdEncode;
       MachineEStop   : BOOL;
       EnableDrives   : BOOL;
       RunDrive1      : BOOL;
       RunDrive2      : BOOL;
    END_VAR
    Drive1CW(Enable:=EnableDrives, EStop:=MachineEStop, Run:=RunDrive1);
    Drive2CW(Enable:=EnableDrives, EStop:=MachineEStop, Run:=RunDrive2);
    END_PROGRAM
    

    This solution requires calling Drive1CW() and Drive2CW() to actually update the output variable CtrlWd. You run into trouble if you do: Drive1CW.Enable:=TRUE; because this never actually runs the FBs code - only sets the VAR_INPUT to TRUE... This is usually not an issue for simple systems. Example:

    Drive1CW.Enable:=TRUE; //CtrlWd not changed
    Drive1CW.MachineEstop:=TRUE; //CtrlWd not changed
    Drive1CW(); //now CtrlWd is calculated
    

    As an OOP alternative, I have started to use Properties, which can trigger code (so I don't need to call the FB explicitly when I set the property to TRUE or FALSE).

    OOP "style":

    FUNCTION_BLOCK CtrlWdEncode
    VAR_OUTPUT
       CtrlWd : INT;
    END_VAR
    PROPERTY Enable : BOOL
    SET
    CtrlWd.0 := Enable;
    PROPERTY Run : BOOL
    SET
    CtrlWd.1 := Run;
    PROPERTY EStop : BOOL
    SET
    CtrlWd.2 := EStop;
    END_FUNCTION_BLOCK
    PROGRAM PLC_PRG
    VAR
       Drive1CW       : CtrlWdEncode;
       Drive2CW       : CtrlWdEncode;
       MachineEStop   : BOOL;
       EnableDrives   : BOOL;
       RunDrive1      : BOOL;
       RunDrive2      : BOOL;
    END_VAR
    Drive1CW.Enable := EnableDrives; //CtrlWd is updated
    Drive1CW.Run := RunDrive1; //CtrlWd is updated
    Drive1CW.EStop := MachineEStop; //CtrlWd is updated
    Drive2CW.Enable := EnableDrives; //CtrlWd is updated
    Drive2CW.Run := RunDrive2; //CtrlWd is updated
    Drive2CW.EStop := MachineEStop; //CtrlWd is updated
    END_PROGRAM
    

    I delete the GET part of the properties - I never want to read the CW bits... I only set them. In this trivial example, the code looks larger and probably more complicated for the OOP solution. However, for me, the advantage appears in real-life programs where you have some chunk of code deciding if drives 1 and 2 need to "run"; and a machine safety chunk of code deciding if estop mode should be triggered. I can just write Drive1CW.Estop := TRUE and I know the CW is already updated. Yes, Drive1CW(Estop:=TRUE) would also work, BUT(!) lets say for some reason that for the ESTOP to work correctly on the device, that several other bits need to change state, then in my Property version, I simply have a more advanced code block:

    PROPERTY EStop : BOOL
    SET
    CtrlWd.2 := EStop;
    CtrlWd.1 := FALSE; //no run bit
    CtrlWd.0 := TRUE; //must be enabled!!!
    

    Again, the traditional way works again but I need a more complicated call - Drive1CW(Estop:=TRUE, Run:=FALSE, Enable:=TRUE). That is OK until I change devices and the new one requires the RUN bit to stay TRUE. Now I have to look in my code for everywhere I set the estop and change Drive1CW(Estop:=TRUE, Run:=FALSE, Enable:=TRUE) to Drive1CW(Estop:=TRUE, Run:=TRUE, Enable:=TRUE)! Also know that properties can also call methods, so if you need to do something every time a bit is set, you can put the "something" in the method and call the method in the property.

    Traditional vs OOP is a 47 day discussion and is ultimately a preference anyway. But in both of these solutions, if the bit coding needs to be altered, I only have to touch one FB.

     
  • tjarcoboerkoel - 2017-04-19

    Hi Scott,

    Thank you for your example. I'll try it out this weekend, a lot of thing going on at the moment.
    Yes, because of the embedded systems my first responds is to keep RAM resource usage as low as possible. I usually have to squeeze every ns from the (realtime) systems for control loops with 10us interrupts. It gives me confidence that you never ran into these limits.

    I have A Schneider Modicon M251 system with:
    - Analogue (temperature) and digital expansion modules
    - two Modbus TCP Remote PWM output modules for valve control (Moxa e1211)
    - A DucoBox focus mechanical ventilation with Modbus TCP,
    - Some energy meters (E&W) on modbus RTU,
    - Water-Water heatpump on modbus TCP.

    With this I need to create a Building management system, with web visualization (see attachment). So things need to be retained in memory for presentation.
    For temperature control I see the advantage of OOP. Write in the variable and trigger the PI-control loop at the same time. OOP seems to be perfect for this.

    Thing is, I'm not sure what to do with the Modbus registers. It's a bulk of data, without any meaning. For example, the DucoBox has 100 subnodes, and each subnode has 20 read and 20 write registers of the type "device" (which can be CO2 sensor, actuator, humidity sensor, main unit, manual switch, etc). So I started to:
    - Create Data Unit Types with structures for each device
    - Created a new union to have a unified "device structure overlay"
    - made an array of 100 of the type "device structure overlay"
    So, I now have memory blocks with a meaning. And now I'm stuck.

    The modbus registers are word addresses, and I don't seem to have an option to write the entire modbus data (pointer) to this array of devices (and back).
    Do you have a suggestion for this?

    Best regards,

    Schakelschema.pdf [351.62 KiB]

     
  • Anonymous - 2017-04-20

    Originally created by: scott_cunningham

    I've solved small Modbus needs by having an array of words and then copying each needed data from that array to my local speaking variables, including all of the conversions I need. This occurs every PLC scan. The classic "brute force method".

    You could possibly set up your structures to be REFERENCE TO WORD and set them to point to the needed Modbus WORD. (I don't think you can have the Modbus be REFERENCE TO you variables...). But you will still need to convert the raw data to something.

    It could be interesting to set up small FBs for each type of sensor, etc. So for the thermocouple, you have a FB_Thermocouple, which has a local var REFERENCE TO WORD, which you link to your Modbus register. Then you have an output var on the FB which is a REAL for the actual temp, which handles the actual conversion. Once you setup your FBs (objects), create a structure of FBs to match your repetitive IO blocks. I've never done that, so maybe it works, maybe it doesn't...

     

Log in to post a comment.