diff options
| author | Saulo VerĂssimo <[email protected]> | 2026-04-07 22:50:29 -0300 |
|---|---|---|
| committer | Saulo VerĂssimo <[email protected]> | 2026-05-12 11:07:44 -0300 |
| commit | 13c6f6a2c7aca7165161d9f32d10087c91aa61aa (patch) | |
| tree | a7ad1df731d2bef6b3d11d322fb2c9e1c7cb6820 /examples | |
| parent | f8a5fc37f3472cb2e3eb584d58e9cf4588dc4329 (diff) | |
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.
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/host/midi2_host_feather/CMakeLists.txt | 38 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/Makefile | 1 | ||||
| -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 | ||||
| -rw-r--r-- | examples/host/midi2_host_feather/src/tusb_config.h | 91 |
7 files changed, 826 insertions, 0 deletions
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 <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 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 <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 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 <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 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 <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; +} 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 |
