Switching Between WinUSB and MassStorage USB Modes

I’m currently working on porting our meter code from NETMF on EMX and G120 to TinyCLR. One of the requirements is for the user to be able to switch between connected (WinUSB) mode and “Disk Drive” (Mass Storage) mode using the buttons, while connected to the PC host.

I have both modes working independently, and can switch from MassStorage to WinUSB, but so far going from WinUSB to MassStorage only works if I unplug and replug the device.

I’ve attached the minimal test code I’ve been working with, hopefully someone can point out what I’m missing, or if this is a bug in the OS?

using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Threading;
using GHIElectronics.TinyCLR.Pins;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Storage;
using GHIElectronics.TinyCLR.Devices.UsbClient;

namespace TinyCLRApplication3
{
    internal class Program
    {
        static UsbClientController usbclientController;
        static MassStorage ms = null;
        static StorageController sd;
        static WinUsb winUsb = null;
        static UsbClientSetting usbClientSetting;
        static bool WantUsb = false;
        static bool WantMs = false;

        public const int LeftButton = SC20260.GpioPin.PF10;
        public const int RightButton = SC20260.GpioPin.PF8;

        private static GpioPin ButtonLeft, ButtonRight;

        static void Main()
        {
            var gpio = GpioController.GetDefault();

            // Left button  = set to WinUSB mode
            // Right button = set to MassStorage mode
            ButtonLeft = gpio.OpenPin(LeftButton);
            ButtonLeft.SetDriveMode(GpioPinDriveMode.InputPullUp);
            ButtonLeft.ValueChangedEdge = GpioPinEdge.FallingEdge;
            ButtonLeft.ValueChanged += ButtonLeft_OnInterrupt;
            ButtonRight = gpio.OpenPin(RightButton);
            ButtonRight.SetDriveMode(GpioPinDriveMode.InputPullUp);
            ButtonRight.ValueChangedEdge = GpioPinEdge.FallingEdge;
            ButtonRight.ValueChanged += ButtonRight_OnInterrupt;

            usbclientController = UsbClientController.GetDefault();

            usbClientSetting = new UsbClientSetting()
            {
                Mode = UsbClientMode.WinUsb,
                ManufactureName = "C-Born Software Systems",
                ProductName = "Anode Drop Meter",
                SerialNumber = "2",
                //Guid = "{77C99034-2428-424a-8130-DC481841429B}",  // Works with NETMF, Doesn't work with GHI TinyCLR (yet?)
                //Guid = "{88BAE032-5A81-49f0-BC3D-A4FF138216D6}",  // Class GUID "USBDevice"
                Guid = "{a5dcbf10-6530-11d2-901f-00c04fb951ed}",    // Interface GUID GUID_DEVINTERFACE_USB_DEVICE = "Attached to a Hub"
                VendorId = 0x1234,
                ProductId = 0x0002,
                MaxPower = 500,
                InterfaceName = "SitCore Based Anode Meter"
            };

            /* Loop around switching between WinUSB (Left Button) and MassStorage (Right Button)
             * 
             * Note that we can switch from MassStorage to WinUSB with cable plugged in,
             * but only changes from WinUSB to MassStorage if cable unplugged.
             * 
             * Is there something more that needs to be done, or is this a bug in TinyCLR?
             */
            while(true)
            {
                if(WantUsb)
                {
                    StopMs();
                    StartWinUsb();
                } else if(WantMs)
                {
                    StopWinUsb();
                    StartMs();
                }

                CheckUsb();
                Thread.Sleep(100);
            }
        }
        // Start Mass Storage
        static void StartMs()
        {
            if (ms != null) return;
            usbclientController = UsbClientController.GetDefault();
            ms = new MassStorage(usbclientController);
            sd = StorageController.FromName(SC20100.StorageController.SdCard);
            ms.DeviceStateChanged += Ms_DeviceStateChanged;
            ms.AttachLogicalUnit(sd.Hdc);
            ms.Enable();
            Debug.WriteLine("MassStorage Started");
        }
        // Sto Mass Storage
        static void StopMs()
        {
            if(ms == null) return;
            ms.Disable();
            ms.RemoveLogicalUnit(sd.Hdc);
            ms.DeviceStateChanged -= Ms_DeviceStateChanged;
            ms.Dispose();
            ms = null;
            Thread.Sleep(5000);
            Debug.WriteLine("MassStorage Stopped");
        }
        // Start WinUSB
        static void StartWinUsb()
        {
            if (winUsb != null) return;
            usbclientController = UsbClientController.GetDefault();
            winUsb = new WinUsb(usbclientController, usbClientSetting);
            winUsb.DeviceStateChanged += Usb_DeviceStateChanged;
            winUsb.DataReceived += Usb_DataReceived;
            winUsb.Enable();
            Debug.WriteLine("WinUsb Started");
        }
        //Stop WinUSB
        static void StopWinUsb()
        {
            if (winUsb == null) return;
            winUsb.Disable();
            winUsb.DeviceStateChanged -= Usb_DeviceStateChanged;
            winUsb.DataReceived -= Usb_DataReceived;
            winUsb.Dispose();
            winUsb = null;
            Thread.Sleep(5000);
            Debug.WriteLine("WinUsb Stopped");
        }

