Music projects

USB MIDI to UART with Raspberry Pi Pico

Last updated: 2025-06-17

Quick links

pi pico

Intro

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.

Using CircuitPython

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).

    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 :)

Arduino and TinyUSB_MIDI

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);
      }
    }

Interesting links