Spark Logic

USB Composite (HID / MIDI / Mass Storage) library

arpruss Sun Dec 03, 2017 8:32 pm

I’ve worked hard to bring the addMidiHID branch of the libmaple-based core up to date, and even made a pull request to merge that into the main branch, but then today I realized that it would be simpler for maintenance and customization to put all the stuff into a separate library, making use of the fact that the core’s USB serial support has __weak references where it matters.

So, here is a USB library compatible with the main libmaple-based core:

(The library is also bundled with Roger’s core.)

As of February 2018, the library supports:

  • USB HID
  • Serial (use CompositeSerial, not Serial)
  • USB MIDI
  • XBox 360 controller
  • Mass storage

You can do various composite combinations, e.g., HID+Serial, Mass storage+HID, etc.

Here’s some example code: an adapter from the IBM PC game port to USB joystick:

#include <GameControllers.h> // https://github.com/arpruss/GameControllersSTM32
#include <USBComposite.h>

USBHID HID; // declare HID plugin instance
HIDJoystick Joystick(HID); // declare Joystick profile instance

GamePortController controller(PA0,PA1,PA2,PA3,PA4,PA5,PA6,PA7);

void setup()
{
// to composite serial, use USBHID_begin_with_serial() instead
USBHID.begin(HID_JOYSTICK); // other options: HID_KEYBOARD, HID_MOUSE, HID_KEYBOARD_JOYSTICK, HID_KEYBOARD_MOUSE, HID_KEYBOARD_MOUSE_JOYSTICK

pinMode(LED_BUILTIN, OUTPUT);

digitalWrite(LED_BUILTIN, 1);
controller.begin();
Joystick.setManualReportMode(true); // aggregate data before sending
}

void loop()
{
GameControllerData_t data;
if (controller.read(&data)) {
Joystick.X(data.joystickX);
Joystick.Y(data.joystickY);
Joystick.Xrotate(data.cX);
Joystick.sliderRight(data.shoulderRight);
uint8_t mask = 1;
for (int i=1; i<=8; i++, mask <<= 1)
Joystick.button(i, (data.buttons & mask) != 0);
Joystick.send();
}

delay(10);
}

Replies

arpruss Sat Feb 03, 2018 4:40 pm

There is one more major USB device type that isn’t yet supported: audio. Anybody want to write a USBAudio plugin?

RogerClark Sat Feb 03, 2018 8:15 pm

Audio is an interesting thought ..

Perhaps it can be used with the Pig-O-Scope code, which already can read the ADC via DMA.

Audio output is a bit more tricky, as the BluePill etc don’t have a DAC. I know there is code to do clever things with PWM to simulate a DAC but I am not sure of the system overhead

There is also Video or Camera input, as we have code that will read the OV7670 camera, at 320 x 240 at around 11 FPS.
I think the frame speed may be a limitation of the ILI9341, that we use to display the image.
( SteveStrong is the guru this)