        //******** Event Handlers **********
        static void ButtonLeft_OnInterrupt(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Want WinUSB Mode");
            WantMs = false;
            WantUsb = true;  
        }
        static void ButtonRight_OnInterrupt(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Want Mass Storage");
            WantUsb = false;
            WantMs = true;
        }
        private static void Ms_DeviceStateChanged(RawDevice sender, DeviceState state)
        {
            Debug.WriteLine("MassStorage changed to " + state.ToString());
        }
        private static void Usb_DeviceStateChanged(RawDevice sender, DeviceState state)
        {
            Debug.WriteLine("WinUsb changed to " + state.ToString());
        }
        private static void Usb_DataReceived(RawDevice sender, uint count)
        {
            Debug.WriteLine("Data received:" + count);
        }

        // WinUSB Send/Receive - Echo back characters received, incremeneted by 1
        static void CheckUsb()
        {
            if ((winUsb != null) && (winUsb.DeviceState == DeviceState.Configured))
            {
                var len = winUsb.Stream.BytesToRead;
                if (len > 0)
                {
                    var dataR = new byte[len];
                    var dataW = new byte[len];
                    int read = winUsb.Stream.Read(dataR);

                    for (var i = 0; i < read; i++)
                    {
                        dataW[i] = (byte)(dataR[i] + 1);
                    }
                    winUsb.Stream.Write(dataW);
                }
            }
        }
    }
}


Hi, I believe it is a bug. We will take a look in next release.

Switching between USB-Masstorage and WinUSB (USBClient) doesn’t work · Issue #1233 · ghi-electronics/TinyCLR-Libraries (github.com)

1 Like

Thanks Dat.
I’ll complete my port as per the test code and look forward to the next release! :slight_smile:

1 Like

I’ve found a workaround for this problem, however it does seem to point to another issue!

If you add a call to briefly Hibernate the system, like

Power.Sleep(rtc.Now.AddSeconds(1))

before switching to MassStorage mode, the device can be switched back and forth between modes while still plugged in.

It appears that part of the the USB is still running even when the device it supposed to be hibernating. If you plug it in while in this mode you get the “USB Device connected” sounds, and Device Manager identifies it as

Unknown USB Device (Device Descriptor Request Failed)

Dropping from WinUSB mode to this Unknown (Failed) mode before switching to MassStorage mode works, and I’ve added to my code pending a fix in the next release.

But - having it appear as a running but failed device when it is supposed to be sleeping does not appear to be correct behaviour?

Yes, it is not correct behavior. We will take a look in next version.

1 Like

@Dat_Tran Some follow-up information pertaining to both this and the Power.Sleep() problem in my other post around this time.

We built some meters, using the 1-second Sleep between stopping WinUSB and starting MassStorage workaround which had worked perfectly on test PCs here, and sent them off to Iceland, telling them there would be a fix in the next firmware release.
Built some more for Australia, sent them off, only to hear that they would not go into MassStorage at all. They tried on various PCs there, no success, so I tried on some newer ones here - and found the same problem. Some running Windows 10,some 11, but it seems to be hardware specific.
On the newer PCs, if the meter had ever been in WinUSB mode since power-up, they would not accept it in MassStorage, even if the switch from WinUSB to MassStorage was made before the meters were even connected to the PC. However if the meter was powered up in MassStorage, they worked fine.

So, another workaround! When switching to MassStorage do a soft reset (Power.Reset()), and on startup check if ResetSource == SystemReset and if so go straight to MassStorage. Messes up the debugger (which itself uses a soft reset), but should work, right?

But no - seems there is a problem with Power.Reset(), it doesn’t start cleanly, and the system is in a strange state, such that the app doesn’t starrt normally, and pressing the Right button causes a complete shutdown. Weird. Could it be because Power.Reset() is being called from an interrupt?

In any case, I somewhat serendipitously found a workaround for this workaround. If I make the 1-second sleep call immediately prior to the Power.Reset() call, all works as hoped, the app starts properly after the reset, sees the reset source, starts in MassStorage mode, and away we go!

Ok, that is in the tiny demo program, and I still have to figure out how to get all these workarounds-on-workarounds into the final software, but that is a problem for another day.

Hopefully there are enough clues in there to help work out the now three problems, WinUSB->MassStorage, Power.Sleep(), and Power.Reset(), all possibly connected?

This has been fixed in the 2.2.0.6100 firmware release, thanks Dat!

2 Likes

@Dat_Tran
Unfortunately it appears the fix in 2.2.0.6100 (and 2.2.0.6200) does not work for all PCs.

For the ones where it does not work, the behaviour appears as before - in Device Manager the device appears as WinUSB (not MassStorage) if it has ever been in that mode since powered up, even if it wasn’t plugged into the PC until the mode was changed to MassStorage. And of course it doesn’t come up in Explorer as a Disk Drive.

