Tim H / Mbed OS UsbKnob

Dependencies:   USBDevice

Committer:
Timmmm
Date:
Mon Feb 13 14:56:00 2017 +0000
Revision:
1:0c3b6cc480b6
Parent:
0:1a3aa2e25db9
Various changes?

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Timmmm 0:1a3aa2e25db9 1 # WinUSB Device Example
Timmmm 0:1a3aa2e25db9 2
Timmmm 0:1a3aa2e25db9 3 If you've ever got frustrated with hardcoded COM ports and parsing text-based serial
Timmmm 0:1a3aa2e25db9 4 protocols, this is for you.
Timmmm 0:1a3aa2e25db9 5
Timmmm 0:1a3aa2e25db9 6 ## Introduction
Timmmm 0:1a3aa2e25db9 7
Timmmm 0:1a3aa2e25db9 8 When we want our microcontroller to communicate with a control program running on a computer
Timmmm 0:1a3aa2e25db9 9 via USB, the usual solution is to use serial-over-USB. It's easy, universally supported, and
Timmmm 0:1a3aa2e25db9 10 signed drivers are often automatically installed - this is even called out as a selling point
Timmmm 0:1a3aa2e25db9 11 by FTDI who make most of the serial-to-USB interface chips used by hobbiests.
Timmmm 0:1a3aa2e25db9 12
Timmmm 0:1a3aa2e25db9 13 There are other advantages of serial control interfaces:
Timmmm 0:1a3aa2e25db9 14
Timmmm 0:1a3aa2e25db9 15 * It doesn't require a specific program to control - you can use an terminal program. My favourite is Termie.
Timmmm 0:1a3aa2e25db9 16 * It's easy to debug and you can easily add printf()-style debugging to your program.
Timmmm 0:1a3aa2e25db9 17 * All microcontrollers support UART.
Timmmm 0:1a3aa2e25db9 18
Timmmm 0:1a3aa2e25db9 19 Eventually however you will run up against the limitations. The highest 'standard' speed is 115200 kb/s. Some
Timmmm 0:1a3aa2e25db9 20 microcontrollers support higher speeds, even up to 3 Mb/s but that is rare. Since the protocol
Timmmm 0:1a3aa2e25db9 21 is stream-based you must create your own framing system or resort to text-based protocol.
Timmmm 0:1a3aa2e25db9 22
Timmmm 0:1a3aa2e25db9 23 Another big problem is that there isn't a good way to detect which COM port your device is plugged
Timmmm 0:1a3aa2e25db9 24 into. Even if your serial API allows enumerating ports (many don't) you might have several different
Timmmm 0:1a3aa2e25db9 25 serial devices plugged in. Distinguishing them becomes a nightmare, especially as the COM port
Timmmm 0:1a3aa2e25db9 26 can change across reboots, and if you move the device between physical USB ports.
Timmmm 0:1a3aa2e25db9 27
Timmmm 0:1a3aa2e25db9 28 A solution - if your microcontroller supports it - is to use USB as nature intended. Turn your
Timmmm 0:1a3aa2e25db9 29 device into a custom USB device and have your program communicate with it via libusb. This
Timmmm 0:1a3aa2e25db9 30 is more complicated, but the end result is better.
Timmmm 0:1a3aa2e25db9 31
Timmmm 0:1a3aa2e25db9 32 Fortunately mBed comes with USB device support for several boards. We'll look at implementing
Timmmm 0:1a3aa2e25db9 33 a simple on/off switch with the FRDM-K22F.
Timmmm 0:1a3aa2e25db9 34
Timmmm 0:1a3aa2e25db9 35 ## WinUSB Devices / WICD
Timmmm 0:1a3aa2e25db9 36
Timmmm 0:1a3aa2e25db9 37 One of the first problems you'll discover - at least on Windows - is the need for signed device drivers.
Timmmm 0:1a3aa2e25db9 38 In the past the only way to support a custom USB device seamlessly on Windows was to write a USB
Timmmm 0:1a3aa2e25db9 39 driver (difficult!) and submit it to Microsoft to be tested and signed (expensive!). Fortunately now
Timmmm 0:1a3aa2e25db9 40 there is a better way.
Timmmm 0:1a3aa2e25db9 41
Timmmm 0:1a3aa2e25db9 42 By configuring our USB device in a certain way we can identify it to Windows as a "WinUSB Device". This
Timmmm 0:1a3aa2e25db9 43 means that Windows 7 and later will automatically install Microsoft's WinUSB driver for it. This
Timmmm 0:1a3aa2e25db9 44 is a generic driver that allows access to USB functions from userspace - i.e. your program.
Timmmm 0:1a3aa2e25db9 45 libusb has support for the WinUSB driver so you can still write cross-platform code.
Timmmm 0:1a3aa2e25db9 46
Timmmm 0:1a3aa2e25db9 47 Before I explain how to implement WinUSB I'll give brief overview of how USB works.
Timmmm 0:1a3aa2e25db9 48
Timmmm 0:1a3aa2e25db9 49 ## A very brief introduction to USB
Timmmm 0:1a3aa2e25db9 50
Timmmm 0:1a3aa2e25db9 51 USB comes in a few flavours:
Timmmm 0:1a3aa2e25db9 52
Timmmm 0:1a3aa2e25db9 53 * USB 1.1 - Supports "Low Speed" (1.5 Mb/s) and "Full Speed" (12 Mb/s)
Timmmm 0:1a3aa2e25db9 54 * USB 2 - Adds support for "High Speed" (480 Mb/s)
Timmmm 0:1a3aa2e25db9 55 * USB 3 - Adds support for "Super Speed" (lots of Mb/s)
Timmmm 0:1a3aa2e25db9 56
Timmmm 0:1a3aa2e25db9 57 To be clear: Low Speed < Full Speed < High Speed < Super Speed. Woo branding. Virtually all microcontrollers
Timmmm 0:1a3aa2e25db9 58 with USB support support USB 2, though almost all of them only support Full Speed, and not High Speed. An exception
Timmmm 0:1a3aa2e25db9 59 is the Atmel SAM3X8E and similar which is found on the Arduino Due.
Timmmm 0:1a3aa2e25db9 60
Timmmm 0:1a3aa2e25db9 61 USB is a packet based protocol. Each transmission and reception is initiated with an 8-byte 'setup packet'. It has a very simple
Timmmm 0:1a3aa2e25db9 62 structure:
Timmmm 0:1a3aa2e25db9 63
Timmmm 0:1a3aa2e25db9 64 struct SetupPacket
Timmmm 0:1a3aa2e25db9 65 {
Timmmm 0:1a3aa2e25db9 66 uint8_t bmRequestType;
Timmmm 0:1a3aa2e25db9 67 uint8_t bRequest;
Timmmm 0:1a3aa2e25db9 68 uint16_t wValue;
Timmmm 0:1a3aa2e25db9 69 uint16_t wIndex;
Timmmm 0:1a3aa2e25db9 70 uint16_t wLength;
Timmmm 0:1a3aa2e25db9 71 };
Timmmm 0:1a3aa2e25db9 72
Timmmm 0:1a3aa2e25db9 73 All transfers are initiated by the host - the peripheral only ever responds to requests. If it wants to send data
Timmmm 0:1a3aa2e25db9 74 it must wait to be polled.
Timmmm 0:1a3aa2e25db9 75
Timmmm 0:1a3aa2e25db9 76 The `bmRequestType` lets the host do various standard and vendor requests. Aside from initating data transfers
Timmmm 0:1a3aa2e25db9 77 it allows the host to request configuration descriptors which describe what the device is (keyboard, mouse, etc.),
Timmmm 0:1a3aa2e25db9 78 its name, manufacturer, data endpoints, and loads more.
Timmmm 0:1a3aa2e25db9 79
Timmmm 0:1a3aa2e25db9 80 The main configuration descriptor describes the most important detail - the endpoints. Endpoints are really just
Timmmm 0:1a3aa2e25db9 81 addresses within the device that you can send data to or from. If you like you can just have one transmit and
Timmmm 0:1a3aa2e25db9 82 one receive endpoint and do your own addressing. However there are some different types of endpoints so you may
Timmmm 0:1a3aa2e25db9 83 wish to have more than two. The simplest type that we'll be looking at is the interrupt endpoint, designed for
Timmmm 0:1a3aa2e25db9 84 small fast bits of data, e.g. keystrokes or mouse movements, or control and status.
Timmmm 0:1a3aa2e25db9 85
Timmmm 0:1a3aa2e25db9 86 You can simply send and receive packets to/from the endpoints, in a very similar maner to UDP.
Timmmm 0:1a3aa2e25db9 87
Timmmm 0:1a3aa2e25db9 88 ## A USBSwitch class
Timmmm 0:1a3aa2e25db9 89
Timmmm 0:1a3aa2e25db9 90 We'll implement a generic USBControl class that allows sending and receiving data packets via interrupt endpoints
Timmmm 0:1a3aa2e25db9 91 and then subclass it to implement a light bulb device with a switch. We'll mirror the device on a desktop
Timmmm 0:1a3aa2e25db9 92 application using Qt, so you can switch the light on and off from the device or the computer, and its state
Timmmm 0:1a3aa2e25db9 93 will update on both.
Timmmm 0:1a3aa2e25db9 94
Timmmm 0:1a3aa2e25db9 95 Finally we'll make it into a WinUSB device so the driver is installed automatically.
Timmmm 0:1a3aa2e25db9 96
Timmmm 0:1a3aa2e25db9 97 ## A note on Vendor and Product IDs
Timmmm 0:1a3aa2e25db9 98
Timmmm 0:1a3aa2e25db9 99 There is one annoying thing about using native USB for communication. Your device must be given a
Timmmm 0:1a3aa2e25db9 100 Vendor ID and Product ID. These 16-bit identifiers are used to distinguish products and are the usual
Timmmm 0:1a3aa2e25db9 101 method a program uses to find plugged-in devices it cares about.
Timmmm 0:1a3aa2e25db9 102
Timmmm 0:1a3aa2e25db9 103 Unfortunately since they are only 16-bit, there are only 65536 possible vendors, and each can only
Timmmm 0:1a3aa2e25db9 104 have 65536 products. This was a deliberate ploy by the USB Implementors' Forum (USB-IF) to allow them
Timmmm 0:1a3aa2e25db9 105 to justify charging exhorbitant fees to claim a Vendor ID.
Timmmm 0:1a3aa2e25db9 106
Timmmm 0:1a3aa2e25db9 107 It is beyond the means of most hobbiests to buy a Vendor ID, and the USB-IF has shut down vendors who
Timmmm 0:1a3aa2e25db9 108 offered their Product IDs for sale for a reasonable price. Therefore the only real solution is to
Timmmm 0:1a3aa2e25db9 109 fuck the USB-IF and squat on an unused or abandoned Vendor ID. Perhaps if enough people do this the
Timmmm 0:1a3aa2e25db9 110 USB-IF will provide a real solution, since there's little they can do to stop it.
Timmmm 0:1a3aa2e25db9 111
Timmmm 0:1a3aa2e25db9 112 I use the Vendor ID 0x0004 which is for Nebraska Furniture Mart (seriously). I doubt they will be using
Timmmm 0:1a3aa2e25db9 113 it any time soon! Others have suggested using 0xF055 (FOSS).
Timmmm 0:1a3aa2e25db9 114
Timmmm 0:1a3aa2e25db9 115 ## How Windows probes WinUSB devices
Timmmm 0:1a3aa2e25db9 116
Timmmm 0:1a3aa2e25db9 117 The first thing Windows does to probe WinUSB devices is to send a GET_DESCRIPTOR request
Timmmm 0:1a3aa2e25db9 118 for the STRING_DESCRIPTOR at offset 0xEE. The value 0xEE is defined by Microsoft to be the index
Timmmm 0:1a3aa2e25db9 119 of the MSOS (MicroSoft Operating System) string.
Timmmm 0:1a3aa2e25db9 120
Timmmm 0:1a3aa2e25db9 121 A WinUSB device should return a valid MSOS string descriptor which looks like this:
Timmmm 0:1a3aa2e25db9 122
Timmmm 0:1a3aa2e25db9 123 struct PACKED WindowsOsString
Timmmm 0:1a3aa2e25db9 124 {
Timmmm 0:1a3aa2e25db9 125 uint8_t bLength; // Length of this structure, 0x12.
Timmmm 0:1a3aa2e25db9 126 uint8_t bDescriptorType; // String descriptor, 0x03 (STRING_DESCRIPTOR)
Timmmm 0:1a3aa2e25db9 127 uint8_t qwSignature[14]; // 'MSFT100' but in UTF-16 ( "M\0S\0F\0T\01\00\00\0" )
Timmmm 0:1a3aa2e25db9 128 uint8_t bMS_VendorCode; // Vendor code to retrieve OS feature desciptors. Can be changed. Example 0x01.
Timmmm 0:1a3aa2e25db9 129 uint8_t bPad; // Padding, 0x00.
Timmmm 0:1a3aa2e25db9 130 };
Timmmm 0:1a3aa2e25db9 131
Timmmm 0:1a3aa2e25db9 132 The only variable information in this sturcture is the bMS_VendorCode value. This can be set to anything.
Timmmm 0:1a3aa2e25db9 133 It is used in the following USB requests that Windows makes.
Timmmm 0:1a3aa2e25db9 134
Timmmm 0:1a3aa2e25db9 135 After obtaining the WindowsOsString structure, Windows makes several vendor requests as using the
Timmmm 0:1a3aa2e25db9 136 bMS_VendorCode value as the request type. The value of wIndex indicates which of several things it wants.
Timmmm 0:1a3aa2e25db9 137 The main ones that concern us are:
Timmmm 0:1a3aa2e25db9 138
Timmmm 0:1a3aa2e25db9 139 #define COMPAT_ID_INDEX (4)
Timmmm 0:1a3aa2e25db9 140 #define EXTENDED_PROPERTIES_INDEX (5)
Timmmm 0:1a3aa2e25db9 141
Timmmm 0:1a3aa2e25db9 142 The first is for the "Compatible IDs" - the device must return a compatible ID of "WINUSB".
Timmmm 0:1a3aa2e25db9 143
Timmmm 0:1a3aa2e25db9 144 The second is for the extended properties. Extended properties allow you to set the device icon, label, and
Timmmm 0:1a3aa2e25db9 145 an interface GUID which can be used by programs instead of the Vendor/Product ID to find the device. This
Timmmm 0:1a3aa2e25db9 146 is actually a great move by Microsoft as the Vendor/Product ID system sucks (see below).
Timmmm 0:1a3aa2e25db9 147
Timmmm 0:1a3aa2e25db9 148 ## Implementation in mBed
Timmmm 0:1a3aa2e25db9 149
Timmmm 0:1a3aa2e25db9 150 To implement this in mBed we first make a WinUSBDevice class that derives from USBDevice:
Timmmm 0:1a3aa2e25db9 151
Timmmm 0:1a3aa2e25db9 152 class WinUSBDevice : public USBDevice
Timmmm 0:1a3aa2e25db9 153 {
Timmmm 0:1a3aa2e25db9 154 public:
Timmmm 0:1a3aa2e25db9 155 WinUSBDevice(uint16_t vendor_id, uint16_t product_id, uint16_t product_release);
Timmmm 0:1a3aa2e25db9 156
Timmmm 0:1a3aa2e25db9 157 We need to intercept setup requests, and possibly handle them before the default handling code runs.
Timmmm 0:1a3aa2e25db9 158 mBed provides a virtual function we can override for that:
Timmmm 0:1a3aa2e25db9 159
Timmmm 0:1a3aa2e25db9 160 // Handle vendor setup requests. Return true if we handle the request, otherwise false to let mBed do its
Timmmm 0:1a3aa2e25db9 161 // default action, which is to call requestSetup().
Timmmm 0:1a3aa2e25db9 162 bool USBCallback_request(); // override; // override commented out because mBed'd online compiler still doesn't use C++11
Timmmm 0:1a3aa2e25db9 163
Timmmm 0:1a3aa2e25db9 164 Finally we need somewhere to store the data that we will return:
Timmmm 0:1a3aa2e25db9 165
Timmmm 0:1a3aa2e25db9 166 private:
Timmmm 0:1a3aa2e25db9 167 WindowsOsString osStringDesc;
Timmmm 0:1a3aa2e25db9 168 CompatIdData compatIdData;
Timmmm 0:1a3aa2e25db9 169 ExtendedPropertyData extendedPropertyData;
Timmmm 0:1a3aa2e25db9 170 };
Timmmm 0:1a3aa2e25db9 171
Timmmm 0:1a3aa2e25db9 172 In the WinUSBDevice constructor we initialise the data to return:
Timmmm 0:1a3aa2e25db9 173
Timmmm 0:1a3aa2e25db9 174 ...
Timmmm 0:1a3aa2e25db9 175
Timmmm 0:1a3aa2e25db9 176 And now we get to the meat - filtering out the WinUSB-related requests to handle, and returning the data
Timmmm 0:1a3aa2e25db9 177 we have crafted:
Timmmm 0:1a3aa2e25db9 178
Timmmm 0:1a3aa2e25db9 179 bool WinUSBDevice::USBCallback_request()
Timmmm 0:1a3aa2e25db9 180 {
Timmmm 0:1a3aa2e25db9 181
Timmmm 1:0c3b6cc480b6 182 Returning false means 'I didn't handle this; carry on as you were'. Returning true means
Timmmm 1:0c3b6cc480b6 183 'I've handled this and stored a response in transfer'.
Timmmm 1:0c3b6cc480b6 184
Timmmm 0:1a3aa2e25db9 185 First we need a reference to the transfer structure which contains both the request information
Timmmm 0:1a3aa2e25db9 186 and provides a place to store our response. The USBDevice::transfer member is actually private
Timmmm 0:1a3aa2e25db9 187 but there is a protected function to access it. (Thank you mBed people - I always knew your APIs were the best!
Timmmm 0:1a3aa2e25db9 188 Except the BLE API; that sucks medium time.)
Timmmm 0:1a3aa2e25db9 189
Timmmm 0:1a3aa2e25db9 190 // This can never be null.
Timmmm 0:1a3aa2e25db9 191 CONTROL_TRANSFER& transfer = *getTransferPtr();
Timmmm 0:1a3aa2e25db9 192
Timmmm 1:0c3b6cc480b6 193 Requests can be addressed to the device, an interface or an endpoint. Most WinUSB requests are addressed
Timmmm 1:0c3b6cc480b6 194 to the device, but the Extended Properties request is sent to the interface, so we'll just ignore what
Timmmm 1:0c3b6cc480b6 195 the recipient is. We only have on interface anyway.
Timmmm 1:0c3b6cc480b6 196
Timmmm 0:1a3aa2e25db9 197
Timmmm 0:1a3aa2e25db9 198 Next we see if this is a request for the 0xEE string descriptor.
Timmmm 0:1a3aa2e25db9 199
Timmmm 0:1a3aa2e25db9 200 // Intercept the request for string descriptor 0xEE
Timmmm 0:1a3aa2e25db9 201 if (transfer.setup.bmRequestType.Type == STANDARD_TYPE &&
Timmmm 0:1a3aa2e25db9 202 transfer.setup.bRequest == GET_DESCRIPTOR &&
Timmmm 0:1a3aa2e25db9 203 DESCRIPTOR_TYPE(transfer.setup.wValue) == STRING_DESCRIPTOR &&
Timmmm 0:1a3aa2e25db9 204 DESCRIPTOR_INDEX(transfer.setup.wValue) == STRING_OFFSET_MSOS)
Timmmm 0:1a3aa2e25db9 205 {
Timmmm 0:1a3aa2e25db9 206
Timmmm 0:1a3aa2e25db9 207 If it is, return our MSOS string descriptor.
Timmmm 0:1a3aa2e25db9 208
Timmmm 0:1a3aa2e25db9 209 transfer.ptr = reinterpret_cast<uint8_t*>(&osStringDesc);
Timmmm 0:1a3aa2e25db9 210 transfer.remaining = osStringDesc.bLength;
Timmmm 0:1a3aa2e25db9 211 transfer.direction = DEVICE_TO_HOST;
Timmmm 0:1a3aa2e25db9 212 return true;
Timmmm 0:1a3aa2e25db9 213 }
Timmmm 0:1a3aa2e25db9 214
Timmmm 0:1a3aa2e25db9 215 After Windows has made that request it will send vendor requests using our custom request type
Timmmm 0:1a3aa2e25db9 216 (GET_MS_DESCRIPTORS).
Timmmm 0:1a3aa2e25db9 217
Timmmm 0:1a3aa2e25db9 218 // Our custom descriptor.
Timmmm 0:1a3aa2e25db9 219 if (transfer.setup.bmRequestType.Type == VENDOR_TYPE &&
Timmmm 0:1a3aa2e25db9 220 transfer.setup.bRequest == GET_MS_DESCRIPTORS)
Timmmm 0:1a3aa2e25db9 221 {
Timmmm 0:1a3aa2e25db9 222
Timmmm 0:1a3aa2e25db9 223 Then we need to see which descriptor Windows wants and return the appropriate one.
Timmmm 0:1a3aa2e25db9 224
Timmmm 0:1a3aa2e25db9 225 switch (transfer.setup.wIndex)
Timmmm 0:1a3aa2e25db9 226 {
Timmmm 0:1a3aa2e25db9 227 case COMPAT_ID_INDEX: // Extended Compat ID
Timmmm 0:1a3aa2e25db9 228 transfer.ptr = reinterpret_cast<uint8_t*>(&compatIdData);
Timmmm 0:1a3aa2e25db9 229 transfer.remaining = sizeof(compatIdData);
Timmmm 0:1a3aa2e25db9 230 transfer.direction = DEVICE_TO_HOST;
Timmmm 0:1a3aa2e25db9 231 return true;
Timmmm 0:1a3aa2e25db9 232 case EXTENDED_PROPERTIES_INDEX: // Extended Properties.
Timmmm 0:1a3aa2e25db9 233 transfer.ptr = reinterpret_cast<uint8_t*>(&extendedPropertyData);
Timmmm 0:1a3aa2e25db9 234 transfer.remaining = sizeof(extendedPropertyData);
Timmmm 0:1a3aa2e25db9 235 transfer.direction = DEVICE_TO_HOST;
Timmmm 0:1a3aa2e25db9 236 return true;
Timmmm 0:1a3aa2e25db9 237 case GENRE_INDEX: // Genre
Timmmm 0:1a3aa2e25db9 238 default:
Timmmm 0:1a3aa2e25db9 239 return false;
Timmmm 0:1a3aa2e25db9 240 }
Timmmm 0:1a3aa2e25db9 241 }
Timmmm 0:1a3aa2e25db9 242
Timmmm 0:1a3aa2e25db9 243 If it wasn't a relevant request, let mBed handle it as it would have.
Timmmm 0:1a3aa2e25db9 244
Timmmm 0:1a3aa2e25db9 245 return false;
Timmmm 0:1a3aa2e25db9 246 }
Timmmm 0:1a3aa2e25db9 247
Timmmm 0:1a3aa2e25db9 248 And we're done! Next we need a USB class that takes advantage of this WinUSBDevice class and
Timmmm 0:1a3aa2e25db9 249 responds to our control transfers.
Timmmm 0:1a3aa2e25db9 250
Timmmm 1:0c3b6cc480b6 251
Timmmm 1:0c3b6cc480b6 252 ...WIP...