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

anv Thu Jan 04, 2018 8:40 am

[arpruss – Wed Jan 03, 2018 6:06 pm] –
I added a no-wait option for output/feature host-to-device, so that the USB HID handler silently updates the output/feature buffers in the background without polling being needed to ensure that the sketch doesn’t miss data.

And added a keyboardwithleds example that echoes keyboard LED status to serial.

Great job!

arpruss Sun Jan 07, 2018 4:17 pm

I’ve changed the main class from USBDevice to USBHIDDevice and its instance from USB to USBHID. I am going to try to make this the last change that breaks existing code that depends on the high level functions in the library, but it’s needed to future compatibility with some non-HID devices.

arpruss Sun Jan 07, 2018 5:22 pm

I’ve integrated a simple xbox360 controller emulator. See the x360 example.

It’s not composited with serial support, unlike the standard USBHID stuff.

arpruss Sun Jan 07, 2018 5:43 pm

I’ve also dumped USB MIDI into the USBHID library (which is somewhat poorly named now). See the mididemo example.

arpruss Sun Jan 07, 2018 6:47 pm

And I just tried serial/HID composite on a Win 8.1 machine. With the Maple serial driver installed, HID doesn’t work. Without the Maple serial driver, serial doesn’t work. :-(

I also added a setSerial() method that lets you turn off the CompositeSerial support, which might be useful for OSes like Win 8.1. Just call USBHID.setSerial(0) before USBHID.begin().

arpruss Sat Jan 20, 2018 3:31 am

Added boot keyboard support. See the BootKeyboard example.

arpruss Sun Jan 21, 2018 3:12 pm

I’ve made the OUTPUT and FEATURE report APIs a bit nicer to use. As a result, I was able to make both Keyboard and BootKeyboard support LEDs more simply. You can now do:
USBHID.begin(HID_KEYBOARD);
Keyboard.begin();
...
ledState = Keyboard.getLEDs();

arpruss Thu Feb 01, 2018 4:05 am

[RogerClark – Wed Dec 13, 2017 10:45 pm] –
Just a thought, but I noticed someone asking about USB Mass storage…

I recall there was some old code from the leaflabs forum, which I tried a long time ago.

I presume that in theory we could add USB mass storage as a library ???

In the “generalizable” branch of the library, I now have working mass storage code. I created a 12K RAM disk hosted on the stm32f1 and successfully formatted it with a hacked version mkdosfs. The API for the mass storage code will change, however.

The “generalizable” branch is meant to allow one to mix and match different “parts” of a composite USB device, e.g., mass storage + hid + serial, or midi + serial, etc. Not all combinations can be expected to work. (E.g., xbox360 + serial doesn’t work.)

RogerClark Thu Feb 01, 2018 9:38 am

very cool

arpruss Fri Feb 02, 2018 2:48 am

I have merged in the changes. The new library uses less RAM but can do all sorts of cool stuff, like HID+MassStorage, etc. There are limits to what combinations work, and trial and error may be required. Because there are so many combinations possible, and the changes to the library were so heavy, there is a pretty good chance of bugs. :-( But at least I made the API backwards compatible to the previous version of the library.

The Mass Storage code is based on Joe Ferner’s code. A fun example of the Mass Storage code is in examples/mass, which is probably the world’s most useless USB drive, backed by 12kb of SRAM in the stm32. The drive is pre-formatted to present as a tiny FAT12 disk, and actually seems to work on Windows 10.

Here’s an example for how to set up Keyboard/Mouse/Joystick + MassStorage:
#include <USBHID.h>
#include <USBMassStorage.h>

// supply driveSize, readFunction(offset, buff, len) and writeFunction(offset, buff, len)

void setup() {
MassStorage.setDrive(0, driveSize, readFunction, writeFunction);
MassStorage.registerComponent();
USBHID.setReportDescriptor(HID_KEYBOARD_MOUSE_JOYSTICK);
USBHID.registerComponent();
USBComposite.begin();
Keyboard.println("hello");
}

void loop() {
MassStorage.loop();
}

RogerClark Fri Feb 02, 2018 5:22 am

Wonderful

I will try it at the weekend, when I get some free time…..

zoomx Fri Feb 02, 2018 8:10 am

Free time! Free time! My kingdom for free time!

I will try it too!

Edit:

Just a basic test.
Got the library from the generalizable branch (there is not mass storage examples in the master branch)
loaded the mass example it doesn’t compile since in MassStorage.h there is a reference to USBComposite.h but there is only the cpp file.
So I got the missing file from the master branch and it compiles.

michael_l Fri Feb 02, 2018 12:25 pm

Very nice work.

So it would be possible to get SD card mounted also by just implementing read and write functions ? one could use also spiffs fs and utilize board’s flash memory, although there’s not much of space .

arpruss Fri Feb 02, 2018 4:07 pm

[michael_l – Fri Feb 02, 2018 12:25 pm] –
So it would be possible to get SD card mounted also by just implementing read and write functions ?

Yes: should be very simple (you also need to read the capacity, of course).

I wanted to try this this morning, but I couldn’t get sdfat (either the official version or victor’s version) to recognize either of the two cards I tried. I think my sd card adapter may be broken.

one could use also spiffs fs and utilize board’s flash memory, although there’s not much of space .

Right.

Also, if you just want the host to have read-only access, you can just use the flash straight with a FAT system, and include a disk image. Might be useful for distributing a small driver or script that goes with a device, I suppose.

michael_l Fri Feb 02, 2018 4:22 pm

[arpruss – Fri Feb 02, 2018 4:07 pm] –
[michael_l – Fri Feb 02, 2018 12:25 pm] –
So it would be possible to get SD card mounted also by just implementing read and write functions ?

Yes: should be very simple (you also need to read the capacity, of course).

I wanted to try this this morning, but I couldn’t get sdfat (either the official version or victor’s version) to recognize either of the two cards I tried. I think my sd card adapter may be broken.

one could use also spiffs fs and utilize board’s flash memory, although there’s not much of space .

Right.

Also, if you just want the host to have read-only access, you can just use the flash straight with a FAT system, and include a disk image. Might be useful for distributing a small driver or script that goes with a device, I suppose.

I hacked earlier SPIFFS to work with WDxxx SPI flash memories on stm32duino. It works but the code is a bit *couch* sketchy. It’s really not a fast fs but feasible for small projects. Together with MSC that would make a nice addition.

I wonder if current libmaple/stm32duino have ready made functions for reading/writing from flash memory ?

arpruss Fri Feb 02, 2018 6:46 pm

[michael_l – Fri Feb 02, 2018 4:22 pm] – I wonder if current libmaple/stm32duino have ready made functions for reading/writing from flash memory ?

Reading is just memcpy.

Writing is here: https://github.com/rogerclarkmelbourne/ … ies/EEPROM

arpruss Sat Feb 03, 2018 4:54 am

I added an SD reader example. It works, but it is ridiculously slow: 11s/mb.

Testing suggests that the low speed is due to SD access rather than due to USB transmission.

michael_l Sat Feb 03, 2018 7:22 am

Great! I’d like to get mass storage (with SD) with serial working in my project so I’ll be testing your code in the near future.

arpruss Sat Feb 03, 2018 2:30 pm

[arpruss – Sat Feb 03, 2018 4:54 am] –
I added an SD reader example. It works, but it is ridiculously slow: 11s/mb.

Testing suggests that the low speed is due to SD access rather than due to USB transmission.

I re-ran the tests this morning and was getting 479 kb/s reading, which is way better. I have a wonky SD card breadboard adapter, so maybe when the connection is poor there are a lot of retries. At the current speed, with the card and file I was testing, reads take 50% longer than the built-in card reader on my laptop, which isn’t that bad.

arpruss Sat Feb 03, 2018 2:34 pm

[michael_l – Fri Feb 02, 2018 4:22 pm] –
I hacked earlier SPIFFS to work with WDxxx SPI flash memories on stm32duino. It works but the code is a bit *couch* sketchy. It’s really not a fast fs but feasible for small projects. Together with MSC that would make a nice addition.

If you port SPIFFS to the internal flash, you should remember that the internal flash can only be written two bytes at a time. The SPIFFS readme suggests that the library assumes you can always turn 1s into 0s. This isn’t correct for the stm32f1 internal flash. You can turn 0xFFFF to anything, but if a memory word holds anything other than 0xFFFF, you can’t write anything to it, not even 0x0000.

Sign in to reply