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