Have a question? ask www.kenlet.com
Home  |  FAQ  |  About  |  Contact  |  View Source   
 
SEARCH:
 
BROWSE:
    My Hood
Edit My Info
View Events
Read Tutorials
Training Modules
View Presentations
Download Tools
Scan News
Get Jobs
Message Forums
School Forums
Member Directory
   
CONTRIBUTE:
    Sign me up!
Post an Event
Submit Tutorials
Upload Tools
Link News
Post Jobs
   
   
Home >  Tutorials >  C# >  Serial COM Simply in C#
Add to MyHood
   Serial COM Simply in C#   [ printer friendly ]
Stats
  Rating: 4.77 out of 5 by 22 users
  Submitted: 03/05/02
Noah Coad ()

 
Serial COM port communications has always been one of my favorite topics. Ever since I was 15 I was writing code to communicate with electronics my Dad or I made.

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).

Adding the MSComm Control

You must add the control to a form instead of just instantiating the control straight from code because it requires special OCX state information and a developing license be included in your program's assembly. By drawing it onto a form, VS.NET handles these for you.
  1. Create a new Windows Form
  2. Add the MSComm COM/OCX Control to your "Windows Forms"
    1. Right Click on the Toolbox
    2. Choose "Customize Toolbox..."
    3. Select and add the "Microsoft Communications Control"
  3. Draw the new control onto your form (Telephone icon)

Properties and Event Info

Here is a quick overview of some important properties of the MSComm control.
  • com.CommPort
    Sets or gets the computer's serial port to be used.

  • com.PortOpen
    Opens or closes the serial port.

  • com.RThreshold
    Sets how many characters should be received before firing the OnComm event. Set to 0 to disable event calling. Set to 1 to fire OnComm every time a character is received.

  • com.InputMode
    On of the MSCommLib.InputModeConstants constants to specify either sending/receiving text strings or byte arrays. Defaults to text which is easier to work with but not as reliable as byte arrays.

  • com.Settings
    Used to setup the port in the format "baud,p,d,s" where baud = baud rate, p = parity, d = # data bits, and s = # stop bits. Ex: com.Settings = "9600,n,8,1"

  • com.Handshaking
    On of the MSCommLib.HandshakeConstants constants to specify the type of handshaking: none, RTS/CTS hardware hs, and/or XOn/XOff software hs

  • com.InBufferCount
    Returns the number of characters waiting in the receive buffer.

  • 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";

Physical Port Layout and the Null Modem

Pin Assignments
9-pin 25-pin Assignment
Sheild 1 Case Ground
1 8 DCD (Data Carrier Detect)
2 3 RX (Receive Data)
3 2 TX (Transmit Data)
4 20 DTR (Data Terminal Ready)
5 7 GND (Signal Ground)
6 6 DSR (Data Set Ready)
7 4 RTS (Request To Send)
8 5 CTS (Clear To Send)
9 22 RI (Ring Indicator)

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 (Pin Side)
   DB25 Female (Solder Side)               DB25 Male (Solder Side)
    ---------------------------------       ---------------------------------
    \ 1  2  3  4  5  6  7  8 ... 13 /       \ 13 ... 8  7  6  5  4  3  2  1 /
     \ 14 15 16 17 18 19 20 ... 25 /         \ 25 ... 20 19 18 17 16 15 14 /
      -----------------------------           -----------------------------



Example Code

To use this:
  1. Create a new form
  2. Add the AxMSCommLib control and name it "com"
  3. Add a Rich Text Box named "rtfTerminal"
  4. Put the constructor code below in the form's constructor
  5. Add the rest of the code. Make sure the settings are right for your system.
  6. 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");
}


Advanced Topic: Loop Based vs Event Based Receiving

In most cases you will want to set RThreshold = 1 to fire the OnComm event every time some data is received. But there are cases when it would be easier to use a loop to wait for and capture incoming data.

