├── images └── mididump.jpg ├── .gitignore ├── LICENSE ├── README.md └── MIDIDump.ino /images/mididump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdsports/MIDIDump/HEAD/images/mididump.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 gdsports625@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M5Stack Portable USB Host MIDI Display 2 | 3 | ![USB MIDI keyboard plugged into M5Stack showing MIDI events](./images/mididump.jpg) 4 | 5 | Plug in a USB MIDI keyboard into the M5Stack USB host port and see MIDI events 6 | on the display. When the screen is full, press Button A to clear. Runs on 7 | battery with a plain USB MIDI keyboard. USB MIDI devices with lots of LEDs or 8 | complex functions such as sampler, sequencers, and displays may draw too much 9 | current for the M5Stack battery. Try powering the M5Stack via its USB port or 10 | power the MIDI device using its own power supply, if available. 11 | 12 | ## M5Stack Core + USB host module 13 | 14 | The M5Stack USB module provides the USB host interface for the printer. The USB 15 | module is based on the MAX3421E USB chip which is supported by the USB Host 16 | Shield 2.0 library. 17 | 18 | ### Hardware 19 | 20 | * M5Stack core (ESP32, battery, display, etc.) https://m5stack.com/collections/m5-core/products/grey-development-core 21 | * M5Stack USB host module (MAX3421E) https://m5stack.com/collections/m5-module/products/usb-module 22 | * USB MIDI keyboard 23 | * USB cable 24 | 25 | ### Libraries 26 | 27 | * USB Host Shield 2.0 https://github.com/felis/USB_Host_Shield_2.0 28 | * Highly recommended: PR #473 29 | * Highly recommended if using USB hubs: PR #414 30 | 31 | ## Arduino upload failure 32 | 33 | I found installing a 1 uF capacitor between GND and RST helps a lot. See 34 | the link for more discussion. 35 | 36 | http://community.m5stack.com/topic/55/simple-fix-when-upload-fails 37 | 38 | ## Battery Power On Failure 39 | 40 | When running on battery, the M5Stack does not turn on when the USB host module 41 | is installed. The M5Stack works fine running on battery but once it is turned 42 | off, it can only be turned on by plugging in power via the USB jack. 43 | 44 | The M5Stack power on works correctly when the USB host module is removed. 45 | -------------------------------------------------------------------------------- /MIDIDump.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2019 gdsports625@gmail.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | // Turn this on slows down MIDI processing. Turn off for 30 | // best performance. 31 | #define DEBUG_USB 0 32 | 33 | #define DBSerial Serial 34 | 35 | /*! Enumeration of MIDI types */ 36 | enum MidiType 37 | { 38 | InvalidType = 0x00, ///< For notifying errors 39 | NoteOff = 0x80, ///< Note Off 40 | NoteOn = 0x90, ///< Note On 41 | AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch 42 | ControlChange = 0xB0, ///< Control Change / Channel Mode 43 | ProgramChange = 0xC0, ///< Program Change 44 | AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch 45 | PitchBend = 0xE0, ///< Pitch Bend 46 | SystemExclusive = 0xF0, ///< System Exclusive 47 | TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame 48 | SongPosition = 0xF2, ///< System Common - Song Position Pointer 49 | SongSelect = 0xF3, ///< System Common - Song Select 50 | TuneRequest = 0xF6, ///< System Common - Tune Request 51 | Clock = 0xF8, ///< System Real Time - Timing Clock 52 | Start = 0xFA, ///< System Real Time - Start 53 | Continue = 0xFB, ///< System Real Time - Continue 54 | Stop = 0xFC, ///< System Real Time - Stop 55 | ActiveSensing = 0xFE, ///< System Real Time - Active Sensing 56 | SystemReset = 0xFF, ///< System Real Time - System Reset 57 | }; 58 | 59 | USB Usb; 60 | USBH_MIDI MIDIUSBH(&Usb); 61 | uint8_t UsbHSysEx[1026]; 62 | uint8_t *UsbHSysExPtr; 63 | 64 | void setup() 65 | { 66 | M5.begin(); 67 | M5.Lcd.fillScreen(TFT_NAVY); 68 | M5.Lcd.setTextSize(3); 69 | M5.Lcd.setTextColor(TFT_MAGENTA, TFT_BLUE); 70 | M5.Lcd.fillRect(0, 0, 320, 30, TFT_BLUE); 71 | M5.Lcd.println("MIDI Dump"); 72 | 73 | M5.Lcd.setTextSize(2); 74 | M5.Lcd.setCursor(0, 31); 75 | M5.Lcd.println("Button A clears"); 76 | M5.Lcd.setTextColor(TFT_YELLOW); 77 | 78 | DBSerial.begin(115200); 79 | if (Usb.Init() == -1) { 80 | DBSerial.println(F("USB host init failed")); 81 | M5.Lcd.println(F("USB host init failed")); 82 | while (1); //halt 83 | } 84 | UsbHSysExPtr = UsbHSysEx; 85 | DBSerial.println(F("USB host running")); 86 | } 87 | 88 | inline void dumpSongSelect(uint8_t n) 89 | { 90 | M5.Lcd.print("Song Select "); 91 | M5.Lcd.println(n); 92 | } 93 | 94 | inline void dumpTimeCodeQuarterFrame(uint8_t n) 95 | { 96 | M5.Lcd.print("Time Code Quarter Frame "); 97 | M5.Lcd.println(n); 98 | } 99 | 100 | inline void dumpProgramChange(uint8_t n, uint8_t chan) 101 | { 102 | M5.Lcd.print("Program Change "); 103 | M5.Lcd.print(n); 104 | M5.Lcd.print(" chan "); 105 | M5.Lcd.println(chan); 106 | } 107 | 108 | inline void dumpAfterTouch(uint8_t n, uint8_t chan) 109 | { 110 | M5.Lcd.print("After Touch "); 111 | M5.Lcd.print(n); 112 | M5.Lcd.print(" chan "); 113 | M5.Lcd.println(chan); 114 | } 115 | 116 | inline void dumpSongPosition(uint16_t pos) 117 | { 118 | M5.Lcd.print("Song Position "); 119 | M5.Lcd.println(pos); 120 | } 121 | 122 | inline void dumpNoteOff(uint8_t note, uint8_t velocity, uint8_t chan) 123 | { 124 | M5.Lcd.print("Note Off "); 125 | M5.Lcd.print(note); 126 | M5.Lcd.print(','); 127 | M5.Lcd.print(velocity); 128 | M5.Lcd.print(" chan "); 129 | M5.Lcd.println(chan); 130 | } 131 | 132 | inline void dumpNoteOn(uint8_t note, uint8_t velocity, uint8_t chan) 133 | { 134 | M5.Lcd.print("Note On "); 135 | M5.Lcd.print(note); 136 | M5.Lcd.print(','); 137 | M5.Lcd.print(velocity); 138 | M5.Lcd.print(" chan "); 139 | M5.Lcd.println(chan); 140 | } 141 | 142 | inline void dumpPolyPressure(uint8_t note, uint8_t velocity, uint8_t chan) 143 | { 144 | M5.Lcd.print("Polyphonic Key Pressure "); 145 | M5.Lcd.print(note); 146 | M5.Lcd.print(','); 147 | M5.Lcd.print(velocity); 148 | M5.Lcd.print(" chan "); 149 | M5.Lcd.println(chan); 150 | } 151 | 152 | inline void dumpControlChange(uint8_t control, uint8_t change, uint8_t chan) 153 | { 154 | M5.Lcd.print("Control Chng "); 155 | M5.Lcd.print(control); 156 | M5.Lcd.print(','); 157 | M5.Lcd.print(change); 158 | M5.Lcd.print(" chan "); 159 | M5.Lcd.println(chan); 160 | } 161 | 162 | inline void dumpPitchBend(uint16_t bend, uint8_t chan) 163 | { 164 | M5.Lcd.print("Pitch Bend "); 165 | M5.Lcd.print(bend); 166 | M5.Lcd.print(" chan "); 167 | M5.Lcd.println(chan); 168 | } 169 | 170 | void dumpSysEx(uint8_t *p, size_t len) 171 | { 172 | char outbuf[8]; 173 | 174 | M5.Lcd.print("SysEx("); 175 | M5.Lcd.print(len); 176 | M5.Lcd.print("):"); 177 | for (size_t i = 0; i < min(16, len); i++) { 178 | snprintf(outbuf, sizeof(outbuf), " %02X", *p++); 179 | M5.Lcd.print(outbuf); 180 | } 181 | M5.Lcd.println(); 182 | } 183 | 184 | void USBHostDump() 185 | { 186 | uint8_t recvBuf[MIDI_EVENT_PACKET_SIZE]; 187 | uint8_t rcode = 0; //return code 188 | uint16_t rcvd; 189 | uint8_t readCount = 0; 190 | 191 | rcode = MIDIUSBH.RecvData( &rcvd, recvBuf); 192 | 193 | //data check 194 | if (rcode != 0 || rcvd == 0) return; 195 | if ( recvBuf[0] == 0 && recvBuf[1] == 0 && recvBuf[2] == 0 && recvBuf[3] == 0 ) { 196 | return; 197 | } 198 | 199 | uint8_t *p = recvBuf; 200 | while (readCount < rcvd) { 201 | if (*p == 0 && *(p + 1) == 0) break; //data end 202 | #if DEBUG_USB 203 | DBSerial.print(F("USB ")); 204 | DBSerial.print(p[0], HEX); 205 | DBSerial.print(' '); 206 | DBSerial.print(p[1], HEX); 207 | DBSerial.print(' '); 208 | DBSerial.print(p[2], HEX); 209 | DBSerial.print(' '); 210 | DBSerial.println(p[3], HEX); 211 | #endif 212 | uint8_t header = *p & 0x0F; 213 | p++; 214 | uint8_t chan = (*p & 0x0F) + 1; 215 | MidiType miditype = (MidiType)(*p & 0xF0); 216 | #if DEBUG_USB 217 | DBSerial.print(F("header, chan, miditype:")); 218 | DBSerial.print(header, HEX); 219 | DBSerial.print(','); 220 | DBSerial.print(chan, HEX); 221 | DBSerial.print(','); 222 | DBSerial.println(miditype, HEX); 223 | #endif 224 | switch (header) { 225 | case 0x00: // Misc. Reserved for future extensions. 226 | break; 227 | case 0x01: // Cable events. Reserved for future expansion. 228 | break; 229 | case 0x02: // Two-byte System Common messages 230 | switch (p[0]) { 231 | case SongSelect: 232 | dumpSongSelect(p[1]); 233 | break; 234 | case TimeCodeQuarterFrame: 235 | dumpTimeCodeQuarterFrame(p[1]); 236 | break; 237 | default: 238 | break; 239 | } 240 | break; 241 | case 0x0C: // Program Change 242 | dumpProgramChange(p[1], chan); 243 | break; 244 | case 0x0D: // Channel Pressure 245 | dumpAfterTouch(p[1], chan); 246 | break; 247 | case 0x03: // Three-byte System Common messages 248 | switch (p[0]) { 249 | case SongPosition: 250 | dumpSongPosition(((p[2] & 0x7F) << 7) | (p[1] & 0x7F)); 251 | break; 252 | default: 253 | break; 254 | } 255 | break; 256 | case 0x08: // Note-off 257 | case 0x09: // Note-on 258 | case 0x0A: // Poly-KeyPress 259 | case 0x0B: // Control Change 260 | case 0x0E: // PitchBend Change 261 | switch (miditype) { 262 | case MidiType::NoteOff: 263 | dumpNoteOff(p[1], p[2], chan); 264 | break; 265 | case MidiType::NoteOn: 266 | dumpNoteOn(p[1], p[2], chan); 267 | break; 268 | case MidiType::AfterTouchPoly: 269 | dumpPolyPressure(p[1], p[2], chan); 270 | break; 271 | case MidiType::ControlChange: 272 | dumpControlChange(p[1], p[2], chan); 273 | break; 274 | case MidiType::PitchBend: 275 | dumpPitchBend(((p[2] & 0x7F) << 7) | (p[1] & 0x7F), chan); 276 | break; 277 | } 278 | break; 279 | 280 | case 0x04: // SysEx starts or continues 281 | UsbHSysExPtr = UsbHSysEx; 282 | memcpy(UsbHSysExPtr, p, 3); 283 | UsbHSysExPtr += 3; 284 | break; 285 | case 0x05: // Single-byte System Common Message or SysEx ends with the following single byte 286 | memcpy(UsbHSysExPtr, p, 1); 287 | UsbHSysExPtr += 1; 288 | dumpSysEx(UsbHSysEx, UsbHSysExPtr - UsbHSysEx); 289 | UsbHSysExPtr = UsbHSysEx; 290 | break; 291 | case 0x06: // SysEx ends with the following two bytes 292 | memcpy(UsbHSysExPtr, p, 2); 293 | UsbHSysExPtr += 2; 294 | dumpSysEx(UsbHSysEx, UsbHSysExPtr - UsbHSysEx); 295 | UsbHSysExPtr = UsbHSysEx; 296 | break; 297 | case 0x07: // SysEx ends with the following three bytes 298 | memcpy(UsbHSysExPtr, p, 3); 299 | UsbHSysExPtr += 3; 300 | dumpSysEx(UsbHSysEx, UsbHSysExPtr - UsbHSysEx); 301 | UsbHSysExPtr = UsbHSysEx; 302 | break; 303 | case 0x0F: // Single Byte, TuneRequest, Clock, Start, Continue, Stop, etc. 304 | switch (p[0]) { 305 | case MidiType::TuneRequest: 306 | M5.Lcd.println("TuneRequest"); 307 | break; 308 | case MidiType::Clock: 309 | M5.Lcd.println("Clock"); 310 | break; 311 | case MidiType::Start: 312 | M5.Lcd.println("Start"); 313 | break; 314 | case MidiType::Continue: 315 | M5.Lcd.println("Continue"); 316 | break; 317 | case MidiType::Stop: 318 | M5.Lcd.println("Stop"); 319 | break; 320 | case MidiType::ActiveSensing: 321 | M5.Lcd.println("Active Sensing"); 322 | break; 323 | case MidiType::SystemReset: 324 | M5.Lcd.println("Reset"); 325 | break; 326 | default: 327 | break; 328 | } 329 | break; 330 | } 331 | p += 3; 332 | readCount += 4; 333 | } 334 | } 335 | 336 | void loop() 337 | { 338 | Usb.Task(); 339 | 340 | M5.update(); 341 | if (M5.BtnA.wasReleased()) { 342 | M5.Lcd.clear(TFT_NAVY); 343 | M5.Lcd.setCursor(0, 0); 344 | M5.Lcd.setTextSize(2); 345 | M5.Lcd.setTextColor(TFT_YELLOW); 346 | } 347 | 348 | if (MIDIUSBH) { 349 | USBHostDump(); 350 | } 351 | } 352 | --------------------------------------------------------------------------------