Skip to content

Commit 02e0e75

Browse files
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.
1 parent b8fa96a commit 02e0e75

File tree

7 files changed

+826
-0
lines changed

7 files changed

+826
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
cmake_minimum_required(VERSION 3.20)
2+
3+
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
4+
5+
project(midi2_host_feather C CXX ASM)
6+
7+
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
8+
9+
# This example requires PIO-USB and Pico SDK (I2C, SSD1306 display)
10+
if(NOT FAMILY STREQUAL "rp2040")
11+
return()
12+
endif()
13+
14+
add_executable(${PROJECT_NAME})
15+
16+
target_sources(${PROJECT_NAME} PUBLIC
17+
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
18+
${CMAKE_CURRENT_SOURCE_DIR}/src/display.c
19+
)
20+
21+
target_include_directories(${PROJECT_NAME} PUBLIC
22+
${CMAKE_CURRENT_SOURCE_DIR}/src
23+
)
24+
25+
family_configure_host_example(${PROJECT_NAME} noos)
26+
27+
# Adafruit Feather RP2040 USB Host: PIO-USB on GP16/GP17
28+
target_compile_definitions(${PROJECT_NAME} PRIVATE
29+
PIO_USB_DP_PIN_DEFAULT=16
30+
)
31+
target_compile_options(${PROJECT_NAME} PRIVATE
32+
-Wno-type-limits
33+
)
34+
35+
# SSD1306 display (I2C via STEMMA QT)
36+
target_link_libraries(${PROJECT_NAME} PUBLIC
37+
hardware_i2c
38+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ../../../examples/build_system/make/make.mk
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2026 Saulo Verissimo
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
// SSD1306 OLED display driver (128x64, I2C) for MIDI 2.0 Host example.
26+
// Adafruit Feather RP2040 USB Host: I2C1 via STEMMA QT (SDA = GP2, SCL = GP3)
27+
//
28+
// Three display phases:
29+
// 1. Splash : title + credits (shown during init)
30+
// 2. Connecting : spinner animation while waiting for device
31+
// 3. Live : header + 6 scrolling log lines + status bar
32+
33+
#include "display.h"
34+
#include <stdio.h>
35+
#include <string.h>
36+
#include "pico/stdlib.h"
37+
#include "hardware/i2c.h"
38+
#include "hardware/gpio.h"
39+
40+
#define I2C_PORT i2c1
41+
#define I2C_SDA 2
42+
#define I2C_SCL 3
43+
#define I2C_FREQ 400000
44+
#define SSD1306_ADDR 0x3C
45+
46+
#define SCR_W 128
47+
#define SCR_H 64
48+
#define PAGES (SCR_H / 8)
49+
#define CHARS_PER_LINE 21
50+
51+
//--------------------------------------------------------------------+
52+
// Framebuffer
53+
//--------------------------------------------------------------------+
54+
55+
static uint8_t fb[SCR_W * PAGES];
56+
57+
//--------------------------------------------------------------------+
58+
// Log buffer (6 lines for live view)
59+
//--------------------------------------------------------------------+
60+
61+
#define LOG_LINES 6
62+
static char log_lines[LOG_LINES][CHARS_PER_LINE + 1];
63+
static int log_count = 0;
64+
65+
//--------------------------------------------------------------------+
66+
// Minimal 5x7 font (ASCII 32-126)
67+
//--------------------------------------------------------------------+
68+
69+
#include "font5x7.h"
70+
71+
//--------------------------------------------------------------------+
72+
// SSD1306 I2C low-level
73+
//--------------------------------------------------------------------+
74+
75+
static void ssd_cmd(uint8_t cmd) {
76+
uint8_t buf[2] = { 0x00, cmd };
77+
i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, 2, false);
78+
}
79+
80+
static void ssd_data(const uint8_t* data, size_t len) {
81+
uint8_t buf[SCR_W + 1];
82+
buf[0] = 0x40;
83+
size_t chunk = (len > SCR_W) ? SCR_W : len;
84+
memcpy(buf + 1, data, chunk);
85+
i2c_write_blocking(I2C_PORT, SSD1306_ADDR, buf, chunk + 1, false);
86+
}
87+
88+
static void ssd_flush(void) {
89+
ssd_cmd(0x21); ssd_cmd(0); ssd_cmd(127);
90+
ssd_cmd(0x22); ssd_cmd(0); ssd_cmd(7);
91+
for (int page = 0; page < PAGES; page++) {
92+
ssd_data(&fb[page * SCR_W], SCR_W);
93+
}
94+
}
95+
96+
//--------------------------------------------------------------------+
97+
// Framebuffer drawing
98+
//--------------------------------------------------------------------+
99+
100+
static void fb_clear(void) {
101+
memset(fb, 0, sizeof(fb));
102+
}
103+
104+
static void fb_char(int x, int y, char c) {
105+
if (c < 32 || c > 126) c = '?';
106+
const uint8_t* glyph = font5x7 + (c - 32) * 5;
107+
int page = y / 8;
108+
int bit_offset = y % 8;
109+
110+
if (page >= PAGES || x + 5 > SCR_W) return;
111+
112+
for (int col = 0; col < 5; col++) {
113+
uint8_t column_data = glyph[col];
114+
fb[(page * SCR_W) + x + col] |= (uint8_t)(column_data << bit_offset);
115+
if (bit_offset > 0 && page + 1 < PAGES) {
116+
fb[((page + 1) * SCR_W) + x + col] |= (uint8_t)(column_data >> (8 - bit_offset));
117+
}
118+
}
119+
}
120+
121+
static void fb_string(int x, int y, const char* str) {
122+
while (*str) {
123+
fb_char(x, y, *str);
124+
x += 6;
125+
if (x + 6 > SCR_W) break;
126+
str++;
127+
}
128+
}
129+
130+
// Draw a horizontal line (1px)
131+
static void fb_hline(int x0, int x1, int y) {
132+
int page = y / 8;
133+
uint8_t mask = (uint8_t)(1 << (y % 8));
134+
if (page >= PAGES) return;
135+
for (int x = x0; x <= x1 && x < SCR_W; x++) {
136+
fb[page * SCR_W + x] |= mask;
137+
}
138+
}
139+
140+
//--------------------------------------------------------------------+
141+
// Phase 1: Splash
142+
//--------------------------------------------------------------------+
143+
144+
void display_init(void) {
145+
i2c_init(I2C_PORT, I2C_FREQ);
146+
gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);
147+
gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);
148+
gpio_pull_up(I2C_SDA);
149+
gpio_pull_up(I2C_SCL);
150+
151+
sleep_ms(100);
152+
153+
// SSD1306 init sequence
154+
ssd_cmd(0xAE);
155+
ssd_cmd(0xD5); ssd_cmd(0x80);
156+
ssd_cmd(0xA8); ssd_cmd(0x3F);
157+
ssd_cmd(0xD3); ssd_cmd(0x00);
158+
ssd_cmd(0x40);
159+
ssd_cmd(0x8D); ssd_cmd(0x14);
160+
ssd_cmd(0x20); ssd_cmd(0x00);
161+
ssd_cmd(0xA1);
162+
ssd_cmd(0xC8);
163+
ssd_cmd(0xDA); ssd_cmd(0x12);
164+
ssd_cmd(0x81); ssd_cmd(0xCF);
165+
ssd_cmd(0xD9); ssd_cmd(0xF1);
166+
ssd_cmd(0xDB); ssd_cmd(0x40);
167+
ssd_cmd(0xA4);
168+
ssd_cmd(0xA6);
169+
ssd_cmd(0xAF);
170+
171+
// Splash screen
172+
fb_clear();
173+
fb_string(4, 8, "TinyUSB MIDI 2.0");
174+
fb_hline(4, 123, 18);
175+
fb_string(16, 24, "USB Host Demo");
176+
fb_string(4, 40, "Feather RP2040");
177+
fb_string(4, 52, "PIO-USB + SSD1306");
178+
ssd_flush();
179+
}
180+
181+
//--------------------------------------------------------------------+
182+
// Phase 2: Connecting (spinner)
183+
//--------------------------------------------------------------------+
184+
185+
static const char SPINNER[] = "|/-\\";
186+
187+
void display_connecting(uint32_t elapsed_ms) {
188+
int idx = (int)((elapsed_ms / 200) % 4);
189+
190+
// Clear bottom 2 pages for spinner area
191+
memset(&fb[6 * SCR_W], 0, SCR_W);
192+
memset(&fb[7 * SCR_W], 0, SCR_W);
193+
194+
char line[CHARS_PER_LINE + 1];
195+
snprintf(line, sizeof(line), "%c Waiting device...", SPINNER[idx]);
196+
fb_string(4, 52, line);
197+
ssd_flush();
198+
}
199+
200+
//--------------------------------------------------------------------+
201+
// Phase 3: Live view
202+
//--------------------------------------------------------------------+
203+
204+
// Layout:
205+
// y=0 : "TinyUSB MIDI 2.0 Host" (header, fixed)
206+
// y=9 : separator line
207+
// y=10 : log line 0
208+
// y=18 : log line 1
209+
// y=26 : log line 2
210+
// y=34 : log line 3
211+
// y=42 : log line 4
212+
// y=50 : log line 5
213+
// y=57 : separator line
214+
// y=58 : status bar
215+
216+
static bool live_mode = false;
217+
218+
static void draw_live_frame(void) {
219+
fb_clear();
220+
fb_string(0, 0, "TinyUSB MIDI 2.0 Host");
221+
fb_hline(0, 127, 9);
222+
fb_hline(0, 127, 57);
223+
}
224+
225+
void display_live_begin(void) {
226+
live_mode = true;
227+
log_count = 0;
228+
memset(log_lines, 0, sizeof(log_lines));
229+
230+
draw_live_frame();
231+
fb_string(0, 58, "Connected");
232+
ssd_flush();
233+
}
234+
235+
void display_log(const char* text, uint16_t color) {
236+
(void)color;
237+
238+
if (!live_mode) {
239+
display_live_begin();
240+
}
241+
242+
// Scroll up if full
243+
if (log_count >= LOG_LINES) {
244+
for (int i = 0; i < LOG_LINES - 1; i++) {
245+
strncpy(log_lines[i], log_lines[i + 1], CHARS_PER_LINE);
246+
log_lines[i][CHARS_PER_LINE] = '\0';
247+
}
248+
log_count = LOG_LINES - 1;
249+
}
250+
251+
strncpy(log_lines[log_count], text, CHARS_PER_LINE);
252+
log_lines[log_count][CHARS_PER_LINE] = '\0';
253+
log_count++;
254+
255+
// Redraw: header + log + status
256+
draw_live_frame();
257+
for (int i = 0; i < log_count && i < LOG_LINES; i++) {
258+
fb_string(0, 10 + i * 8, log_lines[i]);
259+
}
260+
ssd_flush();
261+
}
262+
263+
void display_status(const char* text) {
264+
if (!live_mode) return;
265+
266+
// Clear status area (page 7)
267+
memset(&fb[7 * SCR_W], 0, SCR_W);
268+
// Redraw separator (might have been cleared)
269+
fb_hline(0, 127, 57);
270+
271+
char status[CHARS_PER_LINE + 1];
272+
strncpy(status, text, CHARS_PER_LINE);
273+
status[CHARS_PER_LINE] = '\0';
274+
fb_string(0, 58, status);
275+
ssd_flush();
276+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2026 Saulo Verissimo
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
#ifndef DISPLAY_H_
26+
#define DISPLAY_H_
27+
28+
#include <stdint.h>
29+
#include <stdbool.h>
30+
31+
// Initialize SSD1306 display and show splash screen
32+
void display_init(void);
33+
34+
// Show waiting/connecting animation (call periodically)
35+
void display_connecting(uint32_t elapsed_ms);
36+
37+
// Transition to live message view
38+
void display_live_begin(void);
39+
40+
// Add a line to the scrolling log area (6 visible lines)
41+
void display_log(const char* text, uint16_t color);
42+
43+
// Update the status bar (bottom line)
44+
void display_status(const char* text);
45+
46+
#endif

0 commit comments

Comments
 (0)