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

RogerClark Sun Dec 03, 2017 9:16 pm

Any idea how we could stop the code hanging if calls are made to Serial ?

arpruss Sun Dec 03, 2017 10:51 pm

[RogerClark – Sun Dec 03, 2017 9:16 pm] –
Any idea how we could stop the code hanging if calls are made to Serial ?

Actually, my note about hanging was unduly alarmist: when I actually tried it, there was no hang.

Nonetheless, to be safe, I just added a USBSerialNOP class that extends USBSerial and does nothing, and made the init code for HID and MIDI overwrite Serial with an instance of USBSerialNOP. We still waste flash space by having the Serial code, but at least we won’t have any issues with interference.

RogerClark Sun Dec 03, 2017 11:59 pm

OK

Thanks

I’ll give the libs a try when I get chance (possibly this evening)

arpruss Mon Dec 04, 2017 3:57 pm

By the way, does the Serial.begin() call have to be there in variants/*/wirish/boards_setup.cpp : board_setup_usb()? All the sketches I’ve looked at or written that use serial call Serial.begin() in setup() anyway.

If the Serial.begin() call were removed from the global setup code in the core, and if one left the issues about using Serial by mistake for the user to worry about out, then the linker wouldn’t link in the USBSerial and usbcdcacm code, and library users would save flash. It’s that one call to Serial.begin() in the default board_setup_usb() that gets the linker to pull in the usb serial code.

What’s weird is that board_setup_usb() is declared __weak and I override it in the library. The override works, but the stupid(?) linker still pulls in the dependencies for the original __weak board_setup_usb(). This happens even in LTO mode, which make no sense to me. But if one comments out the Serial.begin() line in the core, all that unnecessary code disappears.

arpruss Mon Dec 04, 2017 5:21 pm

Nevermind the suggestion to remove Serial.begin(). It’s needed for the serial-reset functionality. :-(

It would still be nice to find some way to override it without bringing in the unnecessary dependencies.

RogerClark Mon Dec 04, 2017 7:56 pm

Yes…
Unfortunately, the Serial.begin is essential so that the core can be triggered to reboot via the IDE

I did look to see if commands can be sent via HID which could be uses to reboot, but i could not find much useful information about how this could be done using the generic Windows HID driver

arpruss Tue Dec 05, 2017 1:18 pm

Maybe the best solution would be to add a menu option that turns off USB Serial in the core. This would be like the addMidiHID branch, but easier to maintain, since all the actual Midi and HID support would be pushed out into libraries, and with two usb menu options instead of four or five. And once ready someone puts in the effort, they can write a HID+serial composite library.

RogerClark Tue Dec 05, 2017 7:59 pm

It would be good if we could add the menu, but its not that simple, because several of the upload methods already dont have USB Serial turned on.

So it would break things for anyone currently using Serial or BMP upload, if it was defaulted to being turned on.

Unfortunately, the menu system in the IDE is not cascading, so there is no way to just have this as an option for the upload methods which currently have USB Serial turned included in the build.

From what I understand, having USB Serial initially turned on, means the code is bigger, but your code turns it off.

i dont see this as a big problem, because conversely, someone may want to use USB HID to start with, but then switch back to USB Serial later in their code. In which case USB Serial would need to be compiled in.

Most BluePills have 128k so I dont think an additional 5k all the time is a big problem

if the problem is that USB Serial starts and then stops again ( because the HID library disables Serial), perhaps there is a way to add a global flag which is set as part if the HID library, which prevents the code calling Serial.begin() in the core.

arpruss Thu Dec 07, 2017 3:51 am

I’ve replaced the three Keyboard-Mouse / Joystick / Keyboard-Mouse-Joystick libraries with a single USB HID library. This requires a single initialization line to select between options:
HID.begin(device); // where HID is your instance of USBHID

RogerClark Thu Dec 07, 2017 4:05 am

Wow

Looks good

I will try to test, when I get time, probably at the weekend

arpruss Thu Dec 07, 2017 5:12 am

Yeah, it’s this one.

One problem I haven’t figured out is how to reset the USB connection. HID.begin(USB_HID_JOYSTICK); HID.end(); HID.begin(USB_HID_KEYBOARD); doesn’t work, at least not with Windows. Windows still sees the joystick device, not the keyboard, even though HID.begin() includes the PA12 reset code.

arpruss Thu Dec 07, 2017 4:55 pm

Looking at the bootloader code, I figured out the issue of how to reset the connection to switch HID identities: I now turn off the USB peripheral in usb_hid_disable() and usb_midi_disable(). Probably something similar should be done in the core with regard to serial.

A minor issue I noticed is that if you send data to the keyboard (and probably other devices) too soon after HID.begin(), it will disappear. A 500ms pause is good enough.

RogerClark Thu Dec 07, 2017 7:56 pm

Serial seems to have the same problem.

Sending too soon after the USB starts, causes the data to be lost.

arpruss Fri Dec 08, 2017 1:55 am

I posted a pull request for powering down USB with Serial.end().

RogerClark Fri Dec 08, 2017 3:21 am

OK

I’m a bit snowed under with PR’s at the moment.

There are 2 urgent ones, because Wire is not working correctly and nor is tone()

And there seem to be other assorted PR’s some of which I will decline

I’m also in the middle of bringing the ZumSpot branch up to date, as it was nearly 300 commits out of date…
So I need to confirm the merge with Head Master (plus some manual changes) are OK.

If your PR is just the addition of lib’s I should be able to take a look at the weekend.

arpruss Fri Dec 08, 2017 4:06 am

No, my PR is just to power down USB when the serial disable function is called. This is needed to allow one to disconnect and start a different USB mode, e.g., switching from Serial to HID or from Serial to MIDI. It’s about five lines of code, ported from the bootloader’s USB powerdown code.

I am not merging in the libraries yet, as I would like to be able to make improvements to them over the next couple of weeks, as I have a project that will use them, so I may have more ideas for them.

I closed my PR to merge in the addMidiHID branch. If you want, I can give you a PR to bring the addMidiHID branch up to date with Master, but I am thinking that that branch is obsolete given the libraries.

RogerClark Fri Dec 08, 2017 6:24 am

If the libraries are as good as, or better than the branch, then we may as well only use the libraries

arpruss Sat Dec 09, 2017 4:03 pm

At this point, the libraries are better than the branch.

One thing I haven’t been able to get working — it also wasn’t working in the branch — is two-way HID RAW. I have one-way (device to host) transmission working, but the receive interrupt function isn’t being called.

arpruss Sat Dec 09, 2017 6:39 pm

The HID RAW stuff is frustrating. USB MIDI works in both directions just fine, and I don’t see any significant difference in the code. I tried sending HID RAW data both from PyWinUSB and Android HID Terminal. The oscilloscope shows that there is data on the line, but I don’t know how to interpret the data.

If anybody has ideas on how to get HID RAW host-to-device working, I would welcome input. Not that I really need it for anything — just would be a nice completion to the library.

Sign in to reply