øõTRiLOGI Ver 5.0 ~ ~ 0,MBRequest 16,Req00 17,Req01 18,Req02 19,Req03 20,Req04 21,Req05 22,Req06 ~ 0,MBDelay 100 ~ ~ $ModbusMaster V2 6/6/19 Gary Dickinson, GDKDesignþ þ Demo code for how to write code for the PLC toþ implement Modbus RTU as a master device without usingþ the ReadModbus/WriteModbus or ReadMB2/WriteMB2 TBASIC statements.þþ This code can support all Modbus Function codes.þ This code is non-blocking so the PLC remains functional duringþ Modbus operations.þþ This code allows significant debugging capabilities.þþ This code can be run on all of the modern TRI PLCs.þþ This code is configured to use a TRI PLC as the slave device.þ %LInitModbus=1st.Scan $The RELAYS, Req00..Req06, invoke a CF to start a specific Modbus transaction.þþThe same RELAYs, Req00..Req06, are used to invoke a matching CF to process theþresponse from the Modbus slave device.þþIf you are setting these RELAYS using On-Line Monitoring you must right-clickþon the RELAY to ensure that it remains set until cleared by the PLC ladderþlogic.þ %LRdHldRegs=/MBRequest*Req00 %LWrt1Reg=/MBRequest*Req01 %LWrtNRegs=/MBRequest*Req02 %LRdCoils=/MBRequest*Req03 %LSetCoil=/MBRequest*Req04 %LClearCoil=/MBRequest*Req05 %LWrtNCoils=/MBRequest*Req06 $The RELAY, MBRequest, simply enables the TIMER, MBDelay. The SV of of MBDelay mustþbe carefully chosen. The SV must be large enough to ensure that the Modbus requestþis assembled, transmitted to the slave device, processed by the slave device, theþslave device must transmit it's response and the master device (this PLC) must bufferþthe full response.þþThe demo code has the SV set to 1000 ms. This is to allow debug. I suggest that youþadjust the SV until the down to the point at which the Modbus errors are detected and thenþdouble it for production usage. Most of my systems use a 50..100 ms delay.þþPlease remember that the MBDelay TIMER has been initialized to count down by 10 msþincrements rather than the usual 100 ms increment. A SV of 100 for the MBDelayþTIMER represents a delay of 1000 ms.þ MBDelay=MBRequest $The next set of rungs handles the response (or lack of) from the slave device.þþNote that the same RELAY that sent the Modbus response is used to invoke theþcorrect CF to handle the response. Also, note that as soon as the CF finishes,þthe RELAY is cleared.þþThis relay trick is the PLC equivalent of a callback function that is commonaltyþused in UNIX and other operating systems. TBASIC lacks any sort of computer goto/callþcapability, so this is my hack to extend the power of the tool set.þ %LRdHldRegsRsp|%DReq00=MBDelay*Req00 %LWrt1RegRsp|%DReq01=MBDelay*Req01 %LWrtNRegRsp|%DReq02=MBDelay*Req02 %LRdCoilsRsp|%DReq03=MBDelay*Req03 %LSetCoilRsp|%DReq04=MBDelay*Req04 %LClearCoilRsp|%DReq05=MBDelay*Req05 %LWrtNCoilsRsp|%DReq06=MBDelay*Req06 ~END_CIRCUIT~ È Fn#0,805 ' InitModbus - system initialization code to support Modbus RTU over RS485 port ' SetBAUD ModbusPort,6 ' RS-485 port 38.4K 8,1,N (Modbus interface) ' Please look at the #Define mechanism for the definition ' of ModbusPort. For your PLC you may need to change the ' port # or you may need to change the data configuration. SetProtocol ModbusPort, 10 ' we are using are own homemade version of Modbus RTU ' and this setting disables the PLC's firmware from ' messing with this port. If you omit this line of code ' you will be fighting with the low-level PLC firmware. Delay 10 ' 10 ms delay to ensure that ModbusPort UART is fully functional HSTIMER 1 ' Timer #1, MBDelay, is configured to work in units of 10 ms ' rather than 100 ms È Fn#1,2342 ' SendMBQuery - this function is called after the non-generic parts of the Modbus query ' (request packet) have been filled in. ' ' SendMBQuery calculates the size to the request packet based on the Modbus function code ' and the arguments and then calculates the packet CRC. ' ' Then this CF queues the entire packet to be transmitted by the PLC low-level firmware. ' SetIO MBRequest ' Indicate that we are handling a Modbus transaction MBStatus = MBSuccess ' assume that the request packet makes sense ' this status may change based on Modbus function code ' and/or packet size limitations. If you run into issue ' with these issues, edit this CF. DM[MBResponse] = EndOfMessage ' mark that no response has been received, yet ' Examine the Modbus function code to determine the request packet size ' if (MBQFunctionCode <= MBWriteSingleReg) ' These MB function codes have a fixed format with 2 16-bit arguments ' following the function code. ' n = 6 ' where the CRC belongs in the query elif (MBQFunctionCode = MBWriteMultiCoils) OR (MBQFunctionCode = MBWriteMultiRegs) ' These MB function codes support a variable number of arguments. ' The number of arguments are specified by the MBQByteCount value. ' n = MBQByteCount + 7 ' where the CRC belongs in the query if (n > (MBQuerySize - 2)) MBStatus = MBArgErr ' unsupported number of arguments return endif else ' Unsupported function code. I don't know how big the request packet ' should be. If you need to add support for another function code, ' then add an new "elif" before this "else" block for your ' new function code. ' MBStatus = MBFCErr ' unsupported MODBUS function code return endif ' If we made it this far, then we need to calculate a CRC for the query ' C = CRC16(DM[MBQuery], n) & &Hffff DM[MBQuery + n] = C / 256 DM[MBQuery + n + 1] = C & &Hff DM[MBQuery + n + 2] = EndOfMessage ' end of message marker ' Now send the contents of the MBQuery buffer to the MODBUS slave ' ' The packaged Modbus request is queued for transmission as rapidly ' as possible. This is done with the hope that the low-level PLC ' firmware will transmit the entire response packet without ' gaps in time between data bytes. ' n = MBQuery while(DM[n] <> EndOfMessage) OutCOMM MODBUSPort, DM[n] n = n + 1 endwhile È Fn#2,4869 ' ProcessMBRsp - Process response from most recent Modbus Request ' ' This custom function handles the generic part of a Modbus response packet. ' This CF: ' 1. Extracts the response packet from the low-level serial port buffer ' using the InCOMM() function. ' 2. Validates that the received response packet makes sense for the ' specific request that was issued. ' 3. Updates the MBStatus variable with the final results for the ' Modbus transaction. ' 4. Clears the MBRequest RELAY indicating that the Modbus Request has ' completed. ' ClrIO MBRequest ' indicate that we have handled the Modbus transaction ' I do this here because the code has multiple exit points (return statements) ' and this ensures that no matter why the code exits the RELAY has ' been dealt with n = 0 ' start at beginning of MBResponse buffer if (MBStatus <> MBSuccess) ' Modbus request was improperly formed and was not transmitted ' to the slave device. There will be no response from the slave ' to process. ' return endif ' there is sufficient delay in the PLC ladder logic to ensure that the Modbus query ' has been transmitted, received by the slave, processed by the slave, ' the response from the slave has been transmitted and buffered by the PLC. ' ' The expectation is that the entire response is buffered and can be read out and saved ' in the DM[]. The following performs this task: ' c = InCOMM(MODBUSPort) while (c <> -1) ' while response from MODBUS slave if (n < MBResponseSize) ' there is room in the local buffer for this response ' DM[MBResponse + n] = c n = n + 1 else ' The local buffer is full, ignore the character, ' empty the receive buffer and return an error value ' while (InCOMM(MODBUSPort) <> - 1) ' flush PLC serial receive buffer for this communication port ' endwhile MBStatus = MBBigRspErr ' No room at the inn... return endif c = InCOMM(MODBUSPort) ' get next character endwhile DM[MBResponse + n] = EndOfMessage ' mark end of message for debug ... if (n = 0) ' No characters received from slave. No response. ' ' There are lot's of reasons that this can happen: ' The master and slave are not configured for the exact same serial configuration ' BAUD rate, 8 data bits, no parity ... ' The serial cabling is messed up. Open wires, crossed. RS485 is a 3-wire connection. You must have ' a ground connection between the devices or you will have problems. ' The slave isn't working. No power ' The slave's Modbus ID is different that the ID in the request. ' And a bunch of other fun stuff. Good luck figuring this out... ' MBStatus = MBTimeout return endif ' At this point we have emptied the MDOBUS receive buffer. ' The assumption is that this represents the entire response. ' This is fairly reasonable as MODBUS RTU is spec'd to transmit data without ' gaps. ' ' Verify that received data is a valid by checking the CRC in the response. ' if (CRC16(DM[MBResponse], n)) ' CRC of entire response including the CRC did not equal &H0000 ' something is very wrong ' MBStatus = MBCRCErr ' CRC invalid return endif ' CRC is valid, so look at the first few characters to see if they make any sense ' as a MODBUS response ' if ((MBQSlaveAddr = MBRSlaveAddr) AND (MBQFunctionCode = MBRFunctionCode)) ' The response data from most Modbus function codes echo the first n data bytes ' from the original query into the response. I suspect that early Modbus slave ' devices didn't have enough memory to have a separate buffer for both the query ' and the response, so they overwrote the query buffer with the response data and ' then sent this back. ' ' I am not checking for this behavior. If you want to verify that the slave device ' is compliant with Modbus specifications then feel free to check the response for ' these additional data bytes. ' MBStatus = MBSuccess return ' exit ASAP as this is the expected response endif ' Something is a bit off in the response packet ' if (MBQFunctionCode ^ MBRFunctionCode) = &H0080 ' MODBUS slaves will set bit #7 in the function code to indicate ' that something in the query offended them. ' ' We are here because this an error report from the slave device. ' ' If you get these codes, it is probably because you are making requests of the ' slave device that it cannot handle. Do not ignore these errors. The slave device ' is trying to clue you in to what YOU are doing wrong. ' MBStatus = MBErrRsp ' Return value is an error msg from slave elif (MBQSlaveAddr <> MBRSlaveAddr) ' Slave address should match the query. ' Perhaps this is a response from a previous MB request. ' MBStatus = MBSlaveIDErr else ' Totally wrong function code. Perhaps from a previous MB request??? ' MBStatus = MBFCodeErr ' Function code value is nonsense endif È Fn#3,2370 ' RdNRegs - Read Holding Registers demo code. ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBReadHoldingRegs ' &H03 ' My demo code uses "zero-based" addressing. Apparently the concept of "0" was new ' when Modbus was defined. The original authors decided that they should document ' internal Modbus registers starting with addresses like 1,2,3... ' ' However, when they got to transmitting the register address over the serial lines ' they transmitted a value 1 less than what the documentation said. So if you wanted to ' access register 1, then you would send the numeric value of 0. If you wanted to ' access register 100 then you would send the value 99. ' ' My code is simply zero-based. The value you specify is exactly the same value as is ' transmitted. Please be aware that some Modbus devices are documented using the "+1" ' convention and others are "zero-based". In most cases, the documentation is unclear ' about this so you will need to write test code or use the CAS Modbus Scanner program ' to figure out how any given device actually works.' ' ' A large number of the data registers in TRI PLCs can be accessed via Modbus RTU. ' ' The following are the zero-based 16-bit data registers that you can access: ' ' TimerPV[x] zero-based addresses start at 128. Just add 127 to the "x" value ' CtrPC[x] zero-based addresses start at 256. Just add 255 to the "x" value ' TIME[x] zero-based addresses start at 512. Just add 511 to the "x" value ' DATE[x] zero-based addresses start at 516. Just add 515 to the "x" value ' ' DM[x] zero-based addresses start at 1000. Just add 999 to the "x" value ' DM32[x] zero-based addresses start at 1000. You must request 2 sequential ' 16-bit registers for each DM32[] value that you want to read. ' To calculate the zero-based address, y, for any given DM[x] access you ' can use the following equation: ' ' y = 2x + 998 ' ' FP[x] Same drill as DM32[x]: ' ' y = 2x + 998 ' ' Setup to read DM[11], DM[12], DM[13] and DM[14] from slave PLC ' ' The zero-based starting address for DM[11] is 1010: ' MBQStartAddrMSB = 1010 / 256 ' MSB of start address for DM[11] MBQStartAddrLSB = 1010 & &hff ' LSB of start address ' Number of registers to read is 4 (four 16-bit registers) ' MBQArg1MSB = &h00 MBQArg1LSB = &h04 Call SendMBQuery ' Calculate CRC and send the query È Fn#4,1909 ' RdHldRegsRsp - extract data from Read Holdings register response ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' extract four 16-bit values from the four 8-bit values sent by the slave device ' DM[1] = DM[MBRData + 0] * 256 + DM[MBRData + 1] DM[2] = DM[MBRData + 2] * 256 + DM[MBRData + 3] DM[3] = DM[MBRData + 4] * 256 + DM[MBRData + 5] DM[4] = DM[MBRData + 6] * 256 + DM[MBRData + 7] ' Q: What if what you wanted was 32-bit data from the slave? ' A: You may be screwed. Modbus has no rules for 32-bit data. You will ' have to make tests to see how your Modbus slave behaves. ' ' On the TRI PLC's DM[1..4] can be accessed as DM32[1] and DM32[2]. The code ' shown above will put all of the data in the correct order. ' /* ' Alternatively, you could build DM32[1] and DM32[2] like this: ' DM32[1] = DM[MBRData + 0] * &h01000000 + DM[MBRData + 1] * &h00010000 + DM[MBRData + 2] * &h00000100 + DM[MBRData + 3] DM32[2] = DM[MBRData + 4] * &h01000000 + DM[MBRData + 5] * &h00010000 + DM[MBRData + 6] * &h00000100 + DM[MBRData + 7] ' it is, also, possible to assemble 32-bit using the SetHigh16 statement. ' I have made measurements of the code execution time and the use of ' SetHigh16 is slower than building the results into a pair of DM[] (16-bit) ' registers. ' DM32[1] = DM[MBRData + 2] * 256 + DM[MBRData + 3] ' LSW SetHigh16 DM32[1], DM[MBRData + 0] * 256 + DM[MBRData + 1] ' MSW ' I suggest that you take the approach that makes the most sense to you, as ' you are the one that has to make the code work. ' */ else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif È Fn#5,1526 ' RdCoils - demo code to build Modbus request to read 24 coil values ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBReadCoils ' &H01 ' My code is zero-based for Modbus addresses. ' The value you specify as an address is exactly what istransmitted. ' ' Please be aware that some Modbus devices are documented using the "+1" ' convention and others are "zero-based". In most cases, the documentation is unclear ' about this so you will need to write test code or use the CAS Modbus Scanner program ' to figure out how any given device actually works.' ' ' A large number TRI PLC 1-bit things can be accessed using the Coil functions: ' ' The following are the zero-based 1-bit items that you can access via Modbus: ' ' INPUT #x zero-based addresses start at 0. Just add -1 to the "x" value ' OUTPUT #x zero-based addresses start at 256. Just add 255 to the "x" value ' TIMER #x zero-based addresses start at 512. Just add 511 to the "x" value ' COUNTER #x zero-based addresses start at 768. Just add 767 to the "x" value ' RELAY #x zero-based addresses start at 1024. Just add 1023 to the "x" value ' The goal is to read the state of 24 TRI PLC RELAYS 1..23 ' ' The starting address to access RELAY 1 is 1024. Just add 1023 to the RELAY number ' MBQStartAddrMSB = 1024 / 256 ' MSB of start address for RELAY 1 MBQStartAddrLSB = 1024 & &hff ' LSB of start address MBQArg1MSB = 0 ' Number of coil to read is 24 MBQArg1LSB = 24 Call SendMBQuery ' Calculate CRC and send the query È Fn#6,1908 ' RdCoilsRsp - extract 24-bit of coil data ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' assemble 16-bit value from the 3 8-bit values returned by the slave device ' ' I am going to warn you that the Modbus behavior for the Read Coils function code ' is probably not what you would imagine. If you issued a command to read coils 1..24 ' what you will get back is 3 bytes of response data: ' ' DM[MBRData + 0] will hold the state of coils 1..8 (bits 7..0) ' DM[MBRData + 1] will hold the state of coils 9..16 (bits 7..0) ' DM[MBRData + 2] will hold the state of coils 17..24 (bits 2..0) ' ' Additionally if the number of coils that was requested is not evenly divisible ' by 8 then then last data byte of the response will be padded with 0s. ' ' Read the Modbus spec and examine very closely how your Modbus device behaves. Don't ' be surprized if the device that you are working with doesn't comply with the Modbus ' specifications. ' The following code just copies the response data into successive DM[] locations ' ' This may not be the most useful thing that you can do with coil data. ' ' In some cases I assemble the data into 16-bit chucks and then write the 16-bit ' version of coil data to one of the RELAY[n] registers. This makes the individual ' coil data bits directly visible to the ladder logic. It is also useful for debug ' ' Think something like this: ' ' RELAY[2] = DM[MBRData + 0] * 256 + DM[MBRData + 1] ' RELAY[1] = DM[MBRData + 2] * 256 ' DM[11] = DM[MBRData + 0] DM[12] = DM[MBRData + 1] DM[13] = DM[MBRData + 2] else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif È Fn#7,453 ' SetCoil - demo code to set a single coil ON ' ' Set a single coil "ON", in this case RELAY #4 for a TRI PLC ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBWriteSingleCoil ' &H05 MBQStartAddrMSB = 1027 / 256 ' MSB of start address for RELAY 4 MBQStartAddrLSB = 1027 & &hff ' LSB of start address MBQArg1MSB = &hff ' &Hff00 sets coil ON MBQArg1LSB = &h00 Call SendMBQuery ' Calculate CRC and send the query È Fn#8,445 ' SetCoilRsp - check response to set the coil ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' Nothing much to do. ' else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif È Fn#9,455 ' ClearCoil - demo code to clear a single coil ' ' Clear a single coil, in this case RELAY #3 for a TRI PLC ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBWriteSingleCoil ' &H05 MBQStartAddrMSB = 1027 / 256 ' MSB of start address for RELAY 4 MBQStartAddrLSB = 1027 & &hff ' LSB of start address MBQArg1MSB = &h00 ' &h0000 sets clears coil MBQArg1LSB = &h00 Call SendMBQuery ' Calculate CRC and send the query È Fn#10,458 ' ClearCoilRsp - check response to clearing a single coil ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' Nothing much to do. ' else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif È Fn#11,584 ' Wrt1Reg - demo code for Modbus Write Single Register function code &h06 ' ' Write a 16-bit value to the slave's DM[12] register ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBWriteSingleReg ' &H06 Write Single Register MBQStartAddrMSB = 1011 / 256 ' MSB of start address for DM[12] MBQStartAddrLSB = 1011 & &hff ' LSB of start address ' Write the 16-bt value &h1234 --> Slave's DM[12] ' MBQArg1MSB = &h12 ' most significant byte of &1234 MBQArg1LSB = &h34 ' least significant byte of &h1234 Call SendMBQuery ' Calculate CRC and send the query È Fn#12,473 ' Wrt1RegRsp - check response to writing a new target frequency to the VFD ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' Nothing much to do. ' else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif È Fn#13,841 ' WrtNCoils - demo code to build request to write multiple coils ' ' The goal is to write values to 5 sequential RELAYS 33..37 ' ' The TRI PLCs do not support this Modbus Function code. If you issue this Modbus ' request you will get an error response from the TRI PLC of "1" which indicates ' that the slave device does not support this function code. ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBWriteMultiCoils ' &H0f n = 33 + 1023 ' Stating Coil in TRI slave PLC (RELAY #31) MBQStartAddrMSB = n / 256 ' Starting Coil address MSB MBQStartAddrLSB = n & &hff ' Starting Coil address LSB MBQArg1MSB = 0 ' Number of coil to write is 5 MBQArg1LSB = 5 MBQByteCount = 1 ' Byte Count DM[MBQData + 0] = &H15 ' Ouput value Call SendMBQuery ' Calculate CRC and send the query È Fn#14,925 ' WrtNCoilsRsp ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' The TRI PLCs don't support the Write Multiple Coils function code ' ' If you attempt to run this code on a TRI PLC, don't expect it to work. ' you shouldn't end up here. ' A = A elif MBStatus = MBErrRsp ' The slave responded with an error response by setting the Function code's most significant ' bit to a 1. The function code value in the response would be &h8f. Then next byte in the ' response is the exception code. ' A = MBRByteCount ' the exception code is at the same location as a ByteCount would have been. else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif È Fn#15,1041 ' WrtNRegs - demo code to build Modbus request to write multiple 16-bit slave registers ' ' Write a 16-bit value to the slave's DM[11..14] registers ' MBQSlaveAddr = SlaveAddr ' Modbus Slave ID MBQFunctionCode = MBWriteMultiRegs ' Write Multiple Registers MBQStartAddrMSB = 1010 / 256 ' MSB of start address for DM[11] MBQStartAddrLSB = 1010 & &hff ' LSB of start address ' Write 4, 16-bit registers ' n = 4 ' number of registers to write, 4 MBQArg1MSB = n / 256 ' Quantity of register to write, MSB MBQArg1LSB = n & &hff ' Quantity of register to write, LB MBQByteCount = n * 2 ' number of data bytes in Modbus RTU request packet ' that will follow (these are the arguments). DM[MBQData + 0] = &h01 ' Data for DM[11] DM[MBQData + 1] = &h23 DM[MBQData + 2] = &h45 ' Data for DM[12] DM[MBQData + 3] = &h67 DM[MBQData + 4] = &h89 ' Data for DM[13] DM[MBQData + 5] = &hab DM[MBQData + 6] = &hcd ' Data for DM[14] DM[MBQData + 7] = &hef Call SendMBQuery ' Calculate CRC and send the query È Fn#16,815 ' PrWrMltCoils ' Call ProcessMBRsp ' process Modbus response (if any) if MBStatus = MBSuccess ' The documented response for the Write Multiple Coils function ' includes an echo of the Stating address and the quantity of coils to write ' ' I am just too lazy to verify that slave behaves according to Modbus protocol ' elif MBStatus = MBErrRsp ' If running this code against a TRI PLC you should get an error response packet ' A = MBRByteCount ' extract the error code value. It's at this location in response... else ' Modbus error ' ' This would be a good place to write some code to keep track of non-responsive ' slave devices... ' ' Think retries... ' ' But what I am going to do is put in a dummy statement that you can set a breakpoint ' and debug what went wrong. ' A = A endif ~END_CUSTFN~ 0,InitModbus 1,SendMBQuery 2,ProcessMBRsp 3,RdHldRegs 4,RdHldRegsRsp 5,RdCoils 6,RdCoilsRsp 7,SetCoil 8,SetCoilRsp 9,ClearCoil 10,ClearCoilRsp 11,Wrt1Reg 12,Wrt1RegRsp 13,WrtNCoils 14,WrtNCoilsRsp 15,WrtNRegs 16,WrtNRegRsp ~END_CUSTFNLABEL~ 0,0, 1,0, 2,0, 3,0, 4,0, ~END_QUICKTAGS~ 0,__Modbus__,Definitions 1,ModbusPort,2 2,SlaveAddr,2 3,__Query__, 4,MBQuery,801 5,MBQuerySize,30 6,MBQSlaveAddr,DM[801] 7,MBQFunctionCode,DM[802] 8,MBQStartAddrMSB,DM[803] 9,MBQStartAddrLSB,DM[804] 10,MBQArg1MSB,DM[805] 11,MBQArg1LSB,DM[806] 12,MBQByteCount,DM[807] 13,MBQData,808 14,MBQDataEnd,830 15,__Response__, 16,MBResponse,831 17,MBResponseSize,30 18,MBRSlaveAddr,DM[831] 19,MBRFunctionCode,DM[832] 20,MBRByteCount,DM[833] 21,MBRData,834 22,MBRDataEnd,860 23,MBStatus,DM[861] 24,__ModbusDefs__,MB Constants 25,EndOfMessage,&h7fff 26,MBSuccess,0 27,MBFCErr,1 28,MBArgErr,2 29,MBBigRspErr,3 30,MBCRCErr,4 31,MBSlaveIDErr,5 32,MBFCodeErr,6 33,MBTimeout,7 34,MBErrRsp,8 35,__MBFunctionCodes__,MB Defines 36,MBReadCoils,&h01 37,MBReadHoldingRegs,&h03 38,MBReadInputRegs,&h04 39,MBWriteSingleCoil,&h05 40,MBWriteSingleReg,&h06 41,MBWriteMultiCoils,&h0f 42,MBWriteMultiRegs,&h10 ~END_DEFINES~ ~END_BREAKPOINTS~ 192.168.1.24:9080 ~END_LASTIPADDR~