Get free access to our online edition!

Nuts & Volts Magazine (February 2010)

Smiley’s Workshop 19: A Simple Terminal

By Joe Pardue    View In Digital Edition  


Smiley's Workshop — Serial Communications - Part 2

Selecting A Serial Port And Getting User Input

Recap

In the last episode, we introduced you to serial communications between a PC and an Arduino using C# or Visual Basic Express .NET (both free from www.microsoft.com/express. (The article shows the C# examples and the Visual Basic examples that are in the Workshop19.zip). This time, we'll look at selecting a serial port, getting user input, and then we will expand on all this to build a simple terminal.

Getting User Input

FIGURE 1. PortSet Test.
 
FIGURE 2. Add New Item.

In this section, we will create a dialog form to allow the user to select an available serial port and set the UART parameters needed for the communication link (Baudrate, Data Bits, Parity, Stop Bits, and Handshaking). To create and test the Settings dialog, we will use a test form — PortSetTest — to call it. Later, we will access this form by clicking the settings menu item from the Simple Terminal (next month, we will use it for a Arduino voltmeter program).

  • Open either C# Express and create a new project named PortSetTest.
  • Create a form to look like Figure 1.
  • The text list is made from six labels using the text shown.
  • In the Solutions Explorer, right-click ‘PortSetTest’ and from the drop-down menus, select Add/New Item as shown in Figure 2.
  • In the Add New Item form shown in Figure 3, highlight ‘Windows Form,’ change the name to ‘PortSettings.cs’ if C#, and click Add.
  • The blank form will open.
  • In the Properties Window, select ‘FormBorderStyle’ and select ‘FixedDialog.’ This type of form can post a DialogResult message that the calling form — in this case, the Simple Terminal form — can process (don’t panic, this is all done for you and is simpler than it sounds). We will use the DialogResult to learn if the user clicked the Settings form Okay or Cancel button.
  • Now you should be able to do some stuff without a lot of hand-holding:

             — Change the form name to ‘Setting.’
             — Add the Smiley.ico icon.
             — Change the form color to Bisque.

  • Add the controls shown below:

             — The upper white box with listBoxPorts is a listBox which you will name listBoxPorts.
             — The control with the label ‘Baudrate’ is a ComboBox; change its name to comboBoxBaud.

