### USB multimedia keys on Arduino STM32

Use STM32 bluepill to create an USB consumer control device and send media commands to computer. Download a library that can be used in conjunction with the Keyboard library.

I switched from a keyboard with media controls to a new one without such functionality. Then I realized I was missing the volume and play/pause buttons from the old keyboard. But I have some development boards which I could use to control media and PC volume. I've seen some projects using the Bluetooth functionality of ESP32 to emulate a keyboard. But for now, wired USB interface is what I want. The cheapest and most capable board for this purpose is the STM32 "bluepill". Although I'll end up buying another keyboard with multimedia buttons sooner or later, now I'm going to program the STM32.

I thought this would be easy. But there are multiple ways of programming this ARM microcontroller. It can be done with STM32 HAL. But I found it hard to develop the USB HID device. I looked for something easier. With the Arduino IDE, you have access to two development kits, one from STmicroelectronics and another one from Roger Clark (which is based on libmaple). I attempted to use the official package from ST, but their USB library only supports a basic keyboard (same as Arduino Keyboard library). I found that the other package, from Roger Clark, supports USB Consumer HID. Although it is based on old libmaple it still works. I decided to work with the official package though.

USB multimedia keys with STM32 on breadboard

STM32F103 MCU has native USB port. The STM32 core support for Arduino comes with Keyboard and Mouse built-in libraries. I tried them in order to get some information about how they work. To my surprise, when using each of them, the host sees an USB composite device with both keyboard and mouse features. I used the Free Device Monitoring Studio to see device capabilities and monitor data transfers. My new USB keyboard is also a composite device with standard keyboard and consumer control device. Its only consumer keys are power/sleep/wakeup, although judging by its HID Descriptor it supports media keys too. Knowing its descriptor, I attempted to modify the backend used by STM32 keyboard and mouse library. I replaced the mouse descriptor with the consumer control device descriptor. I ended up with an Arduino library for STM32 which creates an USB composite device made of a keyboard and a consumer control device (with multimedia keys support).

This wasn't as easy as I thought, since I found the USB documentation quite hard to understand. Thankfully, there are some other similar projects out there which I could adapt for my purpose. The keyboard and mouse libraries for STM32 link to functions from the following sources to implement those USB devices:

C sources for STM32 USB HID

I copied these sources and added them to a new library. The most important modification is the USB device descriptor for the consumer control device. I replaced the mouse descriptor with the following one:

__ALIGN_BEGIN static uint8_t HID_CONSUMER_ReportDesc[HID_CONSUMER_REPORT_DESC_SIZE]  __ALIGN_END = {
0x05, 0x0C,        // Usage Page (Consumer)
0x09, 0x01,        // Usage (Consumer Control)
0xA1, 0x01,        // Collection (Application)
0x85, 0x02,        //   Report ID (VOLUME_REPORT )
0x19, 0x00,        //   Usage Minimum (Unassigned)
0x2A, 0x3C, 0x02,  //   Usage Maximum (AC Format)
0x15, 0x00,        //   Logical Minimum (0)
0x26, 0x3C, 0x02,  //   Logical Maximum (572)
0x95, 0x01,        //   Report Count (1)
0x75, 0x10,        //   Report Size (16)
0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              // End Collection
};

This one is taken from HidKbd project. I found it quite hard to understand official USB documentation. If you want you can create your own HID descriptor. There is even a tool, a very old one, here that can help you create USB descriptors. You can define sets of features (only specific commands that the device can issue) or you can inform the host that the device supports all available commands. The above descriptor reports a single "collection" of features which enable the possibility of up to 572 commands (I believe this is the total number of existing consumer device commands).

After replacing all instances of the word "mouse" with "consumer" in all the required sources I had to generate a correct report to send to host. The "report" is actually an array of bytes which holds the pressed keys. I don't know how many bytes should I send and what they should contain specifically. Thanks to the HidKbd sources, I found that with the above descriptor I must send a 3 bytes report, the first being the report ID 0x02 and the next one is the key code. The last byte should be set to 0x00. To simulate the release of a key I have to send a report with 0x00 as the key code.

uint8_t m[3] = {0x02, 0x00, 0x00 };

void MediaKeyboard_::press(uint8_t mediaKey) {
m[1] = mediaKey;
HID_Composite_consumer_sendReport(m, 3);
}

void MediaKeyboard_::release() {
delay(10);
m[1] = 0x00;
HID_Composite_consumer_sendReport(m, 3);
}

There is a delay before releasing the key, otherwise the host may not take the report into account. The following media keys are defined, yet you can send any other code from HID Usage Tables [PDF] document (see pages 75-85).

#define VOLUME_UP 0xE9
#define VOLUME_DOWN 0xEA
#define VOLUME_MUTE 0xE2
#define MEDIA_PLAY_PAUSE 0xCD
#define MEDIA_STOP 0xB7
#define MEDIA_NEXT 0xB5
#define MEDIA_PREV 0xB6

The library that you can download at the end of this post is compatible with STM32 Cores for Arduino IDE. I have written about the installation of the support package here. Follow the instructions to install the boards package before continuing.

One last thing before getting to the hardware part: if you're building an USB device, you may want to customize its name. By default, the device will identify similar to BLUEPILL_F103C8 HID in FS Mode. This definition can be found usbd_desc.c file from C:\Users\<user>\AppData\Local\Arduino15\packages\STM32\hardware\stm32\1.8.0\cores\arduino\stm32\usb folder (locate the Arduino folder in user's home on other operating systems).

For testing purposes, I built the following circuit on a breadboard. A rotary encoder is used to change volume and some push buttons control media play, pause, stop and previous/next.

STM32 USB media keyboard (test circuit)

Usage of the library is pretty straightforward. Include the header, call MediaKeyboard.begin(); and add at least one second delay after it to allow the host operating system to install the driver. Do this in setup() section. Emulating a key press requires calling two functions:

MediaKeyboard.press(VOLUME_MUTE);
MediaKeyboard.release();

If you include the Keyboard header too, you can use functions from this library. Not that you should not call Keyboard.begin(); because once you call MediaKeyboard.begin(); both keyboard and consumer control device are initialized and ready to use. To be able to compile and run any sketch using this library, after you select the STM32 board, in Tools menu, set USB support to HID (keyboard and mouse).