Functions
The Coyote IDE compiler supports four categories of functions:
General Functions
Modbus Remote Functions
Serial Functions
Timer Functions
General Functions
There is only one general function available in the Coyote IDE compiler, abs.
abs
abs calculates the absolute value of an integer.
Syntax: abs( expr )
abs(-5) would return 5. abs(12) would return 12.
Modbus Remote Functions
Modbus remote functions send requests to other Modbus devices and handle their responses. They cannot be used to read Modbus registers on the host device. Those registers should be accessed directly.
There are four Modbus remote functions for reading Modbus registers, two for writing Modbus registers, and one for handling Modbus errors.
Note:
The port number in these function calls are zero-based, just like everything else in C. Is this the same as the Modbus protocol? Yes and no. To access the first discrete input port, you would send a Starting Address of 0x0000, but the Modbus documentation refers to this as port 1.
So, when using the MBGet___ and MBPut___ calls, be sure to subtract 1 from the published port number you are trying to access.
MBGetDI
MBGetDI reads a discrete input from another Modbus device with a Read Discrete Inputs (0x02) Modbus command.
Syntax: MBGetDI( MBAddr, PortNum )
The MBGetDI function sends a Read Discrete Inputs request to a Modbus device at Modbus address MBAddr and requests the value of discrete input identified by the zero-based PortNum.
If the request is successful, then the MBGetDI function will return the value read. If the request fails, then the return value is undefined. It does not return anything specific to indicate a failure. Always call the MBGetErr function afterwards to test for success.
MBGetDO
MBGetDO reads a discrete output from another Modbus device with a Read Coils (0x01) Modbus command.
Syntax: MBGetDO( MBAddr, PortNum )
The MBGetDO function sends a Read Coils request to a Modbus device at Modbus address MBAddr and requests the value of discrete output identified by the zero-based PortNum.
If the request is successful, then the MBGetDO function will return the value read. If the request fails, then the return value is undefined. It does not return anything specific to indicate a failure. Always call the MBGetErr function afterwards to test for success.
Note:
This function returns 0 for off and 1 for on. It does not return 0xFF00.
MBGetErr
MBGetErr returns the error code from the last MBGet___ or MBPut___ call made.
Syntax: MBGetErr()
The MBGetErr function will either return an error code sent from the remote device or a special code to indicate a failure to contact that device. Known responses (although it is possible that a device would return a custom error code that is not listed below) are:
Code |
Name |
0x00 |
NoErr |
0x01 |
IllegalFuction |
0x02 |
IllegalDataAddr |
0x03 |
IllegalDataValue |
0x04 |
SlaveDeviceFailure |
0x05 |
Acknowledge |
0x06 |
SlaveDeviceBusy |
0x08 |
MemoryParityError |
0x0A |
GatewayNoPath |
0x0B |
GatewayNoResponse |
0xFD |
NoResponse |
The MBGetErr function will return an Acknowledge error if the Modbus device received a response to the last request, but the response did not match the request. It will return a NoResponse error if no response was received within the configured timeout period.
This timeout may be configured from the mirror panel of the configuration utility. It defaults to 600ms.
MBGetHold
MBGetHold reads a holding register from another Modbus device with a Read Holding Registers (0x03) Modbus command.
Syntax: MBGetHold( MBAddr, PortNum )
The MBGetHold function sends a Read Holding Registers request to a Modbus device at Modbus address MBAddr and requests the value of holding register identified by the zero-based PortNum.
If the request is successful, then the MBGetHold function will return the value read. If the request fails, then the return value is undefined. It does not return anything specific to indicate a failure. Always call the MBGetErr function afterwards to test for success.
MBGetInput
MBGetInput reads an input register from another Modbus device with a Read Input Registers (0x04) Modbus command.
Syntax: MBGetHold( MBAddr, PortNum )
The MBGetHold function sends a Read Input Registers request to a Modbus device at Modbus address MBAddr and requests the value of discrete input identified by the zero-based PortNum.
If the request is successful, then the MBGetInput function will return the value read. If the request fails, then the return value is undefined. It does not return anything specific to indicate a failure. Always call the MBGetErr function afterwards to test for success.
MBPutDO
MBPutDO writes to a discrete output on another Modbus device with a Write Single Coil (0x05) Modbus command.
Syntax: MBPutDO( MBAddr, PortNum, Value )
The MBPutDO function sends a Write Single Coil request to a Modbus device at Modbus address MBAddr and instructs it to set the value of the discrete output identified by the zero-based PortNum to the value indicated by Value. The output will be turned off if Value is 0 and on for any other value.
The MBPutDO function does not return a value. You can call the MBGetErr function afterwards to test for success.
MBPutHold
MBPutHold writes to a holding register on another Modbus device with a Write Single Register (0x06) Modbus command.
Syntax: MBPutHold( MBAddr, PortNum, Value )
The MBPutHold function sends a Write Single Register request to a Modbus device at Modbus address MBAddr and instructs it to set the value of the holding register identified by the zero-based PortNum to the value indicated by Value.
The MBPutHold function does not return a value. You can call the MBGetErr function afterwards to test for success.
Serial Functions
The serial functions send and receive non-Modbus data over the serial port. They can handle constant strings and integers formatted as text.
There are two functions for sending serial data and two for receiving it.
chr
The chr function is used to output an ASCII character of a given numerical value.
Syntax: chr( value )
For example, suppose you wanted to send a Modbus request similar to that shown in the example for CRC, but the target Modbus address was not a constant:
SerialOut(chr(MBAddr) +
"\x02\x00\x00\x00\x08" + CRC);
This would send the request, but instead of using ASCII code 247 for the Modbus address, it would send the value saved in variable MBAddr.
You should note that integer variables contain 16-bit values, but ASCII codes are 8-bit values. If you attempt to send the chr of any value outside the range of 0 - 255, only the least-signifigant eight bits will actually be sent.
CRC
Strictly speaking, CRC is not a function. It is a pseudo-op that can be used inside SerialIn and SerialOut calls. Placing it inside a SerialOut call indicates that a CRC-16 should be appended to the serial data being sent. Placing it inside a SerialIn call indicates that the serial parser should expect to receive a CRC-16.
For example, if you wanted to manually send a Modbus request to a device at Modbus address 247 (0xF7) to read the states of DI1 - DI8, you could use:
SerialOut("\xF7\x02\x00\x00\x00\x08" + CRC);
This would send the request and append the appropriate CRC-16 so that the request would validate. To operate correctly, the entire request should be output in a single SerialOut call and the CRC token should be output at the end.
If you wanted to parse a response from Modbus device 247 and you expected the response to be a 0x00 (you can find related, but much more interesting examples under chr and ord), you could call:
RetVal = SerialIn("\xF7\x02\x01\x00" + CRC);
RetVal will be set to any of its usual values, or 3 to indicate that the message was received, but the CRC received could not be validated.
Similarly to the SerialOut call, the entire expression to be parsed should appear in a single SerialIn call and the CRC should only appear at the end.
FormatInt
The FormatInt function converts an integer argument into text. It can only be used within a SerialOut function.
Syntax: FormatInt( expr [, optional arguments ] )
The FormatInt function renders the expression expr as text for output with a SerialOut function. It accepts a variety of parameters which are all optional and passed by name (order is unimportant):
Parameter |
Values |
Meaning |
Commas |
0-1 |
0=no commas, 1=add commas |
FSign |
0-1 |
0=omit +, 1=force sign character |
RightJ |
0-1 |
0=left justify, 1=right justify |
Width |
0-15 |
minimum # of characters |
ZeroF |
0-1 |
0=don't pad, 1=pad with 0s |
All optional parameters may be set to a constant value (i.e. Width=W is prohibited) and will default to 0 if not listed. For example, let's suppose you wanted to output the variable Value as text and wanted to format it with commas and to force a sign character to be included (i.e. render 1234 as +1,234):
FormatInt(Value, Commas=1, FSign=1)
Any combination of parameters may be included, but ZeroF only really makes sense when both RightJ and Width are assigned values too.
ord
The ord function is used to receive the numerical value of an ASCII character.
Syntax: ord( &var )
The value set in var is undefined should the parse time out. Always test the return code from SerialIn before acting upon the value in var.
For example, suppose you wanted to receive a Modbus response similar to that shown in the example for CRC, but you did not anticipate that the data value would have to be 0:
RetVal = SerialIn("\xF7\x02\x01" +
ord(&Data) + CRC);
This would parse the response, but instead of telling the SerialIn function what to expect for data; it would accept any data value and record the ASCII value of that byte in variable, Data.
The destination variable must be marked with an ampersand (&).
ParseInt
The ParseInt function indicates that the SerialIn function should parse an integer and store it in the named variable. It can only be used within a SerialIn function.
Syntax: ParseInt( &var )
The ParseInt function will parse integers and expects them to be formatted without commas or a +. The destination variable must be marked with an ampersand (&).
The value set in var is undefined should the parse time out. Always test the return code from SerialIn before acting upon the value in var.
SerialIn
The SerialIn function listens for serial input that matches the text passed to the SerialIn expression, expr.
Syntax: SerialIn( timer, expr )
The SerialIn function monitors the serial port until it receives text matching the expression, expr, or until the timer, timer, expires; whichever happens first.
timer should name a timer variable that has been pre-initialized to the timeout value. This allows you to set a timeout for an entire conversation. Set the timeout at the top of the code block, send and receive text, and should the timer run down, all remaining SerialIn functions will return immediately.
The expr expression is an odd beast. It can be constructed from constants and ParseInt functions. You can assemble pieces of this expression with the concatenation operator (+) and the ternary operator (? and :). Here's a simple example that would parse the sentence "I have 5 apples.":
SerialIn("I have " + ParseInt(&N) + " apples.")
The above will tell SerialIn to watch for the first part of the text, then watch for an integer (which will be stored in variable N), and then watch for the last part of the text.
The SerialIn function will return one of three values depending on how successful the parse was:
Value |
Meaning |
0 |
Successful parse |
1 |
Successful parse with extra characters |
2 |
Parse failure - timed out |
3 |
CRC failure |
A return code of 1 indicates success, but that extra characters were received before one or more of the segments parsed. This usually happens in radio environments where a radio transmission is received from another source. To check for success, it is best to compare that the SerialIn did not have a fatal error (SerialIn(...) < 2) rather than testing for success (SerialIn(...) == 0).
Remember, on a failed parse, the value saved by ParseInt is undefined. So always check the return value from SerialIn first!
Also, the IDE compiler parses the escape code \n as carriage-return (0x0D) and line-feed (0x0A). If you want to watch for only a carriage return, be sure to use the escape code \x0D instead of \n.
SerialOut
The SerialOut function outputs text to the serial port as indicated by expression expr.
Syntax: SerialOut( expr )
The SerialOut function sends expr to the serial port. The expr expression can be constructed from constants and FormatInt functions. You can assemble pieces of this expression with the concatenation operator (+) and the ternary operator (? and :).
For example, the expression:
SerialOut("I have " + FormatInt(N) + " apples.")
would output I have 5 apples. if the variable N had the value 5.
Keep in mind that the IDE compiler parses the escape code \n as carriage-return (0x0D) and line-feed (0x0A). If you want to output only a carriage return, be sure to use the escape code \x0D instead of \n.
The SerialOut function does not return a value.
Timer Functions
The timer functions operate on timer variables. These functions are called in a manner that will be familiar to C++ (or most any other OOD language) programmers.
There are three timer functions, two for controlling the timer and one for waiting.
start
The start function restarts a timer after it was stopped with a call to stop (as described below).
Syntax: timer.start()
All timers are created in their running state. This function does not need to be called to initialize your timer.
stop
The stop function pauses a timer. While stopped, the timer value is frozen and will not continue to decrement. The timer may still be changed by assigning a new value to it, but it will not go back to counting until the start function is called.
Syntax: timer.stop()
Note:
Stopping and starting a timer can reduce its precision. The more often a timer is stopped and restarted, the less accurrate it will be. In fact, letting a timer run for less than a millisecond at a time can cause the timer to fail completely and not count down at all.
Here's a snippet of code that demonstrates starting and stopping a timer. This chunk waits for the digital input DI[0] to be ON for a total of 5 seconds. The timer will be paused while the input is OFF:
timer T = 5000; // 5000ms = 5s
while (T) // Loop until the timer hits 0
{
if (not DI[0]) // Pause the timer?
{
// Yes, pause the timer until DI1 is back ON
T.stop();
do ; until (DI[0]);
T.start();
}
}
Technically, the outermost brackets are not required (since there is only one statement executed during the while loop), but it is good practice to include them whenever the included statement is more than a simple command.
wait
The wait function holds execution until the timer times out. It accepts an optional parameter which sets the timer value first.
Syntax: timer.wait( [ expr ] )
If the expression expr is present, then this indicates how many milliseconds the wait function should wait. Otherwise wait will wait on the current value of the timer.