Search code examples
usbmicrocontrollerlpc

My USB device (microcontroller) prevents Windows from booting


I've been developing an application for the NXP LPC1788 microcontroller that involves communicating with a host PC using USB.

In general the application works fine. However, something I've noticed with multiple different computers is that if the microcontroller is powered-on and connected to a powered-off computer, and the computer is then turned on, the computer will not boot. Unplugging the microcontroller and trying to boot the computer again will resolve the problem.

Why is the microcontroller preventing the computer from booting? I've Googled the problem and it seems that this issue occurs when Windows tries to treat a USB device as bootable when it shouldn't be.

Is there a way I can prevent this happening with my application? What information should I provide to help others diagnose the problem?

In case it's useful, below is the micro's device descriptor:

/* USB Standard Device Descriptor */
const uint8_t USB_DeviceDescriptor[] = {
  USB_DEVICE_DESC_SIZE,              /* bLength */
  USB_DEVICE_DESCRIPTOR_TYPE,        /* bDescriptorType */
  WBVAL(0x0200), /* 2.00 */          /* bcdUSB */
  0xFF,                              /* bDeviceClass */
  0x00,                              /* bDeviceSubClass */
  0x00,                              /* bDeviceProtocol */
  USB_MAX_PACKET_SIZE,               /* bMaxPacketSize0 */
  WBVAL(<...>),                      /* idVendor */
  WBVAL(<...>),                      /* idProduct */
  WBVAL(0x0100), /* 1.00 */          /* bcdDevice */
  0x04,                              /* iManufacturer */
  0x30,                              /* iProduct */
  0x42,                              /* iSerialNumber */
  0x01                               /* bNumConfigurations */
};

Here is the configuration descriptor:

const uint8_t USB_ConfigDescriptor[] = {
/* Configuration 1 */
  USB_CONFIGURATION_DESC_SIZE,       /* bDescriptorType */
  USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */
  WBVAL(                             /* wTotalLength */
    1*USB_CONFIGURATION_DESC_SIZE +
    1*USB_INTERFACE_DESC_SIZE     +
    2*USB_ENDPOINT_DESC_SIZE
  ),
  0x01,                              /* bNumInterfaces */
  0x01,                              /* bConfigurationValue */
  0x00,                              /* iConfiguration */
  USB_CONFIG_SELF_POWERED /*|*/      /* bmAttributes */
/*USB_CONFIG_REMOTE_WAKEUP*/,
  USB_CONFIG_POWER_MA(100),          /* bMaxPower */
/* Interface 0, Alternate Setting 0, MSC Class */
  USB_INTERFACE_DESC_SIZE,           /* bLength */
  USB_INTERFACE_DESCRIPTOR_TYPE,     /* bDescriptorType */
  0x00,                              /* bInterfaceNumber */
  0x00,                              /* bAlternateSetting */
  0x02,                              /* bNumEndpoints */
  0xFF,                              /* bInterfaceClass */
  0x0,                               /* bInterfaceSubClass */
  0x0,                               /* bInterfaceProtocol */
  0x5C,                              /* iInterface */
/* Bulk In Endpoint (data) */
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  USB_ENDPOINT_IN(2),                /* bEndpointAddress */
  USB_ENDPOINT_TYPE_BULK,            /* bmAttributes */
  WBVAL(0x0040),                     /* wMaxPacketSize */
  0x0,                               /* bInterval */
/* Bulk Out Endpoint (data) */
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  USB_ENDPOINT_OUT(2),               /* bEndpointAddress */
  USB_ENDPOINT_TYPE_BULK,            /* bmAttributes */
  WBVAL(0x0040),                     /* wMaxPacketSize */
  0x0,                               /* bInterval */
/* Terminator */
  0                                  /* bLength */
};

EDIT

String descriptor (replaced letters with underscores):

const uint8_t USB_StringDescriptor[] = {
/* Index 0x00: LANGID Codes */
  0x04,                              /* bLength */
  USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
  WBVAL(0x0409), /* US English */    /* wLANGID */
/* Index 0x04: Manufacturer */
  0x2C,                              /* bLength */
  USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
  '_',0,
  '_',0,
  '_',0,
  ' ',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  ' ',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  ' ',0,
/* Index 0x30: Product */
  0x14,                              /* bLength */
  USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  '_',0,
  ' ',0,
/* Index 0x42: Serial Number */
  0x1A,                              /* bLength */
  USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
/* Index 0x5C: Interface 0, Alternate Setting 0 */
  0x0E,                              /* bLength */
  USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
  '0',0,
};

EDIT 2

Changing iManufacturer, iProduct and iSerialNumber to 0x1, 0x2 and 0x3 respectively makes it so that USBLyzer doesn't fetch the string descriptors, which isn't a promising sign. If I use the original index values, it does. I haven't checked how this affects the computer's ability to boot.

EDIT 3

I noticed that my iSerialNumber and iInterface parameters were 2 off. 0x30+0x14 = 0x44.

I'll see if that's fixed anything.

EDIT 4

After correcting the two parameters above, my issue seems to be fixed. TurboJ brought my attention to the fact that index values are meant to be small e.g. 0x1, 0x2, 0x3...

NXP seem to have a different idea of how to do it and I copied their way, but I think I can modify the code at some later time to make it work like it does for other USB devices.


Solution

  • I corrected my problem and improved over NXP's method.

    NXP's example USB projects kept all the string descriptors in a single array. They then implemented the "USB Get Descriptor" functionality in the following way:

    case USB_STRING_DESCRIPTOR_TYPE:
      EP0Data.pData = (uint8_t *)USB_StringDescriptor + SetupPacket.wValue.WB.L;
      len = ((USB_STRING_DESCRIPTOR *)EP0Data.pData)->bLength;
      break;
    

    This meant that they had to specify iManufacturer, iProduct, etc. using ridiculous index values such as (in their case) 0x04, 0x20 and 0x48.

    In my own code, I modified their approach. I maintain the string descriptors as an array of arrays:

    const uint8_t USB_StringDescriptor[][100] = {
      {
      /* Index 0x00: LANGID Codes */
      0x04,                              /* bLength */
      USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
      WBVAL(0x0409), /* US English */    /* wLANGID */
      },
    
      {
      /* Index 0x01: Manufacturer */
      0x2A,                              /* bLength */
      USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
      // ...
      },
    
      {
      /* Index 0x02: Product */
      0x12,                              /* bLength */
      USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
      // ...
      },
    
      {
      /* Index 0x03: Serial Number */
      0x1A,                              /* bLength */
      USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
      // ...
      },
    
      {
      /* Index 0x04: Interface 0, Alternate Setting 0 */
      0x0E,                              /* bLength */
      USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */
      // ...
      }
    };
    

    I then change the way that string descriptor requests are processed:

    case USB_STRING_DESCRIPTOR_TYPE:
      EP0Data.pData = (uint8_t *)USB_StringDescriptor[SetupPacket.wValue.WB.L];
      len = ((USB_STRING_DESCRIPTOR *)EP0Data.pData)->bLength;
      break;
    

    This way, within my device descriptor I can specify iManufacturer, iProduct, etc. as simply 0x01, 0x02...

    I've tested this with two computers and both are able to boot successfully when my application is connected and running.