From 290470bda38ce75bbadcce8939435953ca483269 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Tue, 24 Mar 2026 23:38:24 -0300 Subject: example: add MIDI 2.0 Device example (midi2_device) Add Device example that plays Twinkle Twinkle Little Star using native UMP format. Demonstrates all MIDI 2.0 Channel Voice message types: 16-bit velocity, 32-bit CC, 32-bit pitch bend, 32-bit channel/poly pressure, per-note management, program change with bank select, and JR timestamps. USB descriptor exposes both Alt Setting 0 (MIDI 1.0) and Alt Setting 1 (UMP) per USB-MIDI 2.0 specification. Tested on: Raspberry Pi Pico (RP2040), Linux (ALSA), Windows (MIDI Services) --- examples/device/CMakeLists.txt | 1 + examples/device/midi2_device/CMakeLists.txt | 33 ++ examples/device/midi2_device/README.md | 59 +++ examples/device/midi2_device/src/main.c | 483 +++++++++++++++++++++ examples/device/midi2_device/src/tusb_config.h | 88 ++++ examples/device/midi2_device/src/usb_descriptors.c | 142 ++++++ 6 files changed, 806 insertions(+) create mode 100644 examples/device/midi2_device/CMakeLists.txt create mode 100644 examples/device/midi2_device/README.md create mode 100644 examples/device/midi2_device/src/main.c create mode 100644 examples/device/midi2_device/src/tusb_config.h create mode 100644 examples/device/midi2_device/src/usb_descriptors.c (limited to 'examples') diff --git a/examples/device/CMakeLists.txt b/examples/device/CMakeLists.txt index 088872711..1432b36bb 100644 --- a/examples/device/CMakeLists.txt +++ b/examples/device/CMakeLists.txt @@ -28,6 +28,7 @@ set(EXAMPLE_LIST hid_multiple_interface midi_test midi_test_freertos + midi2_device msc_dual_lun mtp net_lwip_webserver diff --git a/examples/device/midi2_device/CMakeLists.txt b/examples/device/midi2_device/CMakeLists.txt new file mode 100644 index 000000000..295af6550 --- /dev/null +++ b/examples/device/midi2_device/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +project(midi2_device C CXX ASM) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) + +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") + return() +endif() + +add_executable(${PROJECT_NAME}) + +# Example source +target_sources(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c + ) + +# Example include +target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +# Configure compilation flags and libraries for the example without RTOS. +# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. +family_configure_device_example(${PROJECT_NAME} noos) + +# Suppress pre-existing warning in usbd.c (uint8_t comparison always true/false) +target_compile_options(${PROJECT_NAME} PRIVATE -Wno-type-limits) diff --git a/examples/device/midi2_device/README.md b/examples/device/midi2_device/README.md new file mode 100644 index 000000000..1731dba57 --- /dev/null +++ b/examples/device/midi2_device/README.md @@ -0,0 +1,59 @@ +# MIDI 2.0 Song Sender + +USB MIDI 2.0 Device example that plays "Twinkle Twinkle Little Star" using +native UMP (Universal MIDI Packet) format with full MIDI 2.0 expression. + +## MIDI 2.0 Features Demonstrated + +- 16-bit Velocity (vs 7-bit MIDI 1.0) +- 32-bit Control Change values +- 32-bit Pitch Bend (vs 14-bit MIDI 1.0) +- 32-bit Channel Pressure (Aftertouch) +- 32-bit Poly Pressure (Per-Note Aftertouch) +- Per-Note Management (MIDI 2.0 exclusive) +- Program Change with Bank Select +- JR Timestamps + +## USB Descriptor + +The device exposes both USB-MIDI 1.0 (Alt Setting 0) and USB-MIDI 2.0 (Alt Setting 1) +as required by the USB-MIDI 2.0 specification. A MIDI 2.0 capable host (e.g. Windows +MIDI Services) will select Alt Setting 1 for native UMP transport. Legacy hosts use +Alt Setting 0 with automatic MIDI 1.0 fallback. + +## Hardware + +- Any RP2040 board with USB (e.g. Raspberry Pi Pico) +- LED on GPIO 25: steady = playing, slow blink = waiting for host + +## Building + +```bash +mkdir build && cd build +cmake -DBOARD=raspberry_pi_pico -DPICO_SDK_FETCH_FROM_GIT=on -G Ninja .. +cmake --build . +``` + +## Flashing + +Hold BOOTSEL, connect USB, drag `midi2_device.uf2` to the RPI-RP2 drive. + +## Testing + +**Linux:** +```bash +aseqdump -p "MIDI 2.0 Device" +``` + +**Windows (MIDI 2.0 native):** +```powershell +midi endpoint list +midi endpoint monitor +``` + +## Song Data + +Twinkle Twinkle Little Star in C major, 120 BPM. Six phrases with dynamic +shaping (pp to ff crescendo and back), pitch bend vibrato on sustained notes, +and channel/poly pressure for expression. All values use genuine MIDI 2.0 +resolution with no 7-bit equivalent. diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c new file mode 100644 index 000000000..515efe07b --- /dev/null +++ b/examples/device/midi2_device/src/main.c @@ -0,0 +1,483 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include "bsp/board_api.h" +#include "tusb.h" +#include "class/midi/midi2_device.h" + +//--------------------------------------------------------------------+ +// MIDI 2.0 UMP Message Type Constants (M2-104-UM, Section 4) +//--------------------------------------------------------------------+ +// Message Type (MT) occupies bits 31-28 of Word 0 +#define UMP_MT_UTILITY 0x00000000 // 32-bit: Utility (NOOP, JR Clock, JR Timestamp) +#define UMP_MT_SYSTEM 0x10000000 // 32-bit: System Common / Real Time +#define UMP_MT_MIDI1_CV 0x20000000 // 32-bit: MIDI 1.0 Channel Voice +#define UMP_MT_DATA64 0x30000000 // 64-bit: Data (SysEx 7-bit) +#define UMP_MT_MIDI2_CV 0x40000000 // 64-bit: MIDI 2.0 Channel Voice +#define UMP_MT_DATA128 0x50000000 // 128-bit: Data (SysEx 8-bit) + +// MIDI 2.0 Channel Voice status (bits 23-20 of Word 0) +#define UMP_STATUS_NOTE_OFF 0x00800000 +#define UMP_STATUS_NOTE_ON 0x00900000 +#define UMP_STATUS_POLY_PRESSURE 0x00A00000 +#define UMP_STATUS_CC 0x00B00000 +#define UMP_STATUS_PROGRAM 0x00C00000 +#define UMP_STATUS_CHAN_PRESSURE 0x00D00000 +#define UMP_STATUS_PITCH_BEND 0x00E00000 +#define UMP_STATUS_PN_MGMT 0x00F00000 // Per-Note Management + +// Note Attribute Types (MIDI 2.0 spec, Section 4.2.6) +#define UMP_ATTR_NONE 0x00 +#define UMP_ATTR_MANUFACTURER 0x01 +#define UMP_ATTR_PROFILE 0x02 +#define UMP_ATTR_PITCH_7_9 0x03 // Pitch 7.9 format + +//--------------------------------------------------------------------+ +// MIDI 2.0 UMP Builders - Full Spec Coverage +//--------------------------------------------------------------------+ + +// Helper: send a 64-bit UMP (2 words) +static inline void ump_send_64(uint32_t w0, uint32_t w1) { + uint32_t words[2] = { w0, w1 }; + tud_midi2_ump_write(words, 2); +} + +// Helper: send a 32-bit UMP (1 word) +static inline void ump_send_32(uint32_t w0) { + tud_midi2_ump_write(&w0, 1); +} + +// -- Utility Messages (MT=0x0, 32-bit) -- + +static inline void ump_noop(void) { + ump_send_32(UMP_MT_UTILITY); +} + +static inline void ump_jr_timestamp(uint16_t timestamp) { + // Word 0: [MT(0x0) | Group(0) | Status(0x0020) | Timestamp(16-bit)] + ump_send_32(UMP_MT_UTILITY | 0x00200000 | (uint32_t)timestamp); +} + +// -- MIDI 2.0 Channel Voice: Note On (MT=0x4, 64-bit) -- +// Word 0: [MT(4):Group(4):Status(4):Channel(4):NoteNumber(8):AttrType(8)] +// Word 1: [Velocity(16):Attribute(16)] +static inline void ump_note_on(uint8_t group, uint8_t channel, + uint8_t pitch, uint16_t velocity, + uint8_t attr_type, uint16_t attr_val) { + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_NOTE_ON | ((uint32_t)(channel & 0x0F) << 16) + | ((uint32_t)(pitch & 0x7F) << 8) + | (uint32_t)(attr_type & 0xFF); + uint32_t w1 = ((uint32_t)(velocity & 0xFFFF) << 16) + | (uint32_t)(attr_val & 0xFFFF); + ump_send_64(w0, w1); +} + +// -- MIDI 2.0 Channel Voice: Note Off (MT=0x4, 64-bit) -- +static inline void ump_note_off(uint8_t group, uint8_t channel, + uint8_t pitch, uint16_t velocity, + uint8_t attr_type, uint16_t attr_val) { + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_NOTE_OFF | ((uint32_t)(channel & 0x0F) << 16) + | ((uint32_t)(pitch & 0x7F) << 8) + | (uint32_t)(attr_type & 0xFF); + uint32_t w1 = ((uint32_t)(velocity & 0xFFFF) << 16) + | (uint32_t)(attr_val & 0xFFFF); + ump_send_64(w0, w1); +} + +// -- MIDI 2.0 Channel Voice: Control Change (MT=0x4, 64-bit) -- +// Word 0: [MT(4):Group(4):Status(0xB):Channel(4):Index(8):Reserved(8)] +// Word 1: [Data(32)] -- full 32-bit CC resolution (vs 7-bit MIDI 1.0) +static inline void ump_cc(uint8_t group, uint8_t channel, + uint8_t index, uint32_t value) { + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_CC | ((uint32_t)(channel & 0x0F) << 16) + | ((uint32_t)(index & 0x7F) << 8); + ump_send_64(w0, value); +} + +// -- MIDI 2.0 Channel Voice: Program Change (MT=0x4, 64-bit) -- +// Word 0: [MT(4):Group(4):Status(0xC):Channel(4):Reserved(8):OptionFlags(8)] +// Word 1: [Program(8):Reserved(8):BankMSB(8):BankLSB(8)] +// OptionFlags bit 0 = Bank Valid +static inline void ump_program_change(uint8_t group, uint8_t channel, + uint8_t program, + bool bank_valid, uint8_t bank_msb, + uint8_t bank_lsb) { + uint8_t flags = bank_valid ? 0x01 : 0x00; + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_PROGRAM | ((uint32_t)(channel & 0x0F) << 16) + | (uint32_t)flags; + uint32_t w1 = ((uint32_t)program << 24) + | ((uint32_t)bank_msb << 8) + | (uint32_t)bank_lsb; + ump_send_64(w0, w1); +} + +// -- MIDI 2.0 Channel Voice: Pitch Bend (MT=0x4, 64-bit) -- +// Word 0: [MT(4):Group(4):Status(0xE):Channel(4):Reserved(16)] +// Word 1: [PitchBend(32)] -- full 32-bit (vs 14-bit MIDI 1.0!) +// 0x80000000 = center, 0x00000000 = min, 0xFFFFFFFF = max +static inline void ump_pitch_bend(uint8_t group, uint8_t channel, + uint32_t value) { + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_PITCH_BEND | ((uint32_t)(channel & 0x0F) << 16); + ump_send_64(w0, value); +} + +// -- MIDI 2.0 Channel Voice: Channel Pressure / Aftertouch (MT=0x4, 64-bit) -- +// Word 0: [MT(4):Group(4):Status(0xD):Channel(4):Reserved(16)] +// Word 1: [Pressure(32)] -- full 32-bit (vs 7-bit MIDI 1.0) +static inline void ump_channel_pressure(uint8_t group, uint8_t channel, + uint32_t pressure) { + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_CHAN_PRESSURE | ((uint32_t)(channel & 0x0F) << 16); + ump_send_64(w0, pressure); +} + +// -- MIDI 2.0 Channel Voice: Poly Pressure / Per-Note Aftertouch -- +// Word 0: [MT(4):Group(4):Status(0xA):Channel(4):NoteNumber(8):Reserved(8)] +// Word 1: [Pressure(32)] +static inline void ump_poly_pressure(uint8_t group, uint8_t channel, + uint8_t pitch, uint32_t pressure) { + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_POLY_PRESSURE | ((uint32_t)(channel & 0x0F) << 16) + | ((uint32_t)(pitch & 0x7F) << 8); + ump_send_64(w0, pressure); +} + +// -- MIDI 2.0 Channel Voice: Per-Note Management (MT=0x4, 64-bit) -- +// Exclusive to MIDI 2.0: controls per-note behavior +// Word 0: [MT(4):Group(4):Status(0xF):Channel(4):NoteNumber(8):Flags(8)] +// Word 1: Reserved +// Flags bit 1 = Reset (S), bit 0 = Detach (D) +static inline void ump_per_note_mgmt(uint8_t group, uint8_t channel, + uint8_t pitch, bool detach, + bool reset) { + uint8_t flags = (reset ? 0x02 : 0x00) | (detach ? 0x01 : 0x00); + uint32_t w0 = UMP_MT_MIDI2_CV | ((uint32_t)(group & 0x0F) << 24) + | UMP_STATUS_PN_MGMT | ((uint32_t)(channel & 0x0F) << 16) + | ((uint32_t)(pitch & 0x7F) << 8) + | (uint32_t)flags; + ump_send_64(w0, 0x00000000); +} + +//--------------------------------------------------------------------+ +// Song Data +//--------------------------------------------------------------------+ + +// Extended note event with MIDI 2.0 expression data +typedef struct { + uint8_t pitch; // MIDI pitch (0-127, 0=rest) + uint16_t duration_ms; // Duration in ms + uint16_t velocity; // 16-bit velocity (MIDI 2.0) + uint32_t pressure; // 32-bit aftertouch (0 = none) + int16_t bend_cents; // Pitch bend in cents (0 = none, for vibrato/ornaments) +} midi2_note_t; + +// 16-bit velocity (MIDI 2.0): values that have NO 7-bit equivalent. +// MIDI 1.0 can only express 128 levels (0x0000, 0x0200, 0x0400 ... 0xFE00). +// These use the full 16-bit range to prove genuine MIDI 2.0 resolution. +#define V_PPP 0x0A3D // 2621 - between MIDI1 vel 5 and 6 +#define V_PP 0x1C71 // 7281 - between MIDI1 vel 14 and 15 +#define V_P 0x3219 // 12825 - between MIDI1 vel 24 and 25 +#define V_MP 0x4F5C // 20316 - between MIDI1 vel 39 and 40 +#define V_MF 0x6E93 // 28307 - between MIDI1 vel 55 and 56 +#define V_F 0x8DA5 // 36261 - between MIDI1 vel 70 and 71 +#define V_FF 0xAC37 // 44087 - between MIDI1 vel 85 and 86 +#define V_FFF 0xDEB8 // 57016 - between MIDI1 vel 111 and 112 + +// Twinkle Twinkle Little Star - Traditional +// Tempo: 120 BPM (500ms per quarter note) +// Key: C major, 4/4 +// Demonstrates all MIDI 2.0 Channel Voice features: +// 16-bit velocity, 32-bit CC, 32-bit pitch bend, +// 32-bit channel pressure, per-note poly pressure, +// per-note management, program change with bank select, +// JR timestamps +static const midi2_note_t song_data[] = { + // Phrase 1: "Twin-kle twin-kle lit-tle star" (C C G G A A G-) + // Crescendo pp -> mp, gentle entry + { .pitch = 60, .duration_ms = 500, .velocity = V_PP, .pressure = 0, .bend_cents = 0 }, // C4 + { .pitch = 60, .duration_ms = 500, .velocity = V_P, .pressure = 0, .bend_cents = 0 }, // C4 + { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 69, .duration_ms = 500, .velocity = V_MF, .pressure = 0x1A3D7E5F, .bend_cents = 0 }, // A4 (32-bit pressure) + { .pitch = 69, .duration_ms = 500, .velocity = V_MF, .pressure = 0x2B851EB9, .bend_cents = 0 }, // A4 (pressure swell) + { .pitch = 67, .duration_ms = 1000,.velocity = V_MF, .pressure = 0x3C6EF373, .bend_cents = 7 }, // G4 (half, bend 7 cents) + + // Phrase 2: "How I won-der what you are" (F F E E D D C-) + // mf, sustained + { .pitch = 65, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // F4 + { .pitch = 65, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // F4 + { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x1E4C2B7A, .bend_cents = 0 }, // E4 + { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x2D5A8FC1, .bend_cents = 0 }, // E4 (aftertouch swell) + { .pitch = 62, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // D4 + { .pitch = 62, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // D4 + { .pitch = 60, .duration_ms = 1000,.velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // C4 (half, resolve) + + // Phrase 3: "Up a-bove the world so high" (G G F F E E D-) + // f, building intensity + { .pitch = 67, .duration_ms = 500, .velocity = V_F, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 67, .duration_ms = 500, .velocity = V_F, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 65, .duration_ms = 500, .velocity = V_F, .pressure = 0x2F8A4E13, .bend_cents = 0 }, // F4 + { .pitch = 65, .duration_ms = 500, .velocity = V_MF, .pressure = 0x41B2C9D7, .bend_cents = 0 }, // F4 (triggers poly pressure) + { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // E4 + { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // E4 + { .pitch = 62, .duration_ms = 1000,.velocity = V_MF, .pressure = 0x537DC2A6, .bend_cents = 13 }, // D4 (half, vibrato 13 cents) + + // Phrase 4: "Like a dia-mond in the sky" (G G F F E E D-) + // ff, expressive peak + { .pitch = 67, .duration_ms = 500, .velocity = V_FF, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 67, .duration_ms = 500, .velocity = V_FF, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 65, .duration_ms = 500, .velocity = V_F, .pressure = 0x44E7B8D2, .bend_cents = 0 }, // F4 (triggers poly pressure) + { .pitch = 65, .duration_ms = 500, .velocity = V_F, .pressure = 0x56A3F14B, .bend_cents = 0 }, // F4 (triggers poly pressure) + { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x2C8E1F5A, .bend_cents = 0 }, // E4 + { .pitch = 64, .duration_ms = 500, .velocity = V_MF, .pressure = 0x1D73A4E8, .bend_cents = 0 }, // E4 + { .pitch = 62, .duration_ms = 1000,.velocity = V_MF, .pressure = 0x63F5B17D, .bend_cents = 19 }, // D4 (half, vibrato 19 cents) + + // Phrase 5: "Twin-kle twin-kle lit-tle star" (C C G G A A G-) + // Diminuendo mf -> mp + { .pitch = 60, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // C4 + { .pitch = 60, .duration_ms = 500, .velocity = V_MF, .pressure = 0, .bend_cents = 0 }, // C4 + { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 67, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // G4 + { .pitch = 69, .duration_ms = 500, .velocity = V_MP, .pressure = 0x1B4F6D83, .bend_cents = 0 }, // A4 + { .pitch = 69, .duration_ms = 500, .velocity = V_P, .pressure = 0x0E29C5A1, .bend_cents = 0 }, // A4 + { .pitch = 67, .duration_ms = 1000,.velocity = V_P, .pressure = 0x2A6D3B9E, .bend_cents = 5 }, // G4 (half, gentle bend 5 cents) + + // Phrase 6: "How I won-der what you are" (F F E E D D C-) + // Dying away mp -> ppp + { .pitch = 65, .duration_ms = 500, .velocity = V_MP, .pressure = 0, .bend_cents = 0 }, // F4 + { .pitch = 65, .duration_ms = 500, .velocity = V_P, .pressure = 0, .bend_cents = 0 }, // F4 + { .pitch = 64, .duration_ms = 500, .velocity = V_P, .pressure = 0, .bend_cents = 0 }, // E4 + { .pitch = 64, .duration_ms = 500, .velocity = V_PP, .pressure = 0, .bend_cents = 0 }, // E4 + { .pitch = 62, .duration_ms = 500, .velocity = V_PP, .pressure = 0, .bend_cents = 0 }, // D4 + { .pitch = 62, .duration_ms = 500, .velocity = V_PPP, .pressure = 0, .bend_cents = 0 }, // D4 + { .pitch = 60, .duration_ms = 2000,.velocity = V_PPP, .pressure = 0x07A1E3C9, .bend_cents = 0 }, // C4 (fermata) + + // Silence before loop + { .pitch = 0, .duration_ms = 1000,.velocity = 0, .pressure = 0, .bend_cents = 0 }, + + // End marker + { .pitch = 0, .duration_ms = 0, .velocity = 0, .pressure = 0, .bend_cents = 0 }, +}; + +#define SONG_LENGTH (sizeof(song_data) / sizeof(midi2_note_t)) + +//--------------------------------------------------------------------+ +// Song Playback State Machine +//--------------------------------------------------------------------+ + +typedef struct { + uint32_t current_note_idx; + uint32_t note_start_ms; + uint8_t active_pitch; + bool note_is_active; + bool setup_sent; // Initial setup (Program Change, CC) sent? + uint32_t loop_count; +} song_state_t; + +static song_state_t song = { 0 }; + +// Forward declarations +void update_song_playback(uint32_t now_ms); +void send_initial_setup(void); + +//--------------------------------------------------------------------+ +// MIDI 2.0 Device Callbacks (override weak stubs from middleware) +//--------------------------------------------------------------------+ + +void tud_midi2_rx_cb(uint8_t itf) { + (void)itf; +} + +//--------------------------------------------------------------------+ +// Initial Setup - Program Change, CC, Per-Note Management +//--------------------------------------------------------------------+ + +void send_initial_setup(void) { + ump_jr_timestamp(0x0001); + ump_program_change(0, 0, 0, true, 0, 0); + ump_cc(0, 0, 7, 0xCCCCCCCC); // Volume 80% (32-bit) + ump_cc(0, 0, 11, 0xFFFFFFFF); // Expression 100% + ump_cc(0, 0, 64, 0x00000000); // Sustain off + ump_cc(0, 0, 1, 0x20000000); // Modulation + ump_cc(0, 0, 10, 0x80000000); // Pan center + ump_per_note_mgmt(0, 0, 0, false, true); // Per-Note reset + ump_pitch_bend(0, 0, 0x80000000); // Pitch Bend center + ump_channel_pressure(0, 0, 0x00000000); + + printf("[SETUP] Piano | Vol 80%% | UMP\r\n"); +} + +//--------------------------------------------------------------------+ +// Pitch Bend Conversion: cents to 32-bit value +//--------------------------------------------------------------------+ + +// Convert pitch bend in cents (-200 to +200) to 32-bit UMP value +// Center = 0x80000000, range = +/- 2 semitones (200 cents) +static inline uint32_t cents_to_pitch_bend(int16_t cents) { + if (cents == 0) return 0x80000000; + // Scale: 200 cents = full range (0x7FFFFFFF deviation from center) + int32_t offset = (int32_t)(((int64_t)cents * 0x7FFFFFFF) / 200); + return (uint32_t)((int32_t)0x80000000 + offset); +} + +//--------------------------------------------------------------------+ +// Song Playback Logic - Full MIDI 2.0 Expression +//--------------------------------------------------------------------+ + +void update_song_playback(uint32_t now_ms) { + const midi2_note_t *current = &song_data[song.current_note_idx]; + + if (!song.setup_sent) { + send_initial_setup(); + song.setup_sent = true; + song.note_start_ms = now_ms; + } + + // Note duration elapsed: send Note Off, advance + if (song.note_is_active && (now_ms - song.note_start_ms) >= current->duration_ms) { + if (song.active_pitch > 0) { + if (current->bend_cents != 0) ump_pitch_bend(0, 0, 0x80000000); + if (current->pressure > 0) ump_channel_pressure(0, 0, 0x00000000); + ump_note_off(0, 0, song.active_pitch, V_P, UMP_ATTR_NONE, 0); + } + + song.note_is_active = false; + song.current_note_idx++; + + if (song.current_note_idx >= SONG_LENGTH) { + song.current_note_idx = 0; + song.setup_sent = false; + song.loop_count++; + printf("\r\n=== Loop %lu ===\r\n", (unsigned long)song.loop_count); + } + + song.note_start_ms = now_ms; + } + + // Start next note + if (!song.note_is_active && song.current_note_idx < SONG_LENGTH) { + const midi2_note_t *next = &song_data[song.current_note_idx]; + + if (next->duration_ms == 0) { + song.current_note_idx = 0; + song.setup_sent = false; + song.loop_count++; + printf("\r\n=== Loop %lu ===\r\n", (unsigned long)song.loop_count); + return; + } + + if (next->pitch > 0) { + ump_jr_timestamp((uint16_t)(now_ms & 0xFFFF)); + if (next->bend_cents != 0) { + ump_pitch_bend(0, 0, cents_to_pitch_bend(next->bend_cents)); + } + ump_note_on(0, 0, next->pitch, next->velocity, UMP_ATTR_NONE, 0); + if (next->pressure > 0) { + ump_channel_pressure(0, 0, next->pressure); + } + if (next->pressure > 0x40000000 && next->duration_ms > 500) { + ump_poly_pressure(0, 0, next->pitch, next->pressure); + } + + song.active_pitch = next->pitch; + song.note_is_active = true; + } + + // Rest: honor duration + if (!song.note_is_active) { + song.active_pitch = 0; + song.note_is_active = true; + song.note_start_ms = now_ms; + } + } +} + +//--------------------------------------------------------------------+ +// Main +//--------------------------------------------------------------------+ + +int main(void) { + board_init(); + printf("\r\n"); + printf("===========================================\r\n"); + printf(" RP2040 MIDI 2.0 Device\r\n"); + printf("===========================================\r\n"); + printf("Tempo: 120 BPM | Format: UMP 64-bit\r\n"); + printf("Song: %u notes with full MIDI 2.0 expression\r\n", + (unsigned)SONG_LENGTH); + printf("Features:\r\n"); + printf(" - 16-bit Velocity (vs 7-bit MIDI 1.0)\r\n"); + printf(" - 32-bit Control Change\r\n"); + printf(" - 32-bit Pitch Bend (vs 14-bit MIDI 1.0)\r\n"); + printf(" - 32-bit Channel Pressure\r\n"); + printf(" - 32-bit Poly Pressure (per-note)\r\n"); + printf(" - Per-Note Management (MIDI 2.0 exclusive)\r\n"); + printf(" - Program Change with Bank Select\r\n"); + printf(" - JR Timestamps\r\n"); + printf("Status: Initializing...\r\n"); + + tusb_rhport_init_t dev_init = {.role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_AUTO}; + tusb_init(BOARD_TUD_RHPORT, &dev_init); + + board_init_after_tusb(); + board_led_write(true); + + uint32_t last_report_ms = 0; + + while (1) { + tud_task(); + + uint32_t now_ms = tusb_time_millis_api(); + + if (tud_midi2_mounted()) { + update_song_playback(now_ms); + board_led_write(song.active_pitch > 0); + } else { + board_led_write((now_ms / 500) & 1); + } + + // Status report every 10 seconds + if (now_ms - last_report_ms > 10000) { + last_report_ms = now_ms; + if (tud_midi2_mounted()) { + printf("[%lums] Playing idx %lu/%u loop %lu\r\n", + (unsigned long)now_ms, + (unsigned long)song.current_note_idx, + (unsigned)SONG_LENGTH, + (unsigned long)song.loop_count); + } else { + printf("[%lums] Waiting for host...\r\n", (unsigned long)now_ms); + } + } + } + + return 0; +} diff --git a/examples/device/midi2_device/src/tusb_config.h b/examples/device/midi2_device/src/tusb_config.h new file mode 100644 index 000000000..1ada0015f --- /dev/null +++ b/examples/device/midi2_device/src/tusb_config.h @@ -0,0 +1,88 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_MIDI2 1 + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_CONFIG_H_ */ diff --git a/examples/device/midi2_device/src/usb_descriptors.c b/examples/device/midi2_device/src/usb_descriptors.c new file mode 100644 index 000000000..ce289bd4d --- /dev/null +++ b/examples/device/midi2_device/src/usb_descriptors.c @@ -0,0 +1,142 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "bsp/board_api.h" +#include "tusb.h" +#include "class/audio/audio.h" +#include "class/midi/midi.h" + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ + +static tusb_desc_device_t const desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xcafe, + .idProduct = 0x4062, // MIDI 2.0 Device + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +uint8_t const * tud_descriptor_device_cb(void) { + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor - MIDI 2.0 +//--------------------------------------------------------------------+ + +enum { + ITF_NUM_MIDI2 = 0, // Audio Control interface + ITF_NUM_MIDI2_STREAMING, // MIDI Streaming interface (auto-created by TUD_MIDI2_DESCRIPTOR) + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MIDI2_DESC_LEN) + +// Endpoint addresses +#define EPNUM_MIDI2_OUT 0x01 +#define EPNUM_MIDI2_IN 0x81 + +static uint8_t const desc_fs_configuration[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + // MIDI 2.0 Interface + TUD_MIDI2_DESCRIPTOR(ITF_NUM_MIDI2, 0, EPNUM_MIDI2_OUT, EPNUM_MIDI2_IN, 64) +}; + +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { + (void) index; + return desc_fs_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER = 1, + STRID_PRODUCT = 2, + STRID_SERIAL = 3, +}; + +static char const *string_desc_arr[] = { + (const char[]) { 0x09, 0x04 }, // 0: Language + "TinyUSB", // 1: Manufacturer + "RP2040 MIDI 2.0", // 2: Product + NULL, // 3: Serial +}; + +static uint16_t _desc_str[32 + 1]; + +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + (void) langid; + size_t chr_count; + + switch ( index ) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: + chr_count = board_usb_get_serial(_desc_str + 1, 32); + break; + + default: + if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) { + return NULL; + } + + const char *str = string_desc_arr[index]; + chr_count = strlen(str); + const size_t max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; + if ( chr_count > max_count ) { + chr_count = max_count; + } + + for ( size_t i = 0; i < chr_count; i++ ) { + _desc_str[1 + i] = str[i]; + } + break; + } + + _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); + return _desc_str; +} -- cgit v1.3.1 From d5c5ac586bfdbf53e18a8cdbb11b978f53b6d059 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Tue, 24 Mar 2026 23:38:36 -0300 Subject: example: add MIDI 2.0 Host example with display (midi2_host) Add Host example that receives UMP from a MIDI 2.0 Device via PIO-USB and displays received messages on a SSD1306 OLED (I2C, 128x64). Features: - Boot checklist on display (PWR, TinyUSB, USB bus, Device, Descriptor, Alt Setting UMP, Mount, Receiving) - Decode and display all MIDI 2.0 Channel Voice messages - PIO-USB Host on rhport 1 (GP12/GP13 for Waveshare RP2350-USB-A) - SSD1306 display via I2C0 (GP4=SDA, GP5=SCL) Also add board definition for Waveshare RP2350-USB-A. Tested: Waveshare RP2350-USB-A (Host) receiving UMP from Raspberry Pi Pico (Device) via USB cable, notes displayed on SSD1306 OLED --- examples/host/CMakeLists.txt | 1 + examples/host/midi2_host/CMakeLists.txt | 37 +++ examples/host/midi2_host/src/display.c | 220 ++++++++++++++ examples/host/midi2_host/src/display.h | 54 ++++ examples/host/midi2_host/src/font5x7.h | 107 +++++++ examples/host/midi2_host/src/main.c | 326 +++++++++++++++++++++ examples/host/midi2_host/src/tusb_config.h | 91 ++++++ .../boards/waveshare_rp2350_usb_a/board.cmake | 2 + .../rp2040/boards/waveshare_rp2350_usb_a/board.h | 49 ++++ 9 files changed, 887 insertions(+) create mode 100644 examples/host/midi2_host/CMakeLists.txt create mode 100644 examples/host/midi2_host/src/display.c create mode 100644 examples/host/midi2_host/src/display.h create mode 100644 examples/host/midi2_host/src/font5x7.h create mode 100644 examples/host/midi2_host/src/main.c create mode 100644 examples/host/midi2_host/src/tusb_config.h create mode 100644 hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake create mode 100644 hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h (limited to 'examples') diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt index 70e0427ab..7c74e3c73 100644 --- a/examples/host/CMakeLists.txt +++ b/examples/host/CMakeLists.txt @@ -13,6 +13,7 @@ set(EXAMPLE_LIST device_info hid_controller midi_rx + midi2_host msc_file_explorer msc_file_explorer_freertos ) diff --git a/examples/host/midi2_host/CMakeLists.txt b/examples/host/midi2_host/CMakeLists.txt new file mode 100644 index 000000000..221de7adf --- /dev/null +++ b/examples/host/midi2_host/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +project(midi2_host C CXX ASM) + +family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) + +if(FAMILY STREQUAL "espressif") + return() +endif() + +add_executable(${PROJECT_NAME}) + +target_sources(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/display.c +) + +target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +family_configure_host_example(${PROJECT_NAME} noos) + +# Waveshare RP2350-USB-A: PIO-USB on GP12/GP13 +target_compile_definitions(${PROJECT_NAME} PRIVATE + PIO_USB_DP_PIN_DEFAULT=12 +) +target_compile_options(${PROJECT_NAME} PRIVATE + -Wno-type-limits +) + +# SSD1306 display (I2C) +target_link_libraries(${PROJECT_NAME} PUBLIC + hardware_i2c +) diff --git a/examples/host/midi2_host/src/display.c b/examples/host/midi2_host/src/display.c new file mode 100644 index 000000000..6d81bcd5c --- /dev/null +++ b/examples/host/midi2_host/src/display.c @@ -0,0 +1,220 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// SSD1306 OLED display driver (128x64, I2C) for MIDI 2.0 Host example. +// Minimal text-only implementation, no graphics library. +// I2C0: SDA = GP4, SCL = GP5, Address = 0x3C + +#include "display.h" +#include +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "hardware/gpio.h" + +#define I2C_PORT i2c0 +#define I2C_SDA 4 +#define I2C_SCL 5 +#define I2C_FREQ 400000 +#define SSD1306_ADDR 0x3C + +#define SCR_W 128 +#define SCR_H 64 +#define PAGES (SCR_H / 8) // 8 pages +#define CHARS_PER_LINE 21 // 128 / 6 = 21 chars + +// Framebuffer +static uint8_t fb[SCR_W * PAGES]; + +// Log buffer +#define LOG_LINES 3 +static char log_lines[LOG_LINES][CHARS_PER_LINE + 1]; +static int log_count = 0; + +// Status line +static char status_text[CHARS_PER_LINE + 1] = ""; + +//--------------------------------------------------------------------+ +// Minimal 5x7 font (ASCII 32-126) +//--------------------------------------------------------------------+ +#include "font5x7.h" + +//--------------------------------------------------------------------+ +// SSD1306 I2C commands +//--------------------------------------------------------------------+ + +static void ssd_cmd(uint8_t cmd) { + uint8_t buf[2] = { 0x00, cmd }; // Co=0, D/C=0 + i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, 2, false); +} + +static void ssd_data(const uint8_t* data, size_t len) { + uint8_t buf[SCR_W + 1]; + buf[0] = 0x40; // Co=0, D/C=1 + size_t chunk = (len > SCR_W) ? SCR_W : len; + memcpy(buf + 1, data, chunk); + i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, chunk + 1, false); +} + +static void ssd_flush(void) { + ssd_cmd(0x21); ssd_cmd(0); ssd_cmd(127); // Column range + ssd_cmd(0x22); ssd_cmd(0); ssd_cmd(7); // Page range + for (int page = 0; page < PAGES; page++) { + ssd_data(&fb[page * SCR_W], SCR_W); + } +} + +//--------------------------------------------------------------------+ +// Framebuffer drawing +//--------------------------------------------------------------------+ + +static void fb_clear(void) { + memset(fb, 0, sizeof(fb)); +} + +static void fb_char(int x, int y, char c) { + if (c < 32 || c > 126) c = '?'; + const uint8_t* glyph = font5x7 + (c - 32) * 5; + int page = y / 8; + int bit_offset = y % 8; + + if (page >= PAGES || x + 5 > SCR_W) return; + + for (int col = 0; col < 5; col++) { + uint8_t column_data = glyph[col]; + fb[(page * SCR_W) + x + col] |= (uint8_t)(column_data << bit_offset); + if (bit_offset > 0 && page + 1 < PAGES) { + fb[((page + 1) * SCR_W) + x + col] |= (uint8_t)(column_data >> (8 - bit_offset)); + } + } +} + +static void fb_string(int x, int y, const char* str) { + while (*str) { + fb_char(x, y, *str); + x += 6; + if (x + 6 > SCR_W) break; + str++; + } +} + +//--------------------------------------------------------------------+ +// Display API +//--------------------------------------------------------------------+ + +void display_init(void) { + i2c_init(I2C_PORT, I2C_FREQ); + gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA); + gpio_pull_up(I2C_SCL); + + sleep_ms(100); + + // SSD1306 init sequence + ssd_cmd(0xAE); // Display off + ssd_cmd(0xD5); ssd_cmd(0x80); // Clock div + ssd_cmd(0xA8); ssd_cmd(0x3F); // Multiplex 64 + ssd_cmd(0xD3); ssd_cmd(0x00); // Display offset + ssd_cmd(0x40); // Start line 0 + ssd_cmd(0x8D); ssd_cmd(0x14); // Charge pump on + ssd_cmd(0x20); ssd_cmd(0x00); // Horizontal addressing + ssd_cmd(0xA1); // Segment remap + ssd_cmd(0xC8); // COM scan direction + ssd_cmd(0xDA); ssd_cmd(0x12); // COM pins + ssd_cmd(0x81); ssd_cmd(0xCF); // Contrast + ssd_cmd(0xD9); ssd_cmd(0xF1); // Pre-charge + ssd_cmd(0xDB); ssd_cmd(0x40); // VCOMH deselect + ssd_cmd(0xA4); // Display from RAM + ssd_cmd(0xA6); // Normal display + ssd_cmd(0xAF); // Display on + + fb_clear(); + fb_string(0, 0, "MIDI 2.0 Host"); + ssd_flush(); +} + +void display_checklist_update(const checklist_t* ck) { + struct { const char* label; bool ok; } items[] = { + { "PWR", ck->pwr_on }, + { "TinyUSB", ck->tusb_init }, + { "USB bus", ck->bus_active }, + { "Device", ck->device_connected }, + { "Descript", ck->descriptor_parsed }, + { "Alt1 UMP", ck->alt_setting_ok }, + { "Mount", ck->mounted }, + { "RX UMP", ck->receiving }, + }; + + // Clear checklist area (lines 1-4, two columns) + for (int p = 1; p <= 4; p++) { + memset(&fb[p * SCR_W], 0, SCR_W); + } + + for (int i = 0; i < 8; i++) { + int col = (i < 4) ? 0 : 64; + int row = (i < 4) ? i : i - 4; + int y = 9 + row * 8; + char line[12]; + snprintf(line, sizeof(line), "%s %s", items[i].ok ? "OK" : "..", items[i].label); + fb_string(col, y, line); + } + + ssd_flush(); +} + +void display_log(const char* text, uint16_t color) { + (void)color; // SSD1306 is monochrome + + if (log_count >= LOG_LINES) { + for (int i = 0; i < LOG_LINES - 1; i++) { + strncpy(log_lines[i], log_lines[i + 1], CHARS_PER_LINE); + } + log_count = LOG_LINES - 1; + } + + strncpy(log_lines[log_count], text, CHARS_PER_LINE); + log_lines[log_count][CHARS_PER_LINE] = '\0'; + log_count++; + + // Draw log area (pages 5-6, y=40-55) + memset(&fb[5 * SCR_W], 0, SCR_W); + memset(&fb[6 * SCR_W], 0, SCR_W); + + for (int i = 0; i < log_count && i < LOG_LINES; i++) { + fb_string(0, 41 + i * 8, log_lines[i]); + } + + ssd_flush(); +} + +void display_status(const char* text) { + strncpy(status_text, text, CHARS_PER_LINE); + status_text[CHARS_PER_LINE] = '\0'; + + // Status on last page (y=56) + memset(&fb[7 * SCR_W], 0, SCR_W); + fb_string(0, 56, status_text); + ssd_flush(); +} diff --git a/examples/host/midi2_host/src/display.h b/examples/host/midi2_host/src/display.h new file mode 100644 index 000000000..a8b0a4b5e --- /dev/null +++ b/examples/host/midi2_host/src/display.h @@ -0,0 +1,54 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef DISPLAY_H_ +#define DISPLAY_H_ + +#include +#include + +typedef struct { + bool pwr_on; + bool tusb_init; + bool bus_active; + bool device_connected; + bool descriptor_parsed; + bool alt_setting_ok; + bool mounted; + bool receiving; +} checklist_t; + +// Initialize SSD1306 display (I2C on GP4/GP5) +void display_init(void); + +// Redraw the checklist area (top portion) +void display_checklist_update(const checklist_t* ck); + +// Add a line to the scrolling log area +void display_log(const char* text, uint16_t color); + +// Update the status bar (bottom line) +void display_status(const char* text); + +#endif diff --git a/examples/host/midi2_host/src/font5x7.h b/examples/host/midi2_host/src/font5x7.h new file mode 100644 index 000000000..e515faac1 --- /dev/null +++ b/examples/host/midi2_host/src/font5x7.h @@ -0,0 +1,107 @@ +// Minimal 5x7 font, ASCII 32-126. Public domain. +// 5 bytes per character (5 columns, 7 rows, LSB=top row) + +#ifndef FONT5X7_H_ +#define FONT5X7_H_ + +#include + +static const uint8_t font5x7[] = { + 0x00,0x00,0x00,0x00,0x00, // 32 (space) + 0x00,0x00,0x5F,0x00,0x00, // 33 ! + 0x00,0x07,0x00,0x07,0x00, // 34 " + 0x14,0x7F,0x14,0x7F,0x14, // 35 # + 0x24,0x2A,0x7F,0x2A,0x12, // 36 $ + 0x23,0x13,0x08,0x64,0x62, // 37 % + 0x36,0x49,0x55,0x22,0x50, // 38 & + 0x00,0x05,0x03,0x00,0x00, // 39 ' + 0x00,0x1C,0x22,0x41,0x00, // 40 ( + 0x00,0x41,0x22,0x1C,0x00, // 41 ) + 0x08,0x2A,0x1C,0x2A,0x08, // 42 * + 0x08,0x08,0x3E,0x08,0x08, // 43 + + 0x00,0x50,0x30,0x00,0x00, // 44 , + 0x08,0x08,0x08,0x08,0x08, // 45 - + 0x00,0x60,0x60,0x00,0x00, // 46 . + 0x20,0x10,0x08,0x04,0x02, // 47 / + 0x3E,0x51,0x49,0x45,0x3E, // 48 0 + 0x00,0x42,0x7F,0x40,0x00, // 49 1 + 0x42,0x61,0x51,0x49,0x46, // 50 2 + 0x21,0x41,0x45,0x4B,0x31, // 51 3 + 0x18,0x14,0x12,0x7F,0x10, // 52 4 + 0x27,0x45,0x45,0x45,0x39, // 53 5 + 0x3C,0x4A,0x49,0x49,0x30, // 54 6 + 0x01,0x71,0x09,0x05,0x03, // 55 7 + 0x36,0x49,0x49,0x49,0x36, // 56 8 + 0x06,0x49,0x49,0x29,0x1E, // 57 9 + 0x00,0x36,0x36,0x00,0x00, // 58 : + 0x00,0x56,0x36,0x00,0x00, // 59 ; + 0x00,0x08,0x14,0x22,0x41, // 60 < + 0x14,0x14,0x14,0x14,0x14, // 61 = + 0x41,0x22,0x14,0x08,0x00, // 62 > + 0x02,0x01,0x51,0x09,0x06, // 63 ? + 0x32,0x49,0x79,0x41,0x3E, // 64 @ + 0x7E,0x11,0x11,0x11,0x7E, // 65 A + 0x7F,0x49,0x49,0x49,0x36, // 66 B + 0x3E,0x41,0x41,0x41,0x22, // 67 C + 0x7F,0x41,0x41,0x22,0x1C, // 68 D + 0x7F,0x49,0x49,0x49,0x41, // 69 E + 0x7F,0x09,0x09,0x01,0x01, // 70 F + 0x3E,0x41,0x41,0x51,0x32, // 71 G + 0x7F,0x08,0x08,0x08,0x7F, // 72 H + 0x00,0x41,0x7F,0x41,0x00, // 73 I + 0x20,0x40,0x41,0x3F,0x01, // 74 J + 0x7F,0x08,0x14,0x22,0x41, // 75 K + 0x7F,0x40,0x40,0x40,0x40, // 76 L + 0x7F,0x02,0x04,0x02,0x7F, // 77 M + 0x7F,0x04,0x08,0x10,0x7F, // 78 N + 0x3E,0x41,0x41,0x41,0x3E, // 79 O + 0x7F,0x09,0x09,0x09,0x06, // 80 P + 0x3E,0x41,0x51,0x21,0x5E, // 81 Q + 0x7F,0x09,0x19,0x29,0x46, // 82 R + 0x46,0x49,0x49,0x49,0x31, // 83 S + 0x01,0x01,0x7F,0x01,0x01, // 84 T + 0x3F,0x40,0x40,0x40,0x3F, // 85 U + 0x1F,0x20,0x40,0x20,0x1F, // 86 V + 0x7F,0x20,0x18,0x20,0x7F, // 87 W + 0x63,0x14,0x08,0x14,0x63, // 88 X + 0x03,0x04,0x78,0x04,0x03, // 89 Y + 0x61,0x51,0x49,0x45,0x43, // 90 Z + 0x00,0x00,0x7F,0x41,0x41, // 91 [ + 0x02,0x04,0x08,0x10,0x20, // 92 backslash + 0x41,0x41,0x7F,0x00,0x00, // 93 ] + 0x04,0x02,0x01,0x02,0x04, // 94 ^ + 0x40,0x40,0x40,0x40,0x40, // 95 _ + 0x00,0x01,0x02,0x04,0x00, // 96 ` + 0x20,0x54,0x54,0x54,0x78, // 97 a + 0x7F,0x48,0x44,0x44,0x38, // 98 b + 0x38,0x44,0x44,0x44,0x20, // 99 c + 0x38,0x44,0x44,0x48,0x7F, // 100 d + 0x38,0x54,0x54,0x54,0x18, // 101 e + 0x08,0x7E,0x09,0x01,0x02, // 102 f + 0x08,0x14,0x54,0x54,0x3C, // 103 g + 0x7F,0x08,0x04,0x04,0x78, // 104 h + 0x00,0x44,0x7D,0x40,0x00, // 105 i + 0x20,0x40,0x44,0x3D,0x00, // 106 j + 0x00,0x7F,0x10,0x28,0x44, // 107 k + 0x00,0x41,0x7F,0x40,0x00, // 108 l + 0x7C,0x04,0x18,0x04,0x78, // 109 m + 0x7C,0x08,0x04,0x04,0x78, // 110 n + 0x38,0x44,0x44,0x44,0x38, // 111 o + 0x7C,0x14,0x14,0x14,0x08, // 112 p + 0x08,0x14,0x14,0x18,0x7C, // 113 q + 0x7C,0x08,0x04,0x04,0x08, // 114 r + 0x48,0x54,0x54,0x54,0x20, // 115 s + 0x04,0x3F,0x44,0x40,0x20, // 116 t + 0x3C,0x40,0x40,0x20,0x7C, // 117 u + 0x1C,0x20,0x40,0x20,0x1C, // 118 v + 0x3C,0x40,0x30,0x40,0x3C, // 119 w + 0x44,0x28,0x10,0x28,0x44, // 120 x + 0x0C,0x50,0x50,0x50,0x3C, // 121 y + 0x44,0x64,0x54,0x4C,0x44, // 122 z + 0x00,0x08,0x36,0x41,0x00, // 123 { + 0x00,0x00,0x7F,0x00,0x00, // 124 | + 0x00,0x41,0x36,0x08,0x00, // 125 } + 0x10,0x08,0x08,0x10,0x08, // 126 ~ +}; + +#endif diff --git a/examples/host/midi2_host/src/main.c b/examples/host/midi2_host/src/main.c new file mode 100644 index 000000000..e23fbfc87 --- /dev/null +++ b/examples/host/midi2_host/src/main.c @@ -0,0 +1,326 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// MIDI 2.0 Host Receiver Example +// +// Receives UMP from a MIDI 2.0 Device via PIO-USB Host. +// SSD1306 OLED (I2C, 128x64) shows boot checklist then received UMP messages. + +#include +#include +#include "bsp/board_api.h" +#include "tusb.h" +#include "class/midi/midi2_host.h" +#include "class/midi/midi.h" +#include "display.h" + +//--------------------------------------------------------------------+ +// Checklist state +//--------------------------------------------------------------------+ + +static checklist_t ck = { 0 }; +static uint32_t note_count = 0; +static uint8_t midi2_idx = 0xFF; // invalid until mount + +//--------------------------------------------------------------------+ +// Note name helper +//--------------------------------------------------------------------+ + +static const char* NOTE_NAMES[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" +}; + +static void note_name(uint8_t pitch, char* buf, size_t len) { + int octave = (pitch / 12) - 1; + snprintf(buf, len, "%s%d", NOTE_NAMES[pitch % 12], octave); +} + +//--------------------------------------------------------------------+ +// UMP Message Type names +//--------------------------------------------------------------------+ + +static const char* mt_name(uint8_t mt) { + switch (mt) { + case 0x0: return "Utility"; + case 0x1: return "System"; + case 0x2: return "M1 CVM"; + case 0x3: return "SysEx7"; + case 0x4: return "M2 CVM"; + case 0x5: return "SysEx8"; + case 0xD: return "Flex"; + case 0xF: return "Stream"; + default: return "?"; + } +} + +//--------------------------------------------------------------------+ +// UMP decoder - extract and display MIDI 2.0 messages +//--------------------------------------------------------------------+ + +static void decode_ump(const uint32_t* words, uint8_t word_count) { + uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F); + char line[64]; + + if (mt == 0x4 && word_count >= 2) { + // MIDI 2.0 Channel Voice Message + uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); + uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F); + + switch (status) { + case 0x9: { // Note On + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + uint16_t vel = (uint16_t)((words[1] >> 16) & 0xFFFF); + char nn[6]; + note_name(pitch, nn, sizeof(nn)); + snprintf(line, sizeof(line), "NoteOn %s ch%u vel=0x%04X", nn, channel, vel); + display_log(line, 0x07E0); // green + note_count++; + break; + } + case 0x8: { // Note Off + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + char nn[6]; + note_name(pitch, nn, sizeof(nn)); + snprintf(line, sizeof(line), "NoteOff %s ch%u", nn, channel); + display_log(line, 0x8410); // grey + break; + } + case 0xB: { // CC + uint8_t idx = (uint8_t)((words[0] >> 8) & 0x7F); + snprintf(line, sizeof(line), "CC%-3u = 0x%08lX", idx, (unsigned long)words[1]); + display_log(line, 0x001F); // blue + break; + } + case 0xC: { // Program Change + uint8_t prog = (uint8_t)((words[1] >> 24) & 0x7F); + snprintf(line, sizeof(line), "ProgChg %u", prog); + display_log(line, 0xFFE0); // yellow + break; + } + case 0xE: { // Pitch Bend + snprintf(line, sizeof(line), "PBend = 0x%08lX", (unsigned long)words[1]); + display_log(line, 0xF81F); // magenta + break; + } + case 0xD: { // Channel Pressure + snprintf(line, sizeof(line), "CPress = 0x%08lX", (unsigned long)words[1]); + display_log(line, 0xFC10); // orange + break; + } + case 0xA: { // Poly Pressure + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + char nn[6]; + note_name(pitch, nn, sizeof(nn)); + snprintf(line, sizeof(line), "PolyP %s = 0x%08lX", nn, (unsigned long)words[1]); + display_log(line, 0xFC10); + break; + } + case 0xF: { // Per-Note Management + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + uint8_t flags = (uint8_t)(words[0] & 0xFF); + snprintf(line, sizeof(line), "PN-Mgmt note=%u flags=0x%02X", pitch, flags); + display_log(line, 0x07FF); // cyan + break; + } + default: { + snprintf(line, sizeof(line), "M2CVM status=0x%X", status); + display_log(line, 0xFFFF); + break; + } + } + } else if (mt == 0x0) { + // Utility (JR Timestamp, NOOP) + uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); + if (status == 0x2) { + uint16_t ts = words[0] & 0xFFFF; + snprintf(line, sizeof(line), "JR-TS = 0x%04X", ts); + display_log(line, 0x8410); + } + } else { + snprintf(line, sizeof(line), "MT=0x%X (%s) w0=0x%08lX", + mt, mt_name(mt), (unsigned long)words[0]); + display_log(line, 0xFFFF); + } +} + +//--------------------------------------------------------------------+ +// MIDI 2.0 Host Callbacks +//--------------------------------------------------------------------+ + +void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) { + (void)idx; + ck.descriptor_parsed = true; + char line[48]; + + snprintf(line, sizeof(line), "bcdMSC=0x%02X%02X proto=%s", + d->bcdMSC_hi, d->bcdMSC_lo, + d->protocol_version ? "MIDI2" : "MIDI1"); + display_log(line, 0x07E0); + + snprintf(line, sizeof(line), "Cables: RX=%u TX=%u", + d->rx_cable_count, d->tx_cable_count); + display_log(line, 0x07E0); + + display_checklist_update(&ck); +} + +void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) { + midi2_idx = idx; + ck.alt_setting_ok = true; + ck.mounted = true; + + char line[48]; + snprintf(line, sizeof(line), "Mounted addr=%u alt=%u", + m->daddr, m->alt_setting_active); + display_log(line, 0x07E0); + + if (m->protocol_version) { + display_log("MIDI 2.0 ready", 0x07E0); + } else { + display_log("MIDI 1.0 only", 0xFFE0); + } + + display_checklist_update(&ck); +} + +void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void)xferred_bytes; + if (!ck.receiving) { + ck.receiving = true; + display_checklist_update(&ck); + } + + uint32_t words[16]; + while (1) { + uint32_t n = tuh_midi2_ump_read(idx, words, 16); + if (n == 0) break; + + // Decode complete UMP messages + uint32_t i = 0; + while (i < n) { + uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F); + uint8_t wc = midi2_ump_word_count(mt); + if (i + wc > n) break; + decode_ump(&words[i], wc); + i += wc; + } + } +} + +void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void)idx; (void)xferred_bytes; +} + +void tuh_midi2_umount_cb(uint8_t idx) { + (void)idx; + midi2_idx = 0xFF; + ck.device_connected = false; + ck.descriptor_parsed = false; + ck.alt_setting_ok = false; + ck.mounted = false; + ck.receiving = false; + note_count = 0; + + display_log("Device disconnected", 0xF800); + display_checklist_update(&ck); +} + +//--------------------------------------------------------------------+ +// Main +//--------------------------------------------------------------------+ + +int main(void) { + board_init(); + + display_init(); + + ck.pwr_on = true; + display_checklist_update(&ck); + + // Init TinyUSB Host (BSP handles PIO-USB configuration via tuh_configure) + tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUSB_SPEED_FULL, + }; + tusb_init(BOARD_TUH_RHPORT, &host_init); + + ck.tusb_init = true; + ck.bus_active = true; + display_checklist_update(&ck); + + char dbg[40]; + snprintf(dbg, sizeof(dbg), "RHPORT=%u PIO_USB=%u", + BOARD_TUH_RHPORT, CFG_TUH_RPI_PIO_USB); + display_log(dbg, 0xFFE0); // yellow + display_status("Waiting for device..."); + + uint32_t last_status_ms = 0; + static uint32_t loop_count = 0; + + while (1) { + tuh_task(); + loop_count++; + + // Update status every 2 seconds + uint32_t now = tusb_time_millis_api(); + if (now - last_status_ms > 2000) { + last_status_ms = now; + char line[22]; + if (ck.receiving) { + snprintf(line, sizeof(line), "Notes:%lu", (unsigned long)note_count); + } else if (ck.mounted) { + snprintf(line, sizeof(line), "Mounted OK!"); + } else if (ck.device_connected) { + snprintf(line, sizeof(line), "Dev found, mounting.."); + } else { + snprintf(line, sizeof(line), "Wait.. t=%lu", (unsigned long)(now/1000)); + } + display_status(line); + } + } + + return 0; +} + +// Generic USB device mount/unmount (any class) +void tuh_mount_cb(uint8_t daddr) { + char line[32]; + uint16_t vid, pid; + tuh_vid_pid_get(daddr, &vid, &pid); + snprintf(line, sizeof(line), "USB %04X:%04X a%u", vid, pid, daddr); + display_log(line, 0x07E0); + ck.device_connected = true; + display_checklist_update(&ck); +} + +void tuh_umount_cb(uint8_t daddr) { + (void)daddr; + display_log("USB disconnected", 0xF800); + ck.device_connected = false; + ck.descriptor_parsed = false; + ck.alt_setting_ok = false; + ck.mounted = false; + ck.receiving = false; + display_checklist_update(&ck); +} diff --git a/examples/host/midi2_host/src/tusb_config.h b/examples/host/midi2_host/src/tusb_config.h new file mode 100644 index 000000000..650df97e1 --- /dev/null +++ b/examples/host/midi2_host/src/tusb_config.h @@ -0,0 +1,91 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------------------------- +// Common Configuration +//-------------------------------------------------------------------- + +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +#ifndef CFG_TUH_MEM_SECTION +#define CFG_TUH_MEM_SECTION +#endif + +#ifndef CFG_TUH_MEM_ALIGN +#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// Host Configuration +// Waveshare RP2350-USB-A: PIO-USB on GP12/GP13 (USB-A Host port) +//-------------------------------------------------------------------- + +#define CFG_TUH_ENABLED 1 + +// PIO-USB Host on rhport 1 (USB-A connector on GP12/GP13) +#define CFG_TUH_RPI_PIO_USB 1 +#define BOARD_TUH_RHPORT 1 + +#ifndef BOARD_TUH_MAX_SPEED +#define BOARD_TUH_MAX_SPEED OPT_MODE_FULL_SPEED +#endif + +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED + +//-------------------------------------------------------------------- +// Driver Configuration +//-------------------------------------------------------------------- + +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 0 +#define CFG_TUH_DEVICE_MAX 1 + +// MIDI 2.0 Host +#define CFG_TUH_MIDI2 1 +#define CFG_TUH_MIDI2_RX_BUFSIZE 512 +#define CFG_TUH_MIDI2_TX_BUFSIZE 512 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake b/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake new file mode 100644 index 000000000..e888c7fb3 --- /dev/null +++ b/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake @@ -0,0 +1,2 @@ +set(PICO_PLATFORM rp2350-arm-s) +set(PICO_BOARD waveshare_rp2350_usb_a) diff --git a/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h b/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h new file mode 100644 index 000000000..0af8a695f --- /dev/null +++ b/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h @@ -0,0 +1,49 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/* metadata: + name: Waveshare RP2350-USB-A + url: https://www.waveshare.com/wiki/RP2350-USB-A +*/ + +#ifndef TUSB_BOARD_H +#define TUSB_BOARD_H + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// PIO_USB +//--------------------------------------------------------------------+ +// Waveshare RP2350-USB-A: PIO-USB on GP12 (D+) / GP13 (D-) +// PICO_DEFAULT_PIO_USB_DP_PIN already defined in SDK board header + +#ifdef __cplusplus + } +#endif + +#endif -- cgit v1.3.1 From fa9edeff9c00ee1fc4ee7ab9938b1a5955a6281a Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Wed, 25 Mar 2026 07:05:56 -0300 Subject: fix: address PR review feedback for MIDI 2.0 drivers Host driver (midi2_host.c): - midih2_open() now returns actual parsed length instead of max_len, preventing composite device interface conflicts - Parsers (alt0/alt1) refactored to return const uint8_t* end pointer following midi_host.c switch/case pattern - Alt 1 CS Endpoint now parses MIDI 2.0 layout (bNumGrpTrmBlk at offset 3 with MIDI_CS_ENDPOINT_GENERAL_2_0 subtype check) instead of reusing MIDI 1.0 struct (bNumEmbMIDIJack) - midih2_set_config() now issues SET_INTERFACE control request via tuh_interface_set() before completing configuration. Falls back to alt 0 if SET_INTERFACE fails - Extracted midih2_set_config_complete() and midih2_set_interface_cb() for async SET_INTERFACE handling Device driver (midi2_device.c): - midi2d_open() skip loop now checks bInterfaceNumber, stopping at interfaces that belong to other functions in composite devices - SET_INTERFACE handler now rejects alt > 1 (returns false/stall) - Named constants for GTB descriptor types and MIDI protocol values Descriptor macros (usbd.h): - TUD_MIDI2_DESC_ALT1_HEAD: iInterface set to 0 (consistent with Alt 0), wTotalLength now uses TUD_MIDI2_DESC_ALT1_CS_LEN to cover all Alt 1 class-specific descriptors - TUD_MIDI2_DESC_ALT1_EP: now accepts GTB ID list via variadic args, emitting complete CS endpoint descriptor Host example: - CMakeLists.txt restricted to rp2040 family (display.c requires Pico SDK headers) - display.c: null terminator after strncpy in log scroll Documentation: - class_drivers.rst updated to reflect SET_INTERFACE behavior and auto-select with fallback Addresses: Codex P1 (#1, #2, #3), Copilot (#4-#9) --- docs/reference/class_drivers.rst | 8 +- examples/host/midi2_host/CMakeLists.txt | 3 +- examples/host/midi2_host/src/display.c | 1 + src/class/midi/midi2_device.c | 14 ++- src/class/midi/midi2_host.c | 213 ++++++++++++++++++++------------ src/device/usbd.h | 24 ++-- 6 files changed, 166 insertions(+), 97 deletions(-) (limited to 'examples') diff --git a/docs/reference/class_drivers.rst b/docs/reference/class_drivers.rst index 9ed332acb..3ac0d8d4e 100644 --- a/docs/reference/class_drivers.rst +++ b/docs/reference/class_drivers.rst @@ -87,7 +87,7 @@ The MIDI 2.0 Host driver enables TinyUSB to enumerate and communicate with USB M **Key Features:** - **Reactive Architecture**: Auto-detects Alt Setting 1 (MIDI 2.0) capability during enumeration -- **Auto-Selection**: Automatically selects the highest available protocol (MIDI 2.0 preferred) +- **Auto-Selection**: Automatically selects the highest available protocol and issues SET_INTERFACE to activate Alt Setting 1 when MIDI 2.0 is detected - **Transparent Stream Messages**: All data (UMP packets + Stream Messages) flow through callbacks - **Memory Safe**: No dynamic allocation, fixed-size instances per device @@ -271,9 +271,9 @@ Architecture The MIDI 2.0 Host driver uses a **reactive, callback-driven architecture** that mirrors the proven patterns in TinyUSB's existing device drivers (CDC, HID, etc.): - **Auto-Detection**: Host automatically detects Alt Setting 1 capability -- **Auto-Selection**: Selects highest protocol available (MIDI 2.0 preferred) -- **Application Control**: App makes protocol behavior decisions via callbacks -- **Transparent I/O**: Stream Messages and UMP packets flow transparently +- **Auto-Selection**: Selects highest protocol available and issues SET_INTERFACE +- **Transparent I/O**: Stream Messages and UMP packets flow through callbacks +- **Callback-Driven**: App receives events via callbacks (descriptor, mount, rx, tx, unmount) Differences from MIDI 1.0 Host ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/examples/host/midi2_host/CMakeLists.txt b/examples/host/midi2_host/CMakeLists.txt index 221de7adf..cd70d122a 100644 --- a/examples/host/midi2_host/CMakeLists.txt +++ b/examples/host/midi2_host/CMakeLists.txt @@ -6,7 +6,8 @@ project(midi2_host C CXX ASM) family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) -if(FAMILY STREQUAL "espressif") +# This example requires PIO-USB and Pico SDK (I2C, SSD1306 display) +if(NOT FAMILY STREQUAL "rp2040") return() endif() diff --git a/examples/host/midi2_host/src/display.c b/examples/host/midi2_host/src/display.c index 6d81bcd5c..4745c2961 100644 --- a/examples/host/midi2_host/src/display.c +++ b/examples/host/midi2_host/src/display.c @@ -190,6 +190,7 @@ void display_log(const char* text, uint16_t color) { if (log_count >= LOG_LINES) { for (int i = 0; i < LOG_LINES - 1; i++) { strncpy(log_lines[i], log_lines[i + 1], CHARS_PER_LINE); + log_lines[i][CHARS_PER_LINE] = '\0'; } log_count = LOG_LINES - 1; } diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index 9363aac11..aecbda4c5 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -505,12 +505,19 @@ uint16_t midi2d_open(uint8_t rhport, const tusb_desc_interface_t* desc_itf, uint } // Skip remaining descriptors (alt setting 1, CS endpoints, GTB) + // Stop at any interface descriptor that is not our MIDI Streaming alt setting while (tu_desc_in_bounds(p_desc, desc_end)) { uint8_t dtype = tu_desc_type(p_desc); - if (dtype != TUSB_DESC_CS_INTERFACE && dtype != TUSB_DESC_CS_ENDPOINT && - dtype != TUSB_DESC_INTERFACE && dtype != TUSB_DESC_ENDPOINT) { + + if (dtype == TUSB_DESC_INTERFACE) { + const tusb_desc_interface_t* next_itf = (const tusb_desc_interface_t*) p_desc; + // Continue only if this is an alternate setting of our own interface + if (next_itf->bInterfaceNumber != desc_midi->bInterfaceNumber) break; + } else if (dtype != TUSB_DESC_CS_INTERFACE && dtype != TUSB_DESC_CS_ENDPOINT && + dtype != TUSB_DESC_ENDPOINT) { break; } + p_desc = tu_desc_next(p_desc); } @@ -528,6 +535,9 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re uint8_t itf_num = tu_u16_low(request->wIndex); uint8_t alt = tu_u16_low(request->wValue); + // Only Alt Setting 0 (MIDI 1.0) and 1 (UMP) are valid + if (alt > 1) return false; + uint8_t idx = find_midi2_itf_by_num(itf_num); if (idx >= CFG_TUD_MIDI2) return false; diff --git a/src/class/midi/midi2_host.c b/src/class/midi/midi2_host.c index beb441b56..5b9f98f8b 100644 --- a/src/class/midi/midi2_host.c +++ b/src/class/midi/midi2_host.c @@ -125,9 +125,10 @@ static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) { // Descriptor parsing //--------------------------------------------------------------------+ -static void midih2_parse_descriptors_alt0(midih2_interface_t *p_midi, +// Parse Alt Setting 0 (MIDI 1.0) descriptors. Returns pointer past last consumed descriptor. +static const uint8_t* midih2_parse_descriptors_alt0(midih2_interface_t *p_midi, const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) { - TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass,); + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL); p_midi->bInterfaceNumber = desc_itf->bInterfaceNumber; @@ -136,93 +137,113 @@ static void midih2_parse_descriptors_alt0(midih2_interface_t *p_midi, uint8_t rx_cable_count = 0; uint8_t tx_cable_count = 0; + bool found_new_interface = false; - while (tu_desc_in_bounds(p_desc, desc_end)) { - if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) { - break; - } - - if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { - const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; - - // Open endpoint and stream - TU_ASSERT(tuh_edpt_open(p_midi->daddr, p_ep),); - if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) { - tu_edpt_stream_open(&p_midi->ep_stream.rx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep)); - tu_edpt_stream_clear(&p_midi->ep_stream.rx); - } else { - tu_edpt_stream_open(&p_midi->ep_stream.tx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep)); - tu_edpt_stream_clear(&p_midi->ep_stream.tx); - } - - p_desc = tu_desc_next(p_desc); + while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) { + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; - if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) { - const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; - if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { - tx_cable_count = p_csep->bNumEmbMIDIJack; + TU_ASSERT(tuh_edpt_open(p_midi->daddr, p_ep), NULL); + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) { + tu_edpt_stream_open(&p_midi->ep_stream.rx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep)); + tu_edpt_stream_clear(&p_midi->ep_stream.rx); } else { - rx_cable_count = p_csep->bNumEmbMIDIJack; + tu_edpt_stream_open(&p_midi->ep_stream.tx, p_midi->daddr, p_ep, tu_edpt_packet_size(p_ep)); + tu_edpt_stream_clear(&p_midi->ep_stream.tx); + } + + p_desc = tu_desc_next(p_desc); + if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) { + const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + tx_cable_count = p_csep->bNumEmbMIDIJack; + } else { + rx_cable_count = p_csep->bNumEmbMIDIJack; + } } + break; } + + default: + break; } - p_desc = tu_desc_next(p_desc); + if (!found_new_interface) { + p_desc = tu_desc_next(p_desc); + } } p_midi->rx_cable_count_alt0 = rx_cable_count; p_midi->tx_cable_count_alt0 = tx_cable_count; + return p_desc; } -static void midih2_parse_descriptors_alt1(midih2_interface_t *p_midi, +// Parse Alt Setting 1 (MIDI 2.0 UMP) descriptors. Returns pointer past last consumed descriptor. +static const uint8_t* midih2_parse_descriptors_alt1(midih2_interface_t *p_midi, const tusb_desc_interface_t *desc_itf, const uint8_t *desc_end) { - TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass,); - TU_VERIFY(desc_itf->bAlternateSetting == 1,); + TU_VERIFY(AUDIO_SUBCLASS_MIDI_STREAMING == desc_itf->bInterfaceSubClass, NULL); + TU_VERIFY(desc_itf->bAlternateSetting == 1, NULL); const uint8_t *p_desc = (const uint8_t *) desc_itf; p_desc = tu_desc_next(p_desc); uint8_t rx_cable_count = 0; uint8_t tx_cable_count = 0; + bool found_new_interface = false; - while (tu_desc_in_bounds(p_desc, desc_end)) { - if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) { - break; - } + while (tu_desc_in_bounds(p_desc, desc_end) && !found_new_interface) { + switch (tu_desc_type(p_desc)) { + case TUSB_DESC_INTERFACE: + found_new_interface = true; + break; - if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE) { - if (tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_HEADER) { - const uint8_t *bcd_ptr = p_desc + 3; - p_midi->bcdMSC_lo = bcd_ptr[0]; - p_midi->bcdMSC_hi = bcd_ptr[1]; + case TUSB_DESC_CS_INTERFACE: + if (tu_desc_subtype(p_desc) == MIDI_CS_INTERFACE_HEADER) { + // bcdMSC at offset 3-4 in CS Interface Header + const uint8_t *bcd_ptr = p_desc + 3; + p_midi->bcdMSC_lo = bcd_ptr[0]; + p_midi->bcdMSC_hi = bcd_ptr[1]; + if (p_midi->bcdMSC_hi == 0x02) { // bcdMSC 0x0200 = USB-MIDI 2.0 + p_midi->protocol_version = 1; + } + } + break; - if (p_midi->bcdMSC_hi == 0x02) { - p_midi->protocol_version = 1; + case TUSB_DESC_ENDPOINT: { + const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + p_desc = tu_desc_next(p_desc); + + if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) { + // MIDI 2.0 CS Endpoint General 2.0: bNumGrpTrmBlk at offset 3 + if (p_desc[0] >= 4 && p_desc[2] == MIDI_CS_ENDPOINT_GENERAL_2_0) { + uint8_t num_grp_trm_blk = p_desc[3]; + if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { + tx_cable_count = num_grp_trm_blk; + } else { + rx_cable_count = num_grp_trm_blk; + } + } } + break; } + + default: + break; } - if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) { - const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *) p_desc; + if (!found_new_interface) { p_desc = tu_desc_next(p_desc); - - if (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) == TUSB_DESC_CS_ENDPOINT) { - const midi_desc_cs_endpoint_t *p_csep = (const midi_desc_cs_endpoint_t *) p_desc; - - if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_OUT) { - tx_cable_count = p_csep->bNumEmbMIDIJack; - } else { - rx_cable_count = p_csep->bNumEmbMIDIJack; - } - } } - - p_desc = tu_desc_next(p_desc); } p_midi->rx_cable_count_alt1 = rx_cable_count; p_midi->tx_cable_count_alt1 = tx_cable_count; + return p_desc; } //--------------------------------------------------------------------+ @@ -327,33 +348,20 @@ uint16_t midih2_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface desc_itf->bInterfaceNumber, desc_itf->bAlternateSetting, dev_addr); // Dispatch to appropriate parser based on Alt Setting + const uint8_t *p_end = NULL; if (desc_itf->bAlternateSetting == 0) { - midih2_parse_descriptors_alt0(p_midi, desc_itf, desc_end); + p_end = midih2_parse_descriptors_alt0(p_midi, desc_itf, desc_end); } else if (desc_itf->bAlternateSetting == 1) { - midih2_parse_descriptors_alt1(p_midi, desc_itf, desc_end); + p_end = midih2_parse_descriptors_alt1(p_midi, desc_itf, desc_end); } - return max_len; + // Return number of bytes consumed (following midi_host.c pattern) + uint16_t const parsed_len = (p_end != NULL) ? (uint16_t)(p_end - desc_start) : 0; + return parsed_len; } -bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) { - uint8_t idx = 0; - for (idx = 0; idx < CFG_TUH_MIDI2; idx++) { - if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) { - break; - } - } - - if (idx >= CFG_TUH_MIDI2) { - // Not our interface (e.g. Audio Control) - pass through to next - usbh_driver_set_config_complete(dev_addr, itf_num); - return true; - } - - midih2_interface_t *p_midi = &_midi2_host[idx]; - - // Auto-select alt setting - midih2_auto_select_alt_setting(p_midi); +static void midih2_set_config_complete(midih2_interface_t *p_midi, uint8_t idx) { + uint8_t dev_addr = p_midi->daddr; // Invoke descriptor_cb tuh_midi2_descriptor_cb_t desc_cb = { @@ -374,7 +382,7 @@ bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) { // Invoke mount_cb tuh_midi2_mount_cb_t mount_cb = { - .daddr = p_midi->daddr, + .daddr = dev_addr, .bInterfaceNumber = p_midi->bInterfaceNumber, .protocol_version = p_midi->protocol_version, .alt_setting_active = p_midi->alt_setting_current, @@ -387,7 +395,56 @@ bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) { tu_edpt_stream_read_xfer(&p_midi->ep_stream.rx); // Signal USBH that configuration is complete - usbh_driver_set_config_complete(dev_addr, itf_num); + usbh_driver_set_config_complete(dev_addr, p_midi->bInterfaceNumber); +} + +static void midih2_set_interface_cb(tuh_xfer_t *xfer) { + uint8_t const dev_addr = xfer->daddr; + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + + // Find our interface + for (uint8_t idx = 0; idx < CFG_TUH_MIDI2; idx++) { + if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) { + if (xfer->result == XFER_RESULT_SUCCESS) { + midih2_set_config_complete(&_midi2_host[idx], idx); + } else { + // SET_INTERFACE failed, fall back to alt 0 + TU_LOG_DRV("MIDI2 SET_INTERFACE failed, falling back to alt 0\r\n"); + _midi2_host[idx].alt_setting_current = 0; + midih2_set_config_complete(&_midi2_host[idx], idx); + } + return; + } + } +} + +bool midih2_set_config(uint8_t dev_addr, uint8_t itf_num) { + uint8_t idx = 0; + for (idx = 0; idx < CFG_TUH_MIDI2; idx++) { + if (_midi2_host[idx].daddr == dev_addr && _midi2_host[idx].bInterfaceNumber == itf_num) { + break; + } + } + + if (idx >= CFG_TUH_MIDI2) { + // Not our interface (e.g. Audio Control) - pass through to next + usbh_driver_set_config_complete(dev_addr, itf_num); + return true; + } + + midih2_interface_t *p_midi = &_midi2_host[idx]; + + // Auto-select alt setting + midih2_auto_select_alt_setting(p_midi); + + // If MIDI 2.0 detected, issue SET_INTERFACE to activate Alt Setting 1 + if (p_midi->alt_setting_current == 1) { + TU_LOG_DRV("MIDI2 requesting SET_INTERFACE alt 1 for itf %u\r\n", itf_num); + TU_ASSERT(tuh_interface_set(dev_addr, itf_num, 1, midih2_set_interface_cb, 0)); + } else { + // MIDI 1.0 only, complete immediately + midih2_set_config_complete(p_midi, idx); + } return true; } diff --git a/src/device/usbd.h b/src/device/usbd.h index af37eff56..a9f4c5f08 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -429,18 +429,20 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ //--------------------------------------------------------------------+ // Alt Setting 1: MS Interface + MS Header (bcdMSC=0x0200) +// wTotalLength covers MS Header + all CS Endpoint descriptors +#define TUD_MIDI2_DESC_ALT1_CS_LEN(_numgtbs) (7 + (4 + (_numgtbs)) * 2) #define TUD_MIDI2_DESC_ALT1_HEAD_LEN (9 + 7) -#define TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx) \ +#define TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx, _numgtbs) \ /* MIDI Streaming Interface, Alt Setting 1 */\ - 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 1, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, _stridx,\ - /* MS Header (MIDI 2.0) */\ - 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), U16_TO_U8S_LE(7) + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 1, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\ + /* MS Header (MIDI 2.0): wTotalLength = header + 2x CS Endpoint */\ + 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0200), U16_TO_U8S_LE(TUD_MIDI2_DESC_ALT1_CS_LEN(_numgtbs)) -// Alt Setting 1: Standard USB Endpoint (7 bytes) + CS Endpoint (subtype 0x02) +// Alt Setting 1: Standard USB Endpoint (7 bytes) + CS Endpoint General 2.0 #define TUD_MIDI2_DESC_ALT1_EP_LEN(_numgtbs) (7 + 4 + (_numgtbs)) -#define TUD_MIDI2_DESC_ALT1_EP(_ep, _epsize, _numgtbs) \ +#define TUD_MIDI2_DESC_ALT1_EP(_ep, _epsize, _numgtbs, ...) \ 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, \ - (uint8_t)(4 + (_numgtbs)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL_2_0, _numgtbs + (uint8_t)(4 + (_numgtbs)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL_2_0, _numgtbs, ## __VA_ARGS__ // Total length: Alt 0 (MIDI 1.0) + Alt 1 (UMP) #define TUD_MIDI2_DESC_LEN (TUD_MIDI_DESC_LEN + TUD_MIDI2_DESC_ALT1_HEAD_LEN + TUD_MIDI2_DESC_ALT1_EP_LEN(1) * 2) @@ -455,11 +457,9 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ TUD_MIDI_JACKID_OUT_EMB(1),\ /* Alt Setting 1 (UMP) */\ - TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx),\ - TUD_MIDI2_DESC_ALT1_EP(_epout, _epsize, 1),\ - 1, /* bAssoGrpTrmBlkID = 1 */\ - TUD_MIDI2_DESC_ALT1_EP(_epin, _epsize, 1),\ - 1 /* bAssoGrpTrmBlkID = 1 */ + TUD_MIDI2_DESC_ALT1_HEAD(_itfnum, _stridx, 1),\ + TUD_MIDI2_DESC_ALT1_EP(_epout, _epsize, 1, 1 /* bAssoGrpTrmBlkID */),\ + TUD_MIDI2_DESC_ALT1_EP(_epin, _epsize, 1, 1 /* bAssoGrpTrmBlkID */) //--------------------------------------------------------------------+ // Audio Descriptor Templates -- cgit v1.3.1 From f8a5fc37f3472cb2e3eb584d58e9cf4588dc4329 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Wed, 25 Mar 2026 07:29:10 -0300 Subject: fix: CI build failures on non-RP2040 platforms - Use UINT32_C(1) instead of 1u for bit shifts >= 16 in midi2_device.c to avoid shift-count-overflow on 16-bit platforms (MSP430) - Add Makefiles for midi2_device and midi2_host examples with family guard (skip if FAMILY != rp2040). These examples require Pico SDK and board-specific hardware - Restrict midi2_device CMakeLists.txt to rp2040 family (matching midi2_host) --- examples/device/midi2_device/CMakeLists.txt | 4 ++-- examples/device/midi2_device/Makefile | 27 +++++++++++++++++++++++++++ examples/host/midi2_host/Makefile | 27 +++++++++++++++++++++++++++ src/class/midi/midi2_device.c | 8 ++++---- 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 examples/device/midi2_device/Makefile create mode 100644 examples/host/midi2_host/Makefile (limited to 'examples') diff --git a/examples/device/midi2_device/CMakeLists.txt b/examples/device/midi2_device/CMakeLists.txt index 295af6550..3f4a876c9 100644 --- a/examples/device/midi2_device/CMakeLists.txt +++ b/examples/device/midi2_device/CMakeLists.txt @@ -7,8 +7,8 @@ project(midi2_device C CXX ASM) # Checks this example is valid for the family and initializes the project family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) -# Espressif has its own cmake build system -if(FAMILY STREQUAL "espressif") +# This example requires RP2040/RP2350 (USB descriptors and config are board-specific) +if(NOT FAMILY STREQUAL "rp2040") return() endif() diff --git a/examples/device/midi2_device/Makefile b/examples/device/midi2_device/Makefile new file mode 100644 index 000000000..09f069c4f --- /dev/null +++ b/examples/device/midi2_device/Makefile @@ -0,0 +1,27 @@ +# This example requires RP2040/RP2350 (USB descriptors and config are board-specific) +ifeq (,$(findstring rp2040,$(FAMILY))) +$(info Skipping midi2_device: requires FAMILY=rp2040) +all: + @: +.DEFAULT: + @: +else + +include ../../../hw/bsp/family_support.mk + +INC += \ + src \ + +# Example source +EXAMPLE_SOURCE += \ + src/main.c \ + src/usb_descriptors.c \ + +SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) + +# Suppress pre-existing warning in usbd.c +CFLAGS_GCC += -Wno-type-limits + +include ../../../hw/bsp/family_rules.mk + +endif diff --git a/examples/host/midi2_host/Makefile b/examples/host/midi2_host/Makefile new file mode 100644 index 000000000..2b4660516 --- /dev/null +++ b/examples/host/midi2_host/Makefile @@ -0,0 +1,27 @@ +# This example requires RP2040/RP2350 (PIO-USB, Pico SDK I2C, SSD1306 display) +ifeq (,$(findstring rp2040,$(FAMILY))) +$(info Skipping midi2_host: requires FAMILY=rp2040) +all: + @: +.DEFAULT: + @: +else + +include ../../../hw/bsp/family_support.mk + +INC += \ + src \ + +# Example source +EXAMPLE_SOURCE += \ + src/main.c \ + src/display.c \ + +SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) + +# Suppress pre-existing warning +CFLAGS_GCC += -Wno-type-limits + +include ../../../hw/bsp/family_rules.mk + +endif diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index aecbda4c5..0029737ce 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -158,10 +158,10 @@ static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) { | ((uint32_t) STREAM_ENDPOINT_INFO << 16) | ((uint32_t) UMP_VER_MAJOR << 8) | (uint32_t) UMP_VER_MINOR; - msg[1] = (1u << 31) // Static Function Blocks flag + msg[1] = (UINT32_C(1) << 31) // Static Function Blocks flag | ((uint32_t)(CFG_TUD_MIDI2_NUM_FUNCTION_BLOCKS & 0x7F) << 24) - | (1u << 9) // MIDI 2.0 Protocol capability - | (1u << 8); // MIDI 1.0 Protocol capability + | (UINT32_C(1) << 9) // MIDI 2.0 Protocol capability + | (UINT32_C(1) << 8); // MIDI 1.0 Protocol capability _nego_send_ump(p_midi, msg, 4); } @@ -214,7 +214,7 @@ static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { uint32_t msg[4] = {0}; msg[0] = ((uint32_t) MT_STREAM << 28) | ((uint32_t) STREAM_FB_INFO << 16) - | (1u << 15) + | (UINT32_C(1) << 15) | ((uint32_t) fb_idx << 8) | 0x02; // bDirection: bidirectional msg[1] = ((uint32_t) 0 << 24) // bFirstGroup -- cgit v1.3.1 From 13c6f6a2c7aca7165161d9f32d10087c91aa61aa Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Tue, 7 Apr 2026 22:50:29 -0300 Subject: example: add MIDI 2.0 Host example for Adafruit Feather RP2040 USB Host Board-specific variant of midi2_host targeting the Adafruit Feather RP2040 with USB Type A Host (product 5723). Hardware differences from the Waveshare RP2350-USB-A example: - PIO-USB on GP16/GP17 (vs GP12/GP13) - I2C1 via STEMMA QT on GP2/GP3 (vs I2C0 GP4/GP5) - USB Host 5V power enable on GP18 Display uses three phases: splash screen, spinner while waiting for a device, then live scrolling UMP message view with 6 visible lines. Tested board-to-board: RP2040 Pico (Device) to Feather RP2040 (Host), SSD1306 128x64 OLED showing decoded MIDI 2.0 messages in real time. --- examples/host/midi2_host_feather/CMakeLists.txt | 38 +++ examples/host/midi2_host_feather/Makefile | 1 + examples/host/midi2_host_feather/src/display.c | 276 +++++++++++++++++++++ examples/host/midi2_host_feather/src/display.h | 46 ++++ examples/host/midi2_host_feather/src/font5x7.h | 107 ++++++++ examples/host/midi2_host_feather/src/main.c | 267 ++++++++++++++++++++ examples/host/midi2_host_feather/src/tusb_config.h | 91 +++++++ 7 files changed, 826 insertions(+) create mode 100644 examples/host/midi2_host_feather/CMakeLists.txt create mode 100644 examples/host/midi2_host_feather/Makefile create mode 100644 examples/host/midi2_host_feather/src/display.c create mode 100644 examples/host/midi2_host_feather/src/display.h create mode 100644 examples/host/midi2_host_feather/src/font5x7.h create mode 100644 examples/host/midi2_host_feather/src/main.c create mode 100644 examples/host/midi2_host_feather/src/tusb_config.h (limited to 'examples') diff --git a/examples/host/midi2_host_feather/CMakeLists.txt b/examples/host/midi2_host_feather/CMakeLists.txt new file mode 100644 index 000000000..5d34b8170 --- /dev/null +++ b/examples/host/midi2_host_feather/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +project(midi2_host_feather C CXX ASM) + +family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) + +# This example requires PIO-USB and Pico SDK (I2C, SSD1306 display) +if(NOT FAMILY STREQUAL "rp2040") + return() +endif() + +add_executable(${PROJECT_NAME}) + +target_sources(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/display.c +) + +target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +family_configure_host_example(${PROJECT_NAME} noos) + +# Adafruit Feather RP2040 USB Host: PIO-USB on GP16/GP17 +target_compile_definitions(${PROJECT_NAME} PRIVATE + PIO_USB_DP_PIN_DEFAULT=16 +) +target_compile_options(${PROJECT_NAME} PRIVATE + -Wno-type-limits +) + +# SSD1306 display (I2C via STEMMA QT) +target_link_libraries(${PROJECT_NAME} PUBLIC + hardware_i2c +) diff --git a/examples/host/midi2_host_feather/Makefile b/examples/host/midi2_host_feather/Makefile new file mode 100644 index 000000000..09172c37f --- /dev/null +++ b/examples/host/midi2_host_feather/Makefile @@ -0,0 +1 @@ +include ../../../examples/build_system/make/make.mk diff --git a/examples/host/midi2_host_feather/src/display.c b/examples/host/midi2_host_feather/src/display.c new file mode 100644 index 000000000..91039fa16 --- /dev/null +++ b/examples/host/midi2_host_feather/src/display.c @@ -0,0 +1,276 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// SSD1306 OLED display driver (128x64, I2C) for MIDI 2.0 Host example. +// Adafruit Feather RP2040 USB Host: I2C1 via STEMMA QT (SDA = GP2, SCL = GP3) +// +// Three display phases: +// 1. Splash : title + credits (shown during init) +// 2. Connecting : spinner animation while waiting for device +// 3. Live : header + 6 scrolling log lines + status bar + +#include "display.h" +#include +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "hardware/gpio.h" + +#define I2C_PORT i2c1 +#define I2C_SDA 2 +#define I2C_SCL 3 +#define I2C_FREQ 400000 +#define SSD1306_ADDR 0x3C + +#define SCR_W 128 +#define SCR_H 64 +#define PAGES (SCR_H / 8) +#define CHARS_PER_LINE 21 + +//--------------------------------------------------------------------+ +// Framebuffer +//--------------------------------------------------------------------+ + +static uint8_t fb[SCR_W * PAGES]; + +//--------------------------------------------------------------------+ +// Log buffer (6 lines for live view) +//--------------------------------------------------------------------+ + +#define LOG_LINES 6 +static char log_lines[LOG_LINES][CHARS_PER_LINE + 1]; +static int log_count = 0; + +//--------------------------------------------------------------------+ +// Minimal 5x7 font (ASCII 32-126) +//--------------------------------------------------------------------+ + +#include "font5x7.h" + +//--------------------------------------------------------------------+ +// SSD1306 I2C low-level +//--------------------------------------------------------------------+ + +static void ssd_cmd(uint8_t cmd) { + uint8_t buf[2] = { 0x00, cmd }; + i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, 2, false); +} + +static void ssd_data(const uint8_t* data, size_t len) { + uint8_t buf[SCR_W + 1]; + buf[0] = 0x40; + size_t chunk = (len > SCR_W) ? SCR_W : len; + memcpy(buf + 1, data, chunk); + i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, chunk + 1, false); +} + +static void ssd_flush(void) { + ssd_cmd(0x21); ssd_cmd(0); ssd_cmd(127); + ssd_cmd(0x22); ssd_cmd(0); ssd_cmd(7); + for (int page = 0; page < PAGES; page++) { + ssd_data(&fb[page * SCR_W], SCR_W); + } +} + +//--------------------------------------------------------------------+ +// Framebuffer drawing +//--------------------------------------------------------------------+ + +static void fb_clear(void) { + memset(fb, 0, sizeof(fb)); +} + +static void fb_char(int x, int y, char c) { + if (c < 32 || c > 126) c = '?'; + const uint8_t* glyph = font5x7 + (c - 32) * 5; + int page = y / 8; + int bit_offset = y % 8; + + if (page >= PAGES || x + 5 > SCR_W) return; + + for (int col = 0; col < 5; col++) { + uint8_t column_data = glyph[col]; + fb[(page * SCR_W) + x + col] |= (uint8_t)(column_data << bit_offset); + if (bit_offset > 0 && page + 1 < PAGES) { + fb[((page + 1) * SCR_W) + x + col] |= (uint8_t)(column_data >> (8 - bit_offset)); + } + } +} + +static void fb_string(int x, int y, const char* str) { + while (*str) { + fb_char(x, y, *str); + x += 6; + if (x + 6 > SCR_W) break; + str++; + } +} + +// Draw a horizontal line (1px) +static void fb_hline(int x0, int x1, int y) { + int page = y / 8; + uint8_t mask = (uint8_t)(1 << (y % 8)); + if (page >= PAGES) return; + for (int x = x0; x <= x1 && x < SCR_W; x++) { + fb[page * SCR_W + x] |= mask; + } +} + +//--------------------------------------------------------------------+ +// Phase 1: Splash +//--------------------------------------------------------------------+ + +void display_init(void) { + i2c_init(I2C_PORT, I2C_FREQ); + gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA); + gpio_pull_up(I2C_SCL); + + sleep_ms(100); + + // SSD1306 init sequence + ssd_cmd(0xAE); + ssd_cmd(0xD5); ssd_cmd(0x80); + ssd_cmd(0xA8); ssd_cmd(0x3F); + ssd_cmd(0xD3); ssd_cmd(0x00); + ssd_cmd(0x40); + ssd_cmd(0x8D); ssd_cmd(0x14); + ssd_cmd(0x20); ssd_cmd(0x00); + ssd_cmd(0xA1); + ssd_cmd(0xC8); + ssd_cmd(0xDA); ssd_cmd(0x12); + ssd_cmd(0x81); ssd_cmd(0xCF); + ssd_cmd(0xD9); ssd_cmd(0xF1); + ssd_cmd(0xDB); ssd_cmd(0x40); + ssd_cmd(0xA4); + ssd_cmd(0xA6); + ssd_cmd(0xAF); + + // Splash screen + fb_clear(); + fb_string(4, 8, "TinyUSB MIDI 2.0"); + fb_hline(4, 123, 18); + fb_string(16, 24, "USB Host Demo"); + fb_string(4, 40, "Feather RP2040"); + fb_string(4, 52, "PIO-USB + SSD1306"); + ssd_flush(); +} + +//--------------------------------------------------------------------+ +// Phase 2: Connecting (spinner) +//--------------------------------------------------------------------+ + +static const char SPINNER[] = "|/-\\"; + +void display_connecting(uint32_t elapsed_ms) { + int idx = (int)((elapsed_ms / 200) % 4); + + // Clear bottom 2 pages for spinner area + memset(&fb[6 * SCR_W], 0, SCR_W); + memset(&fb[7 * SCR_W], 0, SCR_W); + + char line[CHARS_PER_LINE + 1]; + snprintf(line, sizeof(line), "%c Waiting device...", SPINNER[idx]); + fb_string(4, 52, line); + ssd_flush(); +} + +//--------------------------------------------------------------------+ +// Phase 3: Live view +//--------------------------------------------------------------------+ + +// Layout: +// y=0 : "TinyUSB MIDI 2.0 Host" (header, fixed) +// y=9 : separator line +// y=10 : log line 0 +// y=18 : log line 1 +// y=26 : log line 2 +// y=34 : log line 3 +// y=42 : log line 4 +// y=50 : log line 5 +// y=57 : separator line +// y=58 : status bar + +static bool live_mode = false; + +static void draw_live_frame(void) { + fb_clear(); + fb_string(0, 0, "TinyUSB MIDI 2.0 Host"); + fb_hline(0, 127, 9); + fb_hline(0, 127, 57); +} + +void display_live_begin(void) { + live_mode = true; + log_count = 0; + memset(log_lines, 0, sizeof(log_lines)); + + draw_live_frame(); + fb_string(0, 58, "Connected"); + ssd_flush(); +} + +void display_log(const char* text, uint16_t color) { + (void)color; + + if (!live_mode) { + display_live_begin(); + } + + // Scroll up if full + if (log_count >= LOG_LINES) { + for (int i = 0; i < LOG_LINES - 1; i++) { + strncpy(log_lines[i], log_lines[i + 1], CHARS_PER_LINE); + log_lines[i][CHARS_PER_LINE] = '\0'; + } + log_count = LOG_LINES - 1; + } + + strncpy(log_lines[log_count], text, CHARS_PER_LINE); + log_lines[log_count][CHARS_PER_LINE] = '\0'; + log_count++; + + // Redraw: header + log + status + draw_live_frame(); + for (int i = 0; i < log_count && i < LOG_LINES; i++) { + fb_string(0, 10 + i * 8, log_lines[i]); + } + ssd_flush(); +} + +void display_status(const char* text) { + if (!live_mode) return; + + // Clear status area (page 7) + memset(&fb[7 * SCR_W], 0, SCR_W); + // Redraw separator (might have been cleared) + fb_hline(0, 127, 57); + + char status[CHARS_PER_LINE + 1]; + strncpy(status, text, CHARS_PER_LINE); + status[CHARS_PER_LINE] = '\0'; + fb_string(0, 58, status); + ssd_flush(); +} diff --git a/examples/host/midi2_host_feather/src/display.h b/examples/host/midi2_host_feather/src/display.h new file mode 100644 index 000000000..ad8ead81d --- /dev/null +++ b/examples/host/midi2_host_feather/src/display.h @@ -0,0 +1,46 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef DISPLAY_H_ +#define DISPLAY_H_ + +#include +#include + +// Initialize SSD1306 display and show splash screen +void display_init(void); + +// Show waiting/connecting animation (call periodically) +void display_connecting(uint32_t elapsed_ms); + +// Transition to live message view +void display_live_begin(void); + +// Add a line to the scrolling log area (6 visible lines) +void display_log(const char* text, uint16_t color); + +// Update the status bar (bottom line) +void display_status(const char* text); + +#endif diff --git a/examples/host/midi2_host_feather/src/font5x7.h b/examples/host/midi2_host_feather/src/font5x7.h new file mode 100644 index 000000000..e515faac1 --- /dev/null +++ b/examples/host/midi2_host_feather/src/font5x7.h @@ -0,0 +1,107 @@ +// Minimal 5x7 font, ASCII 32-126. Public domain. +// 5 bytes per character (5 columns, 7 rows, LSB=top row) + +#ifndef FONT5X7_H_ +#define FONT5X7_H_ + +#include + +static const uint8_t font5x7[] = { + 0x00,0x00,0x00,0x00,0x00, // 32 (space) + 0x00,0x00,0x5F,0x00,0x00, // 33 ! + 0x00,0x07,0x00,0x07,0x00, // 34 " + 0x14,0x7F,0x14,0x7F,0x14, // 35 # + 0x24,0x2A,0x7F,0x2A,0x12, // 36 $ + 0x23,0x13,0x08,0x64,0x62, // 37 % + 0x36,0x49,0x55,0x22,0x50, // 38 & + 0x00,0x05,0x03,0x00,0x00, // 39 ' + 0x00,0x1C,0x22,0x41,0x00, // 40 ( + 0x00,0x41,0x22,0x1C,0x00, // 41 ) + 0x08,0x2A,0x1C,0x2A,0x08, // 42 * + 0x08,0x08,0x3E,0x08,0x08, // 43 + + 0x00,0x50,0x30,0x00,0x00, // 44 , + 0x08,0x08,0x08,0x08,0x08, // 45 - + 0x00,0x60,0x60,0x00,0x00, // 46 . + 0x20,0x10,0x08,0x04,0x02, // 47 / + 0x3E,0x51,0x49,0x45,0x3E, // 48 0 + 0x00,0x42,0x7F,0x40,0x00, // 49 1 + 0x42,0x61,0x51,0x49,0x46, // 50 2 + 0x21,0x41,0x45,0x4B,0x31, // 51 3 + 0x18,0x14,0x12,0x7F,0x10, // 52 4 + 0x27,0x45,0x45,0x45,0x39, // 53 5 + 0x3C,0x4A,0x49,0x49,0x30, // 54 6 + 0x01,0x71,0x09,0x05,0x03, // 55 7 + 0x36,0x49,0x49,0x49,0x36, // 56 8 + 0x06,0x49,0x49,0x29,0x1E, // 57 9 + 0x00,0x36,0x36,0x00,0x00, // 58 : + 0x00,0x56,0x36,0x00,0x00, // 59 ; + 0x00,0x08,0x14,0x22,0x41, // 60 < + 0x14,0x14,0x14,0x14,0x14, // 61 = + 0x41,0x22,0x14,0x08,0x00, // 62 > + 0x02,0x01,0x51,0x09,0x06, // 63 ? + 0x32,0x49,0x79,0x41,0x3E, // 64 @ + 0x7E,0x11,0x11,0x11,0x7E, // 65 A + 0x7F,0x49,0x49,0x49,0x36, // 66 B + 0x3E,0x41,0x41,0x41,0x22, // 67 C + 0x7F,0x41,0x41,0x22,0x1C, // 68 D + 0x7F,0x49,0x49,0x49,0x41, // 69 E + 0x7F,0x09,0x09,0x01,0x01, // 70 F + 0x3E,0x41,0x41,0x51,0x32, // 71 G + 0x7F,0x08,0x08,0x08,0x7F, // 72 H + 0x00,0x41,0x7F,0x41,0x00, // 73 I + 0x20,0x40,0x41,0x3F,0x01, // 74 J + 0x7F,0x08,0x14,0x22,0x41, // 75 K + 0x7F,0x40,0x40,0x40,0x40, // 76 L + 0x7F,0x02,0x04,0x02,0x7F, // 77 M + 0x7F,0x04,0x08,0x10,0x7F, // 78 N + 0x3E,0x41,0x41,0x41,0x3E, // 79 O + 0x7F,0x09,0x09,0x09,0x06, // 80 P + 0x3E,0x41,0x51,0x21,0x5E, // 81 Q + 0x7F,0x09,0x19,0x29,0x46, // 82 R + 0x46,0x49,0x49,0x49,0x31, // 83 S + 0x01,0x01,0x7F,0x01,0x01, // 84 T + 0x3F,0x40,0x40,0x40,0x3F, // 85 U + 0x1F,0x20,0x40,0x20,0x1F, // 86 V + 0x7F,0x20,0x18,0x20,0x7F, // 87 W + 0x63,0x14,0x08,0x14,0x63, // 88 X + 0x03,0x04,0x78,0x04,0x03, // 89 Y + 0x61,0x51,0x49,0x45,0x43, // 90 Z + 0x00,0x00,0x7F,0x41,0x41, // 91 [ + 0x02,0x04,0x08,0x10,0x20, // 92 backslash + 0x41,0x41,0x7F,0x00,0x00, // 93 ] + 0x04,0x02,0x01,0x02,0x04, // 94 ^ + 0x40,0x40,0x40,0x40,0x40, // 95 _ + 0x00,0x01,0x02,0x04,0x00, // 96 ` + 0x20,0x54,0x54,0x54,0x78, // 97 a + 0x7F,0x48,0x44,0x44,0x38, // 98 b + 0x38,0x44,0x44,0x44,0x20, // 99 c + 0x38,0x44,0x44,0x48,0x7F, // 100 d + 0x38,0x54,0x54,0x54,0x18, // 101 e + 0x08,0x7E,0x09,0x01,0x02, // 102 f + 0x08,0x14,0x54,0x54,0x3C, // 103 g + 0x7F,0x08,0x04,0x04,0x78, // 104 h + 0x00,0x44,0x7D,0x40,0x00, // 105 i + 0x20,0x40,0x44,0x3D,0x00, // 106 j + 0x00,0x7F,0x10,0x28,0x44, // 107 k + 0x00,0x41,0x7F,0x40,0x00, // 108 l + 0x7C,0x04,0x18,0x04,0x78, // 109 m + 0x7C,0x08,0x04,0x04,0x78, // 110 n + 0x38,0x44,0x44,0x44,0x38, // 111 o + 0x7C,0x14,0x14,0x14,0x08, // 112 p + 0x08,0x14,0x14,0x18,0x7C, // 113 q + 0x7C,0x08,0x04,0x04,0x08, // 114 r + 0x48,0x54,0x54,0x54,0x20, // 115 s + 0x04,0x3F,0x44,0x40,0x20, // 116 t + 0x3C,0x40,0x40,0x20,0x7C, // 117 u + 0x1C,0x20,0x40,0x20,0x1C, // 118 v + 0x3C,0x40,0x30,0x40,0x3C, // 119 w + 0x44,0x28,0x10,0x28,0x44, // 120 x + 0x0C,0x50,0x50,0x50,0x3C, // 121 y + 0x44,0x64,0x54,0x4C,0x44, // 122 z + 0x00,0x08,0x36,0x41,0x00, // 123 { + 0x00,0x00,0x7F,0x00,0x00, // 124 | + 0x00,0x41,0x36,0x08,0x00, // 125 } + 0x10,0x08,0x08,0x10,0x08, // 126 ~ +}; + +#endif diff --git a/examples/host/midi2_host_feather/src/main.c b/examples/host/midi2_host_feather/src/main.c new file mode 100644 index 000000000..7c996355d --- /dev/null +++ b/examples/host/midi2_host_feather/src/main.c @@ -0,0 +1,267 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// MIDI 2.0 Host Example: Adafruit Feather RP2040 USB Host +// +// Receives UMP from a MIDI 2.0 Device via PIO-USB (GP16/GP17). +// SSD1306 OLED (I2C1, GP2/GP3) shows splash, spinner, then live UMP messages. + +#include +#include +#include "bsp/board_api.h" +#include "tusb.h" +#include "hardware/gpio.h" +#include "class/midi/midi2_host.h" +#include "class/midi/midi.h" +#include "display.h" + +//--------------------------------------------------------------------+ +// State +//--------------------------------------------------------------------+ + +static uint32_t note_count = 0; +static uint8_t midi2_idx = 0xFF; +static bool device_connected = false; +static bool mounted = false; + +//--------------------------------------------------------------------+ +// Note name helper +//--------------------------------------------------------------------+ + +static const char* NOTE_NAMES[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" +}; + +static void note_name(uint8_t pitch, char* buf, size_t len) { + int octave = (pitch / 12) - 1; + snprintf(buf, len, "%s%d", NOTE_NAMES[pitch % 12], octave); +} + +//--------------------------------------------------------------------+ +// UMP decoder +//--------------------------------------------------------------------+ + +static void decode_ump(const uint32_t* words, uint8_t word_count) { + uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F); + char line[64]; + + if (mt == 0x4 && word_count >= 2) { + uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); + uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F); + + switch (status) { + case 0x9: { + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + uint16_t vel = (uint16_t)((words[1] >> 16) & 0xFFFF); + char nn[6]; + note_name(pitch, nn, sizeof(nn)); + snprintf(line, sizeof(line), "NoteOn %s ch%u v%04X", nn, channel, vel); + display_log(line, 0x07E0); + note_count++; + break; + } + case 0x8: { + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + char nn[6]; + note_name(pitch, nn, sizeof(nn)); + snprintf(line, sizeof(line), "NoteOff %s ch%u", nn, channel); + display_log(line, 0x8410); + break; + } + case 0xB: { + uint8_t idx = (uint8_t)((words[0] >> 8) & 0x7F); + snprintf(line, sizeof(line), "CC%-3u %08lX", idx, (unsigned long)words[1]); + display_log(line, 0x001F); + break; + } + case 0xC: { + uint8_t prog = (uint8_t)((words[1] >> 24) & 0x7F); + snprintf(line, sizeof(line), "ProgChg %u", prog); + display_log(line, 0xFFE0); + break; + } + case 0xE: { + snprintf(line, sizeof(line), "PBend %08lX", (unsigned long)words[1]); + display_log(line, 0xF81F); + break; + } + case 0xD: { + snprintf(line, sizeof(line), "CPress %08lX", (unsigned long)words[1]); + display_log(line, 0xFC10); + break; + } + case 0xA: { + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + char nn[6]; + note_name(pitch, nn, sizeof(nn)); + snprintf(line, sizeof(line), "PolyP %s %08lX", nn, (unsigned long)words[1]); + display_log(line, 0xFC10); + break; + } + case 0xF: { + uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); + uint8_t flags = (uint8_t)(words[0] & 0xFF); + snprintf(line, sizeof(line), "PN-Mgmt n%u f%02X", pitch, flags); + display_log(line, 0x07FF); + break; + } + default: { + snprintf(line, sizeof(line), "M2CVM s=0x%X", status); + display_log(line, 0xFFFF); + break; + } + } + } else if (mt == 0x0) { + uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); + if (status == 0x2) { + uint16_t ts = words[0] & 0xFFFF; + snprintf(line, sizeof(line), "JR-TS %04X", ts); + display_log(line, 0x8410); + } + } else { + snprintf(line, sizeof(line), "MT=0x%X w0=%08lX", + mt, (unsigned long)words[0]); + display_log(line, 0xFFFF); + } +} + +//--------------------------------------------------------------------+ +// MIDI 2.0 Host Callbacks +//--------------------------------------------------------------------+ + +void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) { + (void)idx; + char line[48]; + snprintf(line, sizeof(line), "%s RX=%u TX=%u", + d->protocol_version ? "MIDI2" : "MIDI1", + d->rx_cable_count, d->tx_cable_count); + display_log(line, 0x07E0); +} + +void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) { + midi2_idx = idx; + mounted = true; + + display_live_begin(); + + if (m->protocol_version) { + display_log("MIDI 2.0 ready", 0x07E0); + } else { + display_log("MIDI 1.0 device", 0xFFE0); + } + display_status("Receiving..."); +} + +void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void)xferred_bytes; + + uint32_t words[16]; + while (1) { + uint32_t n = tuh_midi2_ump_read(idx, words, 16); + if (n == 0) break; + + uint32_t i = 0; + while (i < n) { + uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F); + uint8_t wc = midi2_ump_word_count(mt); + if (i + wc > n) break; + decode_ump(&words[i], wc); + i += wc; + } + } +} + +void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void)idx; (void)xferred_bytes; +} + +void tuh_midi2_umount_cb(uint8_t idx) { + (void)idx; + midi2_idx = 0xFF; + device_connected = false; + mounted = false; + note_count = 0; + display_log("Disconnected", 0xF800); + display_status("Waiting..."); +} + +//--------------------------------------------------------------------+ +// Main +//--------------------------------------------------------------------+ + +int main(void) { + board_init(); + + // Enable 5V to USB-A port (Feather RP2040 USB Host: GP18) + gpio_init(18); + gpio_set_dir(18, GPIO_OUT); + gpio_put(18, 1); + + display_init(); + sleep_ms(1500); + + tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUSB_SPEED_FULL, + }; + tusb_init(BOARD_TUH_RHPORT, &host_init); + + uint32_t last_status_ms = 0; + + while (1) { + tuh_task(); + + uint32_t now = tusb_time_millis_api(); + + if (!mounted) { + // Spinner while waiting for device + if (now - last_status_ms > 200) { + last_status_ms = now; + display_connecting(now); + } + } else { + // Update note count every 2 seconds + if (now - last_status_ms > 2000) { + last_status_ms = now; + char line[22]; + snprintf(line, sizeof(line), "Notes: %lu", (unsigned long)note_count); + display_status(line); + } + } + } + + return 0; +} + +// Generic USB device mount/unmount +void tuh_mount_cb(uint8_t daddr) { + (void)daddr; + device_connected = true; +} + +void tuh_umount_cb(uint8_t daddr) { + (void)daddr; + device_connected = false; + mounted = false; +} diff --git a/examples/host/midi2_host_feather/src/tusb_config.h b/examples/host/midi2_host_feather/src/tusb_config.h new file mode 100644 index 000000000..362d0e2d0 --- /dev/null +++ b/examples/host/midi2_host_feather/src/tusb_config.h @@ -0,0 +1,91 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +#ifndef CFG_TUH_MEM_SECTION +#define CFG_TUH_MEM_SECTION +#endif + +#ifndef CFG_TUH_MEM_ALIGN +#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//--------------------------------------------------------------------+ +// Host Configuration +// Adafruit Feather RP2040 USB Host: PIO-USB on GP16/GP17 (USB-A Host port) +//--------------------------------------------------------------------+ + +#define CFG_TUH_ENABLED 1 + +// PIO-USB Host on rhport 1 (USB-A connector on GP16/GP17) +#define CFG_TUH_RPI_PIO_USB 1 +#define BOARD_TUH_RHPORT 1 + +#ifndef BOARD_TUH_MAX_SPEED +#define BOARD_TUH_MAX_SPEED OPT_MODE_FULL_SPEED +#endif + +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED + +//--------------------------------------------------------------------+ +// Driver Configuration +//--------------------------------------------------------------------+ + +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 0 +#define CFG_TUH_DEVICE_MAX 1 + +// MIDI 2.0 Host +#define CFG_TUH_MIDI2 1 +#define CFG_TUH_MIDI2_RX_BUFSIZE 512 +#define CFG_TUH_MIDI2_TX_BUFSIZE 512 + +#ifdef __cplusplus +} +#endif + +#endif -- cgit v1.3.1 From c4847006dbb7565b71055837103ec788c4cb6fee Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Tue, 7 Apr 2026 23:04:03 -0300 Subject: fix: skip midi2_host_feather build on non-RP2040 platforms The Makefile was missing the FAMILY=rp2040 guard, causing CI failures on stm32h7rs and other non-RP2040 targets. Matches the approach used by midi2_host. --- examples/host/midi2_host_feather/Makefile | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/host/midi2_host_feather/Makefile b/examples/host/midi2_host_feather/Makefile index 09172c37f..933d19434 100644 --- a/examples/host/midi2_host_feather/Makefile +++ b/examples/host/midi2_host_feather/Makefile @@ -1 +1,27 @@ -include ../../../examples/build_system/make/make.mk +# This example requires RP2040 (PIO-USB, Pico SDK I2C, SSD1306 display) +ifeq (,$(findstring rp2040,$(FAMILY))) +$(info Skipping midi2_host_feather: requires FAMILY=rp2040) +all: + @: +.DEFAULT: + @: +else + +include ../../../hw/bsp/family_support.mk + +INC += \ + src \ + +# Example source +EXAMPLE_SOURCE += \ + src/main.c \ + src/display.c \ + +SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) + +# Suppress pre-existing warning +CFLAGS_GCC += -Wno-type-limits + +include ../../../hw/bsp/family_rules.mk + +endif -- cgit v1.3.1 From b4880761bcadbee86d047e0a3b329859926caad5 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Tue, 7 Apr 2026 23:46:10 -0300 Subject: refactor: replace midi2_host (Waveshare) with midi2_host_feather Remove the Waveshare RP2350-USB-A host example and board definition. The Feather RP2040 USB Host is the TinyUSB reference board for PIO-USB host and does not require the CFG_TUSB_DEBUG workaround needed by the Waveshare (R13 pull-up issue). One host example is sufficient for the PR. The Feather variant has an improved display with splash, spinner, and 6-line live view. --- examples/host/CMakeLists.txt | 2 +- examples/host/midi2_host/CMakeLists.txt | 38 --- examples/host/midi2_host/Makefile | 27 -- examples/host/midi2_host/src/display.c | 221 -------------- examples/host/midi2_host/src/display.h | 54 ---- examples/host/midi2_host/src/font5x7.h | 107 ------- examples/host/midi2_host/src/main.c | 326 --------------------- examples/host/midi2_host/src/tusb_config.h | 91 ------ .../boards/waveshare_rp2350_usb_a/board.cmake | 2 - .../rp2040/boards/waveshare_rp2350_usb_a/board.h | 49 ---- 10 files changed, 1 insertion(+), 916 deletions(-) delete mode 100644 examples/host/midi2_host/CMakeLists.txt delete mode 100644 examples/host/midi2_host/Makefile delete mode 100644 examples/host/midi2_host/src/display.c delete mode 100644 examples/host/midi2_host/src/display.h delete mode 100644 examples/host/midi2_host/src/font5x7.h delete mode 100644 examples/host/midi2_host/src/main.c delete mode 100644 examples/host/midi2_host/src/tusb_config.h delete mode 100644 hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake delete mode 100644 hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h (limited to 'examples') diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt index 7c74e3c73..2cf2c10af 100644 --- a/examples/host/CMakeLists.txt +++ b/examples/host/CMakeLists.txt @@ -13,7 +13,7 @@ set(EXAMPLE_LIST device_info hid_controller midi_rx - midi2_host + midi2_host_feather msc_file_explorer msc_file_explorer_freertos ) diff --git a/examples/host/midi2_host/CMakeLists.txt b/examples/host/midi2_host/CMakeLists.txt deleted file mode 100644 index cd70d122a..000000000 --- a/examples/host/midi2_host/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) - -project(midi2_host C CXX ASM) - -family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) - -# This example requires PIO-USB and Pico SDK (I2C, SSD1306 display) -if(NOT FAMILY STREQUAL "rp2040") - return() -endif() - -add_executable(${PROJECT_NAME}) - -target_sources(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/display.c -) - -target_include_directories(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src -) - -family_configure_host_example(${PROJECT_NAME} noos) - -# Waveshare RP2350-USB-A: PIO-USB on GP12/GP13 -target_compile_definitions(${PROJECT_NAME} PRIVATE - PIO_USB_DP_PIN_DEFAULT=12 -) -target_compile_options(${PROJECT_NAME} PRIVATE - -Wno-type-limits -) - -# SSD1306 display (I2C) -target_link_libraries(${PROJECT_NAME} PUBLIC - hardware_i2c -) diff --git a/examples/host/midi2_host/Makefile b/examples/host/midi2_host/Makefile deleted file mode 100644 index 2b4660516..000000000 --- a/examples/host/midi2_host/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -# This example requires RP2040/RP2350 (PIO-USB, Pico SDK I2C, SSD1306 display) -ifeq (,$(findstring rp2040,$(FAMILY))) -$(info Skipping midi2_host: requires FAMILY=rp2040) -all: - @: -.DEFAULT: - @: -else - -include ../../../hw/bsp/family_support.mk - -INC += \ - src \ - -# Example source -EXAMPLE_SOURCE += \ - src/main.c \ - src/display.c \ - -SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) - -# Suppress pre-existing warning -CFLAGS_GCC += -Wno-type-limits - -include ../../../hw/bsp/family_rules.mk - -endif diff --git a/examples/host/midi2_host/src/display.c b/examples/host/midi2_host/src/display.c deleted file mode 100644 index 4745c2961..000000000 --- a/examples/host/midi2_host/src/display.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// SSD1306 OLED display driver (128x64, I2C) for MIDI 2.0 Host example. -// Minimal text-only implementation, no graphics library. -// I2C0: SDA = GP4, SCL = GP5, Address = 0x3C - -#include "display.h" -#include -#include -#include "pico/stdlib.h" -#include "hardware/i2c.h" -#include "hardware/gpio.h" - -#define I2C_PORT i2c0 -#define I2C_SDA 4 -#define I2C_SCL 5 -#define I2C_FREQ 400000 -#define SSD1306_ADDR 0x3C - -#define SCR_W 128 -#define SCR_H 64 -#define PAGES (SCR_H / 8) // 8 pages -#define CHARS_PER_LINE 21 // 128 / 6 = 21 chars - -// Framebuffer -static uint8_t fb[SCR_W * PAGES]; - -// Log buffer -#define LOG_LINES 3 -static char log_lines[LOG_LINES][CHARS_PER_LINE + 1]; -static int log_count = 0; - -// Status line -static char status_text[CHARS_PER_LINE + 1] = ""; - -//--------------------------------------------------------------------+ -// Minimal 5x7 font (ASCII 32-126) -//--------------------------------------------------------------------+ -#include "font5x7.h" - -//--------------------------------------------------------------------+ -// SSD1306 I2C commands -//--------------------------------------------------------------------+ - -static void ssd_cmd(uint8_t cmd) { - uint8_t buf[2] = { 0x00, cmd }; // Co=0, D/C=0 - i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, 2, false); -} - -static void ssd_data(const uint8_t* data, size_t len) { - uint8_t buf[SCR_W + 1]; - buf[0] = 0x40; // Co=0, D/C=1 - size_t chunk = (len > SCR_W) ? SCR_W : len; - memcpy(buf + 1, data, chunk); - i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, chunk + 1, false); -} - -static void ssd_flush(void) { - ssd_cmd(0x21); ssd_cmd(0); ssd_cmd(127); // Column range - ssd_cmd(0x22); ssd_cmd(0); ssd_cmd(7); // Page range - for (int page = 0; page < PAGES; page++) { - ssd_data(&fb[page * SCR_W], SCR_W); - } -} - -//--------------------------------------------------------------------+ -// Framebuffer drawing -//--------------------------------------------------------------------+ - -static void fb_clear(void) { - memset(fb, 0, sizeof(fb)); -} - -static void fb_char(int x, int y, char c) { - if (c < 32 || c > 126) c = '?'; - const uint8_t* glyph = font5x7 + (c - 32) * 5; - int page = y / 8; - int bit_offset = y % 8; - - if (page >= PAGES || x + 5 > SCR_W) return; - - for (int col = 0; col < 5; col++) { - uint8_t column_data = glyph[col]; - fb[(page * SCR_W) + x + col] |= (uint8_t)(column_data << bit_offset); - if (bit_offset > 0 && page + 1 < PAGES) { - fb[((page + 1) * SCR_W) + x + col] |= (uint8_t)(column_data >> (8 - bit_offset)); - } - } -} - -static void fb_string(int x, int y, const char* str) { - while (*str) { - fb_char(x, y, *str); - x += 6; - if (x + 6 > SCR_W) break; - str++; - } -} - -//--------------------------------------------------------------------+ -// Display API -//--------------------------------------------------------------------+ - -void display_init(void) { - i2c_init(I2C_PORT, I2C_FREQ); - gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); - gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); - gpio_pull_up(I2C_SDA); - gpio_pull_up(I2C_SCL); - - sleep_ms(100); - - // SSD1306 init sequence - ssd_cmd(0xAE); // Display off - ssd_cmd(0xD5); ssd_cmd(0x80); // Clock div - ssd_cmd(0xA8); ssd_cmd(0x3F); // Multiplex 64 - ssd_cmd(0xD3); ssd_cmd(0x00); // Display offset - ssd_cmd(0x40); // Start line 0 - ssd_cmd(0x8D); ssd_cmd(0x14); // Charge pump on - ssd_cmd(0x20); ssd_cmd(0x00); // Horizontal addressing - ssd_cmd(0xA1); // Segment remap - ssd_cmd(0xC8); // COM scan direction - ssd_cmd(0xDA); ssd_cmd(0x12); // COM pins - ssd_cmd(0x81); ssd_cmd(0xCF); // Contrast - ssd_cmd(0xD9); ssd_cmd(0xF1); // Pre-charge - ssd_cmd(0xDB); ssd_cmd(0x40); // VCOMH deselect - ssd_cmd(0xA4); // Display from RAM - ssd_cmd(0xA6); // Normal display - ssd_cmd(0xAF); // Display on - - fb_clear(); - fb_string(0, 0, "MIDI 2.0 Host"); - ssd_flush(); -} - -void display_checklist_update(const checklist_t* ck) { - struct { const char* label; bool ok; } items[] = { - { "PWR", ck->pwr_on }, - { "TinyUSB", ck->tusb_init }, - { "USB bus", ck->bus_active }, - { "Device", ck->device_connected }, - { "Descript", ck->descriptor_parsed }, - { "Alt1 UMP", ck->alt_setting_ok }, - { "Mount", ck->mounted }, - { "RX UMP", ck->receiving }, - }; - - // Clear checklist area (lines 1-4, two columns) - for (int p = 1; p <= 4; p++) { - memset(&fb[p * SCR_W], 0, SCR_W); - } - - for (int i = 0; i < 8; i++) { - int col = (i < 4) ? 0 : 64; - int row = (i < 4) ? i : i - 4; - int y = 9 + row * 8; - char line[12]; - snprintf(line, sizeof(line), "%s %s", items[i].ok ? "OK" : "..", items[i].label); - fb_string(col, y, line); - } - - ssd_flush(); -} - -void display_log(const char* text, uint16_t color) { - (void)color; // SSD1306 is monochrome - - if (log_count >= LOG_LINES) { - for (int i = 0; i < LOG_LINES - 1; i++) { - strncpy(log_lines[i], log_lines[i + 1], CHARS_PER_LINE); - log_lines[i][CHARS_PER_LINE] = '\0'; - } - log_count = LOG_LINES - 1; - } - - strncpy(log_lines[log_count], text, CHARS_PER_LINE); - log_lines[log_count][CHARS_PER_LINE] = '\0'; - log_count++; - - // Draw log area (pages 5-6, y=40-55) - memset(&fb[5 * SCR_W], 0, SCR_W); - memset(&fb[6 * SCR_W], 0, SCR_W); - - for (int i = 0; i < log_count && i < LOG_LINES; i++) { - fb_string(0, 41 + i * 8, log_lines[i]); - } - - ssd_flush(); -} - -void display_status(const char* text) { - strncpy(status_text, text, CHARS_PER_LINE); - status_text[CHARS_PER_LINE] = '\0'; - - // Status on last page (y=56) - memset(&fb[7 * SCR_W], 0, SCR_W); - fb_string(0, 56, status_text); - ssd_flush(); -} diff --git a/examples/host/midi2_host/src/display.h b/examples/host/midi2_host/src/display.h deleted file mode 100644 index a8b0a4b5e..000000000 --- a/examples/host/midi2_host/src/display.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef DISPLAY_H_ -#define DISPLAY_H_ - -#include -#include - -typedef struct { - bool pwr_on; - bool tusb_init; - bool bus_active; - bool device_connected; - bool descriptor_parsed; - bool alt_setting_ok; - bool mounted; - bool receiving; -} checklist_t; - -// Initialize SSD1306 display (I2C on GP4/GP5) -void display_init(void); - -// Redraw the checklist area (top portion) -void display_checklist_update(const checklist_t* ck); - -// Add a line to the scrolling log area -void display_log(const char* text, uint16_t color); - -// Update the status bar (bottom line) -void display_status(const char* text); - -#endif diff --git a/examples/host/midi2_host/src/font5x7.h b/examples/host/midi2_host/src/font5x7.h deleted file mode 100644 index e515faac1..000000000 --- a/examples/host/midi2_host/src/font5x7.h +++ /dev/null @@ -1,107 +0,0 @@ -// Minimal 5x7 font, ASCII 32-126. Public domain. -// 5 bytes per character (5 columns, 7 rows, LSB=top row) - -#ifndef FONT5X7_H_ -#define FONT5X7_H_ - -#include - -static const uint8_t font5x7[] = { - 0x00,0x00,0x00,0x00,0x00, // 32 (space) - 0x00,0x00,0x5F,0x00,0x00, // 33 ! - 0x00,0x07,0x00,0x07,0x00, // 34 " - 0x14,0x7F,0x14,0x7F,0x14, // 35 # - 0x24,0x2A,0x7F,0x2A,0x12, // 36 $ - 0x23,0x13,0x08,0x64,0x62, // 37 % - 0x36,0x49,0x55,0x22,0x50, // 38 & - 0x00,0x05,0x03,0x00,0x00, // 39 ' - 0x00,0x1C,0x22,0x41,0x00, // 40 ( - 0x00,0x41,0x22,0x1C,0x00, // 41 ) - 0x08,0x2A,0x1C,0x2A,0x08, // 42 * - 0x08,0x08,0x3E,0x08,0x08, // 43 + - 0x00,0x50,0x30,0x00,0x00, // 44 , - 0x08,0x08,0x08,0x08,0x08, // 45 - - 0x00,0x60,0x60,0x00,0x00, // 46 . - 0x20,0x10,0x08,0x04,0x02, // 47 / - 0x3E,0x51,0x49,0x45,0x3E, // 48 0 - 0x00,0x42,0x7F,0x40,0x00, // 49 1 - 0x42,0x61,0x51,0x49,0x46, // 50 2 - 0x21,0x41,0x45,0x4B,0x31, // 51 3 - 0x18,0x14,0x12,0x7F,0x10, // 52 4 - 0x27,0x45,0x45,0x45,0x39, // 53 5 - 0x3C,0x4A,0x49,0x49,0x30, // 54 6 - 0x01,0x71,0x09,0x05,0x03, // 55 7 - 0x36,0x49,0x49,0x49,0x36, // 56 8 - 0x06,0x49,0x49,0x29,0x1E, // 57 9 - 0x00,0x36,0x36,0x00,0x00, // 58 : - 0x00,0x56,0x36,0x00,0x00, // 59 ; - 0x00,0x08,0x14,0x22,0x41, // 60 < - 0x14,0x14,0x14,0x14,0x14, // 61 = - 0x41,0x22,0x14,0x08,0x00, // 62 > - 0x02,0x01,0x51,0x09,0x06, // 63 ? - 0x32,0x49,0x79,0x41,0x3E, // 64 @ - 0x7E,0x11,0x11,0x11,0x7E, // 65 A - 0x7F,0x49,0x49,0x49,0x36, // 66 B - 0x3E,0x41,0x41,0x41,0x22, // 67 C - 0x7F,0x41,0x41,0x22,0x1C, // 68 D - 0x7F,0x49,0x49,0x49,0x41, // 69 E - 0x7F,0x09,0x09,0x01,0x01, // 70 F - 0x3E,0x41,0x41,0x51,0x32, // 71 G - 0x7F,0x08,0x08,0x08,0x7F, // 72 H - 0x00,0x41,0x7F,0x41,0x00, // 73 I - 0x20,0x40,0x41,0x3F,0x01, // 74 J - 0x7F,0x08,0x14,0x22,0x41, // 75 K - 0x7F,0x40,0x40,0x40,0x40, // 76 L - 0x7F,0x02,0x04,0x02,0x7F, // 77 M - 0x7F,0x04,0x08,0x10,0x7F, // 78 N - 0x3E,0x41,0x41,0x41,0x3E, // 79 O - 0x7F,0x09,0x09,0x09,0x06, // 80 P - 0x3E,0x41,0x51,0x21,0x5E, // 81 Q - 0x7F,0x09,0x19,0x29,0x46, // 82 R - 0x46,0x49,0x49,0x49,0x31, // 83 S - 0x01,0x01,0x7F,0x01,0x01, // 84 T - 0x3F,0x40,0x40,0x40,0x3F, // 85 U - 0x1F,0x20,0x40,0x20,0x1F, // 86 V - 0x7F,0x20,0x18,0x20,0x7F, // 87 W - 0x63,0x14,0x08,0x14,0x63, // 88 X - 0x03,0x04,0x78,0x04,0x03, // 89 Y - 0x61,0x51,0x49,0x45,0x43, // 90 Z - 0x00,0x00,0x7F,0x41,0x41, // 91 [ - 0x02,0x04,0x08,0x10,0x20, // 92 backslash - 0x41,0x41,0x7F,0x00,0x00, // 93 ] - 0x04,0x02,0x01,0x02,0x04, // 94 ^ - 0x40,0x40,0x40,0x40,0x40, // 95 _ - 0x00,0x01,0x02,0x04,0x00, // 96 ` - 0x20,0x54,0x54,0x54,0x78, // 97 a - 0x7F,0x48,0x44,0x44,0x38, // 98 b - 0x38,0x44,0x44,0x44,0x20, // 99 c - 0x38,0x44,0x44,0x48,0x7F, // 100 d - 0x38,0x54,0x54,0x54,0x18, // 101 e - 0x08,0x7E,0x09,0x01,0x02, // 102 f - 0x08,0x14,0x54,0x54,0x3C, // 103 g - 0x7F,0x08,0x04,0x04,0x78, // 104 h - 0x00,0x44,0x7D,0x40,0x00, // 105 i - 0x20,0x40,0x44,0x3D,0x00, // 106 j - 0x00,0x7F,0x10,0x28,0x44, // 107 k - 0x00,0x41,0x7F,0x40,0x00, // 108 l - 0x7C,0x04,0x18,0x04,0x78, // 109 m - 0x7C,0x08,0x04,0x04,0x78, // 110 n - 0x38,0x44,0x44,0x44,0x38, // 111 o - 0x7C,0x14,0x14,0x14,0x08, // 112 p - 0x08,0x14,0x14,0x18,0x7C, // 113 q - 0x7C,0x08,0x04,0x04,0x08, // 114 r - 0x48,0x54,0x54,0x54,0x20, // 115 s - 0x04,0x3F,0x44,0x40,0x20, // 116 t - 0x3C,0x40,0x40,0x20,0x7C, // 117 u - 0x1C,0x20,0x40,0x20,0x1C, // 118 v - 0x3C,0x40,0x30,0x40,0x3C, // 119 w - 0x44,0x28,0x10,0x28,0x44, // 120 x - 0x0C,0x50,0x50,0x50,0x3C, // 121 y - 0x44,0x64,0x54,0x4C,0x44, // 122 z - 0x00,0x08,0x36,0x41,0x00, // 123 { - 0x00,0x00,0x7F,0x00,0x00, // 124 | - 0x00,0x41,0x36,0x08,0x00, // 125 } - 0x10,0x08,0x08,0x10,0x08, // 126 ~ -}; - -#endif diff --git a/examples/host/midi2_host/src/main.c b/examples/host/midi2_host/src/main.c deleted file mode 100644 index e23fbfc87..000000000 --- a/examples/host/midi2_host/src/main.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// MIDI 2.0 Host Receiver Example -// -// Receives UMP from a MIDI 2.0 Device via PIO-USB Host. -// SSD1306 OLED (I2C, 128x64) shows boot checklist then received UMP messages. - -#include -#include -#include "bsp/board_api.h" -#include "tusb.h" -#include "class/midi/midi2_host.h" -#include "class/midi/midi.h" -#include "display.h" - -//--------------------------------------------------------------------+ -// Checklist state -//--------------------------------------------------------------------+ - -static checklist_t ck = { 0 }; -static uint32_t note_count = 0; -static uint8_t midi2_idx = 0xFF; // invalid until mount - -//--------------------------------------------------------------------+ -// Note name helper -//--------------------------------------------------------------------+ - -static const char* NOTE_NAMES[] = { - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" -}; - -static void note_name(uint8_t pitch, char* buf, size_t len) { - int octave = (pitch / 12) - 1; - snprintf(buf, len, "%s%d", NOTE_NAMES[pitch % 12], octave); -} - -//--------------------------------------------------------------------+ -// UMP Message Type names -//--------------------------------------------------------------------+ - -static const char* mt_name(uint8_t mt) { - switch (mt) { - case 0x0: return "Utility"; - case 0x1: return "System"; - case 0x2: return "M1 CVM"; - case 0x3: return "SysEx7"; - case 0x4: return "M2 CVM"; - case 0x5: return "SysEx8"; - case 0xD: return "Flex"; - case 0xF: return "Stream"; - default: return "?"; - } -} - -//--------------------------------------------------------------------+ -// UMP decoder - extract and display MIDI 2.0 messages -//--------------------------------------------------------------------+ - -static void decode_ump(const uint32_t* words, uint8_t word_count) { - uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F); - char line[64]; - - if (mt == 0x4 && word_count >= 2) { - // MIDI 2.0 Channel Voice Message - uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); - uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F); - - switch (status) { - case 0x9: { // Note On - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - uint16_t vel = (uint16_t)((words[1] >> 16) & 0xFFFF); - char nn[6]; - note_name(pitch, nn, sizeof(nn)); - snprintf(line, sizeof(line), "NoteOn %s ch%u vel=0x%04X", nn, channel, vel); - display_log(line, 0x07E0); // green - note_count++; - break; - } - case 0x8: { // Note Off - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - char nn[6]; - note_name(pitch, nn, sizeof(nn)); - snprintf(line, sizeof(line), "NoteOff %s ch%u", nn, channel); - display_log(line, 0x8410); // grey - break; - } - case 0xB: { // CC - uint8_t idx = (uint8_t)((words[0] >> 8) & 0x7F); - snprintf(line, sizeof(line), "CC%-3u = 0x%08lX", idx, (unsigned long)words[1]); - display_log(line, 0x001F); // blue - break; - } - case 0xC: { // Program Change - uint8_t prog = (uint8_t)((words[1] >> 24) & 0x7F); - snprintf(line, sizeof(line), "ProgChg %u", prog); - display_log(line, 0xFFE0); // yellow - break; - } - case 0xE: { // Pitch Bend - snprintf(line, sizeof(line), "PBend = 0x%08lX", (unsigned long)words[1]); - display_log(line, 0xF81F); // magenta - break; - } - case 0xD: { // Channel Pressure - snprintf(line, sizeof(line), "CPress = 0x%08lX", (unsigned long)words[1]); - display_log(line, 0xFC10); // orange - break; - } - case 0xA: { // Poly Pressure - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - char nn[6]; - note_name(pitch, nn, sizeof(nn)); - snprintf(line, sizeof(line), "PolyP %s = 0x%08lX", nn, (unsigned long)words[1]); - display_log(line, 0xFC10); - break; - } - case 0xF: { // Per-Note Management - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - uint8_t flags = (uint8_t)(words[0] & 0xFF); - snprintf(line, sizeof(line), "PN-Mgmt note=%u flags=0x%02X", pitch, flags); - display_log(line, 0x07FF); // cyan - break; - } - default: { - snprintf(line, sizeof(line), "M2CVM status=0x%X", status); - display_log(line, 0xFFFF); - break; - } - } - } else if (mt == 0x0) { - // Utility (JR Timestamp, NOOP) - uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); - if (status == 0x2) { - uint16_t ts = words[0] & 0xFFFF; - snprintf(line, sizeof(line), "JR-TS = 0x%04X", ts); - display_log(line, 0x8410); - } - } else { - snprintf(line, sizeof(line), "MT=0x%X (%s) w0=0x%08lX", - mt, mt_name(mt), (unsigned long)words[0]); - display_log(line, 0xFFFF); - } -} - -//--------------------------------------------------------------------+ -// MIDI 2.0 Host Callbacks -//--------------------------------------------------------------------+ - -void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) { - (void)idx; - ck.descriptor_parsed = true; - char line[48]; - - snprintf(line, sizeof(line), "bcdMSC=0x%02X%02X proto=%s", - d->bcdMSC_hi, d->bcdMSC_lo, - d->protocol_version ? "MIDI2" : "MIDI1"); - display_log(line, 0x07E0); - - snprintf(line, sizeof(line), "Cables: RX=%u TX=%u", - d->rx_cable_count, d->tx_cable_count); - display_log(line, 0x07E0); - - display_checklist_update(&ck); -} - -void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) { - midi2_idx = idx; - ck.alt_setting_ok = true; - ck.mounted = true; - - char line[48]; - snprintf(line, sizeof(line), "Mounted addr=%u alt=%u", - m->daddr, m->alt_setting_active); - display_log(line, 0x07E0); - - if (m->protocol_version) { - display_log("MIDI 2.0 ready", 0x07E0); - } else { - display_log("MIDI 1.0 only", 0xFFE0); - } - - display_checklist_update(&ck); -} - -void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) { - (void)xferred_bytes; - if (!ck.receiving) { - ck.receiving = true; - display_checklist_update(&ck); - } - - uint32_t words[16]; - while (1) { - uint32_t n = tuh_midi2_ump_read(idx, words, 16); - if (n == 0) break; - - // Decode complete UMP messages - uint32_t i = 0; - while (i < n) { - uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F); - uint8_t wc = midi2_ump_word_count(mt); - if (i + wc > n) break; - decode_ump(&words[i], wc); - i += wc; - } - } -} - -void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) { - (void)idx; (void)xferred_bytes; -} - -void tuh_midi2_umount_cb(uint8_t idx) { - (void)idx; - midi2_idx = 0xFF; - ck.device_connected = false; - ck.descriptor_parsed = false; - ck.alt_setting_ok = false; - ck.mounted = false; - ck.receiving = false; - note_count = 0; - - display_log("Device disconnected", 0xF800); - display_checklist_update(&ck); -} - -//--------------------------------------------------------------------+ -// Main -//--------------------------------------------------------------------+ - -int main(void) { - board_init(); - - display_init(); - - ck.pwr_on = true; - display_checklist_update(&ck); - - // Init TinyUSB Host (BSP handles PIO-USB configuration via tuh_configure) - tusb_rhport_init_t host_init = { - .role = TUSB_ROLE_HOST, - .speed = TUSB_SPEED_FULL, - }; - tusb_init(BOARD_TUH_RHPORT, &host_init); - - ck.tusb_init = true; - ck.bus_active = true; - display_checklist_update(&ck); - - char dbg[40]; - snprintf(dbg, sizeof(dbg), "RHPORT=%u PIO_USB=%u", - BOARD_TUH_RHPORT, CFG_TUH_RPI_PIO_USB); - display_log(dbg, 0xFFE0); // yellow - display_status("Waiting for device..."); - - uint32_t last_status_ms = 0; - static uint32_t loop_count = 0; - - while (1) { - tuh_task(); - loop_count++; - - // Update status every 2 seconds - uint32_t now = tusb_time_millis_api(); - if (now - last_status_ms > 2000) { - last_status_ms = now; - char line[22]; - if (ck.receiving) { - snprintf(line, sizeof(line), "Notes:%lu", (unsigned long)note_count); - } else if (ck.mounted) { - snprintf(line, sizeof(line), "Mounted OK!"); - } else if (ck.device_connected) { - snprintf(line, sizeof(line), "Dev found, mounting.."); - } else { - snprintf(line, sizeof(line), "Wait.. t=%lu", (unsigned long)(now/1000)); - } - display_status(line); - } - } - - return 0; -} - -// Generic USB device mount/unmount (any class) -void tuh_mount_cb(uint8_t daddr) { - char line[32]; - uint16_t vid, pid; - tuh_vid_pid_get(daddr, &vid, &pid); - snprintf(line, sizeof(line), "USB %04X:%04X a%u", vid, pid, daddr); - display_log(line, 0x07E0); - ck.device_connected = true; - display_checklist_update(&ck); -} - -void tuh_umount_cb(uint8_t daddr) { - (void)daddr; - display_log("USB disconnected", 0xF800); - ck.device_connected = false; - ck.descriptor_parsed = false; - ck.alt_setting_ok = false; - ck.mounted = false; - ck.receiving = false; - display_checklist_update(&ck); -} diff --git a/examples/host/midi2_host/src/tusb_config.h b/examples/host/midi2_host/src/tusb_config.h deleted file mode 100644 index 650df97e1..000000000 --- a/examples/host/midi2_host/src/tusb_config.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef TUSB_CONFIG_H_ -#define TUSB_CONFIG_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -//-------------------------------------------------------------------- -// Common Configuration -//-------------------------------------------------------------------- - -#ifndef CFG_TUSB_MCU -#error CFG_TUSB_MCU must be defined -#endif - -#ifndef CFG_TUSB_OS -#define CFG_TUSB_OS OPT_OS_NONE -#endif - -#ifndef CFG_TUSB_DEBUG -#define CFG_TUSB_DEBUG 0 -#endif - -#ifndef CFG_TUH_MEM_SECTION -#define CFG_TUH_MEM_SECTION -#endif - -#ifndef CFG_TUH_MEM_ALIGN -#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) -#endif - -//-------------------------------------------------------------------- -// Host Configuration -// Waveshare RP2350-USB-A: PIO-USB on GP12/GP13 (USB-A Host port) -//-------------------------------------------------------------------- - -#define CFG_TUH_ENABLED 1 - -// PIO-USB Host on rhport 1 (USB-A connector on GP12/GP13) -#define CFG_TUH_RPI_PIO_USB 1 -#define BOARD_TUH_RHPORT 1 - -#ifndef BOARD_TUH_MAX_SPEED -#define BOARD_TUH_MAX_SPEED OPT_MODE_FULL_SPEED -#endif - -#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED - -//-------------------------------------------------------------------- -// Driver Configuration -//-------------------------------------------------------------------- - -#define CFG_TUH_ENUMERATION_BUFSIZE 256 - -#define CFG_TUH_HUB 0 -#define CFG_TUH_DEVICE_MAX 1 - -// MIDI 2.0 Host -#define CFG_TUH_MIDI2 1 -#define CFG_TUH_MIDI2_RX_BUFSIZE 512 -#define CFG_TUH_MIDI2_TX_BUFSIZE 512 - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake b/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake deleted file mode 100644 index e888c7fb3..000000000 --- a/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.cmake +++ /dev/null @@ -1,2 +0,0 @@ -set(PICO_PLATFORM rp2350-arm-s) -set(PICO_BOARD waveshare_rp2350_usb_a) diff --git a/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h b/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h deleted file mode 100644 index 0af8a695f..000000000 --- a/hw/bsp/rp2040/boards/waveshare_rp2350_usb_a/board.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -/* metadata: - name: Waveshare RP2350-USB-A - url: https://www.waveshare.com/wiki/RP2350-USB-A -*/ - -#ifndef TUSB_BOARD_H -#define TUSB_BOARD_H - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// PIO_USB -//--------------------------------------------------------------------+ -// Waveshare RP2350-USB-A: PIO-USB on GP12 (D+) / GP13 (D-) -// PICO_DEFAULT_PIO_USB_DP_PIN already defined in SDK board header - -#ifdef __cplusplus - } -#endif - -#endif -- cgit v1.3.1 From 43a72cb614e2fdd27788057a6f9e843d458f1ca5 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 13:00:05 -0300 Subject: midi2: device example - portable, add protocol fallback Remove the rp2040-only guard; the example now follows the audio_test pattern (only Espressif uses its own build system). Add CMakePresets.json and generic product strings. The runtime fallback picks the path that matches the host state, per message, mirroring the idea behind the UAC examples: Alt 0 USB-MIDI 1.0 32-bit Event Packets (packet_write) Alt 1 + MIDI 1.0 negotiated UMP MT 0x2 (ump_write) Alt 1 + MIDI 2.0 negotiated UMP MT 0x4 (ump_write) Build-tested on rp2040, rp2350, stm32f0/f1/f2/f3/f4/f7/g0/g4/h5/h7/l0/l4/u0/u5/wb/c0, samd2x_l2x, samd5x_e5x, same7x, nrf, imxrt, lpc17, lpc55, mcx, ra, da1469x, efm32. Ref #3571 --- examples/device/midi2_device/CMakeLists.txt | 4 +- examples/device/midi2_device/CMakePresets.json | 6 + examples/device/midi2_device/Makefile | 11 - examples/device/midi2_device/src/main.c | 299 ++++++++++++++++++--- examples/device/midi2_device/src/usb_descriptors.c | 2 +- 5 files changed, 276 insertions(+), 46 deletions(-) create mode 100644 examples/device/midi2_device/CMakePresets.json (limited to 'examples') diff --git a/examples/device/midi2_device/CMakeLists.txt b/examples/device/midi2_device/CMakeLists.txt index 3f4a876c9..295af6550 100644 --- a/examples/device/midi2_device/CMakeLists.txt +++ b/examples/device/midi2_device/CMakeLists.txt @@ -7,8 +7,8 @@ project(midi2_device C CXX ASM) # Checks this example is valid for the family and initializes the project family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) -# This example requires RP2040/RP2350 (USB descriptors and config are board-specific) -if(NOT FAMILY STREQUAL "rp2040") +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") return() endif() diff --git a/examples/device/midi2_device/CMakePresets.json b/examples/device/midi2_device/CMakePresets.json new file mode 100644 index 000000000..5cd8971e9 --- /dev/null +++ b/examples/device/midi2_device/CMakePresets.json @@ -0,0 +1,6 @@ +{ + "version": 6, + "include": [ + "../../../hw/bsp/BoardPresets.json" + ] +} diff --git a/examples/device/midi2_device/Makefile b/examples/device/midi2_device/Makefile index 09f069c4f..829d9da59 100644 --- a/examples/device/midi2_device/Makefile +++ b/examples/device/midi2_device/Makefile @@ -1,12 +1,3 @@ -# This example requires RP2040/RP2350 (USB descriptors and config are board-specific) -ifeq (,$(findstring rp2040,$(FAMILY))) -$(info Skipping midi2_device: requires FAMILY=rp2040) -all: - @: -.DEFAULT: - @: -else - include ../../../hw/bsp/family_support.mk INC += \ @@ -23,5 +14,3 @@ SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) CFLAGS_GCC += -Wno-type-limits include ../../../hw/bsp/family_rules.mk - -endif diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c index 515efe07b..2e2488752 100644 --- a/examples/device/midi2_device/src/main.c +++ b/examples/device/midi2_device/src/main.c @@ -186,6 +186,226 @@ static inline void ump_per_note_mgmt(uint8_t group, uint8_t channel, ump_send_64(w0, 0x00000000); } +//--------------------------------------------------------------------+ +// MIDI 1.0 Channel Voice Builders (UMP MT 0x2, 32-bit) +//--------------------------------------------------------------------+ +// Used on Alt 1 when negotiated protocol is MIDI 1.0. +// Word layout: [MT(0x2) | Group(4b) | Status(8b) | Data1(8b) | Data2(8b)] +// Status nibbles: 0x8=NoteOff, 0x9=NoteOn, 0xA=PolyPress, 0xB=CC, +// 0xC=ProgChg, 0xD=ChanPress, 0xE=PitchBend. + +static inline void ump_midi1_send(uint8_t group, uint8_t status, + uint8_t data1, uint8_t data2) { + uint32_t w = UMP_MT_MIDI1_CV + | ((uint32_t)(group & 0x0F) << 24) + | ((uint32_t)status << 16) + | ((uint32_t)data1 << 8) + | (uint32_t)data2; + ump_send_32(w); +} + +static inline void ump_midi1_note_on(uint8_t group, uint8_t channel, + uint8_t note, uint8_t vel7) { + ump_midi1_send(group, 0x90 | (channel & 0x0F), note & 0x7F, vel7 & 0x7F); +} + +static inline void ump_midi1_note_off(uint8_t group, uint8_t channel, + uint8_t note, uint8_t vel7) { + ump_midi1_send(group, 0x80 | (channel & 0x0F), note & 0x7F, vel7 & 0x7F); +} + +static inline void ump_midi1_cc(uint8_t group, uint8_t channel, + uint8_t cc, uint8_t val7) { + ump_midi1_send(group, 0xB0 | (channel & 0x0F), cc & 0x7F, val7 & 0x7F); +} + +static inline void ump_midi1_program(uint8_t group, uint8_t channel, + uint8_t program) { + ump_midi1_send(group, 0xC0 | (channel & 0x0F), program & 0x7F, 0); +} + +static inline void ump_midi1_pitch_bend(uint8_t group, uint8_t channel, + uint16_t value14) { + // 14-bit pitch bend: LSB first, then MSB. Center = 0x2000. + ump_midi1_send(group, 0xE0 | (channel & 0x0F), + (uint8_t)(value14 & 0x7F), + (uint8_t)((value14 >> 7) & 0x7F)); +} + +static inline void ump_midi1_channel_pressure(uint8_t group, uint8_t channel, + uint8_t val7) { + ump_midi1_send(group, 0xD0 | (channel & 0x0F), val7 & 0x7F, 0); +} + +static inline void ump_midi1_poly_pressure(uint8_t group, uint8_t channel, + uint8_t note, uint8_t val7) { + ump_midi1_send(group, 0xA0 | (channel & 0x0F), note & 0x7F, val7 & 0x7F); +} + +//--------------------------------------------------------------------+ +// USB-MIDI 1.0 32-bit Event Packet Builders (Alt 0 transport) +//--------------------------------------------------------------------+ +// Used on Alt 0 (USB-MIDI 1.0). Each packet is 4 raw bytes: +// [(Cable << 4) | CIN] [Status] [Data1] [Data2] +// CIN = Code Index Number. See USB-MIDI 1.0 spec Section 4. + +static inline void midi1_pkt_send(uint8_t cable, uint8_t cin, + uint8_t status, uint8_t data1, + uint8_t data2) { + uint8_t packet[4] = { + (uint8_t)(((cable & 0x0F) << 4) | (cin & 0x0F)), + status, data1, data2 + }; + tud_midi2_packet_write(packet); +} + +static inline void midi1_pkt_note_on(uint8_t cable, uint8_t channel, + uint8_t note, uint8_t vel7) { + midi1_pkt_send(cable, 0x9, 0x90 | (channel & 0x0F), + note & 0x7F, vel7 & 0x7F); +} + +static inline void midi1_pkt_note_off(uint8_t cable, uint8_t channel, + uint8_t note, uint8_t vel7) { + midi1_pkt_send(cable, 0x8, 0x80 | (channel & 0x0F), + note & 0x7F, vel7 & 0x7F); +} + +static inline void midi1_pkt_cc(uint8_t cable, uint8_t channel, + uint8_t cc, uint8_t val7) { + midi1_pkt_send(cable, 0xB, 0xB0 | (channel & 0x0F), + cc & 0x7F, val7 & 0x7F); +} + +static inline void midi1_pkt_program(uint8_t cable, uint8_t channel, + uint8_t program) { + midi1_pkt_send(cable, 0xC, 0xC0 | (channel & 0x0F), + program & 0x7F, 0); +} + +static inline void midi1_pkt_pitch_bend(uint8_t cable, uint8_t channel, + uint16_t value14) { + midi1_pkt_send(cable, 0xE, 0xE0 | (channel & 0x0F), + (uint8_t)(value14 & 0x7F), + (uint8_t)((value14 >> 7) & 0x7F)); +} + +static inline void midi1_pkt_channel_pressure(uint8_t cable, uint8_t channel, + uint8_t val7) { + midi1_pkt_send(cable, 0xD, 0xD0 | (channel & 0x0F), val7 & 0x7F, 0); +} + +static inline void midi1_pkt_poly_pressure(uint8_t cable, uint8_t channel, + uint8_t note, uint8_t val7) { + midi1_pkt_send(cable, 0xA, 0xA0 | (channel & 0x0F), + note & 0x7F, val7 & 0x7F); +} + +//--------------------------------------------------------------------+ +// Scaling Helpers (MIDI 2.0 ↔ MIDI 1.0) +//--------------------------------------------------------------------+ + +static inline uint8_t scale_vel16_to_vel7(uint16_t v16) { return (uint8_t)(v16 >> 9); } +static inline uint8_t scale_val32_to_val7(uint32_t v32) { return (uint8_t)(v32 >> 25); } +static inline uint16_t scale_pb32_to_pb14(uint32_t pb32) { return (uint16_t)(pb32 >> 18); } + +//--------------------------------------------------------------------+ +// Dispatch Layer - Transport + Protocol Fallback +//--------------------------------------------------------------------+ +// Follows the same idea as the UAC examples (`tud_descriptor_configuration_cb` +// returning UAC1 or UAC2 based on bus speed): pick the path that matches the +// state the host put us in. Here the decision is made per message because +// MIDI 2.0 advertises both alts in a single config descriptor; the host +// selects via SetInterface and UMP Stream protocol negotiation. + +static void send_note_on(uint8_t grp, uint8_t ch, uint8_t note, uint16_t vel16) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + midi1_pkt_note_on(grp, ch, note, scale_vel16_to_vel7(vel16)); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + ump_midi1_note_on(grp, ch, note, scale_vel16_to_vel7(vel16)); + } else { + ump_note_on(grp, ch, note, vel16, UMP_ATTR_NONE, 0); + } +} + +static void send_note_off(uint8_t grp, uint8_t ch, uint8_t note, uint16_t vel16) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + midi1_pkt_note_off(grp, ch, note, scale_vel16_to_vel7(vel16)); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + ump_midi1_note_off(grp, ch, note, scale_vel16_to_vel7(vel16)); + } else { + ump_note_off(grp, ch, note, vel16, UMP_ATTR_NONE, 0); + } +} + +static void send_cc(uint8_t grp, uint8_t ch, uint8_t cc, uint32_t val32) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + midi1_pkt_cc(grp, ch, cc, scale_val32_to_val7(val32)); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + ump_midi1_cc(grp, ch, cc, scale_val32_to_val7(val32)); + } else { + ump_cc(grp, ch, cc, val32); + } +} + +static void send_program_change(uint8_t grp, uint8_t ch, uint8_t program, + bool with_bank, uint8_t bank_msb, + uint8_t bank_lsb) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + if (with_bank) { + midi1_pkt_cc(grp, ch, 0x00, bank_msb); + midi1_pkt_cc(grp, ch, 0x20, bank_lsb); + } + midi1_pkt_program(grp, ch, program); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + if (with_bank) { + ump_midi1_cc(grp, ch, 0x00, bank_msb); + ump_midi1_cc(grp, ch, 0x20, bank_lsb); + } + ump_midi1_program(grp, ch, program); + } else { + ump_program_change(grp, ch, program, with_bank, bank_msb, bank_lsb); + } +} + +static void send_pitch_bend(uint8_t grp, uint8_t ch, uint32_t pb32) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + midi1_pkt_pitch_bend(grp, ch, scale_pb32_to_pb14(pb32)); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + ump_midi1_pitch_bend(grp, ch, scale_pb32_to_pb14(pb32)); + } else { + ump_pitch_bend(grp, ch, pb32); + } +} + +static void send_channel_pressure(uint8_t grp, uint8_t ch, uint32_t val32) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + midi1_pkt_channel_pressure(grp, ch, scale_val32_to_val7(val32)); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + ump_midi1_channel_pressure(grp, ch, scale_val32_to_val7(val32)); + } else { + ump_channel_pressure(grp, ch, val32); + } +} + +static void send_poly_pressure(uint8_t grp, uint8_t ch, uint8_t note, + uint32_t val32) { + uint8_t alt = tud_midi2_alt_setting(); + if (alt == 0) { + midi1_pkt_poly_pressure(grp, ch, note, scale_val32_to_val7(val32)); + } else if (tud_midi2_protocol() == MIDI_PROTOCOL_MIDI1) { + ump_midi1_poly_pressure(grp, ch, note, scale_val32_to_val7(val32)); + } else { + ump_poly_pressure(grp, ch, note, val32); + } +} + //--------------------------------------------------------------------+ // Song Data //--------------------------------------------------------------------+ @@ -316,23 +536,42 @@ void tud_midi2_rx_cb(uint8_t itf) { (void)itf; } +// Reset playback state and re-send setup when host switches alt setting or +// renegotiates the UMP Stream protocol. +void tud_midi2_set_itf_cb(uint8_t itf, uint8_t alt) { + (void)itf; + song.setup_sent = false; + printf("[ALT] Host selected alt=%u\r\n", (unsigned)alt); +} + //--------------------------------------------------------------------+ // Initial Setup - Program Change, CC, Per-Note Management //--------------------------------------------------------------------+ void send_initial_setup(void) { - ump_jr_timestamp(0x0001); - ump_program_change(0, 0, 0, true, 0, 0); - ump_cc(0, 0, 7, 0xCCCCCCCC); // Volume 80% (32-bit) - ump_cc(0, 0, 11, 0xFFFFFFFF); // Expression 100% - ump_cc(0, 0, 64, 0x00000000); // Sustain off - ump_cc(0, 0, 1, 0x20000000); // Modulation - ump_cc(0, 0, 10, 0x80000000); // Pan center - ump_per_note_mgmt(0, 0, 0, false, true); // Per-Note reset - ump_pitch_bend(0, 0, 0x80000000); // Pitch Bend center - ump_channel_pressure(0, 0, 0x00000000); + // JR Timestamp is UMP-only (Utility MT 0x0); skipped on Alt 0 transport. + if (tud_midi2_alt_setting() == 1) ump_jr_timestamp(0x0001); + + send_program_change(0, 0, 0, true, 0, 0); + send_cc(0, 0, 7, 0xCCCCCCCC); // Volume 80% + send_cc(0, 0, 11, 0xFFFFFFFF); // Expression 100% + send_cc(0, 0, 64, 0x00000000); // Sustain off + send_cc(0, 0, 1, 0x20000000); // Modulation + send_cc(0, 0, 10, 0x80000000); // Pan center + + // Per-Note Management is MIDI 2.0 exclusive (MT 0x4 status 0xF). + // Skipped when the active path falls back to MIDI 1.0 in any form. + if (tud_midi2_alt_setting() == 1 && + tud_midi2_protocol() == MIDI_PROTOCOL_MIDI2) { + ump_per_note_mgmt(0, 0, 0, false, true); + } - printf("[SETUP] Piano | Vol 80%% | UMP\r\n"); + send_pitch_bend(0, 0, 0x80000000); // Center + send_channel_pressure(0, 0, 0x00000000); + + printf("[SETUP] Piano | Vol 80%% | alt=%u proto=%u\r\n", + (unsigned)tud_midi2_alt_setting(), + (unsigned)tud_midi2_protocol()); } //--------------------------------------------------------------------+ @@ -364,9 +603,9 @@ void update_song_playback(uint32_t now_ms) { // Note duration elapsed: send Note Off, advance if (song.note_is_active && (now_ms - song.note_start_ms) >= current->duration_ms) { if (song.active_pitch > 0) { - if (current->bend_cents != 0) ump_pitch_bend(0, 0, 0x80000000); - if (current->pressure > 0) ump_channel_pressure(0, 0, 0x00000000); - ump_note_off(0, 0, song.active_pitch, V_P, UMP_ATTR_NONE, 0); + if (current->bend_cents != 0) send_pitch_bend(0, 0, 0x80000000); + if (current->pressure > 0) send_channel_pressure(0, 0, 0x00000000); + send_note_off(0, 0, song.active_pitch, V_P); } song.note_is_active = false; @@ -395,16 +634,19 @@ void update_song_playback(uint32_t now_ms) { } if (next->pitch > 0) { - ump_jr_timestamp((uint16_t)(now_ms & 0xFFFF)); + // JR Timestamp is UMP-only; skip on Alt 0 transport. + if (tud_midi2_alt_setting() == 1) { + ump_jr_timestamp((uint16_t)(now_ms & 0xFFFF)); + } if (next->bend_cents != 0) { - ump_pitch_bend(0, 0, cents_to_pitch_bend(next->bend_cents)); + send_pitch_bend(0, 0, cents_to_pitch_bend(next->bend_cents)); } - ump_note_on(0, 0, next->pitch, next->velocity, UMP_ATTR_NONE, 0); + send_note_on(0, 0, next->pitch, next->velocity); if (next->pressure > 0) { - ump_channel_pressure(0, 0, next->pressure); + send_channel_pressure(0, 0, next->pressure); } if (next->pressure > 0x40000000 && next->duration_ms > 500) { - ump_poly_pressure(0, 0, next->pitch, next->pressure); + send_poly_pressure(0, 0, next->pitch, next->pressure); } song.active_pitch = next->pitch; @@ -428,20 +670,13 @@ int main(void) { board_init(); printf("\r\n"); printf("===========================================\r\n"); - printf(" RP2040 MIDI 2.0 Device\r\n"); + printf(" TinyUSB MIDI 2.0 Device\r\n"); printf("===========================================\r\n"); - printf("Tempo: 120 BPM | Format: UMP 64-bit\r\n"); - printf("Song: %u notes with full MIDI 2.0 expression\r\n", - (unsigned)SONG_LENGTH); - printf("Features:\r\n"); - printf(" - 16-bit Velocity (vs 7-bit MIDI 1.0)\r\n"); - printf(" - 32-bit Control Change\r\n"); - printf(" - 32-bit Pitch Bend (vs 14-bit MIDI 1.0)\r\n"); - printf(" - 32-bit Channel Pressure\r\n"); - printf(" - 32-bit Poly Pressure (per-note)\r\n"); - printf(" - Per-Note Management (MIDI 2.0 exclusive)\r\n"); - printf(" - Program Change with Bank Select\r\n"); - printf(" - JR Timestamps\r\n"); + printf("Tempo: 120 BPM | Song: %u notes\r\n", (unsigned)SONG_LENGTH); + printf("Transport + protocol fallback:\r\n"); + printf(" Alt 0 -> USB-MIDI 1.0 32-bit Event Packets (packet_write)\r\n"); + printf(" Alt 1 + MIDI1 -> UMP MT 0x2 MIDI 1.0 Channel Voice (ump_write)\r\n"); + printf(" Alt 1 + MIDI2 -> UMP MT 0x4 MIDI 2.0 Channel Voice (ump_write)\r\n"); printf("Status: Initializing...\r\n"); tusb_rhport_init_t dev_init = {.role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_AUTO}; diff --git a/examples/device/midi2_device/src/usb_descriptors.c b/examples/device/midi2_device/src/usb_descriptors.c index ce289bd4d..19a43f2d8 100644 --- a/examples/device/midi2_device/src/usb_descriptors.c +++ b/examples/device/midi2_device/src/usb_descriptors.c @@ -99,7 +99,7 @@ enum { static char const *string_desc_arr[] = { (const char[]) { 0x09, 0x04 }, // 0: Language "TinyUSB", // 1: Manufacturer - "RP2040 MIDI 2.0", // 2: Product + "TinyUSB MIDI 2.0", // 2: Product NULL, // 3: Serial }; -- cgit v1.3.1 From 3efac1b28c4174b0734ef106ce8496fa39657b47 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 13:00:30 -0300 Subject: midi2: host example - simplify and rename to midi2_host Drop Feather-specific scaffolding (SSD1306 display, font, PIO-USB pin override) and rename the directory to midi2_host. The example is now a single main.c + tusb_config.h pair following the midi_rx pattern, with a printf-only UMP decoder for Channel Voice messages. Build-tested on adafruit_feather_rp2040_usb_host, daisyseed (stm32h7), mimxrt1010_evk, stm32f407disco, raspberry_pi_pico2, b_u585i_iot2a, portenta_c33, stm32h503nucleo, stlinkv3mini, feather_stm32f405. Ref #3571 --- examples/host/CMakeLists.txt | 2 +- examples/host/midi2_host/CMakeLists.txt | 29 +++ examples/host/midi2_host/CMakePresets.json | 6 + examples/host/midi2_host/Makefile | 13 + examples/host/midi2_host/src/main.c | 165 ++++++++++++ examples/host/midi2_host/src/tusb_config.h | 106 ++++++++ examples/host/midi2_host_feather/CMakeLists.txt | 38 --- examples/host/midi2_host_feather/Makefile | 27 -- examples/host/midi2_host_feather/src/display.c | 276 --------------------- examples/host/midi2_host_feather/src/display.h | 46 ---- examples/host/midi2_host_feather/src/font5x7.h | 107 -------- examples/host/midi2_host_feather/src/main.c | 267 -------------------- examples/host/midi2_host_feather/src/tusb_config.h | 91 ------- 13 files changed, 320 insertions(+), 853 deletions(-) create mode 100644 examples/host/midi2_host/CMakeLists.txt create mode 100644 examples/host/midi2_host/CMakePresets.json create mode 100644 examples/host/midi2_host/Makefile create mode 100644 examples/host/midi2_host/src/main.c create mode 100644 examples/host/midi2_host/src/tusb_config.h delete mode 100644 examples/host/midi2_host_feather/CMakeLists.txt delete mode 100644 examples/host/midi2_host_feather/Makefile delete mode 100644 examples/host/midi2_host_feather/src/display.c delete mode 100644 examples/host/midi2_host_feather/src/display.h delete mode 100644 examples/host/midi2_host_feather/src/font5x7.h delete mode 100644 examples/host/midi2_host_feather/src/main.c delete mode 100644 examples/host/midi2_host_feather/src/tusb_config.h (limited to 'examples') diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt index 2cf2c10af..7c74e3c73 100644 --- a/examples/host/CMakeLists.txt +++ b/examples/host/CMakeLists.txt @@ -13,7 +13,7 @@ set(EXAMPLE_LIST device_info hid_controller midi_rx - midi2_host_feather + midi2_host msc_file_explorer msc_file_explorer_freertos ) diff --git a/examples/host/midi2_host/CMakeLists.txt b/examples/host/midi2_host/CMakeLists.txt new file mode 100644 index 000000000..0ec03bf5f --- /dev/null +++ b/examples/host/midi2_host/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +project(midi2_host C CXX ASM) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) + +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") + return() +endif() + +add_executable(${PROJECT_NAME}) + +# Example source +target_sources(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ) + +# Example include +target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +# Configure compilation flags and libraries for the example without RTOS. +# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. +family_configure_host_example(${PROJECT_NAME} noos) diff --git a/examples/host/midi2_host/CMakePresets.json b/examples/host/midi2_host/CMakePresets.json new file mode 100644 index 000000000..5cd8971e9 --- /dev/null +++ b/examples/host/midi2_host/CMakePresets.json @@ -0,0 +1,6 @@ +{ + "version": 6, + "include": [ + "../../../hw/bsp/BoardPresets.json" + ] +} diff --git a/examples/host/midi2_host/Makefile b/examples/host/midi2_host/Makefile new file mode 100644 index 000000000..f8292385e --- /dev/null +++ b/examples/host/midi2_host/Makefile @@ -0,0 +1,13 @@ +include ../../../hw/bsp/family_support.mk + +INC += \ + src \ + + +# Example source +EXAMPLE_SOURCE += \ + src/main.c + +SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../../hw/bsp/family_rules.mk diff --git a/examples/host/midi2_host/src/main.c b/examples/host/midi2_host/src/main.c new file mode 100644 index 000000000..63b08318c --- /dev/null +++ b/examples/host/midi2_host/src/main.c @@ -0,0 +1,165 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Minimal USB-MIDI 2.0 host example. +// Receives UMP from any MIDI 2.0 device, prints each packet to stdout. + +#include +#include +#include "bsp/board_api.h" +#include "tusb.h" +#include "class/midi/midi2_host.h" + +//--------------------------------------------------------------------+ +// State +//--------------------------------------------------------------------+ + +static uint8_t midi2_idx = 0xFF; + +//--------------------------------------------------------------------+ +// UMP printer - shows MT and word(s) in hex; decodes Channel Voice +//--------------------------------------------------------------------+ + +static void print_ump(const uint32_t* words, uint8_t wc) { + uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F); + uint8_t group = (uint8_t)((words[0] >> 24) & 0x0F); + + if (mt == 0x4 && wc >= 2) { + // MIDI 2.0 Channel Voice + uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); + uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F); + uint8_t data1 = (uint8_t)((words[0] >> 8) & 0x7F); + switch (status) { + case 0x9: + printf("[g%u ch%u] M2 NoteOn n=%u vel=%04X attr=%04X\r\n", + group, channel, data1, + (unsigned)((words[1] >> 16) & 0xFFFF), + (unsigned)(words[1] & 0xFFFF)); + break; + case 0x8: + printf("[g%u ch%u] M2 NoteOff n=%u vel=%04X\r\n", + group, channel, data1, + (unsigned)((words[1] >> 16) & 0xFFFF)); + break; + case 0xB: + printf("[g%u ch%u] M2 CC#%u = %08lX\r\n", + group, channel, data1, (unsigned long)words[1]); + break; + case 0xC: + printf("[g%u ch%u] M2 ProgChg %u\r\n", + group, channel, (unsigned)((words[1] >> 24) & 0x7F)); + break; + case 0xD: + printf("[g%u ch%u] M2 ChanPress %08lX\r\n", + group, channel, (unsigned long)words[1]); + break; + case 0xE: + printf("[g%u ch%u] M2 PitchBend %08lX\r\n", + group, channel, (unsigned long)words[1]); + break; + default: + printf("[g%u ch%u] M2 status=0x%X w0=%08lX w1=%08lX\r\n", + group, channel, status, + (unsigned long)words[0], (unsigned long)words[1]); + break; + } + } else if (mt == 0x2 && wc == 1) { + // MIDI 1.0 Channel Voice + uint8_t status = (uint8_t)((words[0] >> 16) & 0xFF); + uint8_t data1 = (uint8_t)((words[0] >> 8) & 0x7F); + uint8_t data2 = (uint8_t)(words[0] & 0x7F); + printf("[g%u] M1 %02X %02X %02X\r\n", group, status, data1, data2); + } else { + printf("UMP MT=0x%X wc=%u w0=%08lX\r\n", + mt, wc, (unsigned long)words[0]); + } +} + +//--------------------------------------------------------------------+ +// MIDI 2.0 Host Callbacks +//--------------------------------------------------------------------+ + +void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) { + (void)idx; + printf("MIDI2 descriptor: %s, RX cables=%u TX cables=%u\r\n", + d->protocol_version ? "MIDI 2.0" : "MIDI 1.0", + d->rx_cable_count, d->tx_cable_count); +} + +void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) { + midi2_idx = idx; + printf("MIDI2 mounted: idx=%u protocol=%s\r\n", + idx, m->protocol_version ? "MIDI 2.0" : "MIDI 1.0"); +} + +void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void)xferred_bytes; + + uint32_t words[16]; + while (1) { + uint32_t n = tuh_midi2_ump_read(idx, words, 16); + if (n == 0) break; + + uint32_t i = 0; + while (i < n) { + uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F); + uint8_t wc = midi2_ump_word_count(mt); + if (i + wc > n) break; + print_ump(&words[i], wc); + i += wc; + } + } +} + +void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) { + (void)idx; (void)xferred_bytes; +} + +void tuh_midi2_umount_cb(uint8_t idx) { + (void)idx; + midi2_idx = 0xFF; + printf("MIDI2 unmounted\r\n"); +} + +//--------------------------------------------------------------------+ +// Main +//--------------------------------------------------------------------+ + +int main(void) { + board_init(); + + printf("\r\nTinyUSB Host MIDI 2.0 Example\r\n"); + + tusb_rhport_init_t host_init = { + .role = TUSB_ROLE_HOST, + .speed = TUSB_SPEED_AUTO, + }; + tusb_init(BOARD_TUH_RHPORT, &host_init); + + while (1) { + tuh_task(); + } + + return 0; +} diff --git a/examples/host/midi2_host/src/tusb_config.h b/examples/host/midi2_host/src/tusb_config.h new file mode 100644 index 000000000..4bd6ef2ea --- /dev/null +++ b/examples/host/midi2_host/src/tusb_config.h @@ -0,0 +1,106 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026 Saulo Verissimo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef TUSB_CONFIG_H_ +#define TUSB_CONFIG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifdef ESP_PLATFORM +#define CFG_TUSB_OS_INC_PATH freertos/ +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +#ifndef CFG_TUH_MEM_SECTION +#define CFG_TUH_MEM_SECTION +#endif + +#ifndef CFG_TUH_MEM_ALIGN +#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//--------------------------------------------------------------------+ +// Host Configuration +//--------------------------------------------------------------------+ + +#define CFG_TUH_ENABLED 1 + +#if CFG_TUSB_MCU == OPT_MCU_RP2040 + // #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller + // #define CFG_TUH_MAX3421 1 // use max3421 as host controller + + // host roothub port is 1 if using either pio-usb or max3421 + #if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421) + #define BOARD_TUH_RHPORT 1 + #endif +#endif + +#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED + +//------------------------- Board Specific --------------------------+ + +#ifndef BOARD_TUH_RHPORT +#define BOARD_TUH_RHPORT 0 +#endif + +#ifndef BOARD_TUH_MAX_SPEED +#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//--------------------------------------------------------------------+ +// Driver Configuration +//--------------------------------------------------------------------+ + +#define CFG_TUH_ENUMERATION_BUFSIZE 256 + +#define CFG_TUH_HUB 1 +#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) + +// USB-MIDI 2.0 Host +#define CFG_TUH_MIDI2 CFG_TUH_DEVICE_MAX +#define CFG_TUH_MIDI2_RX_BUFSIZE 512 +#define CFG_TUH_MIDI2_TX_BUFSIZE 512 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/host/midi2_host_feather/CMakeLists.txt b/examples/host/midi2_host_feather/CMakeLists.txt deleted file mode 100644 index 5d34b8170..000000000 --- a/examples/host/midi2_host_feather/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) - -project(midi2_host_feather C CXX ASM) - -family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) - -# This example requires PIO-USB and Pico SDK (I2C, SSD1306 display) -if(NOT FAMILY STREQUAL "rp2040") - return() -endif() - -add_executable(${PROJECT_NAME}) - -target_sources(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/display.c -) - -target_include_directories(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src -) - -family_configure_host_example(${PROJECT_NAME} noos) - -# Adafruit Feather RP2040 USB Host: PIO-USB on GP16/GP17 -target_compile_definitions(${PROJECT_NAME} PRIVATE - PIO_USB_DP_PIN_DEFAULT=16 -) -target_compile_options(${PROJECT_NAME} PRIVATE - -Wno-type-limits -) - -# SSD1306 display (I2C via STEMMA QT) -target_link_libraries(${PROJECT_NAME} PUBLIC - hardware_i2c -) diff --git a/examples/host/midi2_host_feather/Makefile b/examples/host/midi2_host_feather/Makefile deleted file mode 100644 index 933d19434..000000000 --- a/examples/host/midi2_host_feather/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -# This example requires RP2040 (PIO-USB, Pico SDK I2C, SSD1306 display) -ifeq (,$(findstring rp2040,$(FAMILY))) -$(info Skipping midi2_host_feather: requires FAMILY=rp2040) -all: - @: -.DEFAULT: - @: -else - -include ../../../hw/bsp/family_support.mk - -INC += \ - src \ - -# Example source -EXAMPLE_SOURCE += \ - src/main.c \ - src/display.c \ - -SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE)) - -# Suppress pre-existing warning -CFLAGS_GCC += -Wno-type-limits - -include ../../../hw/bsp/family_rules.mk - -endif diff --git a/examples/host/midi2_host_feather/src/display.c b/examples/host/midi2_host_feather/src/display.c deleted file mode 100644 index 91039fa16..000000000 --- a/examples/host/midi2_host_feather/src/display.c +++ /dev/null @@ -1,276 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// SSD1306 OLED display driver (128x64, I2C) for MIDI 2.0 Host example. -// Adafruit Feather RP2040 USB Host: I2C1 via STEMMA QT (SDA = GP2, SCL = GP3) -// -// Three display phases: -// 1. Splash : title + credits (shown during init) -// 2. Connecting : spinner animation while waiting for device -// 3. Live : header + 6 scrolling log lines + status bar - -#include "display.h" -#include -#include -#include "pico/stdlib.h" -#include "hardware/i2c.h" -#include "hardware/gpio.h" - -#define I2C_PORT i2c1 -#define I2C_SDA 2 -#define I2C_SCL 3 -#define I2C_FREQ 400000 -#define SSD1306_ADDR 0x3C - -#define SCR_W 128 -#define SCR_H 64 -#define PAGES (SCR_H / 8) -#define CHARS_PER_LINE 21 - -//--------------------------------------------------------------------+ -// Framebuffer -//--------------------------------------------------------------------+ - -static uint8_t fb[SCR_W * PAGES]; - -//--------------------------------------------------------------------+ -// Log buffer (6 lines for live view) -//--------------------------------------------------------------------+ - -#define LOG_LINES 6 -static char log_lines[LOG_LINES][CHARS_PER_LINE + 1]; -static int log_count = 0; - -//--------------------------------------------------------------------+ -// Minimal 5x7 font (ASCII 32-126) -//--------------------------------------------------------------------+ - -#include "font5x7.h" - -//--------------------------------------------------------------------+ -// SSD1306 I2C low-level -//--------------------------------------------------------------------+ - -static void ssd_cmd(uint8_t cmd) { - uint8_t buf[2] = { 0x00, cmd }; - i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, 2, false); -} - -static void ssd_data(const uint8_t* data, size_t len) { - uint8_t buf[SCR_W + 1]; - buf[0] = 0x40; - size_t chunk = (len > SCR_W) ? SCR_W : len; - memcpy(buf + 1, data, chunk); - i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, chunk + 1, false); -} - -static void ssd_flush(void) { - ssd_cmd(0x21); ssd_cmd(0); ssd_cmd(127); - ssd_cmd(0x22); ssd_cmd(0); ssd_cmd(7); - for (int page = 0; page < PAGES; page++) { - ssd_data(&fb[page * SCR_W], SCR_W); - } -} - -//--------------------------------------------------------------------+ -// Framebuffer drawing -//--------------------------------------------------------------------+ - -static void fb_clear(void) { - memset(fb, 0, sizeof(fb)); -} - -static void fb_char(int x, int y, char c) { - if (c < 32 || c > 126) c = '?'; - const uint8_t* glyph = font5x7 + (c - 32) * 5; - int page = y / 8; - int bit_offset = y % 8; - - if (page >= PAGES || x + 5 > SCR_W) return; - - for (int col = 0; col < 5; col++) { - uint8_t column_data = glyph[col]; - fb[(page * SCR_W) + x + col] |= (uint8_t)(column_data << bit_offset); - if (bit_offset > 0 && page + 1 < PAGES) { - fb[((page + 1) * SCR_W) + x + col] |= (uint8_t)(column_data >> (8 - bit_offset)); - } - } -} - -static void fb_string(int x, int y, const char* str) { - while (*str) { - fb_char(x, y, *str); - x += 6; - if (x + 6 > SCR_W) break; - str++; - } -} - -// Draw a horizontal line (1px) -static void fb_hline(int x0, int x1, int y) { - int page = y / 8; - uint8_t mask = (uint8_t)(1 << (y % 8)); - if (page >= PAGES) return; - for (int x = x0; x <= x1 && x < SCR_W; x++) { - fb[page * SCR_W + x] |= mask; - } -} - -//--------------------------------------------------------------------+ -// Phase 1: Splash -//--------------------------------------------------------------------+ - -void display_init(void) { - i2c_init(I2C_PORT, I2C_FREQ); - gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); - gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); - gpio_pull_up(I2C_SDA); - gpio_pull_up(I2C_SCL); - - sleep_ms(100); - - // SSD1306 init sequence - ssd_cmd(0xAE); - ssd_cmd(0xD5); ssd_cmd(0x80); - ssd_cmd(0xA8); ssd_cmd(0x3F); - ssd_cmd(0xD3); ssd_cmd(0x00); - ssd_cmd(0x40); - ssd_cmd(0x8D); ssd_cmd(0x14); - ssd_cmd(0x20); ssd_cmd(0x00); - ssd_cmd(0xA1); - ssd_cmd(0xC8); - ssd_cmd(0xDA); ssd_cmd(0x12); - ssd_cmd(0x81); ssd_cmd(0xCF); - ssd_cmd(0xD9); ssd_cmd(0xF1); - ssd_cmd(0xDB); ssd_cmd(0x40); - ssd_cmd(0xA4); - ssd_cmd(0xA6); - ssd_cmd(0xAF); - - // Splash screen - fb_clear(); - fb_string(4, 8, "TinyUSB MIDI 2.0"); - fb_hline(4, 123, 18); - fb_string(16, 24, "USB Host Demo"); - fb_string(4, 40, "Feather RP2040"); - fb_string(4, 52, "PIO-USB + SSD1306"); - ssd_flush(); -} - -//--------------------------------------------------------------------+ -// Phase 2: Connecting (spinner) -//--------------------------------------------------------------------+ - -static const char SPINNER[] = "|/-\\"; - -void display_connecting(uint32_t elapsed_ms) { - int idx = (int)((elapsed_ms / 200) % 4); - - // Clear bottom 2 pages for spinner area - memset(&fb[6 * SCR_W], 0, SCR_W); - memset(&fb[7 * SCR_W], 0, SCR_W); - - char line[CHARS_PER_LINE + 1]; - snprintf(line, sizeof(line), "%c Waiting device...", SPINNER[idx]); - fb_string(4, 52, line); - ssd_flush(); -} - -//--------------------------------------------------------------------+ -// Phase 3: Live view -//--------------------------------------------------------------------+ - -// Layout: -// y=0 : "TinyUSB MIDI 2.0 Host" (header, fixed) -// y=9 : separator line -// y=10 : log line 0 -// y=18 : log line 1 -// y=26 : log line 2 -// y=34 : log line 3 -// y=42 : log line 4 -// y=50 : log line 5 -// y=57 : separator line -// y=58 : status bar - -static bool live_mode = false; - -static void draw_live_frame(void) { - fb_clear(); - fb_string(0, 0, "TinyUSB MIDI 2.0 Host"); - fb_hline(0, 127, 9); - fb_hline(0, 127, 57); -} - -void display_live_begin(void) { - live_mode = true; - log_count = 0; - memset(log_lines, 0, sizeof(log_lines)); - - draw_live_frame(); - fb_string(0, 58, "Connected"); - ssd_flush(); -} - -void display_log(const char* text, uint16_t color) { - (void)color; - - if (!live_mode) { - display_live_begin(); - } - - // Scroll up if full - if (log_count >= LOG_LINES) { - for (int i = 0; i < LOG_LINES - 1; i++) { - strncpy(log_lines[i], log_lines[i + 1], CHARS_PER_LINE); - log_lines[i][CHARS_PER_LINE] = '\0'; - } - log_count = LOG_LINES - 1; - } - - strncpy(log_lines[log_count], text, CHARS_PER_LINE); - log_lines[log_count][CHARS_PER_LINE] = '\0'; - log_count++; - - // Redraw: header + log + status - draw_live_frame(); - for (int i = 0; i < log_count && i < LOG_LINES; i++) { - fb_string(0, 10 + i * 8, log_lines[i]); - } - ssd_flush(); -} - -void display_status(const char* text) { - if (!live_mode) return; - - // Clear status area (page 7) - memset(&fb[7 * SCR_W], 0, SCR_W); - // Redraw separator (might have been cleared) - fb_hline(0, 127, 57); - - char status[CHARS_PER_LINE + 1]; - strncpy(status, text, CHARS_PER_LINE); - status[CHARS_PER_LINE] = '\0'; - fb_string(0, 58, status); - ssd_flush(); -} diff --git a/examples/host/midi2_host_feather/src/display.h b/examples/host/midi2_host_feather/src/display.h deleted file mode 100644 index ad8ead81d..000000000 --- a/examples/host/midi2_host_feather/src/display.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef DISPLAY_H_ -#define DISPLAY_H_ - -#include -#include - -// Initialize SSD1306 display and show splash screen -void display_init(void); - -// Show waiting/connecting animation (call periodically) -void display_connecting(uint32_t elapsed_ms); - -// Transition to live message view -void display_live_begin(void); - -// Add a line to the scrolling log area (6 visible lines) -void display_log(const char* text, uint16_t color); - -// Update the status bar (bottom line) -void display_status(const char* text); - -#endif diff --git a/examples/host/midi2_host_feather/src/font5x7.h b/examples/host/midi2_host_feather/src/font5x7.h deleted file mode 100644 index e515faac1..000000000 --- a/examples/host/midi2_host_feather/src/font5x7.h +++ /dev/null @@ -1,107 +0,0 @@ -// Minimal 5x7 font, ASCII 32-126. Public domain. -// 5 bytes per character (5 columns, 7 rows, LSB=top row) - -#ifndef FONT5X7_H_ -#define FONT5X7_H_ - -#include - -static const uint8_t font5x7[] = { - 0x00,0x00,0x00,0x00,0x00, // 32 (space) - 0x00,0x00,0x5F,0x00,0x00, // 33 ! - 0x00,0x07,0x00,0x07,0x00, // 34 " - 0x14,0x7F,0x14,0x7F,0x14, // 35 # - 0x24,0x2A,0x7F,0x2A,0x12, // 36 $ - 0x23,0x13,0x08,0x64,0x62, // 37 % - 0x36,0x49,0x55,0x22,0x50, // 38 & - 0x00,0x05,0x03,0x00,0x00, // 39 ' - 0x00,0x1C,0x22,0x41,0x00, // 40 ( - 0x00,0x41,0x22,0x1C,0x00, // 41 ) - 0x08,0x2A,0x1C,0x2A,0x08, // 42 * - 0x08,0x08,0x3E,0x08,0x08, // 43 + - 0x00,0x50,0x30,0x00,0x00, // 44 , - 0x08,0x08,0x08,0x08,0x08, // 45 - - 0x00,0x60,0x60,0x00,0x00, // 46 . - 0x20,0x10,0x08,0x04,0x02, // 47 / - 0x3E,0x51,0x49,0x45,0x3E, // 48 0 - 0x00,0x42,0x7F,0x40,0x00, // 49 1 - 0x42,0x61,0x51,0x49,0x46, // 50 2 - 0x21,0x41,0x45,0x4B,0x31, // 51 3 - 0x18,0x14,0x12,0x7F,0x10, // 52 4 - 0x27,0x45,0x45,0x45,0x39, // 53 5 - 0x3C,0x4A,0x49,0x49,0x30, // 54 6 - 0x01,0x71,0x09,0x05,0x03, // 55 7 - 0x36,0x49,0x49,0x49,0x36, // 56 8 - 0x06,0x49,0x49,0x29,0x1E, // 57 9 - 0x00,0x36,0x36,0x00,0x00, // 58 : - 0x00,0x56,0x36,0x00,0x00, // 59 ; - 0x00,0x08,0x14,0x22,0x41, // 60 < - 0x14,0x14,0x14,0x14,0x14, // 61 = - 0x41,0x22,0x14,0x08,0x00, // 62 > - 0x02,0x01,0x51,0x09,0x06, // 63 ? - 0x32,0x49,0x79,0x41,0x3E, // 64 @ - 0x7E,0x11,0x11,0x11,0x7E, // 65 A - 0x7F,0x49,0x49,0x49,0x36, // 66 B - 0x3E,0x41,0x41,0x41,0x22, // 67 C - 0x7F,0x41,0x41,0x22,0x1C, // 68 D - 0x7F,0x49,0x49,0x49,0x41, // 69 E - 0x7F,0x09,0x09,0x01,0x01, // 70 F - 0x3E,0x41,0x41,0x51,0x32, // 71 G - 0x7F,0x08,0x08,0x08,0x7F, // 72 H - 0x00,0x41,0x7F,0x41,0x00, // 73 I - 0x20,0x40,0x41,0x3F,0x01, // 74 J - 0x7F,0x08,0x14,0x22,0x41, // 75 K - 0x7F,0x40,0x40,0x40,0x40, // 76 L - 0x7F,0x02,0x04,0x02,0x7F, // 77 M - 0x7F,0x04,0x08,0x10,0x7F, // 78 N - 0x3E,0x41,0x41,0x41,0x3E, // 79 O - 0x7F,0x09,0x09,0x09,0x06, // 80 P - 0x3E,0x41,0x51,0x21,0x5E, // 81 Q - 0x7F,0x09,0x19,0x29,0x46, // 82 R - 0x46,0x49,0x49,0x49,0x31, // 83 S - 0x01,0x01,0x7F,0x01,0x01, // 84 T - 0x3F,0x40,0x40,0x40,0x3F, // 85 U - 0x1F,0x20,0x40,0x20,0x1F, // 86 V - 0x7F,0x20,0x18,0x20,0x7F, // 87 W - 0x63,0x14,0x08,0x14,0x63, // 88 X - 0x03,0x04,0x78,0x04,0x03, // 89 Y - 0x61,0x51,0x49,0x45,0x43, // 90 Z - 0x00,0x00,0x7F,0x41,0x41, // 91 [ - 0x02,0x04,0x08,0x10,0x20, // 92 backslash - 0x41,0x41,0x7F,0x00,0x00, // 93 ] - 0x04,0x02,0x01,0x02,0x04, // 94 ^ - 0x40,0x40,0x40,0x40,0x40, // 95 _ - 0x00,0x01,0x02,0x04,0x00, // 96 ` - 0x20,0x54,0x54,0x54,0x78, // 97 a - 0x7F,0x48,0x44,0x44,0x38, // 98 b - 0x38,0x44,0x44,0x44,0x20, // 99 c - 0x38,0x44,0x44,0x48,0x7F, // 100 d - 0x38,0x54,0x54,0x54,0x18, // 101 e - 0x08,0x7E,0x09,0x01,0x02, // 102 f - 0x08,0x14,0x54,0x54,0x3C, // 103 g - 0x7F,0x08,0x04,0x04,0x78, // 104 h - 0x00,0x44,0x7D,0x40,0x00, // 105 i - 0x20,0x40,0x44,0x3D,0x00, // 106 j - 0x00,0x7F,0x10,0x28,0x44, // 107 k - 0x00,0x41,0x7F,0x40,0x00, // 108 l - 0x7C,0x04,0x18,0x04,0x78, // 109 m - 0x7C,0x08,0x04,0x04,0x78, // 110 n - 0x38,0x44,0x44,0x44,0x38, // 111 o - 0x7C,0x14,0x14,0x14,0x08, // 112 p - 0x08,0x14,0x14,0x18,0x7C, // 113 q - 0x7C,0x08,0x04,0x04,0x08, // 114 r - 0x48,0x54,0x54,0x54,0x20, // 115 s - 0x04,0x3F,0x44,0x40,0x20, // 116 t - 0x3C,0x40,0x40,0x20,0x7C, // 117 u - 0x1C,0x20,0x40,0x20,0x1C, // 118 v - 0x3C,0x40,0x30,0x40,0x3C, // 119 w - 0x44,0x28,0x10,0x28,0x44, // 120 x - 0x0C,0x50,0x50,0x50,0x3C, // 121 y - 0x44,0x64,0x54,0x4C,0x44, // 122 z - 0x00,0x08,0x36,0x41,0x00, // 123 { - 0x00,0x00,0x7F,0x00,0x00, // 124 | - 0x00,0x41,0x36,0x08,0x00, // 125 } - 0x10,0x08,0x08,0x10,0x08, // 126 ~ -}; - -#endif diff --git a/examples/host/midi2_host_feather/src/main.c b/examples/host/midi2_host_feather/src/main.c deleted file mode 100644 index 7c996355d..000000000 --- a/examples/host/midi2_host_feather/src/main.c +++ /dev/null @@ -1,267 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -// MIDI 2.0 Host Example: Adafruit Feather RP2040 USB Host -// -// Receives UMP from a MIDI 2.0 Device via PIO-USB (GP16/GP17). -// SSD1306 OLED (I2C1, GP2/GP3) shows splash, spinner, then live UMP messages. - -#include -#include -#include "bsp/board_api.h" -#include "tusb.h" -#include "hardware/gpio.h" -#include "class/midi/midi2_host.h" -#include "class/midi/midi.h" -#include "display.h" - -//--------------------------------------------------------------------+ -// State -//--------------------------------------------------------------------+ - -static uint32_t note_count = 0; -static uint8_t midi2_idx = 0xFF; -static bool device_connected = false; -static bool mounted = false; - -//--------------------------------------------------------------------+ -// Note name helper -//--------------------------------------------------------------------+ - -static const char* NOTE_NAMES[] = { - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" -}; - -static void note_name(uint8_t pitch, char* buf, size_t len) { - int octave = (pitch / 12) - 1; - snprintf(buf, len, "%s%d", NOTE_NAMES[pitch % 12], octave); -} - -//--------------------------------------------------------------------+ -// UMP decoder -//--------------------------------------------------------------------+ - -static void decode_ump(const uint32_t* words, uint8_t word_count) { - uint8_t mt = (uint8_t)((words[0] >> 28) & 0x0F); - char line[64]; - - if (mt == 0x4 && word_count >= 2) { - uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); - uint8_t channel = (uint8_t)((words[0] >> 16) & 0x0F); - - switch (status) { - case 0x9: { - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - uint16_t vel = (uint16_t)((words[1] >> 16) & 0xFFFF); - char nn[6]; - note_name(pitch, nn, sizeof(nn)); - snprintf(line, sizeof(line), "NoteOn %s ch%u v%04X", nn, channel, vel); - display_log(line, 0x07E0); - note_count++; - break; - } - case 0x8: { - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - char nn[6]; - note_name(pitch, nn, sizeof(nn)); - snprintf(line, sizeof(line), "NoteOff %s ch%u", nn, channel); - display_log(line, 0x8410); - break; - } - case 0xB: { - uint8_t idx = (uint8_t)((words[0] >> 8) & 0x7F); - snprintf(line, sizeof(line), "CC%-3u %08lX", idx, (unsigned long)words[1]); - display_log(line, 0x001F); - break; - } - case 0xC: { - uint8_t prog = (uint8_t)((words[1] >> 24) & 0x7F); - snprintf(line, sizeof(line), "ProgChg %u", prog); - display_log(line, 0xFFE0); - break; - } - case 0xE: { - snprintf(line, sizeof(line), "PBend %08lX", (unsigned long)words[1]); - display_log(line, 0xF81F); - break; - } - case 0xD: { - snprintf(line, sizeof(line), "CPress %08lX", (unsigned long)words[1]); - display_log(line, 0xFC10); - break; - } - case 0xA: { - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - char nn[6]; - note_name(pitch, nn, sizeof(nn)); - snprintf(line, sizeof(line), "PolyP %s %08lX", nn, (unsigned long)words[1]); - display_log(line, 0xFC10); - break; - } - case 0xF: { - uint8_t pitch = (uint8_t)((words[0] >> 8) & 0x7F); - uint8_t flags = (uint8_t)(words[0] & 0xFF); - snprintf(line, sizeof(line), "PN-Mgmt n%u f%02X", pitch, flags); - display_log(line, 0x07FF); - break; - } - default: { - snprintf(line, sizeof(line), "M2CVM s=0x%X", status); - display_log(line, 0xFFFF); - break; - } - } - } else if (mt == 0x0) { - uint8_t status = (uint8_t)((words[0] >> 20) & 0x0F); - if (status == 0x2) { - uint16_t ts = words[0] & 0xFFFF; - snprintf(line, sizeof(line), "JR-TS %04X", ts); - display_log(line, 0x8410); - } - } else { - snprintf(line, sizeof(line), "MT=0x%X w0=%08lX", - mt, (unsigned long)words[0]); - display_log(line, 0xFFFF); - } -} - -//--------------------------------------------------------------------+ -// MIDI 2.0 Host Callbacks -//--------------------------------------------------------------------+ - -void tuh_midi2_descriptor_cb(uint8_t idx, const tuh_midi2_descriptor_cb_t* d) { - (void)idx; - char line[48]; - snprintf(line, sizeof(line), "%s RX=%u TX=%u", - d->protocol_version ? "MIDI2" : "MIDI1", - d->rx_cable_count, d->tx_cable_count); - display_log(line, 0x07E0); -} - -void tuh_midi2_mount_cb(uint8_t idx, const tuh_midi2_mount_cb_t* m) { - midi2_idx = idx; - mounted = true; - - display_live_begin(); - - if (m->protocol_version) { - display_log("MIDI 2.0 ready", 0x07E0); - } else { - display_log("MIDI 1.0 device", 0xFFE0); - } - display_status("Receiving..."); -} - -void tuh_midi2_rx_cb(uint8_t idx, uint32_t xferred_bytes) { - (void)xferred_bytes; - - uint32_t words[16]; - while (1) { - uint32_t n = tuh_midi2_ump_read(idx, words, 16); - if (n == 0) break; - - uint32_t i = 0; - while (i < n) { - uint8_t mt = (uint8_t)((words[i] >> 28) & 0x0F); - uint8_t wc = midi2_ump_word_count(mt); - if (i + wc > n) break; - decode_ump(&words[i], wc); - i += wc; - } - } -} - -void tuh_midi2_tx_cb(uint8_t idx, uint32_t xferred_bytes) { - (void)idx; (void)xferred_bytes; -} - -void tuh_midi2_umount_cb(uint8_t idx) { - (void)idx; - midi2_idx = 0xFF; - device_connected = false; - mounted = false; - note_count = 0; - display_log("Disconnected", 0xF800); - display_status("Waiting..."); -} - -//--------------------------------------------------------------------+ -// Main -//--------------------------------------------------------------------+ - -int main(void) { - board_init(); - - // Enable 5V to USB-A port (Feather RP2040 USB Host: GP18) - gpio_init(18); - gpio_set_dir(18, GPIO_OUT); - gpio_put(18, 1); - - display_init(); - sleep_ms(1500); - - tusb_rhport_init_t host_init = { - .role = TUSB_ROLE_HOST, - .speed = TUSB_SPEED_FULL, - }; - tusb_init(BOARD_TUH_RHPORT, &host_init); - - uint32_t last_status_ms = 0; - - while (1) { - tuh_task(); - - uint32_t now = tusb_time_millis_api(); - - if (!mounted) { - // Spinner while waiting for device - if (now - last_status_ms > 200) { - last_status_ms = now; - display_connecting(now); - } - } else { - // Update note count every 2 seconds - if (now - last_status_ms > 2000) { - last_status_ms = now; - char line[22]; - snprintf(line, sizeof(line), "Notes: %lu", (unsigned long)note_count); - display_status(line); - } - } - } - - return 0; -} - -// Generic USB device mount/unmount -void tuh_mount_cb(uint8_t daddr) { - (void)daddr; - device_connected = true; -} - -void tuh_umount_cb(uint8_t daddr) { - (void)daddr; - device_connected = false; - mounted = false; -} diff --git a/examples/host/midi2_host_feather/src/tusb_config.h b/examples/host/midi2_host_feather/src/tusb_config.h deleted file mode 100644 index 362d0e2d0..000000000 --- a/examples/host/midi2_host_feather/src/tusb_config.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2026 Saulo Verissimo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef TUSB_CONFIG_H_ -#define TUSB_CONFIG_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -//--------------------------------------------------------------------+ -// Common Configuration -//--------------------------------------------------------------------+ - -#ifndef CFG_TUSB_MCU -#error CFG_TUSB_MCU must be defined -#endif - -#ifndef CFG_TUSB_OS -#define CFG_TUSB_OS OPT_OS_NONE -#endif - -#ifndef CFG_TUSB_DEBUG -#define CFG_TUSB_DEBUG 0 -#endif - -#ifndef CFG_TUH_MEM_SECTION -#define CFG_TUH_MEM_SECTION -#endif - -#ifndef CFG_TUH_MEM_ALIGN -#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4))) -#endif - -//--------------------------------------------------------------------+ -// Host Configuration -// Adafruit Feather RP2040 USB Host: PIO-USB on GP16/GP17 (USB-A Host port) -//--------------------------------------------------------------------+ - -#define CFG_TUH_ENABLED 1 - -// PIO-USB Host on rhport 1 (USB-A connector on GP16/GP17) -#define CFG_TUH_RPI_PIO_USB 1 -#define BOARD_TUH_RHPORT 1 - -#ifndef BOARD_TUH_MAX_SPEED -#define BOARD_TUH_MAX_SPEED OPT_MODE_FULL_SPEED -#endif - -#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED - -//--------------------------------------------------------------------+ -// Driver Configuration -//--------------------------------------------------------------------+ - -#define CFG_TUH_ENUMERATION_BUFSIZE 256 - -#define CFG_TUH_HUB 0 -#define CFG_TUH_DEVICE_MAX 1 - -// MIDI 2.0 Host -#define CFG_TUH_MIDI2 1 -#define CFG_TUH_MIDI2_RX_BUFSIZE 512 -#define CFG_TUH_MIDI2_TX_BUFSIZE 512 - -#ifdef __cplusplus -} -#endif - -#endif -- cgit v1.3.1 From 214de5181c7d011035f0071b3bb99c1c79206876 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 20:39:20 -0300 Subject: midi2: fix CI matrix with only.txt and skip.txt Restrict midi2_host build to MCUs with USB host support (cloned from midi_rx) and skip SAMD11 from midi2_device (ROM overflow on link). --- examples/device/midi2_device/skip.txt | 1 + examples/host/midi2_host/only.txt | 34 ++++++++++++++++++++++++++++++++++ examples/host/midi2_host/skip.txt | 1 + 3 files changed, 36 insertions(+) create mode 100644 examples/device/midi2_device/skip.txt create mode 100644 examples/host/midi2_host/only.txt create mode 100644 examples/host/midi2_host/skip.txt (limited to 'examples') diff --git a/examples/device/midi2_device/skip.txt b/examples/device/midi2_device/skip.txt new file mode 100644 index 000000000..eadb6e74a --- /dev/null +++ b/examples/device/midi2_device/skip.txt @@ -0,0 +1 @@ +mcu:SAMD11 diff --git a/examples/host/midi2_host/only.txt b/examples/host/midi2_host/only.txt new file mode 100644 index 000000000..c71aacd87 --- /dev/null +++ b/examples/host/midi2_host/only.txt @@ -0,0 +1,34 @@ +family:hpmicro +family:samd21 +family:samd5x_e5x +mcu:CH32V20X +mcu:ESP32P4 +mcu:ESP32S2 +mcu:ESP32S3 +mcu:KINETIS_KL +mcu:LPC175X_6X +mcu:LPC177X_8X +mcu:LPC18XX +mcu:LPC40XX +mcu:LPC43XX +mcu:LPC54 +mcu:LPC55 +mcu:MAX3421 +mcu:MIMXRT10XX +mcu:MIMXRT11XX +mcu:MIMXRT1XXX +mcu:MSP432E4 +mcu:RAXXX +mcu:RP2040 +mcu:RW61X +mcu:RX65X +mcu:STM32C0 +mcu:STM32F4 +mcu:STM32F7 +mcu:STM32G0 +mcu:STM32H5 +mcu:STM32H7 +mcu:STM32H7RS +mcu:STM32N6 +mcu:STM32U3 +mcu:STM32U5 diff --git a/examples/host/midi2_host/skip.txt b/examples/host/midi2_host/skip.txt new file mode 100644 index 000000000..308796869 --- /dev/null +++ b/examples/host/midi2_host/skip.txt @@ -0,0 +1 @@ +board:lpcxpresso54114 -- cgit v1.3.1 From 7c214f9d4f47cfadf18fc7e67d15cc22b1bc6f99 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 21:35:21 -0300 Subject: midi2: add sources to TINYUSB_SRC_C, skip family Make build was missing midi2_device.c/midi2_host.c from src/tinyusb.mk. Skip stm32h7s3nucleo board which exposes a pre-existing uninitialized warning in its board.h (board_init2) under Make+LTO. --- examples/device/midi2_device/skip.txt | 1 + examples/host/midi2_host/skip.txt | 1 + src/tinyusb.mk | 2 ++ 3 files changed, 4 insertions(+) (limited to 'examples') diff --git a/examples/device/midi2_device/skip.txt b/examples/device/midi2_device/skip.txt index eadb6e74a..3bc342bff 100644 --- a/examples/device/midi2_device/skip.txt +++ b/examples/device/midi2_device/skip.txt @@ -1 +1,2 @@ mcu:SAMD11 +board:stm32h7s3nucleo diff --git a/examples/host/midi2_host/skip.txt b/examples/host/midi2_host/skip.txt index 308796869..ee7ed0fc0 100644 --- a/examples/host/midi2_host/skip.txt +++ b/examples/host/midi2_host/skip.txt @@ -1 +1,2 @@ board:lpcxpresso54114 +board:stm32h7s3nucleo diff --git a/src/tinyusb.mk b/src/tinyusb.mk index e3ef35dcf..365043927 100644 --- a/src/tinyusb.mk +++ b/src/tinyusb.mk @@ -10,6 +10,7 @@ TINYUSB_SRC_C += \ src/class/dfu/dfu_rt_device.c \ src/class/hid/hid_device.c \ src/class/midi/midi_device.c \ + src/class/midi/midi2_device.c \ src/class/msc/msc_device.c \ src/class/mtp/mtp_device.c \ src/class/net/ecm_rndis_device.c \ @@ -23,5 +24,6 @@ TINYUSB_SRC_C += \ src/class/cdc/cdc_host.c \ src/class/hid/hid_host.c \ src/class/midi/midi_host.c \ + src/class/midi/midi2_host.c \ src/class/msc/msc_host.c \ src/class/vendor/vendor_host.c \ -- cgit v1.3.1 From 478e04778b8674b5ef4bf2557d305a06871ac473 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 21:44:22 -0300 Subject: midi2: remove unused ump_noop helper arm-clang fails with -Werror=unused-function; arm-gcc was silently omitting it via LTO. The helper was never called. --- examples/device/midi2_device/src/main.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'examples') diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c index 2e2488752..cc918e198 100644 --- a/examples/device/midi2_device/src/main.c +++ b/examples/device/midi2_device/src/main.c @@ -72,10 +72,6 @@ static inline void ump_send_32(uint32_t w0) { // -- Utility Messages (MT=0x0, 32-bit) -- -static inline void ump_noop(void) { - ump_send_32(UMP_MT_UTILITY); -} - static inline void ump_jr_timestamp(uint16_t timestamp) { // Word 0: [MT(0x0) | Group(0) | Status(0x0020) | Timestamp(16-bit)] ump_send_32(UMP_MT_UTILITY | 0x00200000 | (uint32_t)timestamp); -- cgit v1.3.1 From 7cf579dd49ab84163497ebc4348d208794a7e635 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 22:37:45 -0300 Subject: midi2: skip 3 more boards with same tcpp0203 BSP bug stm32h573i_dk, stm32n657nucleo and stm32n6570dk have the same pre-existing uninitialized io_ctx.GetTick in their board.h that we already skip on stm32h7s3nucleo. Trips with Make+LTO; CMake passes. --- examples/device/midi2_device/skip.txt | 6 ++++++ examples/host/midi2_host/skip.txt | 6 ++++++ 2 files changed, 12 insertions(+) (limited to 'examples') diff --git a/examples/device/midi2_device/skip.txt b/examples/device/midi2_device/skip.txt index 3bc342bff..76d694958 100644 --- a/examples/device/midi2_device/skip.txt +++ b/examples/device/midi2_device/skip.txt @@ -1,2 +1,8 @@ mcu:SAMD11 +# Skip boards exposing a pre-existing uninitialized io_ctx.GetTick in +# their board.h (called from board_init2 before TCPP0203_RegisterBusIO). +# Trips with Make+LTO; CMake passes. board:stm32h7s3nucleo +board:stm32h573i_dk +board:stm32n657nucleo +board:stm32n6570dk diff --git a/examples/host/midi2_host/skip.txt b/examples/host/midi2_host/skip.txt index ee7ed0fc0..2232e513a 100644 --- a/examples/host/midi2_host/skip.txt +++ b/examples/host/midi2_host/skip.txt @@ -1,2 +1,8 @@ board:lpcxpresso54114 +# Skip boards exposing a pre-existing uninitialized io_ctx.GetTick in +# their board.h (called from board_init2 before TCPP0203_RegisterBusIO). +# Trips with Make+LTO; CMake passes. board:stm32h7s3nucleo +board:stm32h573i_dk +board:stm32n657nucleo +board:stm32n6570dk -- cgit v1.3.1 From 2a58e8256ebe683106ef3070b5b418c8ba88c8f0 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sat, 16 May 2026 22:54:25 -0300 Subject: midi2: skip.txt comments --- examples/device/midi2_device/skip.txt | 3 --- examples/host/midi2_host/skip.txt | 3 --- 2 files changed, 6 deletions(-) (limited to 'examples') diff --git a/examples/device/midi2_device/skip.txt b/examples/device/midi2_device/skip.txt index 76d694958..4cabba2fb 100644 --- a/examples/device/midi2_device/skip.txt +++ b/examples/device/midi2_device/skip.txt @@ -1,7 +1,4 @@ mcu:SAMD11 -# Skip boards exposing a pre-existing uninitialized io_ctx.GetTick in -# their board.h (called from board_init2 before TCPP0203_RegisterBusIO). -# Trips with Make+LTO; CMake passes. board:stm32h7s3nucleo board:stm32h573i_dk board:stm32n657nucleo diff --git a/examples/host/midi2_host/skip.txt b/examples/host/midi2_host/skip.txt index 2232e513a..0f96db3e9 100644 --- a/examples/host/midi2_host/skip.txt +++ b/examples/host/midi2_host/skip.txt @@ -1,7 +1,4 @@ board:lpcxpresso54114 -# Skip boards exposing a pre-existing uninitialized io_ctx.GetTick in -# their board.h (called from board_init2 before TCPP0203_RegisterBusIO). -# Trips with Make+LTO; CMake passes. board:stm32h7s3nucleo board:stm32h573i_dk board:stm32n657nucleo -- cgit v1.3.1 From 116395a1b24f2cb1787b4110a8c3749a0435f1ee Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Sun, 17 May 2026 14:05:03 +0200 Subject: bsp: fix tcpp0203 lto uninit error Signed-off-by: HiFiPhile --- examples/device/midi2_device/skip.txt | 4 ---- examples/host/midi2_host/skip.txt | 4 ---- hw/bsp/stm32h5/boards/stm32h573i_dk/board.h | 5 +++++ hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h | 5 +++++ hw/bsp/stm32n6/boards/stm32n6570dk/board.h | 5 +++++ hw/bsp/stm32n6/boards/stm32n657nucleo/board.h | 5 +++++ 6 files changed, 20 insertions(+), 8 deletions(-) (limited to 'examples') diff --git a/examples/device/midi2_device/skip.txt b/examples/device/midi2_device/skip.txt index 4cabba2fb..eadb6e74a 100644 --- a/examples/device/midi2_device/skip.txt +++ b/examples/device/midi2_device/skip.txt @@ -1,5 +1 @@ mcu:SAMD11 -board:stm32h7s3nucleo -board:stm32h573i_dk -board:stm32n657nucleo -board:stm32n6570dk diff --git a/examples/host/midi2_host/skip.txt b/examples/host/midi2_host/skip.txt index 0f96db3e9..308796869 100644 --- a/examples/host/midi2_host/skip.txt +++ b/examples/host/midi2_host/skip.txt @@ -1,5 +1 @@ board:lpcxpresso54114 -board:stm32h7s3nucleo -board:stm32h573i_dk -board:stm32n657nucleo -board:stm32n6570dk diff --git a/hw/bsp/stm32h5/boards/stm32h573i_dk/board.h b/hw/bsp/stm32h5/boards/stm32h573i_dk/board.h index 5788837ab..e9c406486 100644 --- a/hw/bsp/stm32h5/boards/stm32h573i_dk/board.h +++ b/hw/bsp/stm32h5/boards/stm32h573i_dk/board.h @@ -204,6 +204,10 @@ int32_t i2c_writereg(uint16_t DevAddr, uint16_t Reg, uint8_t *pData, uint16_t Le return 0; } +static int32_t i2c_get_tick(void) { + return (int32_t) HAL_GetTick(); +} + static inline void board_init2(void) { TCPP0203_IO_t io_ctx; @@ -212,6 +216,7 @@ static inline void board_init2(void) { io_ctx.DeInit = board_tcpp0203_deinit; io_ctx.ReadReg = i2c_readreg; io_ctx.WriteReg = i2c_writereg; + io_ctx.GetTick = i2c_get_tick; TU_ASSERT(TCPP0203_RegisterBusIO(&tcpp0203_obj, &io_ctx) == TCPP0203_OK, ); diff --git a/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h b/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h index b1446414e..996eb1515 100644 --- a/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h +++ b/hw/bsp/stm32h7rs/boards/stm32h7s3nucleo/board.h @@ -223,6 +223,10 @@ int32_t i2c_writereg(uint16_t DevAddr, uint16_t Reg, uint8_t *pData, uint16_t Le return 0; } +static int32_t i2c_get_tick(void) { + return (int32_t) HAL_GetTick(); +} + static inline void board_init2(void) { TCPP0203_IO_t io_ctx; @@ -231,6 +235,7 @@ static inline void board_init2(void) { io_ctx.DeInit = board_tcpp0203_deinit; io_ctx.ReadReg = i2c_readreg; io_ctx.WriteReg = i2c_writereg; + io_ctx.GetTick = i2c_get_tick; TU_ASSERT(TCPP0203_RegisterBusIO(&tcpp0203_obj, &io_ctx) == TCPP0203_OK, ); diff --git a/hw/bsp/stm32n6/boards/stm32n6570dk/board.h b/hw/bsp/stm32n6/boards/stm32n6570dk/board.h index 4d162bbca..0cba5e1f9 100644 --- a/hw/bsp/stm32n6/boards/stm32n6570dk/board.h +++ b/hw/bsp/stm32n6/boards/stm32n6570dk/board.h @@ -244,6 +244,10 @@ static int32_t i2c_writereg(uint16_t DevAddr, uint16_t Reg, uint8_t *pData, uint return 0; } +static int32_t i2c_get_tick(void) { + return (int32_t) HAL_GetTick(); +} + static inline void board_init2(void) { TCPP0203_IO_t io_ctx; @@ -252,6 +256,7 @@ static inline void board_init2(void) { io_ctx.DeInit = board_tcpp0203_deinit; io_ctx.ReadReg = i2c_readreg; io_ctx.WriteReg = i2c_writereg; + io_ctx.GetTick = i2c_get_tick; TU_ASSERT(TCPP0203_RegisterBusIO(&tcpp0203_obj, &io_ctx) == TCPP0203_OK, ); diff --git a/hw/bsp/stm32n6/boards/stm32n657nucleo/board.h b/hw/bsp/stm32n6/boards/stm32n657nucleo/board.h index c26367af6..be9ea7a31 100644 --- a/hw/bsp/stm32n6/boards/stm32n657nucleo/board.h +++ b/hw/bsp/stm32n6/boards/stm32n657nucleo/board.h @@ -239,6 +239,10 @@ int32_t i2c_writereg(uint16_t DevAddr, uint16_t Reg, uint8_t *pData, uint16_t Le return 0; } +static int32_t i2c_get_tick(void) { + return (int32_t) HAL_GetTick(); +} + static inline void board_init2(void) { TCPP0203_IO_t io_ctx; @@ -247,6 +251,7 @@ static inline void board_init2(void) { io_ctx.DeInit = board_tcpp0203_deinit; io_ctx.ReadReg = i2c_readreg; io_ctx.WriteReg = i2c_writereg; + io_ctx.GetTick = i2c_get_tick; TU_ASSERT(TCPP0203_RegisterBusIO(&tcpp0203_obj, &io_ctx) == TCPP0203_OK, ); -- cgit v1.3.1 From b31e7cdbbedec4a3286700f3ec24bb63fd7c224f Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Sun, 17 May 2026 19:56:04 -0300 Subject: midi2: drain pattern for RX FIFO Default RX/TX buffers to EPSIZE for both device and host. Document drain-in-loop on ump_read; example device callback drains until empty. --- examples/device/midi2_device/src/main.c | 8 +++++++- src/class/midi/midi2_device.h | 12 ++++++++++-- src/class/midi/midi2_host.h | 8 ++++++++ src/tusb_option.h | 4 ++-- 4 files changed, 27 insertions(+), 5 deletions(-) (limited to 'examples') diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c index cc918e198..d5f9bae24 100644 --- a/examples/device/midi2_device/src/main.c +++ b/examples/device/midi2_device/src/main.c @@ -529,7 +529,13 @@ void send_initial_setup(void); //--------------------------------------------------------------------+ void tud_midi2_rx_cb(uint8_t itf) { - (void)itf; + // Drain the RX FIFO in a loop until empty. Leaving words in the FIFO + // across callbacks can prevent subsequent bulk OUT transfers from landing. + uint32_t words[8]; + uint32_t n; + while ((n = tud_midi2_n_ump_read(itf, words, TU_ARRAY_SIZE(words))) > 0) { + (void) n; + } } // Reset playback state and re-send setup when host switches alt setting or diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h index b0bbd7572..6c29b8ddf 100644 --- a/src/class/midi/midi2_device.h +++ b/src/class/midi/midi2_device.h @@ -57,11 +57,11 @@ extern "C" { #endif #ifndef CFG_TUD_MIDI2_TX_BUFSIZE - #define CFG_TUD_MIDI2_TX_BUFSIZE (4 * CFG_TUD_MIDI2_TX_EPSIZE) + #define CFG_TUD_MIDI2_TX_BUFSIZE CFG_TUD_MIDI2_TX_EPSIZE #endif #ifndef CFG_TUD_MIDI2_RX_BUFSIZE - #define CFG_TUD_MIDI2_RX_BUFSIZE (4 * CFG_TUD_MIDI2_RX_EPSIZE) + #define CFG_TUD_MIDI2_RX_BUFSIZE CFG_TUD_MIDI2_RX_EPSIZE #endif #ifndef CFG_TUD_MIDI2_NUM_GROUPS @@ -119,6 +119,14 @@ uint8_t tud_midi2_n_alt_setting(uint8_t itf); bool tud_midi2_n_negotiated(uint8_t itf); uint8_t tud_midi2_n_protocol(uint8_t itf); +// Read up to max_words UMP words from the RX FIFO. Returns the number of +// words actually read (0 if FIFO is empty). +// +// NOTE: this function returns when max_words is reached or when the FIFO is +// empty, whichever comes first. Applications should invoke it in a loop +// until it returns 0 to guarantee the RX FIFO is fully drained per +// tud_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can +// prevent subsequent bulk OUT transfers from landing. uint32_t tud_midi2_n_ump_read(uint8_t itf, uint32_t* words, uint32_t max_words); uint32_t tud_midi2_n_ump_write(uint8_t itf, const uint32_t* words, uint32_t count); diff --git a/src/class/midi/midi2_host.h b/src/class/midi/midi2_host.h index d2de55270..47039eb85 100644 --- a/src/class/midi/midi2_host.h +++ b/src/class/midi/midi2_host.h @@ -77,6 +77,14 @@ uint8_t tuh_midi2_get_cable_count(uint8_t idx); // Application API - I/O //--------------------------------------------------------------------+ +// Read up to max_words UMP words from the RX FIFO. Returns the number of +// words actually read (0 if FIFO is empty). +// +// NOTE: this function returns when max_words is reached or when the FIFO is +// empty, whichever comes first. Applications should invoke it in a loop +// until it returns 0 to guarantee the RX FIFO is fully drained per +// tuh_midi2_rx_cb callback. Leaving words in the FIFO across callbacks can +// prevent subsequent bulk IN transfers from landing. uint32_t tuh_midi2_ump_read(uint8_t idx, uint32_t* words, uint32_t max_words); uint32_t tuh_midi2_ump_write(uint8_t idx, const uint32_t* words, uint32_t count); uint32_t tuh_midi2_write_flush(uint8_t idx); diff --git a/src/tusb_option.h b/src/tusb_option.h index 0114b86ee..74eb8cc06 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -824,11 +824,11 @@ #endif #ifndef CFG_TUH_MIDI2_RX_BUFSIZE - #define CFG_TUH_MIDI2_RX_BUFSIZE (4 * TUH_EPSIZE_BULK_MAX) + #define CFG_TUH_MIDI2_RX_BUFSIZE TUH_EPSIZE_BULK_MAX #endif #ifndef CFG_TUH_MIDI2_TX_BUFSIZE - #define CFG_TUH_MIDI2_TX_BUFSIZE (4 * TUH_EPSIZE_BULK_MAX) + #define CFG_TUH_MIDI2_TX_BUFSIZE TUH_EPSIZE_BULK_MAX #endif #ifndef CFG_TUH_MIDI2_LOG_LEVEL -- cgit v1.3.1 From b54c8666ec35efb48305d943c87bc48800dd7d15 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Mon, 18 May 2026 23:08:03 +0200 Subject: update example Signed-off-by: HiFiPhile --- examples/device/midi2_device/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/device/midi2_device/src/main.c b/examples/device/midi2_device/src/main.c index d5f9bae24..62741ac41 100644 --- a/examples/device/midi2_device/src/main.c +++ b/examples/device/midi2_device/src/main.c @@ -252,7 +252,7 @@ static inline void midi1_pkt_send(uint8_t cable, uint8_t cin, (uint8_t)(((cable & 0x0F) << 4) | (cin & 0x0F)), status, data1, data2 }; - tud_midi2_packet_write(packet); + (void) tud_midi2_packet_write(packet, 1); } static inline void midi1_pkt_note_on(uint8_t cable, uint8_t channel, -- cgit v1.3.1