Hello friends, I hope you are doing well. In this tutorial, we will build a simple serial terminal in Visual Basic 2010. The application will detect the COM ports available on a Windows computer, open a selected port, transmit text, receive incoming data and close the connection cleanly.

This article keeps Visual Basic 2010 because that is the environment used in the original project and screenshots. According to the official Microsoft lifecycle record, Visual Studio 2010 reached the end of support in July 2020, so you should use a supported Visual Studio and .NET version for a new production application. However, the serial communication principles explained here still apply to maintenance work, laboratory computers and older engineering projects.

We will begin with the electrical and protocol basics, create the Windows Forms interface, write the complete VB code and then test the terminal with a loopback connection. I will also explain why received data cannot be written directly to a Windows Forms control from the serial event handler.

Figure: The original terminal interface includes connection controls and separate areas for transmitting and receiving text.

What Is Serial Communication?

Serial communication transfers bits one after another over a communication channel. In a basic asynchronous UART link, one device transmits on its TX line and the other receives on its RX line. The devices normally share a ground reference. A full-duplex link uses separate TX and RX conductors, so both sides can transmit at the same time.

A COM port is the Windows name for a serial interface. A built-in RS-232 port, a USB-to-UART converter and some Bluetooth serial profiles can all appear as names such as COM3 or COM8. The name identifies the Windows port; it does not tell us the electrical voltage used by the attached hardware.

SettingMeaningValue used here
Baud rateNumber of symbols transmitted per second9600 baud by default
Data bitsPayload bits in each frame8
ParityOptional error-detection bitNone
Stop bitsMarks the end of a frame1
HandshakeHardware or software flow controlNone

The common notation 9600 8N1 therefore means 9600 baud, eight data bits, no parity and one stop bit. A normal 8N1 character contains one start bit, eight data bits and one stop bit, which gives ten transmitted bits per byte. The ideal maximum payload rate is:

Payload rate = 9600 bits/s / 10 bits per byte = 960 bytes/s

This is a theoretical value before application delays and protocol overhead. Both endpoints must use matching settings. A mismatch may produce unreadable characters, incomplete messages or no useful data.

Check the Electrical Interface Before Connecting Hardware

A microcontroller UART commonly uses 3.3 V or 5 V logic, while a traditional computer RS-232 connector uses inverted positive and negative voltage levels. Do not connect a microcontroller UART directly to a DB9 RS-232 port. Use a suitable level translator, such as a MAX232 circuit, or a USB-to-UART adapter with the correct logic voltage.

For a three-wire UART connection, connect the adapter TX to the device RX, adapter RX to device TX and the grounds together. Check the device documentation before joining grounds when either circuit is mains-powered or otherwise isolated. The software cannot correct an unsafe or incompatible electrical connection.

Controls Used in the Visual Basic Serial Terminal

Our application uses the System.IO.Ports.SerialPort class. Microsoft documents this class as the interface for synchronous and event-driven I/O through a serial port. We will place a SerialPort component on the form so Visual Studio creates and disposes the component with the form.

Create the following controls. The Name property is the identifier used in code, while the Text property is the caption shown to the user.

ControlName propertyText or purpose
FormfrmMainSerial Terminal
ComboBoxcmbPortAvailable COM ports
ComboBoxcmbBaudBaud-rate choices
ButtonbtnConnectConnect
ButtonbtnDisconnectDisconnect
TextBoxtxtTransmitText to transmit
ButtonbtnSendSend
RichTextBoxrtbReceivedReceived text
SerialPortSerialPort1Serial connection component

Step 1: Create a Windows Forms Project

  1. Open Microsoft Visual Basic 2010 Express or Visual Studio 2010.
  2. Select File, New Project.
  3. Select Windows Forms Application under Visual Basic.
  4. Name the project SerialPortInterface and select OK.
Figure: Begin the serial-terminal project from the Visual Basic 2010 Express Start Page.

The New Project dialog in your edition may look slightly different, but the application type must be a Visual Basic Windows Forms application.

Figure: Select a Visual Basic Windows Forms application and give the serial-terminal project a name.

Visual Studio creates a blank form and the files required for the project. The form is the visual surface on which we will place the terminal controls.

Figure: The newly created project opens with an empty form ready for the terminal controls.

Step 2: Configure the Main Form

