Last active
June 13, 2024 01:27
-
-
Save todbot/0a7824bcf8bfa708d1be723d0331a12d to your computer and use it in GitHub Desktop.
USB MIDI Host using PIO on RP2040 USB Type A Host Feather
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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