Post by john-robinson on Limiting Memory Access of an Array to Within its Bounds
CODESYS Forge
talk
(Post)
Recently we had an issue regarding some simple code to calculate a rolling average. The code indexes from zero to 199 to properly store the current input into a circular buffer which then allows us to calculate a rolling average: VAR input_5s : REAL; outs_arr : ARRAY[0..199] OF REAL; i : USINT := 0; END_VAR ___ //this code runs every five seconds, calculating a rolling average outs_arr[i] := input_5s; i := i + 1; output := OSCAT_BASIC.ARRAY_AVG(ADR(outs_arr), SIZEOF(outs_arr)); IF i >= SIZEOF(outs_arr) THEN i := 0; END_IF There is a simple bug in this code where the index will be set to 0 when it has surpassed the length of the array in bytes (800 in this case) rather than larger than the number of reals in the array (200). The solution here is simple, replacing i >= SIZEOF(outs_arr) with i >= SIZEOF(outs_arr)/SIZEOF(outs_arr[0]). In this example when the index increased to 201 and the line outs_arr[201] := input_5s was called, codesys arbitrarily wrote to the address in memory that is where outs_arr[201] would be if the array was that long. I would like to find a way to wrap the codesys array inside of a wrapper class that checks if an input is within the bounds of an array before writing to that value. I know how I would implement that for a specific array, I could create a method or class that takes an input of an array of variable length, ie. ARRAY[*] OF REAL, but I don't know how to make this for any data type. I am wondering if anyone has ever done anything similar to this, or has any better suggestions to ensure that none of the programmers on this application accidentally create code that can arbitrarily write to other locations in memory.
Last updated: 2024-03-05
Post by duvanmoreno24 on Modbus writing on value change
CODESYS Forge
talk
(Post)
Hi all, I want to know if someone has an idea of how I can write on value change in Modbus Codesys. I have a Wago PLC and I was used to work with E-cockpit which it was quite easy to do that without the necessity to trigger any value when there was a change in the variable ( I will put how easy is ). how you can see just changing the trigger in "On value Change" will do that channel writing automatically when It detects a change in those arrays. On the other hand, in Codesys if I enable the rising edge in Codesys It ask me to put a bool variable and if triggers is going to write that value. That is making me that I have to create a function or a logic to detect the change, the problem I have is that doing that is very tedious. I first approach I got it was to create a Function who returns a bool when the value change, but I tried to keep the old value but what is happening is that in Functions all the data is erased every cycle so I can not keep any Old value. so in the Main program the trigger is going to be TRUE all the time due, the old value is cero every cycle. The second approach I got it was using a function Block (POU_1) and it works but I dont want to instance that function for every Channel or value that I want to check if the value change, Basically if I have 200 values to write trhough modbus I have to create 200 instances of that function which I think it is not practicall at all. It should be a better way to implement this as e-Cockpit from Wago Does. However, I haven't been able to know how.
Last updated: 2024-03-26
Post by gilbert-mh on CAA net base TCP client cause PLC to crash - Kernel message : N0HZ_local_softirq_pending 80
CODESYS Forge
talk
(Post)
Hello all, I have been trying to implement a TCP client on a Festo PLC (CPX-E-CEC-M1) and it looks like it works well except that after some time (greatly varies between a few hours to more than 100h) my PLC crash. When I look into the log file the only thing I see is that before the crash happens a few kernel warnings : N0HZ_local_softirq_pending 80 and then the crash. I've looked into this warning and from what I could find on the net it seems that this is warning is triggered when the ethernet link is down. I've tried to correct this bug for quite some time and what I know is that : - The crash is caused by my TCP client, when I remove it from my code I see no crash - The crash happens more quickly the more the TCP client is used. - The time before the crash is not directly proportional to the number of communications or their size. But it looks like it is just more likely to happen if the client connect to the server at a higher frequency. - The precedent observation makes it seem unlikely that the crash is caused by some memory overflow because then the crash speed would be more proportional to the amount of data exchanged. SO from these observations, I believe that the crash could be caused by the PLC trying to connect to a server while there is some kind of issue with the ethernet link resulting in the PLC getting stuck in some indefinite state and making it crash. This still seems a bit unlikely to me because if the ethernet is down it simply shouldn't be able to contact the server and the communication would just fail which doesn't cause my PLC to crash. Has anyone encountered the same kind of problem (with the same kernel message) ? I am pretty sure the warning is not the direct cause of the crash but just an indicator that something is wrong with my PLC. Thanks in advance
Last updated: 2024-01-12
Post by ofey on EtherCAT fieldbus
CODESYS Forge
talk
(Post)
Hi, everyone! I'm trying to set up a PLC controller and a connection to an EtherCAT slave device in Codesys. I want to create a flexible program that I can upload to multiple controllers with different remote IO connected (same program). On one plant i may have 5 AI-cards and 3 DO-cards, and on another I may have 4 AI-cards and 2 DO-cards. For not needing to maintain several different programs wih different devices defined in the program (one for each set up) I thought that using a remote IO would make it easier having a single program. That way I could refer to different memory addresses instead of predefined slots/channels and IO's, that would give me errors if there was a different IO on the plant than what the program expected. When I tried setting up the etherCAT master, I saw that I had to define the etherCAT slave devices with the different IO'cards for me to be able to refer to the memory addresses in a PRG. Exactly what I was trying to avoid. My setup is something like a program that can handle 16 separate pump controls. In a year maybe 6 plans get deployed, and depending on how large the project is, the number of pumps can vary between 4 and 16. And the managers dont want to have IO's for all 16 pumps on every cabinet, and I dont want to maintain 16 separate projects files in case of updates etc. I thought the best way to tackle this was having a single project that read/write data to the different pump IO's by remote IO (fieldbus ethercat) addresses. And the number of pump controls are activated by an external GUI. If pump 1-6 is activated by the GUI, then the PLC-program tries to read/write input/outputs from predefined addresses for the expected IO's. My test setup is a PFC200 WAGO controller and a EtherCAT fieldbus coupler (750-354) with some IO. I hope I didn't explain this too horrible, and if there is a more easy and elegant solution for this challenge I appreciate a feedback on this.
Last updated: 2024-04-08
Post by greenwood on CODESYS Control Raspberry Pi mit Servotreiber T6 von StepperOnline
CODESYS Forge
talk
(Post)
Hallo, ich versuche, eine Modbus-RTU-Kommunikation zwischen meinem Raspberry Pi mit CODESYS Control für Raspberry Pi 64 SL und einem Servotreiber von StepperOnline, Typ T6, herzustellen. Die Verbindung ist wie folgt: RJ45-Stecker am Servotreiber -> Kabel mit RJ45 an einem Ende und USB-A-Stecker am anderen Ende -> Seriell-zu-USB-Konverter -> Raspberry Pi. Der Seriell-zu-USB-Konverter und die Kabel habe ich zusammen mit dem Motor und Treiber von StepperOnline gekauft und sie sind dafür gedacht, den Servotreiber mit einem Computer zu verbinden, auf dem deren Setup-Software läuft. dmesg | grep tty auf dem Pi sagt mir, dass der USB-zu-Seriell-Konverter auf ttyusb0 ist. Ich weiß nicht, wie man das in einen COM-Port übersetzt, ich habe COM 1 genommen. Ich habe ein Projekt in Codesys erstellt und ein Modbus_COM-Gerät hinzugefügt, einen Modbus_Master_COM_Port und einen Modbus_Slave_COM_Port angehängt. Auf der Registerkarte "Allgemein" des Modbus_COM habe ich die folgenden Werte eingestellt: Slave address 1 Baud rate 9600 Parity None Data bits 8 Stop bits 2 Ich habe den Servotreiber auf die gleichen Werte eingestellt. (Ich habe auch andere Werte getestet, aber mit dem gleichen Ergebnis). Auf der Registerkarte "Modbus Slave Channel" des Modbus_Slave_COM_Port habe ich einen Kanal hinzugefügt und die folgenden Werte eingetragen: Access type Read Holding Registers (Function Code 3) Read Register offset 0x0000 Length 1 Ich habe noch keinen Code geschrieben, weil ich noch nicht herausgefunden habe, wie man die Kommunikation programmiert. Wenn ich das Projekt zum Raspberry Pi herunterlade scheint der Modbus_Master_COM_Port zu laufen (grünes Symbol), aber der Modbus_Slave_COM_Port nicht (rotes Dreiecksymbol). Wenn ich einen anderen COM-Port eintrage, haben sowohl der Master als auch der Slave das rote Dreiecksymbol. Ich habe dies auch mit meinem Windows-PC unter Verwendung von Codesys Control Win 64 versucht und die gleichen Ergebnisse bekommen. Ich wäre dankbar für jede Hilfe oder Tipps, wie ich den Grund dafür herausfinden kann, warum der Servotreiberreiber nicht reagiert.
Last updated: 2024-05-31
Post by george32 on CSV file and string manipulation.
CODESYS Forge
talk
(Post)
Dear folks, I think I have a rather simple question but I could not find the right answer to my question: I have made with Excel a CSV file where I would like to have some general data regarding my program variables. I have made an program what let me read the file. The string I am currently get is at follows: 'IP_Adres;192.168.45.12$R$NPort_number;2000$R$NCycle_time;43$R$NStart_Standard_IO;20$R$N' Now I want to split the string in multiple part, which I later would connect to the right variable. By Google and experimenting I have reached to the following code for the first part of the splitting proces: // Splitting the BOM of the string: Received_string := FileReadString; IF LEFT(STR:=New_string,3)= '' THEN Received_string_without_BOM :=RIGHT(STR:= Received_string,SIZE:= (LEN(STR:= Received_string))-3); END_IF //Splitting the remaining string in part for later declaration. WHILE index = 0 DO index_split_part := FIND(STR1:= Received_string_without_BOM,STR2:= '$R$N'); Part_of_String[index]:=LEFT(STR:=Received_string_without_BOM, SIZE:= index_split_part); index := index + 1; END_WHILE However in the splitting proces I could not understand what is really happening. I understand that the Find() function returns the first value the $R$N in the Received_string_without_BOM. This would mean that the index_split_part := 23 I|P| _ |A |d|r|e|s|;|1_|9 |2 |. |1 |6 |8 |. |4 |5 |. |1 |2 |$ |R |$ |N |P | 1|2| 3 |4 |5|6|7|0|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27| So the next part is to read the first 23 characters of the Received_string_without_BOM with the LEFT() function. I expected that the outcome the following was: 'IP_Adres;192.168.45.12$'. However the outcome is: 'IP_Adres;192.168.45.12$R'. I do not understand where the R after the $ sign comes from, because its place is 24 so it would not be added to the part_of the_string[index]. If I hard coded value 24 for the size it gives me the following return: 'IP_Adres;192.168.45.12$R$N'. I would expect everything till the R but the code adds the $N also to the string. I hope someone could explain to my what I am seeing wrong in my point of view? With kind regards, George
Last updated: 2024-09-27
Post by ihatemaryfisher on Sorting array of any-sized structure
CODESYS Forge
talk
(Post)
In my machine's operation, I need to display multiples tables containing arrays of structured variables. The arrays change during operation, and my supervisor has advised me to write a new bubble-sort for each array. I think I can make a function to sort an array of any data type. This was my own project, and I'm a relatively new coder. I want to know the weaknesses in my approach, and a better method, if one exists. As far as I can test, the function accepts an array of a structured variable of any size, and sort it by any VAR in that structure. But it relies heavily on pointers, which I've heard are bad practice? Function call: // SORT BY BYTE-SIZED VAR IF xDoIt[6] THEN FUNBubbleSortSansBuffer( IN_pbySourcePointer := ADR(astArray[1]), // address of first byte in first element of array IN_pbyComparePointer:= ADR(astArray[1].byCompByte), // points to first byte of the comparing variable (variable you sort by) IN_uiStructureSize := SIZEOF(TYPE_STRUCTURE), // size, in bytes, of the structured variable IN_uiCompareSize := SIZEOF(astArray[1].byCompByte), // size, in bytes, of the comparing variable (variable you sort by) diArrayElements := UPPER_BOUND(astArray,1), // number of elements in array IN_xSmallToLarge := xSortOrder // whether to sort by small2large or large2small ); END_IF Function: FUNCTION FUNBubbleSortSansBuffer : BOOL VAR_INPUT IN_pbySourcePointer : POINTER TO BYTE; // points to beginning of array (first byte of first element) IN_pbyComparePointer: POINTER TO BYTE; // points to first byte of the comparing variable (variable you sort by) IN_uiStructureSize : UINT; // size, in bytes, of the structured variable IN_uiCompareSize : UINT; // size, in bytes, of the comparing variable (variable you sort by) diArrayElements : DINT; // number of elements in array IN_xSmallToLarge : BOOL; // whether to sort by small2large or large2small END_VAR VAR j : DINT; // repeat iteration over array until array ends i : DINT; // iterarte over array, swapping when necesary k : DINT; // iterator from 1 to size of structure (stepping 'through' a single element in array) dwSize : DWORD; // internal var for use in MEMUtils.MemCpy(<size>) // FOR SORTING BY BYTE VAR pbySourcePointer : POINTER TO BYTE; pbySourcePointer2 : POINTER TO BYTE; pbyComparePointer : POINTER TO BYTE; pbyComparePointer2 : POINTER TO BYTE; pbyPointerToBuffer : POINTER TO BYTE; // pointer to single byte buffer byBufferByte : BYTE; // single byte buffer END_VAR dwSize := UINT_TO_DWORD(IN_uiStructureSize); // get structure size (number of bytes) pbyPointerToBuffer := ADR(byBufferByte); // assign pointer to address of buffer byte (because MEMUtils.MemCpy requires a pointer input) CASE IN_uiCompareSize OF // depending on the size of the VAR to sort by (current functionality for BYTE and WORD/INT 1: // BYTE (8 BIT) FOR j := 1 TO diArrayElements DO // for number of elements in array FOR i := 1 TO (diArrayElements-1) DO // same thing, but row[i+1] row is included in swap logic pbySourcePointer := IN_pbySourcePointer + dwSize*(i-1); // point at #1 byte in array element[i] pbySourcePointer2 := pbySourcePointer + dwSize; // point at #1 byte in array element[i+1] // NOTE: because of memory locations, each array element is offset from one another by a number of bytes equal to the size of the structure // We can "walk" from array[i] to array[i+1] via steps equal to the size of the structure // e.g., ADR(array[i+1]) == ADR(array[i]) + SIZEOF([array datatype]) pbyComparePointer := IN_pbyComparePointer + dwSize*(i-1); // point to sorting variable in array element[i] pbyComparePointer2 := pbyComparePointer + dwSize; // point to sorting variable in array element[i+1] // using sort order (small -> large/large -> small) IF SEL(IN_xSmallToLarge, (pbyComparePointer2^ > pbyComparePointer^),(pbyComparePointer2^ < pbyComparePointer^)) THEN // This is where it gets tricky. We've identified pointers for the starting bytes of aArray[i] and aArray[i+1] // and we know the size of aArray[i]. We are going to swap individual bytes, one at a time, from aArray[i] and aArray[i+1] // this allows us to use only a single byte var as a buffer or temporary data storage // e.g., consider a structure consisting of a word, a byte, and a string. it is stored like this // |------WORD-------| |--BYTE-| |STRING------...| // astArray[1] == 1000 0100 0010 0001 1100 0011 1010 1010.... etc // astArray[2] == 0001 0010 0100 1000 0011 1100 0101 0101.... etc // performing a single swap (copy into a buffer, etc.) of the first byte of each array element creates this // astArray[1] == 0001 0100 0010 0001 1100 0011 1010 1010.... etc // astArray[2] == 1000 0010 0100 1000 0011 1100 0101 0101.... etc // incrementing the pointer adresses for the swap by 1 and swapping again swaps the next byte in each array element // astArray[1] == 0001 0010 0010 0001 1100 0011 1010 1010.... etc // astArray[2] == 1000 0100 0100 1000 0011 1100 0101 0101.... etc // continuing this from k to SIZEOF(TYPE_STRUCTURE) results in a toally swapped row FOR k := 1 TO IN_uiStructureSize DO // copy single byte[k] of array element 1 to buffer MEMUtils.MemCpy(pbyDest := (pbyPointerToBuffer), pbySrc := (pbySourcePointer+k-1), dwSize := 1); // copy single byte[k] of array element 2 to 1 MEMUtils.MemCpy(pbyDest := pbySourcePointer+k-1, pbySrc := (pbySourcePointer2+k-1), dwSize := 1); // copy buffer to byte[k] array element 2 MEMUtils.MemCpy(pbyDest := (pbySourcePointer2+k-1), pbySrc := pbyPointerToBuffer, dwSize := 1); END_FOR END_IF END_FOR END_FOR
Last updated: 2023-08-17
Post by mondinmr on Direct Pointers in IOMapping for EtherCAT with IoDrvEthercatLib.ETCSlave_Dia
CODESYS Forge
talk
(Post)
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
Last updated: 2024-02-13
To search for an exact phrase, put it in quotes. Example: "getting started docs"
To exclude a word or phrase, put a dash in front of it. Example: docs -help
To search on specific fields, use these field names instead of a general text search. You can group with AND
or OR
.