Select the form and open the Properties window. Change the (Name) property from Form1 to frmMain. Change its Text property to Serial Terminal. The name makes the form easy to identify in code, while the text appears in its title bar.

Figure: Configure the form properties before adding the port-selection and communication controls.

Step 3: Add Port and Baud-Rate Controls

Drag two ComboBox controls, two Button controls and two Label controls from the Toolbox to the form. Arrange them in a clear connection section.

Figure: The connection area groups the port and baud-rate selectors with the connection buttons.
  • Name the first ComboBox cmbPort and label it COM Port.
  • Name the second ComboBox cmbBaud and label it Baud Rate.
  • Name the buttons btnConnect and btnDisconnect. Set their Text properties to Connect and Disconnect.
  • Set each ComboBox DropDownStyle to DropDownList if you want the user to select only a detected or predefined value.

Visual Basic identifiers are normally case-insensitive, but spelling still matters. Using the exact names in the table ensures that the event handlers and references in our code point to the intended controls.

Step 4: Add Transmit and Receive Controls

Add two GroupBox controls. Set their Text properties to Transmit Data and Received Data. Put a TextBox named txtTransmit and a Button named btnSend inside the transmit group. Set the button text to Send. Put a RichTextBox named rtbReceived inside the receive group.

Figure: The form now includes separate transmit and receive areas alongside the connection controls.

Finally, find SerialPort in the Components section of the Toolbox and drag it onto the form. It appears in the component tray below the designer because it has no visible user interface. Keep its name as SerialPort1. The code will set its communication properties before opening the port.

Step 5: Add the Visual Basic Code

Press F7 or right-click the form and select View Code. The code editor opens as shown below.

Figure: The form's code editor is where the serial-port event handlers are added.

Replace the form class code with the following example. Leave the designer-generated file unchanged. This version handles an empty port list, validates the baud rate, reports common connection errors, marshals received data to the user-interface thread and closes the port when the form exits.

Imports System.IO
Imports System.IO.Ports

