├── .gitignore ├── CMakeLists.txt ├── README.md ├── pico-example-midi.c ├── pico_sdk_import.cmake ├── tusb_config.h └── usb_descriptors.c /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | _deps 4 | cmake-* 5 | build 6 | .DS_Store 7 | *.pdf 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | include(pico_sdk_import.cmake) 4 | 5 | project(test_project C CXX ASM) 6 | 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_CXX_STANDARD 17) 9 | pico_sdk_init() 10 | 11 | add_executable(pico-example-midi 12 | pico-example-midi.c 13 | usb_descriptors.c 14 | ) 15 | 16 | pico_enable_stdio_usb(pico-example-midi 0) 17 | pico_enable_stdio_uart(pico-example-midi 1) 18 | 19 | target_include_directories(pico-example-midi PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 20 | 21 | pico_add_extra_outputs(pico-example-midi) 22 | target_link_libraries(pico-example-midi PRIVATE pico_stdlib tinyusb_device tinyusb_board) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Pico: MIDI demo. 2 | 3 | This is [the MIDI example from Tinyusb][tinymidi], tweaked and configured such that it'll compile on a Raspberry Pi Pico out of the box. 4 | 5 | It uses a highly similar CMake configuration to the [Raspberry Pi Examples][examples], so should be compilable in a very similar way: I'm just cribbing from the ["Getting started with Raspberry Pi Pico" document][picostart]. 6 | 7 | When compiled successfully, the Pico will appear as a USB MIDI Device, emitting a stream of note data. You can connect this to a software synthesizer, or view it in a MIDI monitor tool to confirm success. 8 | 9 | [tinymidi]: https://github.com/raspberrypi/tinyusb/tree/pico/examples/device/midi_test/src 10 | [examples]: https://github.com/raspberrypi/pico-examples/ 11 | [picostart]: https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf 12 | -------------------------------------------------------------------------------- /pico-example-midi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | #include "hardware/gpio.h" 4 | #include "pico/binary_info.h" 5 | 6 | #include "bsp/board.h" 7 | #include "tusb.h" 8 | 9 | enum { 10 | BLINK_NOT_MOUNTED = 250, 11 | BLINK_MOUNTED = 1000, 12 | BLINK_SUSPENDED = 2500, 13 | }; 14 | 15 | static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; 16 | const uint LED_PIN = PICO_DEFAULT_LED_PIN; 17 | 18 | void led_blinking_task(void); 19 | void midi_task(void); 20 | 21 | int main() { 22 | board_init(); 23 | 24 | tusb_init(); 25 | 26 | while (1) 27 | { 28 | tud_task(); // tinyusb device task 29 | led_blinking_task(); 30 | midi_task(); 31 | } 32 | } 33 | 34 | //--------------------------------------------------------------------+ 35 | // Device callbacks 36 | //--------------------------------------------------------------------+ 37 | 38 | // Invoked when device is mounted 39 | void tud_mount_cb(void) 40 | { 41 | blink_interval_ms = BLINK_MOUNTED; 42 | } 43 | 44 | // Invoked when device is unmounted 45 | void tud_umount_cb(void) 46 | { 47 | blink_interval_ms = BLINK_NOT_MOUNTED; 48 | } 49 | 50 | // Invoked when usb bus is suspended 51 | // remote_wakeup_en : if host allow us to perform remote wakeup 52 | // Within 7ms, device must draw an average of current less than 2.5 mA from bus 53 | void tud_suspend_cb(bool remote_wakeup_en) 54 | { 55 | (void) remote_wakeup_en; 56 | blink_interval_ms = BLINK_SUSPENDED; 57 | } 58 | 59 | // Invoked when usb bus is resumed 60 | void tud_resume_cb(void) 61 | { 62 | blink_interval_ms = BLINK_MOUNTED; 63 | } 64 | 65 | //--------------------------------------------------------------------+ 66 | // MIDI Task 67 | //--------------------------------------------------------------------+ 68 | 69 | // Variable that holds the current position in the sequence. 70 | uint32_t note_pos = 0; 71 | 72 | // Store example melody as an array of note values 73 | uint8_t note_sequence[] = 74 | { 75 | 74,78,81,86,90,93,98,102,57,61,66,69,73,78,81,85,88,92,97,100,97,92,88,85,81,78, 76 | 74,69,66,62,57,62,66,69,74,78,81,86,90,93,97,102,97,93,90,85,81,78,73,68,64,61, 77 | 56,61,64,68,74,78,81,86,90,93,98,102 78 | }; 79 | 80 | void midi_task(void) 81 | { 82 | static uint32_t start_ms = 0; 83 | uint8_t msg[3]; 84 | 85 | // send note every 1000 ms 86 | if (board_millis() - start_ms < 286) return; // not enough time 87 | start_ms += 286; 88 | 89 | // Previous positions in the note sequence. 90 | int previous = note_pos - 1; 91 | 92 | // If we currently are at position 0, set the 93 | // previous position to the last note in the sequence. 94 | if (previous < 0) previous = sizeof(note_sequence) - 1; 95 | 96 | // Send Note On for current position at full velocity (127) on channel 1. 97 | msg[0] = 0x90; // Note On - Channel 1 98 | msg[1] = note_sequence[note_pos]; // Note Number 99 | msg[2] = 127; // Velocity 100 | tud_midi_n_stream_write(0, 0, msg, 3); 101 | 102 | // Send Note Off for previous note. 103 | msg[0] = 0x80; // Note Off - Channel 1 104 | msg[1] = note_sequence[previous]; // Note Number 105 | msg[2] = 0; // Velocity 106 | tud_midi_n_stream_write(0, 0, msg, 3); 107 | 108 | // Increment position 109 | note_pos++; 110 | 111 | // If we are at the end of the sequence, start over. 112 | if (note_pos >= sizeof(note_sequence)) note_pos = 0; 113 | } 114 | 115 | //--------------------------------------------------------------------+ 116 | // BLINKING TASK 117 | //--------------------------------------------------------------------+ 118 | void led_blinking_task(void) 119 | { 120 | static uint32_t start_ms = 0; 121 | static bool led_state = false; 122 | 123 | // Blink every interval ms 124 | if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time 125 | start_ms += blink_interval_ms; 126 | 127 | board_led_write(led_state); 128 | led_state = 1 - led_state; // toggle 129 | } 130 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading Raspberry Pi Pico SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 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 14 | * all 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 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //-------------------------------------------------------------------- 34 | // COMMON CONFIGURATION 35 | //-------------------------------------------------------------------- 36 | 37 | // defined by compiler flags for flexibility 38 | #ifndef CFG_TUSB_MCU 39 | #error CFG_TUSB_MCU must be defined 40 | #endif 41 | 42 | #if CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ 43 | CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 44 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) 45 | #else 46 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 47 | #endif 48 | 49 | #ifndef CFG_TUSB_OS 50 | #define CFG_TUSB_OS OPT_OS_PICO 51 | #endif 52 | 53 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 54 | // #define CFG_TUSB_DEBUG 0 55 | 56 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 57 | * Tinyusb use follows macros to declare transferring memory so that they can be put 58 | * into those specific section. 59 | * e.g 60 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 61 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 62 | */ 63 | #ifndef CFG_TUSB_MEM_SECTION 64 | #define CFG_TUSB_MEM_SECTION 65 | #endif 66 | 67 | #ifndef CFG_TUSB_MEM_ALIGN 68 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 69 | #endif 70 | 71 | //-------------------------------------------------------------------- 72 | // DEVICE CONFIGURATION 73 | //-------------------------------------------------------------------- 74 | 75 | #ifndef CFG_TUD_ENDPOINT0_SIZE 76 | #define CFG_TUD_ENDPOINT0_SIZE 64 77 | #endif 78 | 79 | //------------- CLASS -------------// 80 | #define CFG_TUD_CDC 0 81 | #define CFG_TUD_MSC 0 82 | #define CFG_TUD_HID 0 83 | #define CFG_TUD_MIDI 1 84 | #define CFG_TUD_VENDOR 0 85 | 86 | // MIDI FIFO size of TX and RX 87 | #define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 88 | #define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 89 | 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | 94 | #endif /* _TUSB_CONFIG_H_ */ 95 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 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 14 | * all 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 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "tusb.h" 27 | 28 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. 29 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. 30 | * 31 | * Auto ProductID layout's Bitmap: 32 | * [MSB] MIDI | HID | MSC | CDC [LSB] 33 | */ 34 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) 35 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ 36 | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) 37 | 38 | //--------------------------------------------------------------------+ 39 | // Device Descriptors 40 | //--------------------------------------------------------------------+ 41 | tusb_desc_device_t const desc_device = 42 | { 43 | .bLength = sizeof(tusb_desc_device_t), 44 | .bDescriptorType = TUSB_DESC_DEVICE, 45 | .bcdUSB = 0x0200, 46 | .bDeviceClass = 0x00, 47 | .bDeviceSubClass = 0x00, 48 | .bDeviceProtocol = 0x00, 49 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 50 | 51 | .idVendor = 0xCafe, 52 | .idProduct = USB_PID, 53 | .bcdDevice = 0x0100, 54 | 55 | .iManufacturer = 0x01, 56 | .iProduct = 0x02, 57 | .iSerialNumber = 0x03, 58 | 59 | .bNumConfigurations = 0x01 60 | }; 61 | 62 | // Invoked when received GET DEVICE DESCRIPTOR 63 | // Application return pointer to descriptor 64 | uint8_t const * tud_descriptor_device_cb(void) 65 | { 66 | return (uint8_t const *) &desc_device; 67 | } 68 | 69 | 70 | //--------------------------------------------------------------------+ 71 | // Configuration Descriptor 72 | //--------------------------------------------------------------------+ 73 | 74 | enum 75 | { 76 | ITF_NUM_MIDI = 0, 77 | ITF_NUM_MIDI_STREAMING, 78 | ITF_NUM_TOTAL 79 | }; 80 | 81 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MIDI_DESC_LEN) 82 | 83 | #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX 84 | // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number 85 | // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... 86 | #define EPNUM_MIDI 0x02 87 | #else 88 | #define EPNUM_MIDI 0x01 89 | #endif 90 | 91 | uint8_t const desc_fs_configuration[] = 92 | { 93 | // Config number, interface count, string index, total length, attribute, power in mA 94 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), 95 | 96 | // Interface number, string index, EP Out & EP In address, EP size 97 | TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 0, EPNUM_MIDI, 0x80 | EPNUM_MIDI, 64) 98 | }; 99 | 100 | #if TUD_OPT_HIGH_SPEED 101 | uint8_t const desc_hs_configuration[] = 102 | { 103 | // Config number, interface count, string index, total length, attribute, power in mA 104 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), 105 | 106 | // Interface number, string index, EP Out & EP In address, EP size 107 | TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 0, EPNUM_MIDI, 0x80 | EPNUM_MIDI, 512) 108 | }; 109 | #endif 110 | 111 | // Invoked when received GET CONFIGURATION DESCRIPTOR 112 | // Application return pointer to descriptor 113 | // Descriptor contents must exist long enough for transfer to complete 114 | uint8_t const * tud_descriptor_configuration_cb(uint8_t index) 115 | { 116 | (void) index; // for multiple configurations 117 | 118 | #if TUD_OPT_HIGH_SPEED 119 | // Although we are highspeed, host may be fullspeed. 120 | return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; 121 | #else 122 | return desc_fs_configuration; 123 | #endif 124 | } 125 | 126 | //--------------------------------------------------------------------+ 127 | // String Descriptors 128 | //--------------------------------------------------------------------+ 129 | 130 | // array of pointer to string descriptors 131 | char const* string_desc_arr [] = 132 | { 133 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 134 | "Raspberry Pi", // 1: Manufacturer 135 | "Pico Demo Device", // 2: Product 136 | "123456", // 3: Serials, should use chip ID 137 | }; 138 | 139 | static uint16_t _desc_str[32]; 140 | 141 | // Invoked when received GET STRING DESCRIPTOR request 142 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 143 | uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) 144 | { 145 | (void) langid; 146 | 147 | uint8_t chr_count; 148 | 149 | if ( index == 0) 150 | { 151 | memcpy(&_desc_str[1], string_desc_arr[0], 2); 152 | chr_count = 1; 153 | }else 154 | { 155 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. 156 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 157 | 158 | if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; 159 | 160 | const char* str = string_desc_arr[index]; 161 | 162 | // Cap at max char 163 | chr_count = strlen(str); 164 | if ( chr_count > 31 ) chr_count = 31; 165 | 166 | // Convert ASCII string into UTF-16 167 | for(uint8_t i=0; i