My favorite scenario is in creating pseudo plug & play port detection for my devices. When my app starts it scans the com ports for my device. Here is some example code to find the port that my device is on. See that by setting RThreshold = 0 and disabling the OnComm event, I can quickly and easily have a completely self contained bit of code to preform the task. Be sure to always including time outs to prevent getting caught in an endless loop.
public short FindDevicePort()
{
    bool PortOkay = true; short TestPort = 0;
    bool found = false;  // Init variables for checking.

    // Stop the OnComm event and normal processing.
    com.RThreshold = 0;
    
    // Close the current port if one is already open.
    if (com.PortOpen) com.PortOpen = false;

    do
    {
        TestPort++;  // Try the next port
        PortOkay = true;
        
        // Attempt to access this port number (mayby it doesn't exist on this computer)
        try {com.CommPort = TestPort;}
        catch (System.Runtime.InteropServices.COMException)
        {PortOkay = false;}
        
        if (PortOkay)  // Continue if the port exists
        {
            // Attempt to open the port (maybe it's already in use by another program)
            try {com.PortOpen = true;}
            catch (System.Runtime.InteropServices.COMException)
            {PortOkay = false;}

            if (PortOkay) // Continue if port was available and opened
            {
                com.Output = "Hello?";  // Send string to request a response.

                long TimeStamp = DateTime.Now.Ticks;  // Time Stamp for Time-out
                string buffer = "";
                bool ElapsedTime;
                do
                {
                    // Don't lock up the entire application,
                    // release control to process other messages.
                    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;
}


Techniques for Sending & Receiving Data

These are frequent issues that arise. If you are experiencing difficulty sending or receiving data, please review these topics and methods for a solution.

Sending Unusual Data w/ Byte Arrays
Generally using the text/string method is easiest, but it can cause unpredictable results when dealing with many special characters. In this case you should use byte arrays. Ex: com.Output = new byte[] {0, 0x41, (byte) 'A', 255};

Receiving Unusual Data w/ Byte Arrays
As with sending, most of the time data comes in through the com port in any form, not just letters A-Z and numbers. If you use a terminal program and see strange characters, you will need to use byte arrays. Here's how:
  1. Setup com.Input for receiving byte arrays. Change your "com.InputMode" to:
    com.InputMode = MSCommLib.InputModeConstants.comInputModeBinary;

  2. When receiving data, store into a byte array.
    Here is an example OnComm event:
    private void OnComm(object sender, EventArgs e)  //  MSCommLib OnComm Event Handler
    {
      // Receive data into a byte array, good for any type of data
        byte[] indata = (byte[]) com.Input;
        
        // Show each byte received in the "Output" window
        foreach (byte b in indata)
          Console.WriteLine("Byte Data: " + b);
    }
    

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)  //  MSCommLib OnComm Event Handler
    {
        // Add to the buffer the incoming data 
        ComBuffer += (string) com.Input;
    
        // 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. 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)  //  MSCommLib OnComm Event Handler
    {
        // 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();
    }
    

  3. 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.

References

  • MSDN Library for the MSComm Control
    Unfortunately not available as MSDN online but is included with previous versions of Visual Studio. If you have the MSComm.ocx installed, it's on your computer at COMM98.CHM. Or available on my ftp server at COMM98.CHM.

  • Tera Term Pro
    A very handy freeware terminal app.

  • NewVBTerm: MSComm Control Techniques [in VB]

  • JustinIO
    Code example of serial communications in native C# without the MSComm.ocx ActiveX control.

Conclusion

With the power of serial port communications you can extend the reach of your control outside the computer box and into the real world. Enjoy!

-=[ Know-a-Code ]=-

Return to Browsing Tutorials

Email this Tutorial to a Friend

Rate this Content:  
low quality  1 2 3 4 5  high quality

Reader's Comments Post a Comment
 
Nice well written tutorial.
-- Reid Jonasson, March 07, 2002
 
Very thorough tutorial. Nice work on the formatting too!
-- Ben Martens, March 07, 2002
 
Good tutorial.
-- Brian Simoneau, March 09, 2002
 
Very thorough and informative.
-- Vincent Chan, March 16, 2002
 
Very useful tutorial! This came in handy for an assignment of mine. Thanks!
-- Jonathan Larson, April 19, 2002
 
This looks great, but in Windows 2000 the MSCOMM32.ocx is not included. It is possible to open a FileStream using the CreateFile function. Do you have any information on that?
-- David Leatham, May 22, 2002
 
Excellent Tutorial. Dealt with all I wanted to know. Thanks mate.
-- Abdul Saheed, May 30, 2002
 
An excellent tutorial and a very friendly and knowledgeable writer.. Thank you Noah...
-- Haluk Gokmen, June 13, 2002
 
Awesome Job Man.

C# to boot! Great use of Event Handlers.
-- Trevor Pinkney, August 21, 2002
 
This is exactely what I was loooking for. I
was trying to get the communication to work by
invoking Win32 calls myself. A nightmare, so much
hassle! I didn't realize you can still use the old
Microsoft OCX Communications Control.

Really good article. It covers all you need
for simple communication via the serial port, it is
very very well structured and the nice layout makes it
pleasant to read.

Good job, thanks mate!
-- raf peymen, June 09, 2003
 
Author's Note:
I have made a wrapper control that provides easy code access to the
MSComm32.ocx control that I use in all my projects. It also contains the
ActiveX control itself, the Comm98.chm help file, and sample code. The
most most up-to-date version is avalible at CoadNet Products.

Good News! You don't have to own VB6, install its ActiveX controls,
or have the MSComm32.ocx control already installed to use the
above code as a library in your project! Just see the 'SerialCom.txt'
file in the code project for more info.

For your reference, here are some other .NET RS-232 serial communication
classes (not supported by myself):
SerialStream
Justin.IO
NETCommOCX
-- Noah Coad, June 27, 2003
 
I just wanted to thank you for taking the time to write that article about C# and MSComm32. I'm new to C# and it really helped. Especially the binary mode w/ byte arrays was just what I needed.
-- Steve Phillips, August 15, 2003
 
I build a Project on Windows2000 using Serial COM C#, It can run on Win2000,
but it can't run on win98 , I had regiest MsComm32.ocx , help me!!!
-- jane wu, September 29, 2003
 
The article is very good but I think there's a quicker way to register the MSComm.ocx, in those cases when you need a fast deployment of your applications.

1. Register your MSComm.ocx

regsvr32.exe MSComm.ocx /s

where regsvr32.exe is located in your <WinDir>\System32
<WinDir> stands for your Windows directory
the "/s" parameter stands for silent registration

2. Register the vbctrls.reg file. You'll find this file on the VisualStudio6 installation kit.

Disclaimer:
Some copyright problems might appear here so this comment comes with NO guarantee that this procedure is legal.
______________________________________________________

There might be another problem using the MSComm.

I've also created a few months ago a class that extends the MSComm and I've found some problems when accessing the MSComm.

My application periodically uses the serial port of a PC (I'm using an extension card so I have four serial ports installed). When the MSComm member of my class is accessed let’s say, from 5 to 5 minutes everything works just fine but if I extend the period to 30 minutes or more I get the following exception:

QueryInterface for interface MSCommLib.IMSComm failed.

If anyone know what might be the problem please let me know ( bogdan.tarnauca@xnet.ro ), ‘cause this problem is quite stressing.

Thanks.
-- Bogdan Tarnauca, October 15, 2003
 
Ahh..
thanks for this tutorial :) ...
i need it :)
-- Serkan Geray, October 24, 2003
 
-- bao ha, November 02, 2003
 
Thanks for the sample you provided on the web which is very useful.
I was able to get the MSCOMM.ocx registered and got the small termimal app.( written in C#) running immediately. By using a remote terminal, I can see both TX and RX are communicating. However, I have run into a problem and don't know how to deal with:
I have my own target board with a modem chip, I have a modem interface
using COM3 on my PC, I can use my PC terminal windows to talk to
my target board with commands such as "ATZ\r", "ATDT###-####\r".
However, if I use my small C# app. using MSCOMM, and use COM3 to
dial out using ATDTphone number, I can tell the DIAL-UP is working as my target
receives the command, acted correctly, and eventually, the modem connection is
established. But, after the connection is established. on the PC side, I did't get any data/msg. back from the PC as from the terminal, some msg. like "CONNECTED AT 38,400bps". If I enter command "ATZ", the modem should
reply with "OK". But, I don't get anything. If my target is busy and I
use ATDT command, I will get "BUSY" or "NO CARRIER" msg. back.
Since "BUSY" was returned if the target is busy. So, the modem
is in Verbose mode by default. "ATV1Q0\r" is not necessary. I also tried
binary and text on the input mode. The results were the same.
I set a break point, and saw the eventHandler was trigged after the
connection was established, but, com.InBufferCount is zero, not data or msg. was received. The commEvent(5) doesn't match with any of the events listed on the class event list.
So, now, I am questioning if I can ever use MSCOMM to make modem connection, or something wrong with the internal Lucent modem driver on my Fujitsu E7110 laptop.
Thanks in advance if you know anything and can tell me your experience
on MSCOMM with modem.

Tom


com.OnComm += new MSCommLib.DMSCommEvents_OnCommEventHandler(com_OnComm);

void com_OnComm()
{
if (com.InBufferCount > 0)
Proc
-- Tom Tang, December 02, 2004
 
The "old-school"-controls-corresponding-problems that yelp such sentences like “You do not have a license to use this ActiveX control” …etc. should be solved by ad hoc recipes under this link(http://support.microsoft.com/kb/q318597/)
-- Shrio QT, December 05, 2004
 
It was really handy to start with for a newbie in communication programming. Simple and good Article to follow and understand.
-- Chitra CK, December 16, 2004
 
Nice Article but I could not find your project resource that you mentioed at the following URL:
http://www.coad.net/products/Default.aspx?From=DevHood&ProductName=SerialCom

Please check it out and if needed mention the correct URL. I need that resource deaparately, as I need to do a COM port programming and I need to submit my project within 26th JULY.

PLEASE HELP ME OUT! PLEASE...
-- kunal mukherjee, July 16, 2005
 
How do I read barcodes which are scanned by a barcode scanner using this code. Please help me!
-- kunal mukherjee, July 16, 2005
 
Copyright © 2001 DevHood® All Rights Reserved