Industrial Automation Tech Note 10 - TNIA10
Abstract:
This document describes the use of Raw Port drivers to send and receive ASCII data to compatible Red Lion Controls (RLC) products.
Products:
Red Lion Controls CR1000 Human Machine Interface (HMI), CR3000 HMI, Data Station Plus (Excludes LE), G3 HMI, G3 Kadet HMI, Graphite® Controller, Graphite HMI, Modular Controller (Excludes LE and V2), and ProducTVity Station™
Use Case:
Raw Port drivers are used when Crimson does not have a driver for a particular device, or when ASCII data needs to be transferred.
Required Software:
Crimson® 3.0 or 3.1
Required Operating System:
Microsoft Windows 2000, or above
Introduction
The port number is used as an argument in all of the functions associated with sending and receiving data through a raw port. In order to identify the port number, start Crimson and click on the Communications section of the Navigation pane. Click on the Communications - RS-485 Comms Port and click Pick... next to the Driver field, select <System> - Raw Serial Port, and then click OK. Refer to the upper right of the Editing Pane or the lower left of the Crimson window in the status bar; Port Number 2, as shown in Figure 1.
Figure 1.
When a Raw Driver is assigned to a port, the On Update parameter is used to define an action that occurs during every communications scan; this is the location that calls a program that acts as a receive routine.
When using TCP/IP, there are two drivers available: Raw TCP/IP Active and Raw TCP/IP Passive. The Active driver is used when the Red Lion Device needs to establish a connection to the other device, it requires the IP address of the remote device and the TCP port that will be used. The Passive driver is used when the remote device will establish a connection with the Red Lion Device, it requires only the TCP Port to monitor for incoming connections.
The majority of the code examples that follow were sourced from the Crimson 3.0 database file that can be downloaded at the bottom of this page.
The first three (3) sections use the RLC Instrument protocol for their examples, specifically communicating to a PAXI. See page 25 of the PAXI datasheet/manual for the protocol specification: http://files.redlion.net/filedepot_download/213/5263.
Basic
Transmitting Fixed Strings
Transmitting a fixed string requires only one function and one line of code.
Send N01TA$
PortPrint(2, "N01TA$"); |
Send 123<CR><LF> (123<carriage return><linefeed>)
PortPrint(2, “123\r\n”); |
Display Entire Received Message
To read a string from the port’s buffer, use the PortInput function.
input = PortInput(2, 0, 13, 500, 0);This will return any ASCII characters that precede a <CR>, if 500ms goes by without a <CR> then it will return a null string. In order to hold the last valid (non-null) received string, an IF statement will be required to verify what was received before populating a tag.input = PortInput(2, 0, 13, 500, 0);
if(input != "") { Basic.Response = input; } |
Here is the code for a full receive routine:
cstring input; input = PortInput(2, 0, 13, 500, 0); if(input != "") { Basic.Response = input; } |
Intermediate
Transmitting Strings with Embedded Variables
In order to embed variables into a string transmission, the variable must be made into a string (if it is not already) and then concatenated with other strings before transmission.
PortPrint(2, "N1VM" + IntToText(Intermediate.Write, 10, 5) + "$"); |
or
output = "N1VM" + IntToText(Intermediate.Write, 10, 5) + "$"; PortPrint(2, output); |
or
output = "N1VM"; output += IntToText(Intermediate.Write, 10, 5); output += "$"; PortPrint(2, output); |
or
Parsing Received String
As in the Basic section the buffer needs read and checked for a non-null string. Depending on the data format, different parsing techniques may be required.
input = PortInput(2, 0, 13, 500, 0); if(input != "") { |
1. The first X characters are a portion of the transmission that needs to be separated:
Node = Left(input, 2); |
2. The next portion of the transmission is X characters after the first space:
space = Find(input, 32, 0); Mnemonic = Mid(input, space+1, 3); |
3. The final portion is the last 12 characters, but is padded with spaces:
temp = Strip(Right(input, 12), ' '); Value = TextToInt(temp, 10); |
Here is the code for a full receive routine:
cstring input; cstring temp; int space; input = PortInput(2, 0, 13, 500, 0); if(input != "") { Intermediate.Node = TextToInt(Left(input, 2), 10); space = Find(input, 32, 0); Intermediate.Mnemonic = Mid(input, space+1, 3); temp = Strip(Right(input, 12), ' '); Intermediate.Value = TextToInt(temp, 10); } |
Advanced
Cyclically Polling Parameters
If more than a single transmission is required to gather all of the needed data, a state machine can be used to cycle through the different transmissions.
switch(State) { // poll counter A case 0: output = "N01TA$"; break; // poll rate case 1: output = "N01TD$"; break; // poll setpoint 1 case 2: output = "N01TM$"; break; } |
Interrupt Polling to Issue Write
If there is a need to write, the polling may need to wait while the write request is serviced, this can be done by using an IF statement before entering the state machine.
// need to write?
if(NeedToWrite) { NeedToWrite = 0; // generate output output = "N1VM"; output += IntToText(Write, 10, 5); output += "$"; // clear the port's buffer ClearRx(port); // send command PortPrint(2, output); // exit program return; } |
Receive: Assuming Data Received is what was Requested
If the incoming data is assumed to be correct, or if there are no identifiers in the response, a similar state machine can be used:
// populate correct tag based on what was requested
switch(State) { // counter A case 0: CountA = value; break; // rate case 1: Rate = value; break; // setpoint 1 case 2: Setpoint1 = value; State = -1; break; } // increment state to poll the next value State ++; |
Receive: Response Includes Identification of Data
If the incoming data includes identifiers in the response, they can be used to determine which tag to populate.
// parse response
// Node Address is the first 2 characters node = TextToInt(Left(input, 2), 10); // the Mnemonic follows a space after the node // find the space space = Find(input, 32, 0); // could also use find(input, ' ', 0) mnemonic = Mid(input, space+1, 3); // the data is the last 12 bytes, padded with spaces, which need to be removed temp = Strip(Right(input, 12), ' '); // could also use strip(left(input, 12), 32); value = TextToInt(temp, 10); // populate correct tag based on what was received if(mnemonic == "CTA") { CountA = value; } else if(mnemonic == "RTE") { Rate = value; } else if(mnemonic == "SP1") { Setpoint1 = value; } |
Receive: Byte by Byte
Receiving byte by byte is required for ASCII protocols, but can also be used for troubleshooting or if the format of the response is unknown. The received data can be loaded into an array, or a string can be built from it, the code below does both.
// this Rx routine will allow you to display all of the bytes received in a transmission
// declare locals int i; int j; int in = PortRead(3, 100); // loop while there is data in the buffer, populating the array while(in != -1) { Array[i++] = in; in = PortRead(3, 100); } // if something was received, build a string if(i > 0) { String = ""; for(j = 0; j <= i; j ++) { String += Array[j]; } } |
Raw UDP
Transmitting
Unlike the Raw TCP/IP drivers, the only configurable property is the port that it listens on. Part of the transmission includes both the target IP address, target UDP port, and byte count.
// Target IP = 192.168.50.11
PortWrite(4, 192); PortWrite(4, 168); PortWrite(4, 50); PortWrite(4, 11); // Target Port = 0x1234 PortWrite(4, 0x12); PortWrite(4, 0x34); // Byte Count = 11 PortWrite(4, 0); PortWrite(4, 11); // UDP Data (0123456789<CR>) PortPrint(4, "0123456789\r"); |
Receiving
Unlike the other raw port drivers, the UDP buffer contains more than just response data. It uses an “R” to identify the beginning of a response, followed by the source IP address, port it came in on, and the byte count.
// Check for Frame
if( PortRead(4, 0) == 'R' ) { // Read Source IP RxIP = PortRead(4, 0) << 24; RxIP |= PortRead(4, 0) << 16; RxIP |= PortRead(4, 0) << 8; RxIP |= PortRead(4, 0) << 0; // Read Source Port RxPort = PortRead(4, 0) << 8; RxPort |= PortRead(4, 0) << 0; // Read Length RxCount = PortRead(4, 0) << 8; RxCount |= PortRead(4, 0) << 0; // Read Data Fill(RxData[0], 0, 40); int n; for( n = 0; n < RxCount; n++ ) { RxData[n] = PortRead(4, n); } // Increment Sequence RxSeq++; } |
Clearing Receive Buffer
Reading from the buffer removes the data from it, a while loop can be used to clear out old data.
// loop until buffer is empty while(PortRead(2, 0) != -1) {} |
Disclaimer
It is the customer's responsibility to review the advice provided herein and its applicability to the system. Red Lion makes no representation about specific knowledge of the customer's system or the specific performance of the system. Red Lion is not responsible for any damage to equipment or connected systems. The use of this document is at your own risk. Red Lion standard product warranty applies.
Red Lion Technical Support
If you have any questions or trouble contact Red Lion Technical Support by clicking here or calling 1-877-432-9908.
For more information: http://www.redlion.net/support/policies-statements/warranty-statement