I hadn’t been aware of the problem because the fix worked on my development and test PCs. However we have now carried out more extensive testing, with the results on seven PCs:

  1. Windows 10 Pro Desktop - Works

  2. Windows 10 Home Laptop - Works

  3. Linux Laptop - Works

  4. Windows 10 Pro Destop - Fails

  5. Windows 10 Pro Laptop - Fails

  6. Windows 11 Pro Desktop - Fails

  7. Windows 10 Home Desktop - Fails

No obvious pattern, could be internal hardware used on PCs?
Testing with older meters using EMX and G120 boards with NETMF 4.2 and 4.3, they work correctly with all these PCs.

I’ve gone back to our old workaround for the SC20/TinyCLR boards for now, doing a power reset when switching modes. Not elegant, but enough to get us going for now.

Hopefully you can take another look at it, see if you can find a PC that fails the test there and figure out what is going wrong.
Thanks, David

@Dat_Tran @Gus_Issa

I’ve just tested this with 2.2.0.7000 running on a Portal, I was somewhat hopeful, but I’m afraid it still doesn’t work. This issue has been outstanding since 2022, it works perfectly well in NETMF, so it must be possible!

Symptoms are as described previously, it will work on my Windows 10 development machine (but very slowly), not on my newer Windows 11 PC.
Clients are on standard corporate builds but again report it working on some but not on others.

The code below is slightly modified from the previous version in this thread as I now have a Portal to test on and modified the buttons to suit. If I’ve made any mistakes in it please point them out, otherwise can we get it in the queue for a firmware fix?

Thanks,
David

using System;
using System.Diagnostics;
using System.Threading;
using GHIElectronics.TinyCLR.Pins;
using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Storage;
using GHIElectronics.TinyCLR.Devices.UsbClient;


namespace TinyCLRMassStorageSwitchTest
{
    internal class Program
    {
        static UsbClientController usbclientController;
        static MassStorage ms = null;
        static StorageController sd;
        static WinUsb winUsb = null;
        static UsbClientSetting usbClientSetting;
        static UsbClientSetting MsClientSetting;
        static bool WantUsb = false;
        static bool WantMs = false;

        public const int LeftButton = SC20260.GpioPin.PF10;
        public const int RightButton = SC20260.GpioPin.PF8;
        public const int DownButton = SC20260.GpioPin.PE3;
        public const int UpButton = SC20260.GpioPin.PB7;

        public const int GLedFader = SC20260.GpioPin.PB0;
        public const int RLedFader = SC20260.GpioPin.PB1;

        private static GpioPin ButtonLeft, ButtonRight, ButtonDown, ButtonUp;
        private static GpioPin GLed, RLed;

