Skip to content

Instantly share code, notes, and snippets.

@todbot
Last active June 13, 2024 01:27
Show Gist options
  • Save todbot/0a7824bcf8bfa708d1be723d0331a12d to your computer and use it in GitHub Desktop.
Save todbot/0a7824bcf8bfa708d1be723d0331a12d to your computer and use it in GitHub Desktop.
USB MIDI Host using PIO on RP2040 USB Type A Host Feather
/**
* Modified 12 Jun 2024 - @todbot -- added USB MIDI forwarding
* originally from: https://github.com/rppicomidi/EZ_USB_MIDI_HOST/blob/main/examples/arduino/EZ_USB_MIDI_HOST_PIO_example/EZ_USB_MIDI_HOST_PIO_example.ino
* Be sure to set "Tools / CPU Speed" to 120 MHz or 240 MHz and
* be sure to set "Tools / USB Stack" to "Adafruit TinyUSB"
*/
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 rppicomidi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
/**
* This demo program is designed to test the USB MIDI Host driver for a single USB
* MIDI device connected to the USB Host port. It sends to the USB MIDI device the
* sequence of half-steps from B-flat to D whose note numbers correspond to the
* transport button LEDs on a Mackie Control compatible control surface. It also
* prints to a UART serial port console the messages received from the USB MIDI device.
*
* This program works with a single USB MIDI device connected via a USB hub, but it
* does not handle multiple USB MIDI devices connected at the same time.
*/
#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"
#define HOST_PIN_DP 16 // Pin used as D+ for host, D- = D+ + 1
#include "EZ_USB_MIDI_HOST.h"
// USB Host object
Adafruit_USBH_Host USBHost;
USING_NAMESPACE_MIDI
USING_NAMESPACE_EZ_USB_MIDI_HOST
RPPICOMIDI_EZ_USB_MIDI_HOST_INSTANCE(usbhMIDI, MidiHostSettingsDefault)
Adafruit_USBD_MIDI usb_midi; // USB MIDI object
MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDIusb); // USB MIDI
static uint8_t midiDevAddr = 0;
static bool core0_booting = true;
static bool core1_booting = true;
/* MIDI IN MESSAGE REPORTING */
static void onMidiError(int8_t errCode)
{
Serial.printf("MIDI Errors: %s %s %s\r\n", (errCode & (1UL << ErrorParse)) ? "Parse":"",
(errCode & (1UL << ErrorActiveSensingTimeout)) ? "Active Sensing Timeout" : "",
(errCode & (1UL << WarningSplitSysEx)) ? "Split SysEx":"");
}
static void onNoteOff(Channel channel, byte note, byte velocity)
{
Serial.printf("C%u: Note off#%u v=%u\r\n", channel, note, velocity);
MIDIusb.sendNoteOff(note, velocity, channel);
}
static void onNoteOn(Channel channel, byte note, byte velocity)
{
MIDIusb.sendNoteOn(note, velocity, channel);
Serial.printf("C%u: Note on#%u v=%u\r\n", channel, note, velocity);
}
static void onPolyphonicAftertouch(Channel channel, byte note, byte amount)
{
Serial.printf("C%u: PAT#%u=%u\r\n", channel, note, amount);
//MIDIusb.sendPolyPressure(note, amount, channel); // deprecated
MIDIusb.sendAfterTouch(note, amount, channel);
}
static void onControlChange(Channel channel, byte controller, byte value)
{
Serial.printf("C%u: CC#%u=%u\r\n", channel, controller, value);
MIDIusb.sendControlChange(controller, value, channel);
}
static void onProgramChange(Channel channel, byte program)
{
Serial.printf("C%u: Prog=%u\r\n", channel, program);
MIDIusb.sendProgramChange(program, channel);
}
static void onAftertouch(Channel channel, byte value)
{
Serial.printf("C%u: AT=%u\r\n", channel, value);
MIDIusb.sendAfterTouch(value, channel);
}
static void onPitchBend(Channel channel, int value)
{
Serial.printf("C%u: PB=%d\r\n", channel, value);
MIDIusb.sendPitchBend(value, channel);
}
static void onSysEx(byte * array, unsigned size)
{
Serial.printf("SysEx:\r\n");
unsigned multipleOf8 = size/8;
unsigned remOf8 = size % 8;
for (unsigned idx=0; idx < multipleOf8; idx++) {
for (unsigned jdx = 0; jdx < 8; jdx++) {
Serial.printf("%02x ", *array++);
}
Serial.printf("\r\n");
}
for (unsigned idx = 0; idx < remOf8; idx++) {
Serial.printf("%02x ", *array++);
}
Serial.printf("\r\n");
}
static void onSMPTEqf(byte data)
{
uint8_t type = (data >> 4) & 0xF;
data &= 0xF;
static const char* fps[4] = {"24", "25", "30DF", "30ND"};
switch (type) {
case 0: Serial.printf("SMPTE FRM LS %u \r\n", data); break;
case 1: Serial.printf("SMPTE FRM MS %u \r\n", data); break;
case 2: Serial.printf("SMPTE SEC LS %u \r\n", data); break;
case 3: Serial.printf("SMPTE SEC MS %u \r\n", data); break;
case 4: Serial.printf("SMPTE MIN LS %u \r\n", data); break;
case 5: Serial.printf("SMPTE MIN MS %u \r\n", data); break;
case 6: Serial.printf("SMPTE HR LS %u \r\n", data); break;
case 7:
Serial.printf("SMPTE HR MS %u FPS:%s\r\n", data & 0x1, fps[(data >> 1) & 3]);
break;
default:
Serial.printf("invalid SMPTE data byte %u\r\n", data);
break;
}
}
static void onSongPosition(unsigned beats)
{
Serial.printf("SongP=%u\r\n", beats);
MIDIusb.sendSongPosition(beats);
}
static void onSongSelect(byte songnumber)
{
Serial.printf("SongS#%u\r\n", songnumber);
MIDIusb.sendSongSelect(songnumber);
}
static void onTuneRequest()
{
Serial.printf("Tune\r\n");
MIDIusb.sendTuneRequest();
}
static void onMidiClock()
{
Serial.printf("Clock\r\n");
MIDIusb.sendClock();
}
static void onMidiStart()
{
Serial.printf("Start\r\n");
MIDIusb.sendStart();
}
static void onMidiContinue()
{
Serial.printf("Cont\r\n");
MIDIusb.sendContinue();
}
static void onMidiStop()
{
Serial.printf("Stop\r\n");
MIDIusb.sendStop();
}
static void onActiveSense()
{
Serial.printf("ASen\r\n");
}
static void onSystemReset()
{
Serial.printf("SysRst\r\n");
}
static void onMidiTick()
{
Serial.printf("Tick\r\n");
}
static void onMidiInWriteFail(uint8_t devAddr, uint8_t cable, bool fifoOverflow)
{
if (fifoOverflow)
Serial.printf("Dev %u cable %u: MIDI IN FIFO overflow\r\n", devAddr, cable);
else
Serial.printf("Dev %u cable %u: MIDI IN FIFO error\r\n", devAddr, cable);
}
static void registerMidiInCallbacks()
{
auto intf = usbhMIDI.getInterfaceFromDeviceAndCable(midiDevAddr, 0);
if (intf == nullptr)
return;
intf->setHandleNoteOff(onNoteOff); // 0x80
intf->setHandleNoteOn(onNoteOn); // 0x90
intf->setHandleAfterTouchPoly(onPolyphonicAftertouch); // 0xA0
intf->setHandleControlChange(onControlChange); // 0xB0
intf->setHandleProgramChange(onProgramChange); // 0xC0
intf->setHandleAfterTouchChannel(onAftertouch); // 0xD0
intf->setHandlePitchBend(onPitchBend); // 0xE0
intf->setHandleSystemExclusive(onSysEx); // 0xF0, 0xF7
intf->setHandleTimeCodeQuarterFrame(onSMPTEqf); // 0xF1
intf->setHandleSongPosition(onSongPosition); // 0xF2
intf->setHandleSongSelect(onSongSelect); // 0xF3
intf->setHandleTuneRequest(onTuneRequest); // 0xF6
intf->setHandleClock(onMidiClock); // 0xF8
// 0xF9 as 10ms Tick is not MIDI 1.0 standard but implemented in the Arduino MIDI Library
intf->setHandleTick(onMidiTick); // 0xF9
intf->setHandleStart(onMidiStart); // 0xFA
intf->setHandleContinue(onMidiContinue); // 0xFB
intf->setHandleStop(onMidiStop); // 0xFC
intf->setHandleActiveSensing(onActiveSense); // 0xFE
intf->setHandleSystemReset(onSystemReset); // 0xFF
intf->setHandleError(onMidiError);
auto dev = usbhMIDI.getDevFromDevAddr(midiDevAddr);
if (dev == nullptr)
return;
dev->setOnMidiInWriteFail(onMidiInWriteFail);
}
/* CONNECTION MANAGEMENT */
static void onMIDIconnect(uint8_t devAddr, uint8_t nInCables, uint8_t nOutCables)
{
Serial.printf("MIDI device at address %u has %u IN cables and %u OUT cables\r\n", devAddr, nInCables, nOutCables);
midiDevAddr = devAddr;
registerMidiInCallbacks();
}
static void onMIDIdisconnect(uint8_t devAddr)
{
Serial.printf("MIDI device at address %u unplugged\r\n", devAddr);
midiDevAddr = 0;
}
/* MAIN LOOP FUNCTIONS */
static void blinkLED(void)
{
const uint32_t intervalMs = 1000;
static uint32_t startMs = 0;
static bool ledState = false;
if ( millis() - startMs < intervalMs)
return;
startMs += intervalMs;
ledState = !ledState;
digitalWrite(LED_BUILTIN, ledState ? HIGH:LOW);
}
/*
static void sendNextNote()
{
static uint8_t firstNote = 0x5b; // Mackie Control rewind
static uint8_t lastNote = 0x5f; // Mackie Control stop
static uint8_t offNote = lastNote;
static uint8_t onNote = firstNote;
// toggle NOTE On, Note Off for the Mackie Control channels 1-8 REC LED
const uint32_t intervalMs = 1000;
static uint32_t startMs = 0;
auto intf = usbhMIDI.getInterfaceFromDeviceAndCable(midiDevAddr, 0);
if (intf == nullptr)
return; // not connected
if ( millis() - startMs < intervalMs)
return; // not enough time
startMs += intervalMs;
intf->sendNoteOn(offNote++, 0, 1);
intf->sendNoteOn(onNote++, 0x7f, 1);
if (offNote > lastNote)
offNote = firstNote;
if (onNote > lastNote)
onNote = firstNote;
}
*/
/* APPLICATION STARTS HERE */
// core1's setup
void setup1() {
#if ARDUINO_ADAFRUIT_FEATHER_RP2040_USB_HOST
pinMode(18, OUTPUT); // Set pin USB_HOST_5V_POWER to HIGH to enable USB power
digitalWrite(18, HIGH);
#endif
//while(!Serial); // wait for native usb
Serial.println("Core1 setup to run TinyUSB host with pio-usb\r\n");
// Check for CPU frequency, must be multiple of 120Mhz for bit-banging USB
uint32_t cpu_hz = clock_get_hz(clk_sys);
if ( cpu_hz != 120000000UL && cpu_hz != 240000000UL ) {
delay(2000); // wait for native usb
Serial.printf("Error: CPU Clock = %lu, PIO USB require CPU clock must be multiple of 120 Mhz\r\n", cpu_hz);
Serial.printf("Change your CPU Clock to either 120 or 240 Mhz in Menu->CPU Speed \r\n");
while(1) delay(1);
}
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
pio_cfg.pin_dp = HOST_PIN_DP;
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
/* Need to swap PIOs so PIO code from CYW43 PIO SPI driver will fit */
pio_cfg.pio_rx_num = 0;
pio_cfg.pio_tx_num = 1;
#endif /* ARDUINO_RASPBERRY_PI_PICO_W */
USBHost.configure_pio_usb(1, &pio_cfg);
// run host stack on controller (rhport) 1
// Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
// host bit-banging processing work done in core1 to free up core0 for other work
usbhMIDI.begin(&USBHost, 1, onMIDIconnect, onMIDIdisconnect);
core1_booting = false;
while(core0_booting) ;
}
// core1's loop
void loop1()
{
USBHost.task();
}
void setup()
{
Serial.begin(115200);
MIDIusb.begin();
MIDIusb.turnThruOff(); // turn off echo
// while(!Serial); // wait for serial port
pinMode(LED_BUILTIN, OUTPUT);
Serial.println("EZ USB MIDI HOST PIO Example for Arduino\r\n");
core0_booting = false;
while(core1_booting) ;
}
void loop() {
// Handle any incoming data; triggers MIDI IN callbacks
usbhMIDI.readAll();
// Do other processing that might generate pending MIDI OUT data
//sendNextNote();
// Tell the USB Host to send as much pending MIDI OUT data as possible
usbhMIDI.writeFlushAll();
// Do other non-USB host processing
blinkLED();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment