diff options
| author | Saulo VerĂssimo <[email protected]> | 2026-05-16 13:00:30 -0300 |
|---|---|---|
| committer | Saulo VerĂssimo <[email protected]> | 2026-05-16 13:00:30 -0300 |
| commit | 3efac1b28c4174b0734ef106ce8496fa39657b47 (patch) | |
| tree | a9539f697da64235a9fb660cb32789fefa736ff1 | |
| parent | 43a72cb614e2fdd27788057a6f9e843d458f1ca5 (diff) | |
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
| -rw-r--r-- | examples/host/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | examples/host/midi2_host/CMakeLists.txt | 29 | ||||
| -rw-r--r-- | examples/host/midi2_host/CMakePresets.json | 6 | ||||
| -rw-r--r-- | examples/host/midi2_host/Makefile | 13 | ||||
| -rw-r--r-- | examples/host/midi2_host/src/main.c | 165 | ||||
| -rw-r--r-- | examples/host/midi2_host/src/tusb_config.h (renamed from examples/host/midi2_host_feather/src/tusb_config.h) | 39 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/CMakeLists.txt | 38 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/Makefile | 27 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/src/display.c | 276 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/src/display.h | 46 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/src/font5x7.h | 107 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/src/main.c | 267 |
12 files changed, 241 insertions, 774 deletions
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 <stdio.h> +#include <string.h> +#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_feather/src/tusb_config.h b/examples/host/midi2_host/src/tusb_config.h index 362d0e2d0..4bd6ef2ea 100644 --- a/examples/host/midi2_host_feather/src/tusb_config.h +++ b/examples/host/midi2_host/src/tusb_config.h @@ -37,6 +37,10 @@ extern "C" { #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 @@ -55,34 +59,45 @@ extern "C" { //--------------------------------------------------------------------+ // 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 +#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 -#ifndef BOARD_TUH_MAX_SPEED -#define BOARD_TUH_MAX_SPEED OPT_MODE_FULL_SPEED + // 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 0 -#define CFG_TUH_DEVICE_MAX 1 +#define CFG_TUH_HUB 1 +#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1) -// MIDI 2.0 Host -#define CFG_TUH_MIDI2 1 -#define CFG_TUH_MIDI2_RX_BUFSIZE 512 -#define CFG_TUH_MIDI2_TX_BUFSIZE 512 +// 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 } 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 <stdio.h> -#include <string.h> -#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 <stdint.h> -#include <stdbool.h> - -// 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 <stdint.h> - -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 <stdio.h> -#include <string.h> -#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; -} |