Public Class frmMain

    Private Sub frmMain_Load(ByVal sender As System.Object,
                             ByVal e As System.EventArgs) Handles MyBase.Load
        cmbBaud.Items.AddRange(New Object() {"9600", "19200", "38400", "57600", "115200"})
        cmbBaud.SelectedIndex = 0

        RefreshPortList()
        SetConnectedState(False)
    End Sub

    Private Sub RefreshPortList()
        Dim ports() As String = SerialPort.GetPortNames()
        Array.Sort(ports)

        cmbPort.Items.Clear()
        cmbPort.Items.AddRange(ports)

        If cmbPort.Items.Count > 0 Then
            cmbPort.SelectedIndex = 0
        End If
    End Sub

    Private Sub btnConnect_Click(ByVal sender As System.Object,
                                 ByVal e As System.EventArgs) Handles btnConnect.Click
        If cmbPort.SelectedItem Is Nothing Then
            MessageBox.Show("No COM port is available. Connect the adapter and try again.")
            Return
        End If

        Dim selectedBaud As Integer
        If Not Integer.TryParse(cmbBaud.Text, selectedBaud) Then
            MessageBox.Show("Select a valid baud rate.")
            Return
        End If

        Try
            SerialPort1.PortName = cmbPort.Text
            SerialPort1.BaudRate = selectedBaud
            SerialPort1.DataBits = 8
            SerialPort1.Parity = Parity.None
            SerialPort1.StopBits = StopBits.One
            SerialPort1.Handshake = Handshake.None
            SerialPort1.Encoding = System.Text.Encoding.ASCII
            SerialPort1.ReadTimeout = 1000
            SerialPort1.WriteTimeout = 1000
            SerialPort1.Open()

            SetConnectedState(True)
        Catch ex As UnauthorizedAccessException
            MessageBox.Show("The port is already in use or access was denied.")
        Catch ex As IOException
            MessageBox.Show("Windows could not open the selected serial device.")
        Catch ex As ArgumentException
            MessageBox.Show("One or more serial-port settings are invalid.")
        Catch ex As InvalidOperationException
            MessageBox.Show("The serial port is already open.")
        End Try
    End Sub

    Private Sub btnDisconnect_Click(ByVal sender As System.Object,
                                    ByVal e As System.EventArgs) Handles btnDisconnect.Click
        CloseSerialPort()
    End Sub

    Private Sub btnSend_Click(ByVal sender As System.Object,
                              ByVal e As System.EventArgs) Handles btnSend.Click
        If Not SerialPort1.IsOpen Then
            MessageBox.Show("Connect to a COM port before sending data.")
            Return
        End If

        Try
            SerialPort1.Write(txtTransmit.Text)
        Catch ex As TimeoutException
            MessageBox.Show("The serial write operation timed out.")
        Catch ex As InvalidOperationException
            MessageBox.Show("The serial port is closed.")
            SetConnectedState(False)
        End Try
    End Sub

    Private Sub SerialPort1_DataReceived(ByVal sender As Object,
                                         ByVal e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
        Try
            Dim incoming As String = SerialPort1.ReadExisting()
            If incoming.Length > 0 Then
                rtbReceived.BeginInvoke(New Action(Of String)(AddressOf AppendReceivedText), incoming)
            End If
        Catch ex As InvalidOperationException
            'The port may have been closed while the event was being processed.
        End Try
    End Sub

    Private Sub AppendReceivedText(ByVal text As String)
        rtbReceived.AppendText(text)
        rtbReceived.SelectionStart = rtbReceived.TextLength
        rtbReceived.ScrollToCaret()
    End Sub

    Private Sub SetConnectedState(ByVal connected As Boolean)
        btnConnect.Enabled = Not connected
        btnDisconnect.Enabled = connected
        btnSend.Enabled = connected
        cmbPort.Enabled = Not connected
        cmbBaud.Enabled = Not connected
    End Sub

    Private Sub CloseSerialPort()
        Try
            If SerialPort1.IsOpen Then
                SerialPort1.Close()
            End If
        Finally
            SetConnectedState(False)
        End Try
    End Sub

    Private Sub frmMain_FormClosing(ByVal sender As Object,
                                    ByVal e As FormClosingEventArgs) Handles MyBase.FormClosing
        CloseSerialPort()
    End Sub

End Class

The completed code editor should resemble the original project view below.

Figure: Original serial-terminal source view; use the revised code in the tutorial for the current example.

How the Serial Port Code Works

Discovering COM Ports

SerialPort.GetPortNames() returns the serial port names known to Windows. The order is not guaranteed, so the example sorts the array before placing it in cmbPort. It also checks whether the list contains an item. This avoids the out-of-range error that occurs when code selects item zero on a computer with no detected serial port.

Opening the Connection

The Connect handler copies the selected port name and baud rate to SerialPort1, explicitly sets the remaining 8N1 parameters and calls Open(). A serial port is an exclusive resource in most normal desktop use. If another terminal, programmer or instance of this application already owns it, Windows may reject the request with an access error.

Once the port opens, the program disables the port settings. Changing PortName, BaudRate or related properties while the port is open can raise an exception, and it can also make the interface state misleading.

Sending Text

SerialPort1.Write(txtTransmit.Text) transmits the entered characters without automatically appending a line ending. If the receiving device expects a command terminated by carriage return and line feed, set SerialPort1.NewLine = vbCrLf and use SerialPort1.WriteLine(txtTransmit.Text). Choose the terminator required by your protocol rather than assuming every device uses the same one.

The example uses ASCII encoding because it is common in simple embedded command protocols. If you need characters outside ASCII, both endpoints must agree on an encoding and on how message boundaries are identified. Binary protocols should use byte arrays and explicit lengths rather than converting arbitrary bytes to text.

Receiving Data on the Correct Thread

The DataReceived event runs on a secondary thread. Windows Forms controls belong to the thread that created them, so writing directly to rtbReceived from that event can cause a cross-thread exception. Microsoft therefore requires the update to be marshalled to the form thread through Invoke or BeginInvoke.

Our event handler reads the characters currently available through ReadExisting() and passes the resulting string to AppendReceivedText with BeginInvoke. The helper then runs on the user-interface thread and safely appends the text. The event is not guaranteed to occur once for every complete message or every byte, so a real protocol must accumulate data until it finds a delimiter, fixed length or another complete-frame condition.

Step 6: Build and Run the Project

  1. Select Build, Build SerialPortInterface.
  2. Correct any compiler errors, paying close attention to control names.
  3. Press F5 to start the application under the debugger.
  4. Select the correct COM port and the same baud rate used by the attached device.
  5. Select Connect, enter a short test message and select Send.
Figure: Build the project after adding the controls and serial-port code.

The build command creates the executable as part of the project output. During development, you normally run the Debug build from a path similar to bin\Debug. A deployment package is a separate concern; copying only the executable may be insufficient if an application has additional files or framework requirements.

Test the Terminal with a Loopback

A loopback test is the simplest way to separate a PC software problem from a remote-device problem. On a compatible USB-to-UART adapter, disconnect external circuitry and join the adapter TX pin to its RX pin. Do not short a conventional RS-232 connector until you have identified the correct pins and interface type.

  1. Insert the adapter and note the COM port assigned by Windows Device Manager.
  2. Start the application and select that port.
  3. Select 9600 baud and connect.
  4. Enter a distinctive message such as Loopback 123.
  5. Select Send. The same text should appear in the received-data box.
Figure: The original terminal interface includes connection controls and separate areas for transmitting and receiving text.

If the text returns correctly, Windows, the adapter, the COM-port settings and the main send/receive path are working together. Remove the loopback wire before connecting the target device.

Common Serial Port Problems

SymptomLikely causeWhat to check
No COM ports appearAdapter is disconnected or its driver is unavailableCheck Device Manager, reconnect the adapter, then restart or add a refresh-port button
Access denied when connectingAnother program has opened the portClose other terminals, debuggers and duplicate application instances
Unreadable charactersBaud rate, parity, data bits or stop bits do not matchSet both endpoints to the same complete frame format
Transmission works only one wayTX and RX wiring is incorrect or a common ground is missingCross TX with RX and verify the electrical interface
Received text arrives in piecesSerial data is a stream and event boundaries are not message boundariesBuffer data and parse it using the protocol delimiter or expected length
Device ignores a typed commandRequired line ending was not sentUse the documented terminator and WriteLine when appropriate
Cross-thread operation errorThe receive event updated a form control directlyUse Invoke or BeginInvoke to update the UI
Application remains connected after closingThe port was not closed during shutdownClose it in the form-closing event as shown above

Practical Review

We created a Visual Basic Windows Forms serial terminal that enumerates COM ports, configures an 8N1 link, transmits text and displays received data. The most important design detail is the separation between the serial receive thread and the user-interface thread. Once you understand that boundary, you can extend this project with timestamps, a clear button, hexadecimal display, file logging or a protocol parser.

Keep the protocol separate from the graphical interface as the project grows. For example, a line-oriented sensor can buffer characters until it receives a newline, while a binary device may use a header, length field, payload and checksum. A visible chunk in DataReceived should never be assumed to equal one complete application message.

Frequently Asked Questions

Can I use this code with Visual Studio 2022 or a newer release?

The same concepts apply, but the project target and package requirements may differ. For a new application, create a supported Visual Basic Windows Forms project and consult the current System.IO.Ports documentation. The screenshots and designer workflow in this tutorial specifically show Visual Studio 2010.

Why does my COM port number change?

Windows assigns COM-port names to detected devices and interfaces. Moving some USB adapters to another socket, reinstalling a driver or using a different adapter can produce a different number. Select the port shown for your device in Device Manager rather than hard-coding COM1.

Should I use Write or WriteLine?

Use Write when the protocol expects exactly the supplied characters or bytes. Use WriteLine when the receiver expects a line terminator, and set the NewLine property to the required sequence. The choice belongs to the device protocol.

Why does DataReceived return only part of a message?

A serial port delivers a byte stream. Operating-system buffering, device timing and event scheduling determine how many characters are available during a particular event. Store incoming data in a buffer and extract messages only after the protocol's completion condition is present.

Can this terminal receive binary data?

Yes, but ReadExisting() is intended for decoded text. For binary communication, read bytes, preserve their exact values and implement the framing rules defined by the device protocol. Avoid mixing text and binary reads without a clear boundary.

Do I need hardware flow control?

Not for the simple three-wire example. Some modems and high-throughput devices require RTS/CTS hardware control or XON/XOFF software control. Set the Handshake property to match the equipment documentation; enabling it on only one side can stop transmission.

Why should the port be closed when the form exits?

Closing releases the Windows handle and allows another program to use the device. It also reduces the chance that a late receive event will try to interact with controls while the form is being destroyed.

You now have a useful foundation for PC-to-microcontroller communication in Visual Basic. Test the software with a loopback first, match all serial settings at both ends and then add your device-specific commands one step at a time.