diff --git a/examples/stm32/f1/stm32-h103/usb_midi/Makefile b/examples/stm32/f1/stm32-h103/usb_midi/Makefile new file mode 100644 index 00000000..57f1712e --- /dev/null +++ b/examples/stm32/f1/stm32-h103/usb_midi/Makefile @@ -0,0 +1,25 @@ +## +## This file is part of the libopencm3 project. +## +## Copyright (C) 2009 Uwe Hermann +## +## This library is free software: you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This library is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## +## You should have received a copy of the GNU Lesser General Public License +## along with this library. If not, see . +## + +BINARY = usbmidi + +LDSCRIPT = ../stm32-h103.ld + +include ../../Makefile.include + diff --git a/examples/stm32/f1/stm32-h103/usb_midi/README.md b/examples/stm32/f1/stm32-h103/usb_midi/README.md new file mode 100644 index 00000000..20f8f173 --- /dev/null +++ b/examples/stm32/f1/stm32-h103/usb_midi/README.md @@ -0,0 +1,47 @@ +# README + +This example implements a USB MIDI device to demonstrate the use of the +USB device stack. It implements the device configuration found in Appendix +B of the Universal Serial Bus Device Class Definition for MIDI Devices. + +The 'USER' button sends note on/note off messages. + +The board will also react to identity request (or any other data sent to +the board) by transmitting an identity message in reply. + +## Board connections + +| Port | Function | Description | +| ----- | -------------- | ----------------------------------------- | +| `CN5` | `(USB_OTG_FS)` | USB acting as device, connect to computer | + +## Testing + +To list midi devices, which should include this demo device + + $ amidi -l + Dir Device Name + IO hw:2,0,0 MIDI demo MIDI 1 + $ + +To record events, while pushing the user button + + $ amidi -d -p hw:2,0,0 + + 90 3C 40 -- key down + 80 3C 40 -- key up + 90 3C 40 + 80 3C 40^C + 12 bytes read + $ + +To query the system identity, note this dump matches sysex\_identity[] in the +source. + + $ amidi -d -p hw:2,0,0 -s Sysexdump.syx + + F0 7E 00 7D 66 66 51 19 00 00 01 00 F7 + ^C + 13 bytes read + $ + diff --git a/examples/stm32/f1/stm32-h103/usb_midi/usbmidi.c b/examples/stm32/f1/stm32-h103/usb_midi/usbmidi.c new file mode 100644 index 00000000..366f5927 --- /dev/null +++ b/examples/stm32/f1/stm32-h103/usb_midi/usbmidi.c @@ -0,0 +1,404 @@ +/* + * This file is part of the libopencm3 project. + * + * Copyright (C) 2014 Daniel Thompson + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * All references in this file come from Universal Serial Bus Device Class + * Definition for MIDI Devices, release 1.0. + */ + +/* + * Table B-1: MIDI Adapter Device Descriptor + */ +static const struct usb_device_descriptor dev = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, /* was 0x0110 in Table B-1 example descriptor */ + .bDeviceClass = 0, /* device defined at interface level */ + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x6666, /* Prototype product vendor ID */ + .idProduct = 0x5119, /* dd if=/dev/random bs=2 count=1 | hexdump */ + .bcdDevice = 0x0100, + .iManufacturer = 1, /* index to string desc */ + .iProduct = 2, /* index to string desc */ + .iSerialNumber = 3, /* index to string desc */ + .bNumConfigurations = 1, +}; + +/* + * Midi specific endpoint descriptors. + */ +static const struct usb_midi_endpoint_descriptor midi_bulk_endp[] = {{ + /* Table B-12: MIDI Adapter Class-specific Bulk OUT Endpoint + * Descriptor + */ + .head = { + .bLength = sizeof(struct usb_midi_endpoint_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_ENDPOINT, + .bDescriptorSubType = USB_MIDI_SUBTYPE_MS_GENERAL, + .bNumEmbMIDIJack = 1, + }, + .jack[0] = { + .baAssocJackID = 0x01, + }, +}, { + /* Table B-14: MIDI Adapter Class-specific Bulk IN Endpoint + * Descriptor + */ + .head = { + .bLength = sizeof(struct usb_midi_endpoint_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_ENDPOINT, + .bDescriptorSubType = USB_MIDI_SUBTYPE_MS_GENERAL, + .bNumEmbMIDIJack = 1, + }, + .jack[0] = { + .baAssocJackID = 0x03, + }, +} }; + +/* + * Standard endpoint descriptors + */ +static const struct usb_endpoint_descriptor bulk_endp[] = {{ + /* Table B-11: MIDI Adapter Standard Bulk OUT Endpoint Descriptor */ + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x01, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 0x40, + .bInterval = 0x00, + + .extra = &midi_bulk_endp[0], + .extralen = sizeof(midi_bulk_endp[0]) +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_ATTR_BULK, + .wMaxPacketSize = 0x40, + .bInterval = 0x00, + + .extra = &midi_bulk_endp[1], + .extralen = sizeof(midi_bulk_endp[1]) +} }; + +/* + * Table B-4: MIDI Adapter Class-specific AC Interface Descriptor + */ +static const struct { + struct usb_audio_header_descriptor_head header_head; + struct usb_audio_header_descriptor_body header_body; +} __attribute__((packed)) audio_control_functional_descriptors = { + .header_head = { + .bLength = sizeof(struct usb_audio_header_descriptor_head) + + 1 * sizeof(struct usb_audio_header_descriptor_body), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_AUDIO_TYPE_HEADER, + .bcdADC = 0x0100, + .wTotalLength = + sizeof(struct usb_audio_header_descriptor_head) + + 1 * sizeof(struct usb_audio_header_descriptor_body), + .binCollection = 1, + }, + .header_body = { + .baInterfaceNr = 0x01, + }, +}; + +/* + * Table B-3: MIDI Adapter Standard AC Interface Descriptor + */ +static const struct usb_interface_descriptor audio_control_iface[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_AUDIO_SUBCLASS_CONTROL, + .bInterfaceProtocol = 0, + .iInterface = 0, + + .extra = &audio_control_functional_descriptors, + .extralen = sizeof(audio_control_functional_descriptors) +} }; + +/* + * Class-specific MIDI streaming interface descriptor + */ +static const struct { + struct usb_midi_header_descriptor header; + struct usb_midi_in_jack_descriptor in_embedded; + struct usb_midi_in_jack_descriptor in_external; + struct usb_midi_out_jack_descriptor out_embedded; + struct usb_midi_out_jack_descriptor out_external; +} __attribute__((packed)) midi_streaming_functional_descriptors = { + /* Table B-6: Midi Adapter Class-specific MS Interface Descriptor */ + .header = { + .bLength = sizeof(struct usb_midi_header_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MS_HEADER, + .bcdMSC = 0x0100, + .wTotalLength = sizeof(midi_streaming_functional_descriptors), + }, + /* Table B-7: MIDI Adapter MIDI IN Jack Descriptor (Embedded) */ + .in_embedded = { + .bLength = sizeof(struct usb_midi_in_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_IN_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EMBEDDED, + .bJackID = 0x01, + .iJack = 0x00, + }, + /* Table B-8: MIDI Adapter MIDI IN Jack Descriptor (External) */ + .in_external = { + .bLength = sizeof(struct usb_midi_in_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_IN_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EXTERNAL, + .bJackID = 0x02, + .iJack = 0x00, + }, + /* Table B-9: MIDI Adapter MIDI OUT Jack Descriptor (Embedded) */ + .out_embedded = { + .head = { + .bLength = sizeof(struct usb_midi_out_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_OUT_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EMBEDDED, + .bJackID = 0x03, + .bNrInputPins = 1, + }, + .source[0] = { + .baSourceID = 0x02, + .baSourcePin = 0x01, + }, + .tail = { + .iJack = 0x00, + } + }, + /* Table B-10: MIDI Adapter MIDI OUT Jack Descriptor (External) */ + .out_external = { + .head = { + .bLength = sizeof(struct usb_midi_out_jack_descriptor), + .bDescriptorType = USB_AUDIO_DT_CS_INTERFACE, + .bDescriptorSubtype = USB_MIDI_SUBTYPE_MIDI_OUT_JACK, + .bJackType = USB_MIDI_JACK_TYPE_EXTERNAL, + .bJackID = 0x04, + .bNrInputPins = 1, + }, + .source[0] = { + .baSourceID = 0x01, + .baSourcePin = 0x01, + }, + .tail = { + .iJack = 0x00, + }, + }, +}; + +/* + * Table B-5: MIDI Adapter Standard MS Interface Descriptor + */ +static const struct usb_interface_descriptor midi_streaming_iface[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_AUDIO_SUBCLASS_MIDISTREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0, + + .endpoint = bulk_endp, + + .extra = &midi_streaming_functional_descriptors, + .extralen = sizeof(midi_streaming_functional_descriptors) +}}; + +static const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = audio_control_iface, +}, { + .num_altsetting = 1, + .altsetting = midi_streaming_iface, +} }; + +/* + * Table B-2: MIDI Adapter Configuration Descriptor + */ +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, /* can be anything, it is updated automatically + when the usb code prepares the descriptor */ + .bNumInterfaces = 2, /* control and data */ + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, /* bus powered */ + .bMaxPower = 0x32, + + .interface = ifaces, +}; + +static char usb_serial_number[25]; /* 12 bytes of desig and a \0 */ + +static const char * usb_strings[] = { + "libopencm3.org", + "MIDI demo", + usb_serial_number +}; + +/* Buffer to be used for control requests. */ +uint8_t usbd_control_buffer[128]; + +/* SysEx identity message, preformatted with correct USB framing information */ +const uint8_t sysex_identity[] = { + 0x04, /* USB Framing (3 byte SysEx) */ + 0xf0, /* SysEx start */ + 0x7e, /* non-realtime */ + 0x00, /* Channel 0 */ + 0x04, /* USB Framing (3 byte SysEx) */ + 0x7d, /* Educational/prototype manufacturer ID */ + 0x66, /* Family code (byte 1) */ + 0x66, /* Family code (byte 2) */ + 0x04, /* USB Framing (3 byte SysEx) */ + 0x51, /* Model number (byte 1) */ + 0x19, /* Model number (byte 2) */ + 0x00, /* Version number (byte 1) */ + 0x04, /* USB Framing (3 byte SysEx) */ + 0x00, /* Version number (byte 2) */ + 0x01, /* Version number (byte 3) */ + 0x00, /* Version number (byte 4) */ + 0x05, /* USB Framing (1 byte SysEx) */ + 0xf7, /* SysEx end */ + 0x00, /* Padding */ + 0x00, /* Padding */ +}; + +static void usbmidi_data_rx_cb(usbd_device *usbd_dev, uint8_t ep) +{ + (void)ep; + + char buf[64]; + int len = usbd_ep_read_packet(usbd_dev, 0x01, buf, 64); + + /* This implementation treats any message from the host as a SysEx + * identity request. This works well enough providing the host + * packs the identify request in a single 8 byte USB message. + */ + if (len) { + while (usbd_ep_write_packet(usbd_dev, 0x81, sysex_identity, + sizeof(sysex_identity)) == 0); + } + + gpio_toggle(GPIOC, GPIO5); +} + +static void usbmidi_set_config(usbd_device *usbd_dev, uint16_t wValue) +{ + (void)wValue; + + usbd_ep_setup(usbd_dev, 0x01, USB_ENDPOINT_ATTR_BULK, 64, + usbmidi_data_rx_cb); + usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_BULK, 64, NULL); +} + +static void button_send_event(usbd_device *usbd_dev, int pressed) +{ + char buf[4] = { + 0x08, /* USB framing: virtual cable 0, note on */ + 0x80, /* MIDI command: note on, channel 1 */ + 60, /* Note 60 (middle C) */ + 64, /* "Normal" velocity */ + }; + + buf[0] |= pressed; + buf[1] |= pressed << 4; + + if(pressed) + gpio_set(GPIOC, GPIO12); + else + gpio_clear(GPIOC, GPIO12); + + while (usbd_ep_write_packet(usbd_dev, 0x81, buf, sizeof(buf)) == 0); +} + +static void button_poll(usbd_device *usbd_dev) +{ + static uint32_t button_state = 0; + + /* This is a simple shift based debounce. It's simplistic because + * although this implements debounce adequately it does not have any + * noise suppression. It is also very wide (32-bits) because it can + * be polled in a very tight loop (no debounce timer). + */ + + uint32_t old_button_state = button_state; + button_state = (button_state << 1) | (GPIOA_IDR & 1); + if ((0 == button_state) != (0 == old_button_state)) { + button_send_event(usbd_dev, !!button_state); + } +} + +int main(void) +{ + int i; + + usbd_device *usbd_dev; + + rcc_clock_setup_in_hse_8mhz_out_72mhz(); + + rcc_periph_clock_enable(RCC_GPIOA); + rcc_periph_clock_enable(RCC_GPIOC); + + /* LED */ + gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO12); + gpio_set(GPIOC, GPIO12); + + /* Button pin */ + gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO0); + + gpio_set(GPIOC, GPIO11); + gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO11); + + usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, usbmidi_set_config); + + for (i = 0; i < 0x80000; i++) + __asm__("nop"); + + gpio_clear(GPIOC, GPIO11); + + while(1) { + usbd_poll(usbd_dev); + button_poll(usbd_dev); + } +}