Skip to content

Contributed by bzhou830, curated by Ai-Thinker

We've already implemented a USB keyboard and a USB mouse. This time, let's make a USB MIDI instrument.

Before we start, let's first see what MIDI is.

1. MIDI Introduction

The MIDI protocol (Musical Instrument Digital Interface) is a real-time communication protocol between performance devices such as electronic instruments and synthesizers, used for transferring real-time performance data between hardware. When the MIDI protocol was born, it aimed to make devices from different instrument manufacturers compatible with each other through a unified communication protocol, for example connecting a Roland keyboard to a Yamaha synthesizer. After extension, MIDI encoding can also be used as a file format for recording music information, known as the "Standard MIDI File format".

2. MIDI Descriptors

Like other USB devices, a MIDI device also needs a device descriptor, an AC interface descriptor, and a MIDI streaming interface descriptor. The corresponding descriptor descriptions can be found in the standard document USB MIDI v2.0.

First, the AC interface descriptor:

The MIDI streaming interface descriptor:

Configure the descriptors according to the document as follows:

cpp
const uint8_t midi_descriptor[] = {
    USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00,
                               USBD_VID, USBD_PID, 0x0100, 0x01),

    USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01,
                               USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),

    /* Standard AC Interface Descriptor */
    0x09, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,

    /* Class-specific AC Interface Descriptor */
    0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x01,

    /* MIDIStreaming Interface Descriptors */
    0x09, 0x04, 0x01, 0x00, 0x02, 0x01, 0x03, 0x00, 0x00,

    /* Class-Specific MS Interface Header Descriptor */
    0x07, 0x24, 0x01, 0x00, 0x01, WBVAL(65),

    MIDI_JACK_DESCRIPTOR_INIT(0x01),

    /* OUT endpoint descriptor */
    0x09, 0x05, MIDI_OUT_EP, 0x02, WBVAL(MIDI_EP_MPS), 0x00, 0x00, 0x00,
    0x05, 0x25, 0x01, 0x01, 0x01,

    /* IN endpoint descriptor */
    0x09, 0x05, MIDI_IN_EP, 0x02, WBVAL(MIDI_EP_MPS), 0x00, 0x00, 0x00,
    0x05, 0x25, 0x01, 0x01, 0x03,

    /*
     * string0 descriptor
     */
    USB_LANGID_INIT(USBD_LANGID_STRING),

    /*
     * string1 descriptor
     */
    0x14,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'C', 0x00,                  /* wcChar0 */
    'h', 0x00,                  /* wcChar1 */
    'e', 0x00,                  /* wcChar2 */
    'r', 0x00,                  /* wcChar3 */
    'r', 0x00,                  /* wcChar4 */
    'y', 0x00,                  /* wcChar5 */
    'U', 0x00,                  /* wcChar6 */
    'S', 0x00,                  /* wcChar7 */
    'B', 0x00,                  /* wcChar8 */

    /*
     * string2 descriptor
     */
    0x28,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    'C', 0x00,                  /* wcChar0 */
    'h', 0x00,                  /* wcChar1 */
    'e', 0x00,                  /* wcChar2 */
    'r', 0x00,                  /* wcChar3 */
    'r', 0x00,                  /* wcChar4 */
    'y', 0x00,                  /* wcChar5 */
    'U', 0x00,                  /* wcChar6 */
    'S', 0x00,                  /* wcChar7 */
    'B', 0x00,                  /* wcChar8 */
    ' ', 0x00,                  /* wcChar9 */
    'M', 0x00,                  /* wcChar10 */
    'I', 0x00,                  /* wcChar11 */
    'D', 0x00,                  /* wcChar12 */
    'I', 0x00,                  /* wcChar13 */
    ' ', 0x00,                  /* wcChar14 */
    'D', 0x00,                  /* wcChar15 */
    'E', 0x00,                  /* wcChar16 */
    'M', 0x00,                  /* wcChar17 */
    'O', 0x00,                  /* wcChar18 */

    /*
     * string3 descriptor
     */
    0x16,                       /* bLength */
    USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
    '2', 0x00,                  /* wcChar0 */
    '0', 0x00,                  /* wcChar1 */
    '2', 0x00,                  /* wcChar2 */
    '1', 0x00,                  /* wcChar3 */
    '0', 0x00,                  /* wcChar4 */
    '3', 0x00,                  /* wcChar5 */
    '1', 0x00,                  /* wcChar6 */
    '0', 0x00,                  /* wcChar7 */
    '0', 0x00,                  /* wcChar8 */
    '0', 0x00,                  /* wcChar9 */
    0x00
};

3. MIDI Messages

The core function of MIDI is to transmit real-time music performance information. Essentially, this information is a series of commands containing pitch, velocity, effect parameters, etc. We call these commands MIDI messages. A MIDI message usually consists of several bytes; the first byte is called the STATUS byte, followed by several DATA bytes. Bit 7 of the STATUS byte is 1, while bit 7 of a DATA byte is 0. Each keyboard note needs to send a 4-byte message to the host. The first byte is [status code + channel number]. The second byte is the note. The numbered musical notation 1234567, sung as do re mi fa sol la si, is represented by one byte, from 0 to 127 — 128 notes in total.

If you don't know music theory, you can read this section, excerpted from "Quan Quan Teaches You to Play USB".

The third byte is the velocity, also from 0 to 127. You can't really feel this velocity directly; its effect on the sound card is volume. The smaller the value, the quieter the sound; 0 means mute, and 127 is the loudest.

4. Translating the Score

Since I don't understand staff notation or music theory, I just picked a numbered musical notation and translated it into MIDI messages.

For simplicity, only the first line is translated.

cpp
static const uint8_t s_note_sequence[] = {
    69, 69, 64, 69, 72, 69, 64, 55,
    69, 69, 64, 69, 72, 69, 64, 55,
    69, 69, 64, 69, 72, 69, 64, 55,

5. Start the Experiment

Compile and flash the code, then install the happyeo software. This software plays the received MIDI messages through the computer's sound card.

In happyeo, configure the input source as the USB MIDI device, and you can hear the MIDI music playing.

Have questions?

For other questions, please visit the unified discussion area: Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23