Unfortunately I have other more pressing commitments of my time, so I can’t help with this
PS. I WIsh I had time to understand the internals of USB as I have several projects where I need to reverse engineer some USB protocols, but they are all on the back-burner because I don’t have time :-(

michael_l Sat Feb 03, 2018 9:24 pm

[arpruss – Sat Feb 03, 2018 4:40 pm] –
There is one more major USB device type that isn’t yet supported: audio. Anybody want to write a USBAudio plugin?

Sorry for simple question but what can I do with the USB Audio – and what kind of specifications there are for USB audio interface ?

arpruss Mon Feb 05, 2018 4:16 am

After a lot of trial and error I managed to find a way to keep the linker from linking in much unneeded code (e.g., if you want to use USBHID, it shouldn’t load mass storage, serial, etc.)

Unfortunately, I had to make some changes to the API, as my previous implementation of USB compositing used classes with virtual functions. But when such a class is initialized, automatically all the virtual functions get linked in, even if they are never used.

Two main changes:
1. USBHID.begin() does not include serial any more. To force serial to be included, use the convenience function USBHID_begin_with_serial() instead. And the USBHID.setSerial() method has been removed.
2. To add a component to the composite device, do: component.registerComponent() instead of the previous USBComposite.add(component). See the mass storage examples.

Also, I renamed the mass storage and x360 classes and/or instances.

Freakeyyy Tue Feb 13, 2018 9:30 pm

Just got to play with the library, it’s working great, except for Serial. Is it just me or is Serial not working yet ? It shows on Device Manager but doesn’t print anything

arpruss Wed Feb 14, 2018 2:25 pm

Are you using CompositeSerial instead of Serial as the libraryt requires?
Try the keyboardwithleds example which should echo the keyboard led status to serial.

Cpt.Credible Wed Feb 28, 2018 4:53 pm

I just got the chance to play with this library today and I love it! I was however wondering, Is this library incompatible with the glorious Francois best (a.k.a. FortySevenEffects) MIDI library? just adding “#include <MIDI.h> in the “midiin.ino” example throws this error:
“‘myMidi midi’ redeclared as a different kind of symbol”
Is there a quick fix to this? do I need to go in and change one of the two libraries?

Aslo, on one of the three computers I have tried it on, the midi device isn’t found by the computer. (a stationary running win10) Its a blue pill board with the wrong resistor on R10 but curiously the Maple serial port shows up and works just fine. could this still be explained by the R10 resistor value? or is there something else wrong?

arpruss Thu Mar 01, 2018 2:19 pm

[Cpt.Credible – Wed Feb 28, 2018 4:53 pm] –
just adding “#include <MIDI.h> in the “midiin.ino” example throws this error:
“‘myMidi midi’ redeclared as a different kind of symbol”

No need to change the libraries. Just change the example by replacing the midi variable in the example with something else, e.g., usbmidi.

Aslo, on one of the three computers I have tried it on, the midi device isn’t found by the computer. (a stationary running win10) Its a blue pill board with the wrong resistor on R10 but curiously the Maple serial port shows up and works just fine. could this still be explained by the R10 resistor value? or is there something else wrong?

I wouldn’t think the resistor thing would have this kind of effect given that the serial shows up. You might check if joystick shows up (e.g., use the SimpleJoystick example).

For me, the MIDI examples work on Win10pro. But I have occasionally had Windows get confused when switching between different USB devices that all have the same vendor and product ids. So if you plugged the board in in a different mode (e.g., serial or HID), you might have confused Windows. You can try deleting the old driver in the Windows device manager, and then hotplugging again, or changing the product id in the code. E.g., in my midiin example, add midi.setProductId(0x25) before calling midi.begin().

madias Thu Mar 01, 2018 3:56 pm

Some words about Mass Storage:
Years ago I argued about the benefit of Mass Storage for a simply reason:
You have finished a project for a third person without really MCU/coding knowledge (call this protoype person “Mother”). There is a mistake in the code you have to correct it, simple for you, but the “third person” cannot upload it as we do. So the “project device” has an upload feature (combined buttons on start, on display, whatever), switching the device to Mass Storage and just plugin to computer, upload the new *.bin file -> ready.
So this should be now possible, isn’t it?

Second:
It should also be possible to switch between USB HID states (like switching from USB-MIDI to USB Mass Storage and so on)?

Regards
Matthias

arduinoob Thu Mar 01, 2018 4:22 pm

I never heard about STM32 before today and I would like to know if the code shared above is compatible with “classic” arduino nano/pro micro.

In fact I would like to use my arduino as an emulated mass storage (like an usb drive) when plugged to a PC providing files stored from a microSD card used with a shield.

sorry for the inconvenience if my message is innapropriate but I can’t find any clue except this page from arduino : https://github.com/arduino/arduino/wiki … ehid-howto

I’m not confident with microcontrollers programming unfortunatelly :(

madias Thu Mar 01, 2018 4:31 pm

The “classic” Arduino (ATmega 328p) has no USB physicially on chip, so you cannot use this code with this devices. So you’ll need at least a ATmega32u4 (“Arduino Leonardo”)
It’s all written here:
https://github.com/arduino-libraries/MIDIUSB
“This library allows an Arduino board with USB capabilites to act as a MIDI instrument over USB. The library is based on PluggableUSB, so is only compatible with IDE 1.6.6 and nightly builds (AVR core > 1.6.8, SAM core > 1.6.4, SAMD core > 1.6.1)
Furthermore, since PluggableUSB only targets boards with native USB capabilities, this library only supports these boards (eg. Leonardo, Micro, Due, Zero and so on)
“
But: You can get a STM32 “blue pill” for under 2 Euros on Ebay/Aliexpress, so try it out!

Keep in mind that the “micro” sold on Ebay/Aliexpress is mostly an Atmega 328p!

Edit: This would be your friend, but keep in mind that it costs more than a blue pill!
https://www.aliexpress.com/item/Pro-Mic … 76437.html

arduinoob Thu Mar 01, 2018 5:03 pm

Thank you madias for your answer !!

I have the version you suggested in the link. I already use the HID.h provided by Arduino to emulate a keyboard but current work seems to only allow keyboard and mouse emulation :-/ Nothing for mass storage emulation ;-(

Cpt.Credible Thu Mar 01, 2018 7:45 pm

[arpruss – Thu Mar 01, 2018 2:19 pm] –
No need to change the libraries. Just change the example by replacing the midi variable in the example with something else, e.g., usbmidi.

Ehem, cough, yes, of course. :oops: :D

[arpruss – Thu Mar 01, 2018 2:19 pm] –
For me, the MIDI examples work on Win10pro. But I have occasionally had Windows get confused when switching between different USB devices that all have the same vendor and product ids. So if you plugged the board in in a different mode (e.g., serial or HID), you might have confused Windows. You can try deleting the old driver in the Windows device manager, and then hotplugging again, or changing the product id in the code. E.g., in my midiin example, add midi.setProductId(0x25) before calling midi.begin().

I’ll try that!

petr Thu Mar 01, 2018 11:01 pm

I have just found this thread (was following the libarra’s thread where someone just posted a link here, thanks!) and I must say wonderful work!!

I have an existing device/project using HID RAW for high speed data transfer protocol that is implemented on ATMEGA16U2 using LUFA library. I’ll try porting it to STM32 now thanks to this new library that supports HID RAW, eventually. Thank you again, @arpruss.

Also, the recent work on USB mass storage is exciting. The possible simple file upload for firmware upgrade is just dream come true :)

arpruss Fri Mar 02, 2018 2:47 am

[petr – Thu Mar 01, 2018 11:01 pm] – I have an existing device/project using HID RAW for high speed data transfer protocol that is implemented on ATMEGA16U2 using LUFA library. I’ll try porting it to STM32 now thanks to this new library that supports HID RAW, eventually.

I should say that HID RAW is not as well tested as other parts of the library. So I won’t be surprised if there are bugs. It’s also set to do host-to-device transmission over the control endpoint, which may not be ideal for speed.

anv Fri Mar 02, 2018 8:00 am

[madias – Thu Mar 01, 2018 3:56 pm] –
You have finished a project for a third person without really MCU/coding knowledge (call this protoype person “Mother”). There is a mistake in the code you have to correct it, simple for you, but the “third person” cannot upload it as we do. So the “project device” has an upload feature (combined buttons on start, on display, whatever), switching the device to Mass Storage and just plugin to computer, upload the new *.bin file -> ready.

I can think on many uses for USB-Storage. I.e. a data logger that can be read just plugging an usb cable and readig as a read-only disk. There is no need to have a complete filesystem on memory. The FAT structures can be simulated on software.

arpruss Fri Mar 02, 2018 7:19 pm

[madias – Thu Mar 01, 2018 3:56 pm] – You have finished a project for a third person without really MCU/coding knowledge (call this protoype person “Mother”). There is a mistake in the code you have to correct it, simple for you, but the “third person” cannot upload it as we do. So the “project device” has an upload feature (combined buttons on start, on display, whatever), switching the device to Mass Storage and just plugin to computer, upload the new *.bin file -> ready.
So this should be now possible, isn’t it?

I think you could do this. You’d probably have to have a virtual disk in the upper 64K of flash, and then you have the host write to that disk, and then you flash from it. But you’d need to worry about bricking the device mid-flash. I think the way I’d implement it would be by writing a custom bootloader which first checks if there is data for flashing in the upper 64K of flash, and if so, it flashes that data and marks it erased. To make the custom bootloader as simple as possible, I would store the .bin file in a container that divides it up into 492 byte chunks (say) and adds a 20 byte header to each chunk, to produce a 512 byte sector. The header would have some magic characters as well as information about where in flash the 492 bytes are supposed to go. The bootloader could then just scan the upper 64K of flash for the magic characters, and write any chunks it finds, and then erase their magic characters after writing them. This would save the bootloader from having to understand FAT.


Second:
It should also be possible to switch between USB HID states (like switching from USB-MIDI to USB Mass Storage and so on)?

Yes. I do it in my Gamecube USB adapter, which can emulate both a standard USB HID device (joystick/keyboard/mouse), or an XBox controller, or dual joysticks, and can switch between the devices by software or hard button.

Basically, you just set up one device, call its begin() method, and then call the end() method, set up another device, and call the begin() method. E.g.:
USBHID.begin(HID_KEYBOARD);
...
USBHID.end(HID_KEYBOARD);
MassStorage.setDrive(0, sizeof(image), read, write);
MassStorage.begin();
...
MassStorage.end();
USBHID.begin(HID_JOYSTICK);
...
USBHID.end();

arpruss Fri Mar 02, 2018 7:25 pm

[arduinoob – Thu Mar 01, 2018 4:22 pm] –
I never heard about STM32 before today and I would like to know if the code shared above is compatible with “classic” arduino nano/pro micro.

No, the code is specific to the STM32F1 platform.

RogerClark Fri Mar 02, 2018 8:31 pm

IMHO the problem with a USB mass storage bootloader is its size. We can barely squeeze the DFU version into 8k

I Hve tried mass storage bootloaders which don’t implement FAT format storage, which are quite small ( not sure if they are less than 8k) but they need special programs on Windows to upload to a non formatted storage device.
Which only makes them marginally better than the DFU based bootloader.

I suppose DFU needs a driver, but you may as well use HID RAW if you need a special tool to upload.

Cpt.Credible Sat Mar 03, 2018 8:56 am

I really like the idea of updating firmware via emulated mass storage device! An example sketch or tutorial on how to get this to work would be epic!

Sign in to reply