Skip to content

Instantly share code, notes, and snippets.

@tecteun
Last active October 10, 2025 13:46
Show Gist options
  • Save tecteun/6094e9afc404d32d0ec2062618f76946 to your computer and use it in GitHub Desktop.
Save tecteun/6094e9afc404d32d0ec2062618f76946 to your computer and use it in GitHub Desktop.
XIAO MIDI Synthesizer RP2040 USB midi HOST/DEVICE hybrid
// be sure to run at / enable 240mhz
// enable operating system "FreeRTOS"
// otherwise it crashes somewhere, after few minutes
/*********************************************************************
MIT license, check LICENSE for more information
Copyright (c) 2023 rppicomidi
Modified from device_info.ino from
https://github.com/sekigon-gonnoc/Pico-PIO-USB/blob/main/examples/arduino/device_info/device_info.ino
2nd Copyright notice below included per license terms.
*********************************************************************/
/*********************************************************************
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
MIT license, check LICENSE for more information
Copyright (c) 2019 Ha Thach for Adafruit Industries
All text above, and the splash screen below must be included in
any redistribution
*********************************************************************/
#ifdef __OPTIMIZE_SIZE__
#error "Please use the Tools->Optimize: Menu to choose any Optimize setting other than -Os"
#endif
#ifndef ARDUINO_ARCH_RP2040
#error "This program only works with RP2040-based boards"
#endif
#if defined(USE_TINYUSB_HOST) || !defined(USE_TINYUSB)
#error "Please use the Menu to select Tools->USB Stack: Adafruit TinyUSB"
#endif
#include "pio_usb.h"
// The following definitions apply to the custom hardware decribed in the
// README.md file for the usb_midi_host library and for the commercial
// Adafruit RP2040 Feather with USB A Host board. If you have your own
// hardware, please change the definitions below
//#include <SoftwareSerial.h>
//SoftwareSerial SSerial(D7, D6); // RX, TX
//#define COM_SERIAL SSerial
// Pin D+ for host, D- = D+ + 1
#ifndef PIN_USB_HOST_DP
#define PIN_USB_HOST_DP D4
#endif
#include "Adafruit_TinyUSB.h"
// USB MIDI object
Adafruit_USBD_MIDI usb_midi;
// USB Host object
Adafruit_USBH_Host USBHost;
// holding the device address of the MIDI device
uint8_t dev_idx = TUSB_INDEX_INVALID_8;
// constants won't change. Used here to set a pin number:
const int ledPin = PIN_LED_G; // the number of the LED pin
// Variables will change:
int ledState = LOW; // ledState used to set the LED
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time LED was updated
// constants won't change:
const long interval = 500; // interval at which to blink (milliseconds)
//https://wiki.seeedstudio.com/XIAO-RP2040-with-Arduino/#rgb-led
int Power = 11;
int PIN = 12;
#define NUMPIXELS 1
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#include "SAM2695Synth.h"
SAM2695Synth<HardwareSerial> synth = SAM2695Synth<HardwareSerial>::getInstance();
volatile byte instrumentmt32 = 0; // variable that will be updated in the ISR
volatile byte instrument = 0; // variable that will be updated in the ISR
void gpio_callback(uint gpio, uint32_t /*event_mask*/)
{
switch(gpio){
case D0:
if(instrument >= 127){
instrument = 0;
}else{
instrument ++;
}
synth.setInstrument(0,CHANNEL_0,instrument);
pixels.setPixelColor(0, pixels.Color(255, 0, 0));
pixels.show();
break;
case D1:
if(instrument <= 0){
instrument = 127;
}else{
instrument--;
}
synth.setInstrument(0,CHANNEL_0,instrument);
pixels.setPixelColor(0, pixels.Color(0, 255, 0));
pixels.show();
break;
case D2:
if(instrumentmt32 >= 127){
instrumentmt32 = 0;
}else{
instrumentmt32 ++;
}
synth.setInstrument(127,CHANNEL_0,instrumentmt32);
pixels.setPixelColor(0, pixels.Color(255, 0, 0));
pixels.show();
break;
case D3:
if(instrumentmt32 <= 0){
instrumentmt32 = 127;
}else{
instrumentmt32--;
}
synth.setInstrument(127,CHANNEL_0,instrumentmt32);
pixels.setPixelColor(0, pixels.Color(0, 255, 0));
pixels.show();
break;
}
Serial1.flush();
}
// the setup function runs once when you press reset or power the board
void setup()
{
pinMode(ledPin, OUTPUT);
usb_midi.begin();
Serial1.setTX(D6);
Serial1.setRX(D7);
synth.begin(Serial1, MIDI_SERIAL_BAUD_RATE); //Serial1.begin(31250);
synth.setInstrument(0,CHANNEL_0,unit_synth_instrument_t::Kalimba);
}
void loop()
{
uint8_t packet[4];
if(usb_midi.available() > 0){
usb_midi.readPacket(packet);
Serial1.write(packet, 4);
}
Serial1.flush();
}
// core1's setup
void setup1() {
pixels.begin();
pinMode(Power,OUTPUT);
digitalWrite(Power, HIGH);
pixels.setPixelColor(0, pixels.Color(255, 0, 0)); // Red color
pixels.show();
pinMode(D0, INPUT_PULLUP);
pinMode(D1, INPUT_PULLUP);
pinMode(D2, INPUT_PULLUP);
pinMode(D3, INPUT_PULLUP);
gpio_set_irq_enabled_with_callback(D0, GPIO_IRQ_EDGE_RISE, true, &gpio_callback);
gpio_set_irq_enabled_with_callback(D1, GPIO_IRQ_EDGE_RISE, true, &gpio_callback);
gpio_set_irq_enabled_with_callback(D2, GPIO_IRQ_EDGE_RISE, true, &gpio_callback);
gpio_set_irq_enabled_with_callback(D3, GPIO_IRQ_EDGE_RISE, true, &gpio_callback);
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
pio_cfg.pin_dp = PIN_USB_HOST_DP;
USBHost.configure_pio_usb(1, &pio_cfg);
USBHost.begin(1);
}
// core1's loop
void loop1()
{
irq_set_enabled(USBCTRL_IRQ, false);
USBHost.task(); //10us timeout
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
ledState = HIGH;
pixels.setPixelColor(0, pixels.Color(random(0,255), random(0,255), random(0,255)));
pixels.show();
} else {
ledState = LOW;
pixels.setPixelColor(0, pixels.Color(0, 0, 0));
pixels.show();
}
// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
irq_set_enabled(USBCTRL_IRQ, true);
}
//--------------------------------------------------------------------+
// TinyUSB Callbacks
//--------------------------------------------------------------------+
// Invoked when device with hid interface is mounted
// Report descriptor is also available for use. tuh_hid_parse_report_descriptor()
// can be used to parse common/simple enough descriptor.
// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped
// therefore report_desc = NULL, desc_len = 0
void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data)
{
if (dev_idx == TUSB_INDEX_INVALID_8) {
// then no MIDI device is currently connected
dev_idx = idx;
}
}
// Invoked when device with hid interface is un-mounted
void tuh_midi_umount_cb(uint8_t idx)
{
if (idx == dev_idx) {
dev_idx = TUSB_INDEX_INVALID_8;
}
}
void tuh_midi_rx_cb(uint8_t idx, uint32_t num_packets)
{
if (dev_idx == idx) {
if (num_packets != 0) {
/*
uint8_t buf[4];
while (tuh_midi_packet_read(dev_addr, buf)) {
Serial1.write(buf, 4);
}
pixels.setPixelColor(0, pixels.Color(2*buf[0], 2*buf[1], 2*buf[2]));
pixels.show();
*/
uint8_t cable_num;
uint8_t buffer[48];
while (1) {
uint32_t bytes_read = tuh_midi_stream_read(dev_idx, &cable_num, buffer, sizeof(buffer));
if (bytes_read == 0)
return;
for (uint32_t jdx = 0; jdx < bytes_read; jdx++) {
Serial1.write(buffer[jdx]);
}
//if(bytes_read > 2){
// pixels.setPixelColor(0, pixels.Color(2*buffer[bytes_read], 2*buffer[bytes_read-1], 2*buffer[bytes_read-2]));
// pixels.show();
//}
}
}
}
}
void tuh_midi_tx_cb(uint8_t idx, uint32_t num_bytes)
{
(void)idx;
(void)num_bytes;
}
@tecteun
Copy link
Author

tecteun commented Jul 20, 2025

getting midi pio usb host and midi usb device working simultaneously for
https://wiki.seeedstudio.com/xiao_midi_synthesizer/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment