This complete Arduino C++ sketch turns any RP2040 board (such as the Raspberry
Pi Pico, RP2040-Zero, or QT Py RP2040) into a native USB-MIDI device. It
receives incoming NoteOn, NoteOff, and ControlChange (CC) commands to light up
addressable LED strips with real-time color and brightness control.
Required Libraries (Install via Arduino Library Manager)
Adafruit_TinyUSB_Arduino (Enables native class-compliant USB-MIDI on RP2040)
MIDI Library by FranC'ois Best (Provides the MIDI state machine parser)
Adafruit_NeoPixel (Drives WS2812B or SK6812 strips via hardware PIO)
Arduino IDE Configuration
Board: Raspberry Pi Pico or Generic RP2040 (using the Raspberry Pi Pico/RP2040
core by Earle F. Philhower).
USB Stack: Go to Tools > USB Stack and select Adafruit TinyUSB.
Arduino Sketch (rp2040_midi_leds.ino)
#include <Adafruit_TinyUSB.h> #include <MIDI.h> #include <Adafruit_NeoPixel.h>
// =========================================================================
// HARDWARE CONFIGURATION // ==================================================
======================= #define LED_PIN 2 // GPIO pin connected to the LED
data line #define NUM_LEDS 144 // Number of LEDs on your strip #define
START_MIDI_NOTE 36 // First MIDI note mapped to LED index 0 (36 = C2) //
Choose LED type: // NEO_GRB + NEO_KHZ800 -> 3-channel WS2812B / WS2811 //
NEO_GRBW + NEO_KHZ800 -> 4-channel SK6812 RGBW (Recommended for dimming)
#define LED_TYPE (NEO_GRB + NEO_KHZ800) // ====================================
===================================== // GLOBAL OBJECTS & VARIABLES //
========================================================================= //
Initialize TinyUSB MIDI interface Adafruit_USBD_MIDI usb_midi;
MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI); // Initialize
NeoPixel strip Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, LED_TYPE); //
Control states uint8_t masterBrightness = 128; // Default global brightness (0
- 255) uint16_t currentHueOffset = 0; // Color wheel shift via CC 12 (0 -
65535) // =====================================================================
==== // MIDI CALLBACK HANDLERS // =============================================
============================ // Called when a MIDI Note On message is received
void handleNoteOn(byte channel, byte note, byte velocity) { // MIDI standard:
Note On with velocity 0 is functionally a Note Off if (velocity == 0) {
handleNoteOff(channel, note, velocity); return; } int ledIndex = note -
START_MIDI_NOTE; // Ensure the note falls within the physical LED bounds if
(ledIndex >= 0 && ledIndex < NUM_LEDS) { // Generate dynamic HSV color: // Hue
spreads across the spectrum per note, shifted by CC 12 uint16_t hue =
(currentHueOffset + (note * 500)) % 65536; uint8_t sat = 255; // Full
saturation uint8_t val = map(velocity, 1, 127, 40, 255); // Scale brightness
to velocity // Convert HSV to
32-bit RGB color value uint32_t rgbColor = strip.ColorHSV(hue, sat, val);
strip.setPixelColor(ledIndex, rgbColor); strip.show(); } } // Called when a
MIDI Note Off message is received void handleNoteOff(byte channel, byte note,
byte velocity) { int ledIndex = note - START_MIDI_NOTE; if (ledIndex >= 0 &&
ledIndex < NUM_LEDS) { strip.setPixelColor(ledIndex, 0); // Turn off specific
LED strip.show(); } } // Called when a Control Change (CC) message is received
void handleControlChange(byte channel, byte number, byte value) { switch
(number) { case 7: // CC 7: Master Volume / Brightness (0 - 127)
masterBrightness = map(value, 0, 127, 0, 255); strip.setBrightness(masterBright
ness); strip.show(); break; case 12: // CC 12: Global Color/Hue Offset (0 -
127) currentHueOffset = map(value, 0, 127, 0, 65535); break; case 120: // CC
120: All Sound Off case 123: // CC 123: All Notes Off strip.clear();
strip.show(); break; default: break; } } // ===================================
====================================== // INITIALIZATION & MAIN LOOP //
========================================================================= void
setup() { // Initialize USB descriptor names USBDevice.setManufacturerDescripto
r("DIY Instruments"); USBDevice.setProductDescriptor("RP2040 LED Visualizer");
// Start MIDI processing MIDI.begin(MIDI_CHANNEL_OMNI); MIDI.turnThruOff(); //
Prevent mirroring received messages back out // Register callback functions
MIDI.setHandleNoteOn(handleNoteOn); MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandleControlChange(handleControlChange); // Initialize NeoPixel strip
strip.begin(); strip.setBrightness(masterBrightness); strip.clear();
strip.show(); // Wait until USB stack is mounted by the host PC while
(!TinyUSBDevice.mounted()) { delay(10); } } void loop() { // Continuously
parse incoming USB-MIDI packets MIDI.read(); }
How Control Commands Map
Note On / Velocity: Striking a key lights the corresponding LED (N -
\text{StartNote}). Striking harder increases the individual LED's brightness
(V = 1127).
CC 7 (Master Brightness): Controls global strip dimming from 0 to 255 without
distorting current note colors.
CC 12 (Hue Offset): Shifts the entire color spectrum across the strip,
allowing you to switch palettes on the fly.
CC 123 (All Notes Off): Instantly clears all active LEDs.
For a demonstration of building a physical setup around an RP2040 board and
NeoPixel strip, see this MIDI NeoPixel Visualizer with RP2040. This video
provides a helpful visual reference for how an RP2040 microcontroller drives
NeoPixel strips directly in response to incoming MIDI note triggers. YouTube
video views will be stored in your YouTube History, and your data will be
stored and used by YouTube according to its Terms of Service