Perhaps I was missing something in VB6. Life seemed so much easier when using the serial port control. Granted, MSCOMM has some serious limitations (see max limit of COM16). But back then, sending a character from ASCII 1 to 255 was a non-issue. If you sent a &HA0 out the serial port, you got &HA0 out the other end. More specifically, if your hardware was streaming binary data in to your serial port, it was simple enough to decode. In .NET that changed.

In my GCS project, my intention is to be able to handle many different types of data input, both ASCII and I had to make a decision on how to handle the data stream. Should I import it as a byte array (encoding is not an issue if you receive data this way) or as a string so I can use SubString (or Mid, Left and Right) functions on the data. Since there's no easy way to do an InStr function (searches the string for an occurance of another string) on a byte array, I opted for the string.

When dealing with human readable data, there's no problem. Even without specifying anything for the .Encoding property of the SerialPort anything that is ASCII 127 or lower will pass through the port, no problem. As soon as you start streaming binary data that goes all the way up to 255, funky things start happening. So what's the deal? Well, in .NET the data coming out of the serial port is automatically encoded (or really decoded). That's because it's sending Unicode data. This means that some characters are not a single byte but instead are represented by 2 bytes.

But I don't need any of that. I just needed one byte per character. Nothing fancy. 1 to 255 is just fine.... but there's no option to turn this "feature" off. The SerialPort1.Encoding property can be set to all sorts of settings. Generally, I knew I needed 8 bits, so UTF7 was not an option. So I tried System.Text.Encoding.UTF8, System.Text.Encoding.GetEncoding(28591), System.Text.Encoding.GetEncoding(65001) System.Text.Encoding.GetEncoding(1251), System.Text.Encoding.GetEncoding(1252). The best results came from GetEncoding(28591) initially. I thought everything was working great... but then throw in Regional settings and everything gets whacky again.

In XP, click Start, Control Panel, Regional and Language Options and change your Standards Format from English (United States) to Polish and you'll see what I mean. It's the craziest thing. I created a sample project using a com0com feedback port to pump ASCII 1 to 255 out one port in VB6 and I received it in to my GCS on the paired port. 246 of the 255 characters came through just fine. 9 of them were goofy (either a 3F...which is the "unknown" replacement by the serial port or something comepletely odd like &H54 where &H98 should have been).

So the Google search began. How if the world do I fix this or turn it off or something? The first solution was to try and use Chr on some ASCII values and ChrW on others...but that didn't fix all of the bad characters either. The data was coming in with the right hex value, but the change from Byte to ASCII was the problem. In the end, the solution was not to look at every byte. I get rid of the Serial Port's DataReceived event and instead am firing a timer every 75 ms looking for .BytesToRead > 0 and using a built-in encoding function to get the right string from the serial port.

nReadCount = serialPortIn.BytesToRead 'Number of Bytes to read
Dim cData(nReadCount - 1) As Byte

serialPortIn.Encoding = System.Text.Encoding.UTF8

nReadResult = serialPortIn.Read(cData, 0, nReadCount) 'Reading the Data

sNewString = System.Text.Encoding.Default.GetString(cData)
sBuffer = sBuffer & sNewString

Originally, I had been allowing the DataReceived event to fire after 1 character arrived in the ReadBuffer and then evaluating the new character(s) one at a time to build my strings. Without the GetString function, I was unable to get the correct string data from the serial port.

E-mail me when people leave their comments –

You need to be a member of diydrones to add comments!

Join diydrones


  • Exactly what I did :)  Multithreading is surprisingly easy in VB.NET:


    Imports System.Threading

    Public Class Form1
        Private t As Thread

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            t = New Thread(AddressOf UpdateSerial)
        End Sub

        Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        End Sub

        Private Sub UpdateSerial()
                Dim i As Long = 0
                Dim lastupdate As Date = Now
                    'Do serial update here
                    i = i + 1
                    If i = Long.MaxValue Then i = 0

                    If Now.Subtract(lastupdate).TotalSeconds > 0.1 Then
                        lastupdate = Now
                    End If
            Catch ex As ThreadAbortException
                Exit Sub
            End Try
        End Sub

        Private Sub UpdateForm(ByVal i As Long)
            If Me.InvokeRequired Then
                Me.Invoke(New Action(Of Long)(AddressOf UpdateForm), New Object() {i})
                Me.Text = i
            End If
        End Sub
    End Class


    The only pitfall I've noticed is that you'll get pretty strange error messages if any routine that was called from something that originated from the separate thread tries to change the properties of any user interface object.  So, you have to create weird routines like UpdateForm above to update user interface elements.

    VB.NET Shop
  • That's right where I am... I switched from the serial event to a timer and it's better...but not great. I'm considering my first venture into multi-threading and really changing things up to optimize the serial port read. I've already got the ability to slow down the graphics update based on poor performance, but not at the serial port. 10Hz everything is smooth as silk. At 50Hz it still works, but it's choppy.

  • Yeah, a byte array has the potential to be faster than strings; concatenating strings (especially big ones) is expensive while setting the values in a pre-dimensioned byte array is pretty quick.  I don't know how you're doing the message handling, but setting a timer rather than having the processing routine trigger on every new byte that comes in is sometimes a time-saver too.  I've had this exact same problem as well (VB.NET processing high-rate serial data from the APM).

    VB.NET Shop
  • As near as I can tell, it's 6 of one 1/2 dozen of the other. Either I handle everything as bytes and convert to string as needed or handle everything as strings and convert to bytes as needed. I might have to go your route to help improve performance. At 115,200 and data flying out of the APM and ArduIMU at 50Hz (way above what a GCS needs) I'm seeing some performance hits on drawing the 3D models and updating Google Earth. Too much time is spent parsing messages and not enough time spent drawing the pretty pictures.

  • Why not have a byte array be your underlying data structure, then convert to a string when you know the byte array contains only ASCII data?  To do that:

            Dim b As Byte() = {65, 66, 67, 44, 68, 69, 70}
            Dim s As String = System.Text.Encoding.ASCII.GetString(b)
            Dim p As String() = s.Split(",")
            For Each param As String In p

  • I have as many ASCII messages to parse as binary. The ASCII strings are comma delimeted. The binary messages are in particular index locations within the messages. Using strings works for both. Using bytes does not work for comma delimited unless I convert to a string first or write a very complicated parsing routine.

  • Don't get me wrong but i didn't understand why you used strings instead of byte arrays. Or may be you could use list (of byte) type and implement sort find etc functions over byte array. This way you could avoid nasty ascii coding decoding.

  • Nick, that's good advice. I didn't know about that.

    Ivan, that doesn't work when you change regional settings (unless Nick's code works)

    Tero, agreed.
  • Unicode is sh*t in our world
  • Try this:
    mySerialPort.Encoding = Encoding.GetEncoding(28591);
This reply was deleted.