Last updated: 2025-06-17
I want to use a Raspberry Pi Pico to get data from my new DRUMPPY software and to pass the data to my Nano_synth.
And as always it was a little more complicated to find information about this than presumed :). After a while I found the simplest solution to the problem, using CircuitPython. But I will also document a solution using the TinyUSB library.
So we get a standard MIDI-USB interface. The MIDI data is passed from the computer via MIDI USB to an Raspberry Pi Pico that mirrors it to a serial port. The serial data can be passed to a MIDI synth.
I found this page: https://github.com/Mental-Noise/Mazan.
We need to do the following steps to get a Pico that on the USB side provides a full MIDI-USB interface (MIDI IN and OUT to Computer host) and on the other side connects to a MIDI UART (Tx = MIDI OUT, Rx = MIDI IN).
Reset Flash memory:
To be sure the flash memory is empty we drag and drop a special UF2 binary called flash_nuke.uf2
onto the Pico when it is in mass storage mode (Connect the Pico to USB while holding the boot button pressed). You find flash_nuke.uf2
here: https://datasheets.raspberrypi.com/soft/flash_nuke.uf2.
Install CircuitPython:
Download the CircuitPython .uf2
file here: https://circuitpython.org/board/raspberry_pi_pico/. Set the Pico again in mass storage mode and copy the file to the Pico. The Pico restarts in mass storage mode and we see a file named code.py
.
Create the code:
Copy the following code to code.py
(override existing code).
import board
import busio
import usb_midi
# Initialize UART MIDI (UART0 on Pin 16 and 17 (default GP0 and GP1))
uart = busio.UART(board.GP16, board.GP17, baudrate=31250)
midi_out = usb_midi.ports[1] # Initialize USB MIDI output
midi_in = usb_midi.ports[0] # Initialize USB MIDI input
while True:
try:
if uart.in_waiting > 0:
# Send received MIDI message from UART to computer via USB MIDI
midi_out.write(uart.read(uart.in_waiting))
msg = midi_in.read()
if msg:
# Send received MIDI message from USB MIDI to external device via UART
uart.write(msg)
except Exception as e:
print("An error occurred:", e)
Done :)
To use the Pico in the Arduino IDE you have to add this line:
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
to the Arduino IDE (File
-> Preferences...
) and to install the Raspberry Pi Pico by Earle F. Philhower in the Boards manager.
Thanks to Gustavo Silveira there is a comprehensive library out there. To get it work watch his video to Make a MIDI Controller with the Raspberry Pi Pico. The code is on github.
He combined the Adafruit TinyUSB library with the Midi library.
Here is the code I used to get this working. Unfortunately I had problems with bigger 'sysex' messages. Apparently TinyUSB supports only messages up to 3 or 4 bytes. So I stopped this and use now the first solution from this page.
#include <Adafruit_TinyUSB_MIDI.h>
Adafruit_TinyUSB_MIDI MIDI;
Adafruit_TinyUSB_MIDI_Input MIDI_Input(MIDI.getMidiInstance());
bool ledState = false; // Variable to keep track of LED state
#define DEBUG
void setup() {
#ifdef DEBUG
SerialTinyUSB.begin(115200); // Start SerialTinyUSB communication for debugging
SerialTinyUSB.println("Debug!");
#endif
Serial1.setTX(16); // right bottom pin 21
Serial1.setRX(17);
Serial1.begin(31250);
MIDI.begin(); // Initialize the MIDI Output
// Initialize the MIDI Input callbacks
MIDI_Input.setHandleNoteOn(handle_note_on);
MIDI_Input.setHandleNoteOff(handle_note_off);
MIDI_Input.setHandleControlChange(handle_control_change);
MIDI_Input.setHandleProgramChange(handle_program_change);
MIDI_Input.setHandleSysEx(handle_sysex);
send_test_notes();
pinMode(LED_BUILTIN, OUTPUT); // Set LED pin as output
digitalWrite(LED_BUILTIN, LOW); // Ensure LED is off initially
}
void loop() {
MIDI_Input.read(); // Process incoming MIDI messages
}
/****** Callback functions **************************************************/
// Callback function for handling incoming Note On messages
void handle_note_on(byte ch, byte note, byte vel) {
#ifdef DEBUG
SerialTinyUSB.println("Note On! Channel: " + String(ch) + ", Note: " +
String(note) + ", Velocity: " + String(vel));
#endif
note_on(ch, note, vel);
toggle_led(); // Toggle LED state
}
// Callback function for handling incoming Note Off messages
void handle_note_off(byte ch, byte note, byte vel) {
#ifdef DEBUG
SerialTinyUSB.println("Note On! Channel: " + String(ch) + ", Note: " +
String(note) + ", Velocity: " + String(vel));
#endif
note_off(ch, note, vel);
toggle_led(); // Toggle LED state
}
// Callback function for handling Control Change messages
void handle_control_change(byte ch, byte ctrl_nr, byte ctrl_val) {
#ifdef DEBUG
SerialTinyUSB.println("Control Change! Channel: " + String(ch) +
", Control Number: " + String(ctrl_nr) +
", Control Value: " + String(ctrl_val));
#endif
toggle_led(); // Toggle LED state
}
// Callback function for handling Program Change messages
void handle_program_change(byte ch, byte prog_nr) {
#ifdef DEBUG
SerialTinyUSB.println("Program Change! Channel: " + String(ch) +
", Program Number: " + String(prog_nr));
#endif
toggle_led(); // Toggle LED state
}
// Callback function for handling System Exclusive (SysEx) messages
void handle_sysex(size_t length, byte* data) {
SerialTinyUSB.print("sizeof(data) ");
SerialTinyUSB.println(sizeof(data));
#ifdef DEBUG
SerialTinyUSB.print("SysEx message! Length: " + String(length) + " Data: ");
for (byte i = 0; i < length; i++) {
SerialTinyUSB.print(data[i], HEX);
SerialTinyUSB.print(" ");
}
SerialTinyUSB.println();
#endif
handle_sysex_id_request(length, data);
SerialTinyUSB.println("-----YES------");
toggle_led(); // Toggle LED state
}
/****** Handle messages functions ****************************************************/
void note_on(byte channel, byte note, byte velocity) {
Serial1.write(0x90 + channel); // send note on command
Serial1.write(note); // send pitch data
Serial1.write(velocity); // send velocity data
}
void note_off(byte channel, byte note, byte velocity) {
Serial1.write(0x80 + channel); // send note off command
Serial1.write(note); // send pitch data
if (velocity == 0) {
Serial1.write((byte)0x00); // send velocity data
}
else {
Serial1.write(velocity);
}
}
/****** Helper functions ****************************************************/
// Function to toggle the built-in LED state
void toggle_led() {
ledState = !ledState; // Toggle LED state
digitalWrite(LED_BUILTIN, ledState ? HIGH : LOW); // Set LED
}
// Send some notes
void send_test_notes() {
for (byte i = 1; i<10; i++) {
note_on(0, 30+i, 64);
delay(100);
note_off(0, 30+i, 0);
delay(100);
}
}