From e9d967c79f55c8b7f514bd2c63bc66e5f7694153 Mon Sep 17 00:00:00 2001 From: Titouan Christophe Date: Sun, 10 Nov 2024 18:55:11 +0100 Subject: [PATCH] samples: subsys: usb: midi: add new sample Add a sample application that demonstrates how to use the new USB-MIDI 2.0 device class. This shows how to set up the device tree, how to exchange MIDI data with the host, and how to use the data accessors provided by the new API. Signed-off-by: Titouan Christophe --- samples/subsys/usb/midi/CMakeLists.txt | 9 +++ samples/subsys/usb/midi/Kconfig | 9 +++ samples/subsys/usb/midi/README.rst | 81 ++++++++++++++++++++++++++ samples/subsys/usb/midi/app.overlay | 19 ++++++ samples/subsys/usb/midi/prj.conf | 11 ++++ samples/subsys/usb/midi/sample.yaml | 11 ++++ samples/subsys/usb/midi/src/main.c | 71 ++++++++++++++++++++++ 7 files changed, 211 insertions(+) create mode 100644 samples/subsys/usb/midi/CMakeLists.txt create mode 100644 samples/subsys/usb/midi/Kconfig create mode 100644 samples/subsys/usb/midi/README.rst create mode 100644 samples/subsys/usb/midi/app.overlay create mode 100644 samples/subsys/usb/midi/prj.conf create mode 100644 samples/subsys/usb/midi/sample.yaml create mode 100644 samples/subsys/usb/midi/src/main.c diff --git a/samples/subsys/usb/midi/CMakeLists.txt b/samples/subsys/usb/midi/CMakeLists.txt new file mode 100644 index 000000000000000..5bb9b0097df2086 --- /dev/null +++ b/samples/subsys/usb/midi/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(usb_midi) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/subsys/usb/midi/Kconfig b/samples/subsys/usb/midi/Kconfig new file mode 100644 index 000000000000000..b6cd6618aff3afd --- /dev/null +++ b/samples/subsys/usb/midi/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Titouan Christophe +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/subsys/usb/midi/README.rst b/samples/subsys/usb/midi/README.rst new file mode 100644 index 000000000000000..29f88b024d570f5 --- /dev/null +++ b/samples/subsys/usb/midi/README.rst @@ -0,0 +1,81 @@ +.. zephyr:code-sample:: midi + :name: USB MIDI device class sample + :relevant-api: usbd_api usb_midi + + USB MIDI 2.0 device class sample + +Overview +******** + +This sample demonstrates how to implement a USB MIDI 2.0 device. It can run on +any board with a USB device controller. This samples sends all MIDI1 messages +sent to the device back to the host. In addition, on boards that have a +user button, pressing and releasing that button will send MIDI1 note on and +note off messages. + +The application exposes a single USB-MIDI interface with a single bidirectional +group terminal. This allows exchanging data with the host on a "virtual wire" +that carries MIDI1 messages, pretty much like a standard USB-MIDI in/out adapter +would provide. The loopback acts as if a real MIDI cable was connected between +the output and the input, and the button acts as if there was a user keyboard +to press a key. + +Building and Running +******************** + +The code can be found in :zephyr_file:`samples/subsys/usb/midi`. + +To build and flash the application: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/usb/midi + :board: nucleo_f429zi + :goals: build flash + :compact: + +Using the MIDI interface +************************ + +Once this sample is flashed, connect the device USB port to a host computer +with MIDI support. For example, on Linux, you can use alsa to access the device: + +.. code-block:: console + + $ amidi -l + Dir Device Name + IO hw:2,1,0 Group 1 (USBD MIDI Sample) + + +The "USBD MIDI Sample" interface should also appear in any program with MIDI +support (for example your favorite Digital Audio Workstation) + +Testing loopback +**************** + +Open a first shell, and start dumping MIDI events: + +.. code-block:: console + + $ amidi -p hw:2,1,0 -d + + +Then, in a second shell, send some MIDI events (for example note-on/note-off): + +.. code-block:: console + + $ amidi -p hw:2,1,0 -S "90427f 804200" + +These events should then appear in the first shell (dump) + +On devboards with a user button, press it and observe that there are some note +on/note off events delivered to the first shell (dump) + +.. code-block:: console + + $ amidi -p hw:2,1,0 -d + + 90 40 7F + 80 40 7F + 90 40 7F + 80 40 7F + [...] diff --git a/samples/subsys/usb/midi/app.overlay b/samples/subsys/usb/midi/app.overlay new file mode 100644 index 000000000000000..c73f4a08e628b9a --- /dev/null +++ b/samples/subsys/usb/midi/app.overlay @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 Titouan Christophe + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + usb_midi: usb_midi { + compatible = "zephyr,usb-midi"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + midi_in_out@0 { + reg = <0 1>; + protocol = "midi1-up-to-128b"; + }; + }; +}; diff --git a/samples/subsys/usb/midi/prj.conf b/samples/subsys/usb/midi/prj.conf new file mode 100644 index 000000000000000..15386b55eaea471 --- /dev/null +++ b/samples/subsys/usb/midi/prj.conf @@ -0,0 +1,11 @@ +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_USBD_MIDI_CLASS=y + +CONFIG_SAMPLE_USBD_PRODUCT="USBD MIDI Sample" +CONFIG_SAMPLE_USBD_PID=0x0010 + +CONFIG_INPUT=y + +CONFIG_LOG=y +CONFIG_USBD_LOG_LEVEL_WRN=y +CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y diff --git a/samples/subsys/usb/midi/sample.yaml b/samples/subsys/usb/midi/sample.yaml new file mode 100644 index 000000000000000..5189725c1a4eb90 --- /dev/null +++ b/samples/subsys/usb/midi/sample.yaml @@ -0,0 +1,11 @@ +sample: + name: USB MIDI 2.0 device class sample +tests: + sample.usb_device_next.midi: + depends_on: usbd + tags: usb + harness: console + harness_config: + type: one_line + regex: + - "USB device support enabled" diff --git a/samples/subsys/usb/midi/src/main.c b/samples/subsys/usb/midi/src/main.c new file mode 100644 index 000000000000000..663f2d1922d0ce2 --- /dev/null +++ b/samples/subsys/usb/midi/src/main.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2024 Titouan Christophe + * + * SPDX-License-Identifier: Apache-2.0 + * + * @file + * @brief Sample application for USB MIDI 2.0 device class + */ + +#include + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(sample_usb_midi, LOG_LEVEL_INF); + +static const struct device *const midi = DEVICE_DT_GET(DT_NODELABEL(usb_midi)); + +/* On boards that have a user button; button press/release -> MIDI note on/off */ +#if DT_NODE_EXISTS(DT_NODELABEL(user_button)) +static const struct device *const button = DEVICE_DT_GET(DT_PARENT(DT_NODELABEL(user_button))); + +static void key_press(struct input_event *evt, void *user_data) +{ + if (!device_is_ready(midi)) { + LOG_ERR("MIDI device not ready"); + } + + uint8_t command = evt->value ? MIDI_NOTE_ON : MIDI_NOTE_OFF; + usb_midi_send(midi, &UMP_MIDI1(0, command, 0, 0x40, 0x7f)); +} +INPUT_CALLBACK_DEFINE(button, key_press, NULL); +#endif + +static void on_midi_packet(const struct device *dev, const union ump *midi_packet) +{ + LOG_INF("Received MIDI packet (MT=%X)", midi_packet->mt); + /* Only send MIDI1 channel voice messages back to the host */ + if (midi_packet->mt == MT_MIDI1_CHANNEL_VOICE) { + const struct ump_midi1 *midi1 = &midi_packet->midi1; + LOG_INF("Send back MIDI1 message chan=%d status=%d %02X %02X", midi1->channel, + midi1->status, midi1->p1, midi1->p2); + usb_midi_send(dev, midi_packet); + } +} + +int main(void) +{ + if (!device_is_ready(midi)) { + LOG_ERR("MIDI device not ready"); + return -1; + } + + usb_midi_set_callback(midi, on_midi_packet); + + struct usbd_context *sample_usbd = sample_usbd_init_device(NULL); + if (sample_usbd == NULL) { + LOG_ERR("Failed to initialize USB device"); + return -1; + } + + if (!usbd_can_detect_vbus(sample_usbd) && usbd_enable(sample_usbd)) { + LOG_ERR("Failed to enable device support"); + return -1; + } + + LOG_INF("USB device support enabled"); + return 0; +}