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