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 Sat Dec 09, 2017 7:56 pm

BTW.

Re: USB power enable and disable

If Serial.end disables power in your PR, Serial.begin should enable power, as it should be possible to all end() and then call begin()

I agree that moving the USB reset to Serial.begin() should be investigated, but I have not had time to look at the existing code to see if there are any pitfalls with this.

arpruss Sat Dec 09, 2017 9:30 pm

Serial.begin() calls usb_cdcacm_enable() which calls usb_init_usblib() which calls (via a callback) usbInit() which powers up the USB peripheral.

Serial.end() currently calls usb_cdcacm_disable() which I think should power down the USB peripheral (it does that in my PR).

I am inclined to think the best place for the GENERIC_BOOTLOADER usb reset code for serial is usb_cdcacm_enable(), because that’s where the disconnect code is for devices that have disconnect hardware. The only issue I foresee with that is that if a user has an explicit Serial.begin(), then there will be two calls to the usb reset code. I don’t know if that’s a problem or not.

RogerClark Sat Dec 09, 2017 11:32 pm

Serial.begin() has some code to prevent it doing anything if its already been called

if (_hasBegun)
return;

arpruss Sun Dec 10, 2017 1:12 am

Oh, good. So putting the reset code into usb_cdcacm_enable() should work fine.

RogerClark Sun Dec 10, 2017 5:32 am

I’m not comfortable with a function to power down the USB with no obvious way to power it back up again

I understand its done elsewhere, its just not obvious to anyone looking in the code.

Perhaps we could have a function that can do both enable and disable the USB power, or have separate functions.

arpruss Sun Dec 10, 2017 6:46 am

Maybe some comments should just be added. The power up only happens in a call back to the serial device code, after a bunch of configuration things are done by the USBLIB. I wouldn’t want to risk moving that elsewhere.

OK.
I didnt mean to move the function. So perhaps some comments would be useful, for anyone else who investigates the USB

arpruss Mon Dec 11, 2017 3:44 am

I’ve added comments about the powerdown and powerup to the PR.

By the way, I still can’t get RAWHID host-to-device to work, but host-to-device feature settings now work in the featuresetting branch of the library.

(In theory, one could use host-to-device feature setting to implement a software-forced reset.)

RogerClark Mon Dec 11, 2017 6:36 am

Re: Host To Host

A few weeks ago, I did try to find if it was possible to send a command via HID, as its supposed to be bi-directional, but did not find anything useful

There was a post on AVRFreaks where someone was using VUSB for HID and wanted to send commands to the AVR but he seemed to have a roadblock with the default HID drivers on Windows, and custom drivers may be required for that functionality

arpruss Mon Dec 11, 2017 2:32 pm

No custom drivers seem to be needed. I am using a simple python script and pywinusb to send a feature report from Windows.

arpruss Mon Dec 11, 2017 5:39 pm

If you want to try out host-to-device USB communication, the featuresetting branch of USBHID includes examples/softjoystick which is a project that mirrors to the PC whatever report it got from the PC via USB HID feature set. (It’s not completely silly. It provides something like vJoy functionality without the need for installing vJoy drivers, so you can implement a joystick in software on the PC. That said, I don’t see a very good use case other than for testing communications.)

To test it on Windows, install it to an stm32f1, run send.py (which needs pywinusb) on the PC, and then look in the Game Controllers config tool in Windows to see if the joystick is moving. (Win-R, joy.cpl, enter, click on “Maple”, click on “Properties”) Another test is whether the fetch line matches the previous send line in the output.
$ c:/python36/python send.py
HID device (vID=0x1eaf, pID=0x0024, v=0x0200); LeafLabs; Maple, Path: \\?\hid#vid_1eaf&pid_0024#6&149a03ce&2&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
send: [3, 7, 0, 0, 0, 15, 0, 0, 0, 2, 8, 32, 128] fetch: [3, 7, 0, 0, 0, 15, 0, 0, 0, 2, 8, 32, 128]

RogerClark Mon Dec 11, 2017 7:15 pm

Cool..

So could you send a command via HID to reboot ??

arpruss Mon Dec 11, 2017 7:47 pm

Yeah. Just add a feature output to a HID description, and then decide on a magic string of bytes that forces a reset when it’s sent to the device.

But one would have to decide which HID description(s) to add it to, and there would be some SRAM wasted (40-300 bytes or so) depending on how one implemented it.

RogerClark Mon Dec 11, 2017 7:54 pm

OK. it’s not so good that it needs more RAM

Also there would need to be binary tools for all platforms to send this command, as people would not necessarily have python installed

