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
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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>
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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*/TYPEFLOOR1:Â Â STRUCTÂ Â Â Â FloorValve2A: Â Â Â Â Â Â BIT:=FALSE;Â Â (*Hal+Gangbegandegrond*)Â Â Â Â FloorValve3A:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Woonkamer,verdeeldin3zones*)Â Â Â Â FloorValve3B:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Woonkamer,verdeeldin3zones*)Â Â Â Â FloorValve3C:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Woonkamer,verdeeldin3zones*)Â Â Â Â FloorValve4A:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Keuken,verdeeldin3zones*)Â Â Â Â FloorValve4B:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Keuken,verdeeldin3zones*)Â Â Â Â FloorValve4C:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Keuken,verdeeldin3zones*)Â Â Â Â FloorValve6A:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Berging,verdeeldin2zones*)Â Â Â Â FloorValve6B:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Berging,verdeeldin2zones*)Â Â Â Â FloorValve7A:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Badkamer+Toilet*)Â Â Â Â FloorValve8A:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Werk/slaapkamer,verdeeldin2zones*)Â Â Â Â FloorValve8B:Â Â Â Â Â Â BIT:=FALSE;Â Â (*Werk/slaapkamer,verdeeldin2zones*)Â Â Â Â Â Â Â Â FloorValve10A: Â Â Â Â Â Â BIT:=FALSE;Â Â (*Overloop1steverdieping*)Â Â Â Â FloorValve11A: Â Â Â Â Â Â BIT:=FALSE;Â Â (*Slaapkamer1steverdieping*)Â Â Â Â FloorValve12A: Â Â Â Â Â Â BIT:=FALSE;Â Â (*Slaapkamer1steverdieping*)Â Â Â Â FloorValve13A: Â Â Â Â Â Â BIT:=FALSE;Â Â (*Slaapkamer1steverdieping*)Â Â END_STRUCTEND_TYPE
/*GVLcode*/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
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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,
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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_BLOCKCwEncodeVAR_INPUT  Enable:BOOL;  Run  :BOOL;  EStop :BOOL;END_VARVAR_OUTPUT  CtrlWd:INT;END_VARCtrlWd.0:=Enable;CtrlWd.1:=Run;CtrlWd.2:=EStop;END_FUNCTION_BLOCKPROGRAMPLC_PRGVAR  Drive1CW    :CtrlWdEncode;  Drive2CW    :CtrlWdEncode;  MachineEStop  :BOOL;  EnableDrives  :BOOL;  RunDrive1   :BOOL;  RunDrive2   :BOOL;END_VARDrive1CW(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:
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_BLOCKCtrlWdEncodeVAR_OUTPUT  CtrlWd:INT;END_VARPROPERTYEnable:BOOLSETCtrlWd.0:=Enable;PROPERTYRun:BOOLSETCtrlWd.1:=Run;PROPERTYEStop:BOOLSETCtrlWd.2:=EStop;END_FUNCTION_BLOCKPROGRAMPLC_PRGVAR  Drive1CW    :CtrlWdEncode;  Drive2CW    :CtrlWdEncode;  MachineEStop  :BOOL;  EnableDrives  :BOOL;  RunDrive1   :BOOL;  RunDrive2   :BOOL;END_VARDrive1CW.Enable:=EnableDrives;//CtrlWdisupdatedDrive1CW.Run:=RunDrive1;//CtrlWdisupdatedDrive1CW.EStop:=MachineEStop;//CtrlWdisupdatedDrive2CW.Enable:=EnableDrives;//CtrlWdisupdatedDrive2CW.Run:=RunDrive2;//CtrlWdisupdatedDrive2CW.EStop:=MachineEStop;//CtrlWdisupdatedEND_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:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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...
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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
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.
Hi,
Thanks for your reply. Your comment is not correct, when using as STRUCT they ARE bitwise members. See documentation:
@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
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.
I get an error, but I don't really understand it. The 'name' is not the same.. Why should the name be the same..?
/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
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!
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.
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,
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:
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:
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":
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:
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.
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]
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...