        static void Main()
        {
            var gpio = GpioController.GetDefault();

            // Left button  = set to WinUSB mode
            // Right button = set to MassStorage mode
            ButtonLeft = gpio.OpenPin(LeftButton);
            ButtonLeft.SetDriveMode(GpioPinDriveMode.InputPullUp);
            ButtonLeft.ValueChangedEdge = GpioPinEdge.FallingEdge;
            ButtonLeft.ValueChanged += ButtonLeft_OnInterrupt;
            ButtonRight = gpio.OpenPin(RightButton);
            ButtonRight.SetDriveMode(GpioPinDriveMode.InputPullUp);
            ButtonRight.ValueChangedEdge = GpioPinEdge.FallingEdge;
            ButtonRight.ValueChanged += ButtonRight_OnInterrupt;

            ButtonDown = gpio.OpenPin(DownButton);
            ButtonDown.SetDriveMode(GpioPinDriveMode.InputPullUp);
            ButtonDown.ValueChangedEdge = GpioPinEdge.FallingEdge;
            ButtonDown.ValueChanged += ButtonDown_OnInterrupt;
            ButtonUp = gpio.OpenPin(UpButton);
            ButtonUp.SetDriveMode(GpioPinDriveMode.InputPullUp);
            ButtonUp.ValueChangedEdge = GpioPinEdge.FallingEdge;
            ButtonUp.ValueChanged += ButtonUp_OnInterrupt;

            GLed = gpio.OpenPin(GLedFader);
            RLed = gpio.OpenPin(RLedFader);
            GLed.SetDriveMode(GpioPinDriveMode.Output);
            RLed.SetDriveMode(GpioPinDriveMode.Output);

            usbClientSetting = new UsbClientSetting()
            {
                Mode = UsbClientMode.WinUsb,
                ManufactureName = "C-Born Software Systems",
                ProductName = "Anode Drop Meter",
                SerialNumber = "2",
                //Guid = "{77C99034-2428-424a-8130-DC481841429B}",  // Works with NETMF, Doesn't work with GHI TinyCLR (yet?)
                //Guid = "{88BAE032-5A81-49f0-BC3D-A4FF138216D6}",  // Class GUID "USBDevice"
                Guid = "{a5dcbf10-6530-11d2-901f-00c04fb951ed}",    // Interface GUID GUID_DEVINTERFACE_USB_DEVICE = "Attached to a Hub"
                VendorId = 0x1234,
                ProductId = 0x0002,
                MaxPower = 500,
                InterfaceName = "SitCore Based Anode Meter"
            };

            MsClientSetting = new UsbClientSetting()
            {
                VendorId = 7071,
                ProductId = 61442,
                BcdUsb = 528,
                BcdDevice = 256,
                MaxPower = 250,
                ManufactureName = "C-Born Software Systems",
                ProductName = "Anodemeter uSD",
                SerialNumber = "1",
                InterfaceName = "Mass Storage",
                Mode = UsbClientMode.MassStorage
            };
            
            /* Loop around switching between WinUSB (Left Button) and MassStorage (Right Button)
             * 
             * Note that we can switch from MassStorage to WinUSB with cable plugged in,
             * but only changes from WinUSB to MassStorage if cable unplugged.
             * 
             * Is there something more that needs to be done, or is this a bug in TinyCLR?
             */

            while (true)
            {   
                if(WantUsb)
                {
                    StopMs();
                    StartWinUsb();
                } else if(WantMs)
                {
                    StopWinUsb();
                    StartMs();
                }

                CheckUsb();
                Thread.Sleep(100);
            }
        }
        // Start Mass Storage
        static void StartMs()
        {
            if (ms != null) return;
            usbclientController = UsbClientController.GetDefault();
            ms = new MassStorage(usbclientController,MsClientSetting);
            //ms = new MassStorage(usbclientController);
            sd = StorageController.FromName(SC20100.StorageController.SdCard);
            ms.DeviceStateChanged += Ms_DeviceStateChanged;
            ms.AttachLogicalUnit(sd.Hdc);
            ms.Enable();
            Debug.WriteLine("MassStorage Started");
        }
        // Stop Mass Storage
        static void StopMs()
        {
            if(ms == null) return;
            ms.Disable();
            ms.RemoveLogicalUnit(sd.Hdc);
            ms.DeviceStateChanged -= Ms_DeviceStateChanged;
            ms.Dispose();
            ms = null;
            Thread.Sleep(5000);
            Debug.WriteLine("MassStorage Stopped");
        }
        // Start WinUSB
        static void StartWinUsb()
        {
            if (winUsb != null) return;
            usbclientController = UsbClientController.GetDefault();
            winUsb = new WinUsb(usbclientController, usbClientSetting);
            winUsb.DeviceStateChanged += Usb_DeviceStateChanged;
            winUsb.DataReceived += Usb_DataReceived;
            winUsb.Enable();
            Debug.WriteLine("WinUsb Started");
        }
        //Stop WinUSB
        static void StopWinUsb()
        {
            if (winUsb == null) return;
            winUsb.Disable();
            winUsb.DeviceStateChanged -= Usb_DeviceStateChanged;
            winUsb.DataReceived -= Usb_DataReceived;
            winUsb.Dispose();
            winUsb = null;
            Thread.Sleep(5000);
            Debug.WriteLine("WinUsb Stopped");
        }

        //******** Event Handlers **********
        static void ButtonLeft_OnInterrupt(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Want WinUSB Mode");
            WantMs = false;
            WantUsb = true;  
        }
        static void ButtonRight_OnInterrupt(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Want Mass Storage");
            WantUsb = false;
            WantMs = true;
        }
        static void ButtonDown_OnInterrupt(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Want Mass Storage");
            WantUsb = false;
            WantMs = true;
        }
        static void ButtonUp_OnInterrupt(GpioPin sender, GpioPinValueChangedEventArgs e)
        {
            Debug.WriteLine("Want WinUSB Mode");
            WantMs = false;
            WantUsb = true;
        }

        private static void Ms_DeviceStateChanged(RawDevice sender, DeviceState state)
        {
            Debug.WriteLine("MassStorage changed to " + state.ToString());
        }
        private static void Usb_DeviceStateChanged(RawDevice sender, DeviceState state)
        {
            Debug.WriteLine("WinUsb changed to " + state.ToString());
        }
        private static void Usb_DataReceived(RawDevice sender, uint count)
        {
            Debug.WriteLine("Data received:" + count);
        }


        // WinUSB Send/Receive - Echo back characters received, incremented by 1
        static void CheckUsb()
        {
            if ((winUsb != null) && (winUsb.DeviceState == DeviceState.Configured))
            {
                var len = winUsb.Stream.BytesToRead;
                if (len > 0)
                {
                    var dataR = new byte[len];
                    var dataW = new byte[len];
                    int read = winUsb.Stream.Read(dataR);

                    for (var i = 0; i < read; i++)
                    {
                        dataW[i] = (byte)(dataR[i] + 1);
                    }
                    winUsb.Stream.Write(dataW);
                }
            }
        }
    }
}

Yes, we will look into

Hi,

We separate this into two issues:

  • MSC mode on STCore is very slow, on G120 is very fast.
  • Switching WinUSB and Masstorage does not work on Win11.

1. MSC mode on STCore is very slow, on G120 is much faster:

We did test on G120E-dev and SITCore, window 10, window11, same SDcard, PC for each test
SD we test: 10Pack 8G 16G Micro SD TF Card SDHC Class 10 Flash Memory Card For Phone Camera | eBay

Window 10/11 :
G120 takes 48 seconds
SITCore takes 25 seconds.

We tried with another MicroSD 8G:

G120 takes 29 seconds
SITCore takes 25 seconds.

Linux Ubuntu 20 :

G120 takes 9 seconds
SITCore takes 9 seconds.

We just want to make sure that, when we ported this feature from G120 to SITCore, we don’t make anything wrong or forget something. And seems this feature is ported correctly, both result on G120 and SITCore are same.

We agree with you, as I remember G120E was fast, around 10 seconds. But it was couple years ago, when Win7 was the king. Now, OS changed, driver changed, and on USB analyzer we are seeing window 10/11 ask a lot of information that need the device response, while Linux just ask few things, that is why the devices are faster when connected to Linux.

What was the last time you test speed on G120? What is NETMF version (we use 4.3.8.1), what is size of SD? When you test, did you test on same PC, same SD?

2. Switching WinUSB and Masstorage does not work on Win11
Can you a bit more detail? Switching doesn’t work, WinUSB Read/Write doesn’t work…

Also, G120 supports WinUSB builtin as SITCore or you write code manually? I don’t think G120 supports WinUSB builtin or I need some coffee.

If you wrote code WinUSB on G120, can you share so we can test?

We also see the comment:

//Guid = "{77C99034-2428-424a-8130-DC481841429B}",  // Works with NETMF, Doesn't work with GHI TinyCLR (yet?)

We tried this GUI, it works. Your problem could be, you used this GUI with different VID or PID on NETMF, that is why when you back to NETMF it works. You need to delete them in Window Register or they need to be same descriptions.

Alright, after investigated, we can summarize what we are seeing:

  • MSC slow, and G120E has same issue
  • Every time switch between MSC/WinUSB mode, memory leak
  • From the second switching to WinUsb, PC says “USB device not recognized”, but WinUsb mode just works fine, Msc does not have this issue.

With your code, you need BcdUsb = 0x200,, without this or value = 0x210 GUI won’t work.

Below is our test code, we are still working on this but make sure we are on the same page.

SITCore:

using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Storage;
using GHIElectronics.TinyCLR.Devices.UsbClient;
using GHIElectronics.TinyCLR.Native;
using GHIElectronics.TinyCLR.Pins;
using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace MscWinUsbSwitch
{
    internal class Program
    {
        static UsbClientController usbclientController;
        static MassStorage ms = null;
        static StorageController sd;
        static WinUsb winUsb = null;
        static UsbClientSetting usbClientSetting;

        public const int RightButton = SC20260.GpioPin.PB7;
        
        private static GpioPin ButtonRight;
        static void Main()
        {
            var gpio = GpioController.GetDefault();
          
            ButtonRight = gpio.OpenPin(RightButton);
            ButtonRight.SetDriveMode(GpioPinDriveMode.InputPullUp);
           
            usbClientSetting = new UsbClientSetting()
            {
                Mode = UsbClientMode.WinUsb,            
                ManufactureName = "C-Born Software Systems",
                ProductName = "Anode Drop Meter",
                SerialNumber = "2",
                Guid = "{77C99034-2428-424a-8130-DC481841429B}",
                VendorId = 0x1234,
                ProductId = 0x0002,
                MaxPower = 500,
                InterfaceName = "SitCore Based Anode Meter",
                BcdUsb = 0x200,
            };

            int cnt = 0;

            Debug.WriteLine("Press PB7 to start testing...");

            while (true)
            {
                if (ButtonRight.Read() == GpioPinValue.Low) {
                    while (ButtonRight.Read() == GpioPinValue.Low) ;


                    cnt++;

                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    Debug.WriteLine("Memory free: " + Memory.ManagedMemory.FreeBytes / 1024 + " KB.");


                }

                if (cnt > 0)
                {
                    if (cnt % 2 == 1)
                    {
                        RestartUsb();
                        StopMs();
                        StartWinUsb();
                    }
                    else
                    {
                        RestartUsb();
                        StopWinUsb();
                        StartMs();
                    }

                    CheckUsb();
                }

                Thread.Sleep(100);
            }
        }

        static void RestartUsb()
        {
            if (usbclientController != null)
            {
                usbclientController.Dispose();

            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            usbclientController = UsbClientController.GetDefault();
        }

        // Start Mass Storage
        static void StartMs()
        {
            if (ms != null) return;

            Debug.WriteLine("MassStorage Starting... please wait");

            usbclientController = UsbClientController.GetDefault();
            ms = new MassStorage(usbclientController);
            sd = StorageController.FromName(SC20100.StorageController.SdCard);
            ms.DeviceStateChanged += Ms_DeviceStateChanged;
            ms.AttachLogicalUnit(sd.Hdc);
            ms.Enable();
            Debug.WriteLine("MassStorage Started");
        }
        // Stop Mass Storage
        static void StopMs()
        {
            if (ms == null)
                return;
            Debug.WriteLine("Stop massstorage... please wait");
            ms.Disable();
            ms.RemoveLogicalUnit(sd.Hdc);
            ms.DeviceStateChanged -= Ms_DeviceStateChanged;
            ms.Dispose();
            ms = null;
            Thread.Sleep(5000);
            Debug.WriteLine("MassStorage Stopped");
        }
        // Start WinUSB
        static void StartWinUsb()
        {
            if (winUsb != null) return;

            Debug.WriteLine("WinUsb Starting... please wait");

            usbclientController = UsbClientController.GetDefault();
            winUsb = new WinUsb(usbclientController, usbClientSetting);
            winUsb.DeviceStateChanged += Usb_DeviceStateChanged;
            winUsb.DataReceived += Usb_DataReceived;
            winUsb.Enable();
            Debug.WriteLine("WinUsb Started");
        }
        //Stop WinUSB
        static void StopWinUsb()
        {
            if (winUsb == null) return;

            Debug.WriteLine("Stop WinUsb... please wait");
            winUsb.Disable();
            winUsb.DeviceStateChanged -= Usb_DeviceStateChanged;
            winUsb.DataReceived -= Usb_DataReceived;
            winUsb.Dispose();
            winUsb = null;
            Thread.Sleep(5000);
            Debug.WriteLine("WinUsb Stopped");
        }
       
        private static void Ms_DeviceStateChanged(GHIElectronics.TinyCLR.Devices.UsbClient.RawDevice sender, DeviceState state)
        {
            //Debug.WriteLine("MassStorage changed to " + state.ToString());
        }
        private static void Usb_DeviceStateChanged(GHIElectronics.TinyCLR.Devices.UsbClient.RawDevice sender, DeviceState state)
        {
            //Debug.WriteLine("WinUsb changed to " + state.ToString());
        }
        private static void Usb_DataReceived(GHIElectronics.TinyCLR.Devices.UsbClient.RawDevice sender, uint count)
        {
            Debug.WriteLine("Data received:" + count);
        }

        // WinUSB Send/Receive - Echo back characters received, incremeneted by 1
        static void CheckUsb()
        {
            if ((winUsb != null) && (winUsb.DeviceState == DeviceState.Configured))
            {
                var len = winUsb.Stream.BytesToRead;
                if (len > 0)
                {
                    var dataR = new byte[len];
                    var dataW = new byte[len];
                    int read = winUsb.Stream.Read(dataR);

                    for (var i = 0; i < read; i++)
                    {
                        dataW[i] = (byte)(dataR[i] + 1);
                    }
                    winUsb.Stream.Write(dataW);
                }
            }
        }
    }
}

Every time WinUsb active, run these from PC Window 11:

using MadWizard.WinUSBNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            USBDevice device = USBDevice.GetSingleDevice("{77C99034-2428-424a-8130-DC481841429B}");

            if (device != null)
            {

                PrintInfo(device);

                USBInterface iface = device.Interfaces[0];


                iface.OutPipe.Write(new byte[] { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 });

                byte[] data = new byte[100];
                int len = iface.InPipe.Read(data);
                PrintHex(data, len);

                Console.ReadLine();
            }
            else
            {
                Console.WriteLine("  No device found");
            }
        }

        static void PrintInfo(USBDevice device)
        {
            foreach (USBInterface iface in device.Interfaces)
            {
                Console.WriteLine("Interface - id:{0}, protocol:{1}, class:{2}, subclass:{3}",
                  iface.Number, iface.Protocol, iface.ClassValue, iface.SubClass);
                foreach (USBPipe pipe in iface.Pipes)
                {
                    Console.WriteLine("  Pipe - address:{0}", pipe.Address);
                }
            }
        }

        static void PrintHex(byte[] data, int length)
        {
            for (int i = 0; i < length; i++)
            {
                Console.Write("{0:x2} ", data[i]);
            }
        }
    }
}

I set up your test code on a Portal, so we are looking at the same thing.

Once your code is triggered it will call RestartUsb() every time through the loop (10 times/second), each time disposing the UsbClientController and instantiating a new one, probably not what you intended.

Even if you don’t trigger the code (don’t press the button), so it is looping with nothing but a Thread.Sleep() and a debug message to print the amount of free memory, it still drops by 1KB every loop, which is disturbing!
Then when it gets to zero free memory it looks like the GC frees up arounf 360kB, and the cycle starts again.
However if is has been triggered, and is in either WinUsb or Ms mode, when it runs out it takes an out-of-memory exception.

When I run your console application, if the device isn’t attached I get “No Device Found”, but if it is attached I get:

C:\WINDOWS\system32>F:\src\ConsoleApp3\bin\Debug\net8.0-windows10.0.22621.0\ConsoleApp3.exe
Unhandled exception. MadWizard.WinUSBNet.USBException: Failed to retrieve device descriptor.
 ---> MadWizard.WinUSBNet.API.APIException: Failed to open WinUSB device handle.
 ---> System.ComponentModel.Win32Exception (5): Access is denied.
   --- End of inner exception stack trace ---
   at MadWizard.WinUSBNet.API.WinUSBDevice.OpenDevice(String devicePathName)
   at MadWizard.WinUSBNet.USBDevice.GetDeviceDescriptor(String devicePath)
   --- End of inner exception stack trace ---
   at MadWizard.WinUSBNet.USBDevice.GetDeviceDescriptor(String devicePath)
   at MadWizard.WinUSBNet.USBDevice..ctor(String devicePathName)
   at MadWizard.WinUSBNet.USBDevice.GetSingleDevice(Guid guid)
   at MadWizard.WinUSBNet.USBDevice.GetSingleDevice(String guidString)
   at ConsoleApp3.Program.Main(String[] args) in F:\src\ConsoleApp3\Program.cs:line 14

… probably because the device is appearing and disappearing 10 times/second!

We started of on the EMX with 2GB uSD cards, then 8GB when you couldn’t get 2GB any more. Now I still have a few 32GB, the smallest I could get at the time, and the rest are 64GB, which have to be formatted as “Large FAT” to work.

Currently I have a 32GB in the Portal, and switching a 64GB between our G120 and SC20 hardware for comparative testing.

NETMF 4.3.8.1 on the G120

For routine testing I’m switching between a Windows 10 and Windows 11 desktop set up as development machines. For pre-flight testing, a couple of laptops, and then client feedback based on their corporate install boxes. I’m not sure the difference is between Windows 10 and 11, it could be more to do with the speed of the machines or specific hardware. In the case of my dev systems, the Win11 box is a lot newer and faster.

If the device has booted into MassStorage and you plug it in, it comes up as MassStorage. If you then switch to WinUSB, that works. But if you switch back to MassStorage, it makes the USB disconnect/reconnect sound, but stays as WinUSB, and Device Manager still shows it as WinUSB.

If you unplug and replug, is still shows as WinUSB, but if you reboot into MassStorage it comes up as MassStorage. That’s my workaround, luckily the SC20 reboots quickly!

EMX and G120 (NETMF 4.2 and 4.3) didn’t support WinUSB, we worked from the examples and added drivers. Adding the drivers to successive versions of Windows became hardware and harder, requiring a LOT of explanation in our User’s Manuals!

TinyCLR having WinUSB work out of the box with no need to add drivers was a relief! However we needed to made changes to our PC gateway service because of the specific GUID required for Windows to automatically use the WinUSB drivers, and the GUID we used was not passed back to our server on insertion, hence the comment about that GUID not working. The PC gateway driver has to be able to recognize both old and new methods.

I’ll try and extract a “minimal version for testing” :slight_smile:
A bit pressed for time now as I’m about to head overseas for a couple of months and everone wants something done before I leave! :sweat:

Don’t you wish you can clone yourself? I do sometimes.

Sent as email

1 Like

Hi, there are few issues with your test code, I tweak them and please try again. The test code on PC has no change. Only change code for SITCore.

Let us know if it works on your PC. For leaking memory, we fixed it but this is different issue. You can enable external memory for now, but I think you don’t need.

