CDC and .Net Framework Serial Ports

I have a CDC test program (netmf 4.3, GHI 4.3.7.10) as shown below. It opens the port and just sends Hello World repeatedly (resetting the port if it fails) :

using System;
using Microsoft.SPOT;
using System.Threading;
using GHI.Usb.Client;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Hardware.UsbClient;

namespace usbtest
{
    public class Program
    {
        public static void Main()
        {
            Debug.Print(Resources.GetString(Resources.StringResources.String1));
            Thread.Sleep(5000);

            while (true)
            {
                Cdc vsp = null;
                try
                {
                    vsp = new Cdc();
                    Controller.ActiveDevice = vsp;

                    byte[] bytes = System.Text.Encoding.UTF8.GetBytes("Hello world!\r\n");
                    while (true)
                    {
                        // Check if connected to PC
                        if (Controller.State !=
                            UsbController.PortState.Running)
                        {
                            Debug.Print("Waiting to connect to PC...");
                        }
                        else
                        {
                            vsp.Stream.Write(bytes, 0, bytes.Length);

                        }
                        Thread.Sleep(1000);
                    }
                }
                catch
                {
                    vsp.Deactivate();
                }
            }
        }
    }
}

And a .Net Framework console program running on the PC:

using System;
using System.IO.Ports;
using System.Text;
using System.Threading.Tasks;

namespace sertest
{
    class Program
    {
        static void Main(string[] args)
        {
            var port = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
            port.Open();
        }
    }
}

And the port.Open() in the PC side of the conversation reliably fails with System.InvalidOperation. The same code works for pretty much every other serial USB device (FTDI, etc), but not a CDC device. In debugging it a bit, I have found that calls to Win32 SetCommState(…) fails for CDC USB devices, and that causes the .Open() to fail. If I call CreateHandle manually, and skip any calls to SetCommState(…), then I can access the PC end of the CDC port successfully, but that requires that I use PInvoke calls to talk to the serial port.

Has anyone else experienced this or found other workarounds?

Also, I have to recreate the CDC port if a send or receive fails (as you can see in the first code snippet), because every other recovery strategy I have tried results in no more serial characters flowing, even if the port appears to recover.

yes

Which OS is this?

Host OS : Win10 with NETMF 4.3.1 and GHI’s most recent sdk and drivers

@ mcalsyn - Does it work if you open the port in Tera Term or other terminal program instead of a .NET application?

Yes - because teraterm fails the SetCommState() call gracefully, whereas .Net System.IO.SerialPort fails the .Open() if the internal SetCommState() call returns false.

@ mcalsyn - What’s the exception message for the InvalidOperationException?

System.IO.IOException was unhandled
HResult=-2147024865
Message=A device attached to the system is not functioning.

Source=System
StackTrace:
at System.IO.Ports.InternalResources.WinIOError(Int32 errorCode, String str)
at System.IO.Ports.SerialStream.InitializeDCB(Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, Boolean discardNull)
at System.IO.Ports.SerialStream…ctor(String portName, Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, Int32 readTimeout, Int32 writeTimeout, Handshake handshake, Boolean dtrEnable, Boolean rtsEnable, Boolean discardNull, Byte parityReplace)
at System.IO.Ports.SerialPort.Open()
at sertest.Program.Main(String[] args) in x:\projects\throwaway\sertest\sertest\Program.cs:line 15
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: (null)

That’s crazy, did you try opening the device directly?

EDIT: Er I mean like opening the device as a file stream.

@ mcalsyn - We were able to reproduce it on Windows 7 and 10. We will take a look.

2 Likes

Yes - both in native code and in managed.

You can call GetCommState(), but if you call SetCommState() from managed or native, then SetCommState() returns false. TeraTerm handles this gracefully (as does my unmanaged code). System.IO.Ports.SerialPort fails the .Open() call if SetCommState() returns false.

If you skip over the SetCommState() and just use the interface at its native 96008N1, then everything works fine. Unfortunately, because SerialPort calls SetCommState() unconditionally under the covers, it’s not possible to change that logic.

fwiw, I have temporarily worked around this in the most heavy-handed way imaginable. I took the Mono source code for SerialPort, and hacked out the SetCommState call. There’s still a real bug here, and the only available fix (pinvoke calls) is nasty - made only slightly less nasty because Mono partly solved it.

I am still very eager for a fix - but I am no longer blocked.

1 Like

@ mcalsyn - I would log that as an issue for NETMF team to fix if it is not there already.

Is this a NETMF issue? It sounds like a windows issue.

@ Mr. John Smith - Right, Windows it is.

@ mcalsyn as a work around try http://serialportstream.codeplex.com/ it is available as a nuget package and solved this problem on a microchip based device I was having problems with… You will need to open the port using OpenDirect() rather than Open()

Hope this helps.

2 Likes

Thanks much for the pointer! Had I seen this earlier, I definitely would have tried this before hacking the Mono code.

SerialPortStream and my hacked mono code are both fine workarounds, but they’re not a fix. It’s not reasonable for a USB serial interface to fail all calls to SetCommState(), and it’s not reasonable to be incompatible with both the .Net Framework and Mono serial port implementations. They may be flawed too, but the other common usb-serial interfaces … ftdi, prolific, etc seem to work with them. Interesting to hear of similar problems with microchip.

GHI is aware of the issue and is looking into it. I am sure there will be some sort of solution that restores the ability to use the stock SerialPort implementation.

Again - thanks for the pointer! I may have other uses for this, so it’s a good link to have.

Just thought I would add this little gem as well [url]http://web.archive.org/web/20121014193532/http://www.cygnal.org/ubb/Forum9/HTML/000945.html[/url] see the point: E) Weird usbser.sys

In windows 10, the compatible id of the USB device could be changed to use the new CDC driver implementation (which should be a lot more stable than the older USBSER implementation) more info here: [url]https://channel9.msdn.com/Events/Build/2015/3-81[/url] and [url]Microsoft Learn: Build skills that open doors in your career

Fixed int next release!

1 Like