arpruss Mon Dec 11, 2017 9:28 pm

[RogerClark – Mon Dec 11, 2017 7:54 pm] –
OK. it’s not so good that it needs more RAM

Also there would need to be binary tools for all platforms to send this command, as people would not necessarily have python installed

Personally, I think this is more trouble than it’s worth. :-) I’d rather just pull the plug or press the reset button instead of testing things on three platforms.

RogerClark Mon Dec 11, 2017 9:53 pm

[arpruss – Mon Dec 11, 2017 9:28 pm] –
[RogerClark – Mon Dec 11, 2017 7:54 pm] –
OK. it’s not so good that it needs more RAM

Also there would need to be binary tools for all platforms to send this command, as people would not necessarily have python installed

Personally, I think this is more trouble than it’s worth. :-) I’d rather just pull the plug or press the reset button instead of testing things on three platforms.

Agreed

anv Tue Dec 12, 2017 9:12 am

There is an usb keyboard library for arduino leonardo that support receiving keyboard leds status from the host. May be you could check how it works. I don’t remember now but if I find it I will tell you.

arpruss Tue Dec 12, 2017 5:54 pm

I took a lot of code from libarra111’s branch and now the library implements a composite serial / HID device, with reset functionality using the standard upload tools. There is rather more flash/SRAM used because of the serial support, but the serial support should be worth it.

The only thing I couldn’t figure out to do is how to override the core’s Serial, so currently if you want to do serial i/o, you need to use CompositeSerial.

Maybe one could make a virtual USBSerial class and make the Serial instance be __weak in the core or something like that. I don’t know enough about C++ inheritance to figure this out right now.

anv Tue Dec 12, 2017 7:43 pm

Just downloaded master branch. The example for twojoysticks does not compile.
Tried usb keyboard. USB HID is detected but can’t be configured.
Serial interface is detected and module loaded.

This is my dmesg output when I plug the device.

74281.945860] usb 1-4: new full-speed USB device number 107 using xhci_hcd
[74282.115902] usb 1-4: New USB device found, idVendor=1eaf, idProduct=0024
[74282.115904] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[74282.115905] usb 1-4: Product: Maple
[74282.115905] usb 1-4: Manufacturer: LeafLabs
[74292.251940] cdc_acm 1-4:1.0: ttyACM0: USB ACM device
[74292.253017] usbhid 1-4:1.2: can’t add hid device: -71
[74292.253026] usbhid: probe of 1-4:1.2 failed with error -71
[74293.769062] cdc_acm 1-4:1.0: failed to set dtr/rts
[74306.967792] cdc_acm 1-4:1.0: failed to set dtr/rts
[74306.969538] cdc_acm 1-4:1.0: failed to set dtr/rts
[74316.062451] cdc_acm 1-4:1.0: failed to set dtr/rts
[74316.063964] cdc_acm 1-4:1.0: failed to set dtr/rts
[74322.061371] cdc_acm 1-4:1.0: failed to set dtr/rts

Source code for the program:

#include <USBHID.h>

const uint8_t reportDescription[] = {
USB_HID_ABS_MOUSE_REPORT_DESCRIPTOR(USB_HID_MOUSE_REPORT_ID)
,USB_HID_KEYBOARD_REPORT_DESCRIPTOR(USB_HID_KEYBOARD_REPORT_ID)
};

HIDAbsMouse mouse;

void setup(){
HID.begin(reportDescription, sizeof(reportDescription));
delay(1000);
mouse.move(0,0);
delay(1000);
mouse.press(MOUSE_LEFT);
mouse.move(500,500);
mouse.release(MOUSE_ALL);
mouse.click(MOUSE_RIGHT);

}

void loop(){
delay(100);
mouse.move(16384,16384);

Keyboard.write('h');
Keyboard.write('o');
Keyboard.write('l');
Keyboard.write('a');
delay(100);
}

arpruss Tue Dec 12, 2017 8:27 pm

[anv – Tue Dec 12, 2017 7:43 pm] –
Just downloaded master branch. The example for twojoysticks does not compile.

Sorry: I changed the API without changing the example. It’s fixed now.

Tried usb keyboard. USB HID is detected but can’t be configured.

That’s not good. I’ve only tested on Windows.

1. Do you know if the libarra111 USB core works for you?

2. Can you try something super-basic like:

#include <USBHID.h>

void setup() {
USB.begin(USB_HID_JOYSTICK);
}

void loop() {
Joystick.X(0);
delay(500);
Joystick.X(1023);
delay(500);
}

Sign in to reply