Pages

Friday, April 13, 2012

Simple Serial Port Monitor in C#.NET

This is an application that I wrote in C#.NET (3.5, VS 2008) in order to interface AVR microcontroller with a PC over the serial port.  I wrote this app as an exercise and to have something available that I can easily modify and extend for different projects and requirements.  I've also included numerous links and references to information on serial ports and many serial port communication example codes.

First, let's create a Windows Form for our GUI. ComboBox'es to input serial port configuration values, Buttons to open, close port and send characters out, TextBox to type in characters to be sent, a RichTextBox as a display. Nothing to it really. Naming convention for the controls are ComboBoxes as cbPortName, cbDataBits, etc., Buttons as btnOpenPort, btnSend, TextBox as txtSend, and richTextBox as rtbDisplay

This example makes use of the SerialPort class available in .NET. SerialPort class was introduced with .NET 2.0 and consequent .NET versions provided improved functionality. System.IO.Ports namespace must be included in order to use this class.

Code:

Create an instance of the SerialPort class and attach an event handler for the DataReceived event. I populated the combo boxes manually during design time, except for port names and parity values. Used the GetPortNames() static method to get the available COM ports in system. (Then an if statement to set COM1 as default since it is the one I am using and don't want to click it everytime the program runs.) SerialPort also has StopBits property which can be used to populate cbStopBits combo box at runtime but it has a bug: One of its values is None (StopBits.None. if selected it will throw an ArgumentOutOfRange Exception) Hence, I choose to add most combo box items manually during design time in Visual Studio.



RichTextBox control provides advanced formatting features. One of which selecting the text color. Using the SelectionColor property to assign different font colors for TX, RX and Error types makes it a bit more visually pleasing.

Another thing to note is that you cannot simply update the rtbDisplay control as rtbDisplay.AppendText(port.ReadExisting());. This is because DataReceived and the Form control are running on different threads and access to Windows Forms controls is not inherently thread safe; you will receive a Cross-thread operation not valid error. ( How to: Make Thread-Safe Calls to Windows Forms Controls) That's why we used the Invoke method.

This is just a quick and dirty program to communicate with the serial port. It could use some improvements such as using try/catch blocks to handle errors more gracefully, a Text/Hex option, file sending option etc., but this should get us started.

using System;
//using System.Collections.Generic;
using System.ComponentModel;
//using System.Data;
using System.Drawing;
//using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;

using SerialPortMonitorApp;

namespace SerialPortMonitorApp
{
  public partial class Form1 : Form
  {
      SerialPort port = new SerialPort();

      public Form1()
      {
          InitializeComponent();
          port.DataReceived += 
               new SerialDataReceivedEventHandler(port_DataReceived);
            
      }

      private void Form1_Load(object sender, EventArgs e)
      {
          SetupControls();
      }

      private void SetupControls()
      {
          btnClosePort.Enabled = false;

          foreach (string str in SerialPort.GetPortNames())
              cbPortName.Items.Add(str);

          foreach (string str in Enum.GetNames(typeof(Parity)))
              cbParity.Items.Add(str);

          if (cbPortName.Items.Contains("COM1"))
              cbPortName.SelectedItem = "COM1";
          else cbPortName.SelectedIndex = 0;

          cbParity.SelectedIndex = 0;
          cbStopBits.SelectedIndex = 0;
          cbBaudRate.SelectedIndex = 3;
          cbDataBits.SelectedIndex = 3;
          cbLineTermination.SelectedIndex = 1;

      }

      private void btnClosePort_Click(object sender, EventArgs e)
      {
          port.Close();
          btnClosePort.Enabled = false;
          btnOpenPort.Text = "Open Port";
          rtbDisplay.SelectionColor = Color.Red;
          rtbDisplay.AppendText(port.PortName + " closed!\n");
          rtbDisplay.ScrollToCaret();
      }

      private void btnSend_Click(object sender, EventArgs e)
      {
          string lineTermination = string.Empty;

          if (!port.IsOpen)
          {
              rtbDisplay.SelectionColor = Color.Red;
              rtbDisplay.AppendText("Error: Not Connected to Port! 
                                     Establish connection first.\n");
              rtbDisplay.ScrollToCaret();
              return;
          }

          rtbDisplay.SelectionColor = Color.Green;
          switch (cbLineTermination.SelectedIndex)
          {
              case 0:
                 lineTermination = string.Empty;
                 break;
              case 1:
                 lineTermination = "\n";
                 break;
              case 2:
                 lineTermination = "\r";
                 break;
              case 3:
                 lineTermination = "\n\r";
                 break;
              default:
                 break;
          }
          rtbDisplay.AppendText("TX: " + txtSend.Text + "\n");
          port.Write(txtSend.Text + lineTermination);
          rtbDisplay.ScrollToCaret();
      }

      private void btnOpenPort_Click(object sender, EventArgs e)
      {
          if (port.IsOpen == true)
            {
              port.Close();
              rtbDisplay.SelectionColor = Color.Red;
              rtbDisplay.AppendText(port.PortName + " closed!\n");
              rtbDisplay.ScrollToCaret();
          }

          port.PortName = cbPortName.SelectedItem.ToString();
          port.BaudRate = int.Parse(cbBaudRate.SelectedItem.ToString());
           
          port.DataBits = int.Parse(cbDataBits.SelectedItem.ToString());
          
          port.StopBits = (StopBits)Enum.Parse(typeof(StopBits), 
                                      cbStopBits.SelectedItem.ToString());
          
          port.Parity = (Parity)Enum.Parse(typeof(Parity), 
                                      cbParity.SelectedItem.ToString());

          port.Open();
          if (port.IsOpen == true)
          {
              btnOpenPort.Text = "Refresh Port";
              btnClosePort.Enabled = true;
              rtbDisplay.SelectionColor = Color.Red;
              rtbDisplay.AppendText(port.PortName + " opened!\n");
              rtbDisplay.ScrollToCaret();
          }
      }

      void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
      {
          this.Invoke(new EventHandler(
              delegate
              {
                  rtbDisplay.SelectionColor = Color.Blue;
                  rtbDisplay.AppendText(port.ReadExisting());
                  rtbDisplay.ScrollToCaret();
              }));
      }

  }
}


More on the web:

Jan Axelson's Lakeview Research
#region Coad's Code (Noah Coad)
Your Electronics Open Source
RS-232 and Serial Communication