FIGURE 3. Add New Item Form. FIGURE 4. Settings Form.
  • Your Settings form should look like Figure 4.
  • Add the DevInfo file to the project.
  • Copy the DevInfo.cs (if C#) to the directory containing the PortSettings project files. You will find the DevInfo file in the Workshop19.zip.
  • Refer to the Add/New Item image as shown in Figure 2.
  • Open the same menus in the Solution Explorer, but instead of selecting Add/New Item, select Add/Existing Item and select DevInfo.cs.
  • Note that we are intentionally not using the SerialPort GetPortNames function since it doesn’t work correctly. (Microsoft released a buggy function — hard to believe, isn’t it?) We will not look at this file in detail since it contains some advanced concepts, but we will use it to get the names of the available ports. The source code is in Workshop19.zip. If you want to learn the gory details, they are contained in The Virtual Serial Port Cookbook available from Nuts & Volts and Smiley Micros.
  • In C#, add to the ‘using’ list at the top of the program:

using DevInfo;

  • In C#, add to the constructor:

public PortSettings()
{
    InitializeComponent();

    // Get a list of the Serial port names
    string[] ports = GetPorts();

    int i = 0;
    foreach (string s in ports)
    {
        if (s != “”)
        {
            listBoxPorts.Items.Insert(i++, s);
        }
    }

    // Set first Serial port as default
    GetCOM(0);

    // Initialize baudrates in combobox;
    comboBoxBaud.Items.AddRange(new object[]wrap
    {“75”,”110”,”134”,”150”,”300”,”600”,wrap
     “1200”,”1800”,”2400”,”4800”,
     ”7200”,”9600”,wrap
     “14400”,”19200”,”38400”,
     ”57600”,”115200”,”128000”});
    
    // Set Handshaking selection
    // We will only use these handshaking types
    comboBoxHandshaking.Items.Add(“None”);
    comboBoxHandshaking.Items.Add(“RTS/CTS”);
    comboBoxHandshaking.Items.Add(“Xon/Xoff”);

    // Set Parity types
    foreach (string s in
    Enum.GetNames(typeof(Parity)))
    {
        comboBoxParity.Items.Add(s);
    }

    // Set Databits
    // FT232R UART interface supports only 7
    // or 8 data bits           
    // comboBoxDataBits.Items.Add(“5”);
    // not supported
    // comboBoxDataBits.Items.Add(“6”);
    // not supported
    comboBoxDataBits.Items.Add(“7”);
    comboBoxDataBits.Items.Add(“8”);

    // Set Stopbits
    // FT232R UART interface supports only 1
    // or 2 stop bits
    // comboBoxStopBits.Items.Add(“None”);
    // not supported
    comboBoxStopBits.Items.Add(“1”);

    //comboBoxStopBits.Items.Add(“1.5”);
    // not supported
    comboBoxStopBits.Items.Add(“2”);

    comboBoxBaud.Text = “19200”;
    comboBoxParity.Text = “None”;
    comboBoxDataBits.Text = “8”;
    comboBoxStopBits.Text = “1”;
    comboBoxHandshaking.Text = “None”;
}

  • Note the word ‘wrap.’ This means that the prior line of code was too long to display so it was wrapped to the next line for the available space and not wrapped for the actual source code. We will use this as a convention throughout our tutorial. If you enter ‘wrap’ in your code, you’ll get an error. (If you think I’m over-explaining this, you haven’t seen my email inbox.)
  • From the Settings Designer form, click the Okay button and the Cancel buttons, and then in the Settings text editor window add the following DialogResults to the functions (in C#,):

        private void buttonOkay_Click(object
        sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
        }

        private void buttonCancel_Click(object
        sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
        }

These functions close the form and post the DialogResult message for the PortSettingsTest form which called the Settings form.

In the source code in Workshop19.zip, you will see #region and #endregion. The IDE uses #region and #endregion to show or hide code sections. When you click on the + or - to the left #region, the code section collapses or expands. We use these to simplify the code visually, making it easier to follow the overall code logic. Collapsing the regions looks like Figure 5.

FIGURE 5. Regions.


Create data assessors and indexChanged functions by clicking the ‘SelectedIndexChanged’ event in the events section (lightning bolt) of the Properties window.

In C#, add:

#region Data Assessors
// Data assessors and index changed functions
// FT232R UART interface supporta
//      7 or 8 data bits
//      1 or 2 stop bits
//      odd / even / mark / space / no parity.
// So these will be the only options available

#region Port Name
// Assessor for the selected port name
private string SelectedPort = “”;
public string selectedPort
{
    get
    {
        return SelectedPort;
    }
    set
    {
        SelectedPort = value;
        labelPort.Text = “Selected Port =
        “ + SelectedPort;
    }
}
#endregion

#region Baudrate
private int SelectedBaudrate;
public int selectedBaudrate
{
    get
    {
        return SelectedBaudrate;
    }
    set
    {
        SelectedBaudrate = value;
        comboBoxBaud.Text = value.ToString();
    }
}

private void comboBoxBaud_SelectedIndexChanged(object sender, EventArgs e)
{         
    selectedBaudrate = wrap
Convert.ToInt32(comboBoxBaud.Items[comboBoxBaud.
SelectedIndex]);
}
#endregion

#region Parity
private Parity SelectedParity;// = Parity.None;
public Parity selectedParity
{
    get
    {
        return SelectedParity;
    }
    set

    {
        SelectedParity = value;
        comboBoxParity.Text = value.ToString();
    }
}

private void comboBoxParity_SelectedIndexChanged(object sender, EventArgs e)
{
    string temp = comboBoxParity.Items[comboBoxParity.SelectedIndex].ToString();

    switch (temp)
    {
        case “Even”:
            selectedParity = Parity.Even;
            break;
        case “Mark”:
            selectedParity = Parity.Mark;
            break;
        case “None”:
            selectedParity = Parity.None;
            break;
        case “Odd”:
            selectedParity = Parity.Odd;
            break;
        case “Space”:
            selectedParity = Parity.Space;
            break;
        default:
            selectedParity = Parity.None;
            break;
    }
}
#endregion

#region StopBits
private StopBits SelectedStopBits = StopBits.One;
public StopBits selectedStopBits
{
    get
    {
        return SelectedStopBits;
    }
    set
    {
        SelectedStopBits = value;
        comboBoxStopBits.Text =
        value.ToString();
    }
}

private void comboBoxStopBits_SelectedIndexChanged(object sender, EventArgs e)
{
    string temp = wrap
comboBoxStopBits.Items[comboBoxStopBits.Selected
Index].ToString();

    switch (temp)
    {
        case “None”:
            selectedStopBits = StopBits.None;
            break;
        case “1”:
            selectedStopBits = StopBits.One;
            break;
        // case “1.5”: // not supported by
                     // FT232R
            // SelectedStopBits =
            // StopBits.OnePointFive;
            //break;
        case “2”:
            selectedStopBits = StopBits.Two;
            break;
        default:
            selectedStopBits = StopBits.One;
            break;
    }
}
#endregion

#region DataBits
private int SelectedDataBits = 8;
public int selectedDataBits
{
    get
    {
        return SelectedDataBits;
    }
    set
    {
        SelectedDataBits = value;
        comboBoxDataBits.Text =
        value.ToString();
    }
}

private void comboBoxDataBits_SelectedIndexChanged(object sender, EventArgs e)
{
    if (comboBoxDataBits.SelectedIndex == 0)
    selectedDataBits = 7;
    else selectedDataBits = 8;
}
#endregion

#region Handshaking
// We will only use None, Xon/Xoff, or Hardware (which is RTS/CTS)
private Handshake SelectedHandshaking = Handshake.None;
public Handshake selectedHandshaking
{
    get
    {
        return SelectedHandshaking;
    }
    set
    {
        SelectedHandshaking = value;
        comboBoxHandshaking.Text =
        value.ToString();
    }
}

private void comboBoxHandshaking_SelectedIndexChanged(object sender, EventArgs e)
{
    if (comboBoxHandshaking.SelectedIndex == 0)
    selectedHandshaking = wrap
Handshake.None;
    else if (comboBoxHandshaking.SelectedIndex
    == 1) selectedHandshaking = wrap
Handshake.RequestToSend;
    else if (comboBoxHandshaking.SelectedIndex
    == 2) wrap
selectedHandshaking = Handshake.XOnXOff;
    else selectedHandshaking = Handshake.None;
}
#endregion

  • We will test the Settings dialog by displaying the data in the PortSetTest form.
  • Open Form1 (PortSettingsTest) and add the following code (in C#,):

public partial class Form1 : Form
{
    private string portname = “Not Initialized”;
    private string baudrate = “Not Initialized”;
    private string parity = “Not Initialized”;
    private string stopbits = “Not Initialized”;
    private string databits = “Not Initialized”;
    private string handshaking = “Not
    Initialized”;

    // Instantiate the PortSettings class
    PortSettings p = new PortSettings();

    public Form1()
    {
        InitializeComponent();

    }

    private void clearSettings()
    {
        portname = “”;
        baudrate = “”;
        databits = “”;
        stopbits = “”;
        parity = “”;
        handshaking = “”;
    }

    private void buttonTest_Click(object sender,
    EventArgs e)
    {
        if (p.ShowDialog() ==
        DialogResult.Cancel)
        {
            // clear out settings
            clearLabels();
        }
        else
        {
            // set labels to Settings values
            setLabels();
        }
    }

    private void clearLabels()
    {
        labelPortName.Text = “PortName”;
        labelBaudRate.Text = “BaudRate”;
        labelParity.Text = “Parity”;
        labelDataBits.Text = “DataBits”;
        labelStopBits.Text = “StopBits”;
        labelHandShaking.Text = “HandShaking”;
    }

    private void setLabels()
    {
        labelPortName.Text = “PortName: “ +
        p.selectedPort;
        labelBaudRate.Text = “BaudRate: “ +
        p.selectedBaudrate.ToString();
        labelParity.Text = “Parity: “ +
        p.selectedParity.ToString();
        labelDataBits.Text = “DataBits: “ +
        p.selectedDataBits.ToString();
        labelStopBits.Text = “StopBits: “ +
        p.selectedStopBits.ToString();
        labelHandShaking.Text = “HandShaking:
        “ + wrap
         p.selectedHandshaking.ToString();
    }
}

  • FIGURE 6. Settings Test.
     
    FIGURE 7. Settings Test Result.
    In the IDE, click the Debug menu and select the ‘start debugging’ item. You should see the blank PortSettingsTest form shown in Figure 1.
  • Click the Test button and you should see the Settings form as shown in Figure 6.
  • The actual port names will, of course (barring a stunning coincidence), be different from those shown. By highlighting and selecting these items and clicking Okay, the settings will propagate back to the PortSettingTest form as shown in Figure 7.

We have covered a lot so far. In the last Workshop, we created a GUI for our Simple Terminal. Then in this workshop, we created and tested a Settings form. You may be a bit tired of PC GUIs by now, but I have some good news. We will be able to use the Settings object in future projects by simply copying the source code to the new projects directory and using the Add/Existing Item technique shown here to get this object into our code. So, the good news is: We never have to code this again! Now you see one of the values of object-oriented programming. You can build and test a useful software object, forget all about how it works, and just use it like a black box.

The Serial Port Class

You may have looked at some of the uses of the Serial Port Class in the source code for the Simple Terminal from Workshop 18. You may have even had one of those ‘what the …’ moments with some of the delegate stuff. Put simply, the code that is communicating with the serial port isn’t actually running with the code for the GUIs. This concerns ‘threads’ or bits of software that are running separately in Windows (each gets a part of each second to run so that all the threads seem like they are running at the same time to the user). When you want one thread to put stuff in another thread, you set a delegate that the first thread can call from the second thread. So, we have to create a delegate in our GUI that allows the serial port thread to set the text in our richTextBox. Yeah, it’s complex, but we don’t really have to go too deep at this point just to use the serial port. This is one of those cases where cookbook coding really comes in handy; you can just safely use the source code and move on until you have time to burn out a few more brain cells and learn the details. If you get to that point, you might consider the Cookbook I mentioned earlier.

Now that we have built and tested the portSettings class, let’s add it to the GUI that we built last month and get our Simple Terminal communicating between the PC and the Arduino.

Bringing It All Together

In Workshop 18, we built the Simple Terminal GUI. Now we want to add the portSettings stuff we just built so that we can make the GUI communicate using the serial port.

  • Create a new Simple Terminal directory for your work and add these files (in C#) from the PortSetTest C# directory copy:

               — Port Settings.cs
               — Port Settings.Designer.cs
               — Port Settings.resz
               — DevInfo.cs

  • Paste the files into your new directory.
  • Following the Add/Existing Items instructions given earlier, add Port Settings and DevInfo to the project.
  • In C# in the ‘using’ list, add:

using PortSet;

Now that you have added the PortSettings and DevInfo Classes to your Simple Terminal project, you are ready to use the .NET System.IO.Ports library that contains the SerialPort Class.

  • Open the Form1 designer and from the Toolbox, drag and drop serialPort1.
  • We will set the serial port properties according to the data we enter in the Port Settings class.
  • In C#, add:

private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
{          
    // Make sure the port isn’t already open
    if (serialPort1.IsOpen)
    {
        MessageBox.Show(“The port must be closed
        before changing the settings.”);
        return;
    }
    else
    {
        // creat an instance of the settings
        // form
        PortSettings settings = new
        PortSettings();

        if (settings.ShowDialog() ==
        DialogResult.OK)
        {
            if (settings.selectedPort != “”)
            {
                // set the serial port to the
                // new settings
                serialPort1.PortName =
                settings.selectedPort;
                serialPort1.BaudRate =
                settings.selectedBaudrate;
                serialPort1.DataBits =
                settings.selectedDataBits;
                serialPort1.Parity =
                settings.selectedParity;
                serialPort1.StopBits =
                settings.selectedStopBits;

                // show the new settings in the
                // form text line
                showSettings();
            }
            else
            {
               MessageBox.Show(“Error:
               Settings form returned with wrap
               no Serial port selected.”);
                return; // bail out
            }

        }
        else
        {
            MessageBox.Show(“Error:
            buttonSetup_Click - Settings wrap
dialog box did not return Okay.”);
            return; // bail out
        }

        // open the port
        try
        {
            serialPort1.Close();
            serialPort1.Open();
            menuStrip1.Items[1].Text =
            “Close Port”;

            showSettings();

        }
        catch (System.Exception ex)
        {
            MessageBox.Show(“Error -
            setupToolStripMenuItem_Click wrap
Exception: “ + ex);
        }
    }
}

// show the settings in the form text line
private void showSettings()
{
    this.Text = “Smiley Micros - “ +
        serialPort1.PortName + “ “ +
        serialPort1.BaudRate.ToString() + “,” +
        serialPort1.Parity + “,” +
        serialPort1.DataBits.ToString() + “,” +
        serialPort1.StopBits;
    if (serialPort1.IsOpen)
    {
        this.Text += “ - Port is open”;
    }
    else
    {
        this.Text += “ - Port is closed”;
    }
}
 

  • In the form designer, click on the ‘Open Port’ menu item.
  • In C#, add:

private void openPortToolStripMenuItem_Click(object sender, EventArgs e)
{
    try
    {
        if (serialPort1.IsOpen)
        {
            serialPort1.Close();
            openPortToolStripMenuItem.Text
            = “Open Port”;
        }
        else
        {
            serialPort1.Open();
            openPortToolStripMenuItem.Text =
            “Close Port”;
        }

        showSettings();
    }
    catch (System.Exception ex)
    {
        MessageBox.Show(“Error - openPortToolStripMenuItem_Click Exception: wrap
                                     “ + ex);
    }
}

  • In the form designer, select the richTextBoxSend component.
  • In the properties window, click the ‘KeyPress’ event and to the event handler created in C#, add:

private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
sendChar(e.KeyChar);
}

private void sendChar(char c)
{
    char[] data = new Char[1];
    data[0] = c;
    try
    {
        serialPort1.Write(data, 0, 1);
    }
    catch
    {
        MessageBox.Show(“Error: sendByte -
failed to send.\nIs the port open?”);
    }
}

  • Our receive functions use a delegate to allow the serial port read thread to write to our receive text box. This is a bit complex, so for the time being just use it as-is.
  • In C#, add:

#region receive functions

// We want to have the serial port thread report back data received, but to
// display that data we must create a delegate
// function to show the data in the
// richTextBox

// define the delegate
public delegate void SetText();
// define an instance of the delegate
SetText setText;

// create a string that will be loaded with the
// data received from the port
public string str = “”;

// note that this function runs in a separate
// thread and thus we must use a
// delegate in order to display the results in
// the richTextBox.
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    // instantiate the delegate to be invoked
    // by this thread
    setText = new SetText(mySetText);

    // load the data into the string
    try
    {
        str = serialPort1.ReadExisting();
    }
    catch (System.Exception ex)
    {
        MessageBox.Show(“Error –
        port_DataReceived Exception: “ + ex);
    }

    // invoke the delegate in the MainForm
    // thread
    this.Invoke(setText);
}

// create the instance of the delegate to be
// used to write the received data to
// the richTextBox
public void mySetText()
{
    // show the text
    richTextBox2.Text += str.ToString();

    moveCaretToEnd();
}

// This rigaramole is needed to keep the last
// received item displayed
private void richTextBoxReceive_TextChanged(object sender, System.EventArgs e)
{
    moveCaretToEnd();
}

private void moveCaretToEnd()
{
    richTextBox1.SelectionStart =
    richTextBox1.Text.Length;
    richTextBox1.SelectionLength = 0;
    richTextBox1.ScrollToCaret();
}
#endregion

So that will give you a working terminal program. Next time, we will use this code to create an Arduino voltmeter that shows ADC data on the PC monitor.  NV


You can find the source code and supplements for this article in Workshop19.zip in the downloads section of the Nuts & Volts website and Smiley Micros website.

Downloads

Smileys Workshop 200911 (Workshop19.zip)



Comments