NET
Unfortunately VS.NET does not have a serial communications framework in place. So this tutorial explains how to easily communicate through a serial port using the MSComm OCX that is included with VB in Visual Studio 6 (and previous versions). You must have at least the ActiveX components of VS6 installed in order to use MSComm because it is a licensed control. Required: MSComm.ocx installed with Visual Studio 6. Note: To obtain MSComm.ocx and it's associated licensing, you can do a custom install of Visual Studio 6 and just install the ActiveX components (about 5MB).
com.Input Returns and removes a stream of data from the receive buffer. Used to check for data waiting. Returns a string if in text mode or byte array if in binary/byte mode. com.Output Writes a stream of data to the transmit buffer. Ex: com.Output = "Hello" sends "Hello" through the serial port. com.CommEvent Returns a MSCommLib.CommEventConstants, MSCommLib.ErrorConstants, or MSCommLib.OnCommConstants constant representing the most recent error or event that occurred. Check this in the OnComm event. com.NullDiscard If true, the serial control will ignore all 0x00 (null) characters come in. You will usually want to disable this so you can receive 0x00 since it may be important. com.InputLen The number of characters the Input property reads from the receive buffer. Setting InputLen to 0 reads the entire contents of the receive buffer when com.Input is used.
OnComm Event The one single event that the com control calls is the OnComm event whenever something happens. To use this, be sure to set RThreshold = 1 and check the InBufferCount inside your event. Use com.CommEvent for more information as to why the OnComm event was fired. Example:
public MyForm() { InitializeComponents(); // Initialize Form Components com.RThreshold = 1; // Fire OnComm event after any data is received com.OnComm += new System.EventHandler(this.OnComm); // Assigns the event handler } private void OnComm(object sender, EventArgs e) // MSCommLib OnComm Event Handler { if (com.InBufferCount > 0) ProcessData((string) com.Input); if (com.CommEvent == MSCommLib.OnCommConstants.comEvCTS) Console.WriteLine("CTS Line Changed"); }
Protocol Development
If you are making your own serial interface/protocol, you really must have a good standard in place. Serial data flows into the com port byte by byte and must be buffered and parsed correctly. Think of it this way, if a terminal sent your computer "Hello World" it may come in as four OnComm triggers: "H", "ello", " Wo", and "rld" The best protocols are usually a mix of these methods. Here are three simple protocol techniques: 1. Beginning and Ending ("Start" & "Stop") Codes This is good for sending text as it lets everybody know when text starts and ends. You simply tack on a non-normal byte at the beginning and end of the text. For example, you'd use '---' to signify the start of the string and '===' to signify the end. So you would use: com.Output = "---Hello World==="; 2. Fixed Length Codes Used for specific commands. You can create your own codes to send and specify what they mean. Say I want to control the lighting in a house, I'd setup a protocol of commands like this: 1st byte = House Code, 2nd byte = Light Code, 3rd byte = On or Off (0 for off, 1 for on) So to turn on the 11th light in my house (house code #3) I'd use: com.Output = new byte[] {3, 11, 0}; 3. Prefixed Data Packet This is probably the most common and flexible but requires the most coding. Just prefix your data packet with the length of the data. The prefix must be a fixed size, such as two bytes which would allow a data packet of up to 65,535 bytes. Then the receiver knows how much data is in the packet because it always takes the first two bytes and uses the rest as the data packet. Example: com.Output = ((char) 00) + ((char) 11) + "Hello World";
The Null Modem Adapter There is a standard serial adapter called a Null Modem adapter (obtainable at most Radio Shacks) that crosses over the TX and RX lines to enable you to connect two computer together. This is important because if you wire one com port directly into another, the transmit lines (TX) will both be connected together and both ports will be trying to transmit on the same line (similarly for the RX lines). A null modem usually crosses over the flow control lines too. Here is a layout diagram if you want to make your own: DB9 Female to DB9 Female 2 | 3 | 7 | 8 | 6&1| 5 | 4 ---- ---- ---- ---- ---- ---- ---3 | 2 | 8 | 7 | 4 | 5 | 6&1
DB25 Female to DB25 Female 2 | 3 | 4 | 5 | 6&8| 7 | 20 ---- ---- ---- ---- ---- ---- ---3 | 2 | 5 | 4 | 20 | 7 | 6&8
Physical Pins Layouts Here is a diagram of the pin layout for the DB9 and DB25 connectors. Most connectors already have little tiny numbers next to the pins. DB9 Male (Pin Side) DB9 Female (Pin Side) DB9 Female (Solder Side) DB9 Male (Solder Side) ------------------------\ 1 2 3 4 5 / \ 5 4 3 2 1 / \ 6 7 8 9 / \ 9 8 7 6 / ----------------DB25 Male (Pin Side) DB25 Female (Solder Side) --------------------------------\ 1 2 3 4 5 6 7 8 ... 13 / \ 14 15 16 17 18 19 20 ... 25 / ----------------------------DB25 Female (Pin Side) DB25 Male (Solder Side) --------------------------------\ 13 ... 8 7 6 5 4 3 2 1 / \ 25 ... 20 19 18 17 16 15 14 / -----------------------------
Example Code
To use this: 1. 2. 3. 4. 5. 6. Create a new form Add the AxMSCommLib control and name it "com" Add a Rich Text Box named "rtfTerminal" Put the constructor code below in the form's constructor Add the rest of the code. Make sure the settings are right for your system. Have another computer with a null modem connecting the two and a terminal programming (such as Tera Term Pro) running.
// Constructor for Form with an AxMSCommLib control on it named "com" public SerialTerm() { // Initialize Form Components InitializeComponent(); // Initialize the COM Port control InitComPort(); // Send data out through the COM port com.Output = "Serial Terminal Initialized"; } private void InitComPort() { // Set the com port to be 1 com.CommPort = 1; // This port is already open, close it to reset it. if (com.PortOpen) com.PortOpen = false; // Trigger the OnComm event whenever data is received com.RThreshold = 1; // Set the port to 9600 baud, no parity bit, 8 data bits, 1 stop bit (all standard) com.Settings = "9600,n,8,1"; // Force the DTR line high, used sometimes to hang up modems com.DTREnable = true; // No handshaking is used com.Handshaking = MSCommLib.HandshakeConstants.comNone; // Don't mess with byte arrays, only works with simple data (characters A-Z and numbers) com.InputMode = MSCommLib.InputModeConstants.comInputModeText; // Use this line instead for byte array input, best for most communications //com.InputMode = MSCommLib.InputModeConstants.comInputModeText; // Read the entire waiting data when com.Input is used com.InputLen = 0; // Don't discard nulls, 0x00 is a useful byte com.NullDiscard = false; // Attach the event handler com.OnComm += new System.EventHandler(this.OnComm); // Open the com port com.PortOpen = true; } private void OnComm(object sender, EventArgs e) // MSCommLib OnComm Event Handler { // If data is waiting in the buffer, process it. // Note: This is using the string method for simple data, be sure // to use byte arrays (described below) for more generic data. if (com.InBufferCount > 0) ProcessComData((string) com.Input); }
private void ProcessComData(string input) { // Send incoming data to a Rich Text Box rtfTerminal.AppendText(input + "\n"); }
System.Windows.Forms.Application.DoEvents(); // If there is data waiting, buffer it in our own string buffer. if (com.InBufferCount > 0) buffer += com.Input; // Look for response from device. found = (buffer.IndexOf("Hi There") > -1); // True if time is up, change the 0.2 for human interaction ElapsedTime = DateTime.Now.Ticks - TimeStamp > TimeSpan.TicksPerSecond * 0.2; // Keep waiting until found or 0.2 seconds are up. } while (!ElapsedTime & !found); } } } while ((TestPort < 4) & !found); // If the device was found, return the port it was found on if (found) return TestPort; // Device not found, return 0. return 0; }
Receiving Data Packets / Timing Issues Remember, data is not received in nice, easy to manage packets. If your devices sends "Hello World", it could come in through the com.Input property in several steps, like "He", "llo ", "W", "orld". This can make it difficult to process incoming data. Here are some techniques to receiving data: 1. Use "Start" & "Stop" Tokens This is by far the preferred method of the three. If you can design your protocol, use one of the methods described above, such as the "Start" and "Stop" codes. Prefix and suffix the data with known codes to signify the beginning and ends of a data packet. Then when data comes in, buffer it and just check the buffer. The example below is with strings since they are generally easier to work with. // We'll use Regular Expressions to match the data 'start'/'stop' tokens using System.Text.RegularExpressions; // Used to buffer the incoming serial data private string ComBuffer = ""; private void OnComm(object sender, EventArgs e) { // Add to the buffer the incoming data ComBuffer += (string) com.Input; // MSCommLib OnComm Event Handler
// Example regular expression test string // string ComBuffer = "trash---Hello World===trash---How Are You?===trash"; // Build a regular expression to match data that // starts with '---' and ends with '===' Regex r = new Regex("---.*?==="); // Cycle through the matches for (Match m = r.Match(ComBuffer); m.Success; m = m.NextMatch()) { // Display the result to the 'Output' debug window Console.WriteLine(m.Value); // Remove the find from the string buffer ComBuffer = ComBuffer.Replace(m.Value, ""); } } 2.
3. Time Interval Packets, Using a Timer If you know that data will be coming in at defined time intervals, say there is always at least a second before the next set of data, then use a timer method. If you used a loop you could easily tie up system resources. Again you will need to buffer the data. // Use the system timers library using System.Timers; // Used to buffer the incoming serial data private string ComBuffer = ""; // Create a new timer for serial data watching
private Timer tmrWaitData = new Timer(); // You will need to initialize the timer private void ExtraInitCode() { // Set the timer interval to one second tmrWaitData.Interval = 1000; // Attach the timer tick event handler tmrWaitData.Elapsed += new ElapsedEventHandler(tmrWaitData_Elapsed); } private void OnComm(object sender, EventArgs e) { // Check to see if data is waiting if (com.InBufferCount > 0) { // Add to the buffer the incoming data ComBuffer += (string) com.Input; // Reset the timer tmrWaitData.Stop(); tmrWaitData.Start(); } } private void tmrWaitData_Elapsed(object sender, ElapsedEventArgs e) { // Show to the 'Output' window the data that was received Console.WriteLine("Data Packet: " + ComBuffer); // Stop the timer now that this data packet // has been successfully received tmrWaitData.Stop(); } 4. // MSCommLib OnComm Event Handler
5. Limit the Incoming Buffer Trigger If you know that data will always come in at a fixed amount, for example 10 characters, you can change the com.RThreshold value to trigger the OnComm event only when that amount has been received. Normally you would want OnComm to be triggered whenever any data is received, but you can change this if you are careful that all your data comes in the same amount of bytes/characters.