using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Storage;
using GHIElectronics.TinyCLR.Devices.UsbClient;
using GHIElectronics.TinyCLR.Native;
using GHIElectronics.TinyCLR.Pins;
using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace MscWinUsbSwitch
{
    internal class Program
    {
        static UsbClientController usbclientController;
        static MassStorage ms = null;
        static StorageController sd;
        static WinUsb winUsb = null;
        static UsbClientSetting usbClientSetting;

        public const int RightButton = SC20260.GpioPin.PB7;
        
        private static GpioPin ButtonRight;

        const int SWITCHING_DELAY = 1;

        static bool msc_stop_event_raised = false;
        static bool winusb_stop_event_raised = false;
        static void Main()
        {
            var gpio = GpioController.GetDefault();
          
            ButtonRight = gpio.OpenPin(RightButton);
            ButtonRight.SetDriveMode(GpioPinDriveMode.InputPullUp);
           
            usbClientSetting = new UsbClientSetting()
            {
                Mode = UsbClientMode.WinUsb,            
                ManufactureName = "C-Born Software Systems",
                ProductName = "Anode Drop Meter",
                SerialNumber = "2",
                Guid = "{77C99034-2428-424a-8130-DC481841429B}",
                VendorId = 0x1234,
                ProductId = 0x0002,
                MaxPower = 500,
                InterfaceName = "SitCore Based Anode Meter",
                BcdUsb = 0x200,
            };

            int cnt = 0;

            Debug.WriteLine("Press PB7 to start testing...");

            while (true)
            {
                if (ButtonRight.Read() == GpioPinValue.Low)
                {
                    while (ButtonRight.Read() == GpioPinValue.Low) ;


                    cnt++;

                    if (cnt > 0)
                    {
                        if (cnt % 2 == 1)
                        {

                            StopMs();
                            RestartUsb();
                            StartWinUsb();

                            //StopWinUsb();
                            //RestartUsb();
                            //StartWinUsb();

                            //StopMs();
                            //RestartUsb();
                            //StartMs();
                        }
                        else
                        {

                            StopWinUsb();
                            RestartUsb();
                            StartMs();

                            //StopWinUsb();
                            //RestartUsb();
                            //StartWinUsb();

                            //StopMs();
                            //RestartUsb();
                            //StartMs();
                        }

                        
                    }

                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    Debug.WriteLine("Memory free: " + Memory.ManagedMemory.FreeBytes);
                }

                CheckUsb();

                Thread.Sleep(100);
            }
        }

        static void RestartUsb()
        {
            if (usbclientController != null)
            {
                usbclientController.Dispose();

            }

            usbclientController = UsbClientController.GetDefault();
        }

        // Start Mass Storage
        static void StartMs()
        {
            if (ms != null) return;

            Debug.WriteLine("MassStorage Starting... please wait");

            ms = new MassStorage(usbclientController);
            sd = StorageController.FromName(SC20100.StorageController.SdCard);
            ms.DeviceStateChanged += Ms_DeviceStateChanged;
            ms.AttachLogicalUnit(sd.Hdc);
            ms.Enable();
            Debug.WriteLine("MassStorage Started");
        }
        // Stop Mass Storage
        static void StopMs()
        {
            if (ms == null)
                return;
            Debug.WriteLine("Stop massstorage... please wait");

            msc_stop_event_raised = false;

            ms.Disable();
           
            //var timeout = SWITCHING_DELAY;

            //while (true)
            //{
            //    Debug.WriteLine("Please wait for massstorage stop.... " + timeout);
            //    Thread.Sleep(1000);
            //    timeout--;

            //    if (timeout <= 0) break;
            //}

            while (msc_stop_event_raised == false)
            {
                Debug.WriteLine("Please wait for massstorage stop.... ");
                Thread.Sleep(1000);
            }

            ms.RemoveLogicalUnit(sd.Hdc);
            ms.DeviceStateChanged -= Ms_DeviceStateChanged;
            ms.Dispose();
            ms = null;

            Debug.WriteLine("Massstorage Stopped");
        }
        // Start WinUSB
        static void StartWinUsb()
        {
            if (winUsb != null) return;

            Debug.WriteLine("WinUsb Starting... please wait");

            winUsb = new WinUsb(usbclientController, usbClientSetting);
            winUsb.DeviceStateChanged += Usb_DeviceStateChanged;
            winUsb.DataReceived += Usb_DataReceived;
            winUsb.Enable();
            Debug.WriteLine("WinUsb Started");
        }
        //Stop WinUSB
        static void StopWinUsb()
        {
            if (winUsb == null) return;

            winusb_stop_event_raised = false;

            Debug.WriteLine("Stop WinUsb... please wait");
            winUsb.Disable();

            while (winusb_stop_event_raised == false)
            {
                Debug.WriteLine("Please wait for WinUsb stop.... ");
                Thread.Sleep(1000);
            }

            winUsb.DeviceStateChanged -= Usb_DeviceStateChanged;
            winUsb.DataReceived -= Usb_DataReceived;
            winUsb.Dispose();
            winUsb = null;

            //var timeout = SWITCHING_DELAY;

            //while (true)
            //{
            //    Debug.WriteLine("Please wait for WinUsb stop.... " + timeout);
            //    Thread.Sleep(1000);
            //    timeout--;

            //    if (timeout <= 0) 
            //        break;
            //}
            //
           
            Debug.WriteLine("WinUsb Stopped");
        }
       
        private static void Ms_DeviceStateChanged(GHIElectronics.TinyCLR.Devices.UsbClient.RawDevice sender, DeviceState state)
        {
            //Debug.WriteLine("MassStorage changed to " + state.ToString());

            if ((int)state != 5)
            {
                msc_stop_event_raised = true;
            }
        }
        private static void Usb_DeviceStateChanged(GHIElectronics.TinyCLR.Devices.UsbClient.RawDevice sender, DeviceState state)
        {
            //Debug.WriteLine("WinUsb changed to " + state.ToString());

            if ((int)state != 5)
            {
                winusb_stop_event_raised = true;
            }
        }
        private static void Usb_DataReceived(GHIElectronics.TinyCLR.Devices.UsbClient.RawDevice sender, uint count)
        {
            Debug.WriteLine("Data received:" + count);
        }

        // WinUSB Send/Receive - Echo back characters received, incremeneted by 1
        static void CheckUsb()
        {
            if ((winUsb != null) && (winUsb.DeviceState == DeviceState.Configured))
            {
                var len = winUsb.Stream.BytesToRead;
                if (len > 0)
                {
                    var dataR = new byte[len];
                    var dataW = new byte[len];
                    int read = winUsb.Stream.Read(dataR);

                    for (var i = 0; i < read; i++)
                    {
                        dataW[i] = (byte)(dataR[i] + 1);
                    }
                    winUsb.Stream.Write(dataW);
                }
            }
        }
    }
}