summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorSaulo VerĂ­ssimo <[email protected]>2026-03-24 23:38:36 -0300
committerSaulo VerĂ­ssimo <[email protected]>2026-05-12 11:07:44 -0300
commitd5c5ac586bfdbf53e18a8cdbb11b978f53b6d059 (patch)
tree2cbcefc824ad76a3aeca6c7b06af1cbee1c68777 /examples
parent290470bda38ce75bbadcce8939435953ca483269 (diff)
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
Diffstat (limited to 'examples')
-rw-r--r--examples/host/CMakeLists.txt1
-rw-r--r--examples/host/midi2_host/CMakeLists.txt37
-rw-r--r--examples/host/midi2_host/src/display.c220
-rw-r--r--examples/host/midi2_host/src/display.h54
-rw-r--r--examples/host/midi2_host/src/font5x7.h107
-rw-r--r--examples/host/midi2_host/src/main.c326
-rw-r--r--examples/host/midi2_host/src/tusb_config.h91
7 files changed, 836 insertions, 0 deletions
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 <stdio.h>
+#include <string.h>
+#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 <stdint.h>
+#include <stdbool.h>
+
+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 <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/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 <stdio.h>
+#include <string.h>
+#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