From a86bf0db2294e9a65a8d77644b6d6509a40ae136 Mon Sep 17 00:00:00 2001 From: Andrew Capon Date: Sun, 21 Feb 2021 08:27:03 +0000 Subject: [PATCH 1/3] Add vscode settings. --- .gitignore | 2 +- .vscode/.cortex-debug.peripherals.state.json | 1 + .vscode/.cortex-debug.registers.state.json | 1 + .vscode/launch.json | 24 ++++++++++++++++++++ .vscode/settings.json | 24 ++++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .vscode/.cortex-debug.peripherals.state.json create mode 100644 .vscode/.cortex-debug.registers.state.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 899299224..39fcec99f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,4 @@ *.app **/build -.vscode + diff --git a/.vscode/.cortex-debug.peripherals.state.json b/.vscode/.cortex-debug.peripherals.state.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/.vscode/.cortex-debug.peripherals.state.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.vscode/.cortex-debug.registers.state.json b/.vscode/.cortex-debug.registers.state.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/.vscode/.cortex-debug.registers.state.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..b996cd2c2 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug", + "type":"cortex-debug", + "cwd": "${workspaceRoot}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "servertype": "external", + // This may need to be arm-none-eabi-gdb depending on your system + "gdbpath" : "arm-none-eabi-gdb", + // Connect to an already running OpenOCD instance + "gdbTarget": "localhost:3333", + "svdFile": "/Volumes/Samsung_T5/Development/pico/pico-sdk/src/rp2040/hardware_regs/rp2040.svd", + "runToMain": true, + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main",Î + "continue" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..aa9a6e96a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + // These settings tweaks to the cmake plugin will ensure + // that you debug using cortex-debug instead of trying to launch + // a Pico binary on the host + "cmake.statusbar.advanced": { + "debug": { + "visibility": "hidden" + }, + "launch": { + "visibility": "hidden" + }, + "build": { + "visibility": "hidden" + }, + "buildTarget": { + "visibility": "hidden" + } + }, + "cmake.buildBeforeRun": true, + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "cmake.configureSettings": { + "PICO_DEOPTIMIZED_DEBUG" : "on" + } +} \ No newline at end of file From 3e2643f4771e030a05e1acb4ed0c0eb4ad16c080 Mon Sep 17 00:00:00 2001 From: Andrew Capon Date: Sun, 28 Feb 2021 09:35:27 +0000 Subject: [PATCH 2/3] Add pico_rgb_keypad_midi example. Turns the rgb_keypad into a simple midi device. --- .vscode/settings.json | 49 ++++ examples/CMakeLists.txt | 1 + examples/pico_rgb_keypad_midi/CMakeLists.txt | 14 ++ examples/pico_rgb_keypad_midi/midi_demo.cpp | 225 ++++++++++++++++++ .../pico_sdk_import.cmake | 64 +++++ examples/pico_rgb_keypad_midi/tusb_config.h | 112 +++++++++ .../pico_rgb_keypad_midi/usb_descriptors.c | 177 ++++++++++++++ 7 files changed, 642 insertions(+) create mode 100644 examples/pico_rgb_keypad_midi/CMakeLists.txt create mode 100644 examples/pico_rgb_keypad_midi/midi_demo.cpp create mode 100644 examples/pico_rgb_keypad_midi/pico_sdk_import.cmake create mode 100644 examples/pico_rgb_keypad_midi/tusb_config.h create mode 100644 examples/pico_rgb_keypad_midi/usb_descriptors.c diff --git a/.vscode/settings.json b/.vscode/settings.json index aa9a6e96a..673f40213 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,5 +20,54 @@ "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", "cmake.configureSettings": { "PICO_DEOPTIMIZED_DEBUG" : "on" + }, + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "map": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp" } } \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 2a8fc013f..3ea51bece 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory(pico_scroll) add_subdirectory(pico_explorer) add_subdirectory(pico_rgb_keypad) add_subdirectory(pico_rtc_display) +add_subdirectory(pico_rgb_keypad_midi) diff --git a/examples/pico_rgb_keypad_midi/CMakeLists.txt b/examples/pico_rgb_keypad_midi/CMakeLists.txt new file mode 100644 index 000000000..1df554cad --- /dev/null +++ b/examples/pico_rgb_keypad_midi/CMakeLists.txt @@ -0,0 +1,14 @@ +set(CMAKE_C_FLAGS_DEBUG "-O0 -g") + +add_executable( + midi_rgb_keypad + midi_demo.cpp + usb_descriptors.c +) +target_include_directories(pico_rgb_keypad INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(midi_rgb_keypad pico_rgb_keypad pico_stdlib tinyusb_device tinyusb_board) + +# create map/bin/hex file etc. +pico_add_extra_outputs(midi_rgb_keypad) diff --git a/examples/pico_rgb_keypad_midi/midi_demo.cpp b/examples/pico_rgb_keypad_midi/midi_demo.cpp new file mode 100644 index 000000000..a3269d792 --- /dev/null +++ b/examples/pico_rgb_keypad_midi/midi_demo.cpp @@ -0,0 +1,225 @@ +// Very simple example that turns the pico_rgb_keypad into a MIDI device +// where each pad sends midi note messages +// +// Based on: +// https://github.com/pimoroni/pimoroni-pico/blob/main/examples/pico_rgb_keypad +// https://github.com/hathach/tinyusb/tree/master/examples/device/midi_test + + +#include +#include +#include +#include + +#include "bsp/board.h" +#include "tusb.h" + +#include "pico_rgb_keypad.hpp" + +using namespace pimoroni; + +PicoRGBKeypad pico_keypad; + +// ButtonNote class contains the button states +// for note ons and note offs +class ButtonNote +{ +public: + typedef enum + { + nsOn, + nsOff + } NoteState; + + ButtonNote(uint8_t btn_num, NoteState note_state) : m_btn_num(btn_num), m_btn_state(note_state) {}; + + const uint8_t GetButtonNum(void) const { return m_btn_num; }; + const uint8_t GetButtonNote(void) const { return 60 + m_btn_num; }; + const NoteState GetButtonState(void) const { return m_btn_state; }; + +private: + uint8_t m_btn_num; + NoteState m_btn_state; +}; + +// button_notes vector contains the note data to send over MIDI +std::vector button_notes; + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_blinking_task(void); +void midi_task(void); +void keypad_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + + tusb_init(); + + pico_keypad.init(); + + pico_keypad.set_brightness(1.0f); + + while (1) + { + tud_task(); // tinyusb device task + led_blinking_task(); + keypad_task(); + midi_task(); + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +//--------------------------------------------------------------------+ +// MIDI Task +// +// Send all note events in button_notes over midi +// and then clear button_notes. +//--------------------------------------------------------------------+ + +void midi_task(void) +{ + static uint32_t start_ms = 0; + + uint8_t const cable_num = 0; // MIDI jack associated with USB endpoint + uint8_t const channel = 0; // 0 for channel 1 + + // The MIDI interface always creates input and output port/jack descriptors + // regardless of these being used or not. Therefore incoming traffic should be read + // (possibly just discarded) to avoid the sender blocking in IO + uint8_t packet[4]; + while(tud_midi_available()) + tud_midi_receive(packet); + + // For each of our note events send the relevent note on or note off over MIDI. + // Note: tudi_midi_write24 is renamed tud_midi_write24 in more recent versions of tinyusb, the pico repo is lagging behind. + for(ButtonNote note : button_notes) + { + if(note.GetButtonState() == ButtonNote::nsOn) + { + // Send Note On at full velocity (127) on channel 1. + tudi_midi_write24(cable_num, 0x90 | channel, note.GetButtonNum(), 127); + } + else + { + // Send Note Off + tudi_midi_write24(cable_num, 0x80 | channel, note.GetButtonNum(), 0); + } + } + + button_notes.clear(); +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +// +// Blinks the LED dependent on USB state. +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // Blink every interval ms + if ( board_millis() - start_ms < blink_interval_ms) + return; // not enough time + + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} + +//--------------------------------------------------------------------+ +// Keypad TASK +// +// Check button states, if changed add relevent ButtonNote to button_notes +// and set pad illumination. +//--------------------------------------------------------------------+ +void keypad_task(void) +{ + static uint16_t last_button_states = 0; + + uint16_t button_states = pico_keypad.get_button_states(); + + if(last_button_states != button_states) + { + uint16_t new_button_states = button_states; + + for(uint8_t pad = 0; pad < pico_keypad.NUM_PADS; pad++) + { + bool old_button_state = last_button_states & 0x01; + bool new_button_state = new_button_states & 0x01; + + if(old_button_state != new_button_state) + { + if(new_button_state) + { + // note on + pico_keypad.illuminate(pad, 0xff, 0xff, 0xff); + button_notes.push_back(ButtonNote(pad, ButtonNote::nsOn)); + } + else + { + // note off + pico_keypad.illuminate(pad, 0x00, 0x00, 0x00); + button_notes.push_back(ButtonNote(pad, ButtonNote::nsOff)); + } + } + + // shift states + new_button_states >>= 1; + last_button_states >>= 1; + } + + last_button_states = button_states; + } + + + pico_keypad.update(); +} diff --git a/examples/pico_rgb_keypad_midi/pico_sdk_import.cmake b/examples/pico_rgb_keypad_midi/pico_sdk_import.cmake new file mode 100644 index 000000000..f63ee3f85 --- /dev/null +++ b/examples/pico_rgb_keypad_midi/pico_sdk_import.cmake @@ -0,0 +1,64 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# todo document + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/examples/pico_rgb_keypad_midi/tusb_config.h b/examples/pico_rgb_keypad_midi/tusb_config.h new file mode 100644 index 000000000..61b9b6552 --- /dev/null +++ b/examples/pico_rgb_keypad_midi/tusb_config.h @@ -0,0 +1,112 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 1 +#define CFG_TUD_VENDOR 0 + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/pico_rgb_keypad_midi/usb_descriptors.c b/examples/pico_rgb_keypad_midi/usb_descriptors.c new file mode 100644 index 000000000..3ffe22544 --- /dev/null +++ b/examples/pico_rgb_keypad_midi/usb_descriptors.c @@ -0,0 +1,177 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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. + * + */ + +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +enum +{ + ITF_NUM_MIDI = 0, + ITF_NUM_MIDI_STREAMING, + ITF_NUM_TOTAL +}; + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MIDI_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... + #define EPNUM_MIDI 0x02 +#else + #define EPNUM_MIDI 0x01 +#endif + +uint8_t const desc_fs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 0, EPNUM_MIDI, 0x80 | EPNUM_MIDI, 64) +}; + +#if TUD_OPT_HIGH_SPEED +uint8_t const desc_hs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 0, EPNUM_MIDI, 0x80 | EPNUM_MIDI, 512) +}; +#endif + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + +#if TUD_OPT_HIGH_SPEED + // Although we are highspeed, host may be fullspeed. + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; +#else + return desc_fs_configuration; +#endif +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "Pimoroni", // 1: Manufacturer + "RGB Keypad", // 2: Product + "123456", // 3: Serials, should use chip ID +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i Date: Sun, 28 Feb 2021 09:54:25 +0000 Subject: [PATCH 3/3] Change pad mappins so that with pico on right hand side notes assend from bottom left --- examples/pico_rgb_keypad_midi/midi_demo.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/pico_rgb_keypad_midi/midi_demo.cpp b/examples/pico_rgb_keypad_midi/midi_demo.cpp index a3269d792..93796efd0 100644 --- a/examples/pico_rgb_keypad_midi/midi_demo.cpp +++ b/examples/pico_rgb_keypad_midi/midi_demo.cpp @@ -1,5 +1,6 @@ // Very simple example that turns the pico_rgb_keypad into a MIDI device -// where each pad sends midi note messages +// where each pad sends midi note messages. +// works with pico on the right hand side, notes ascending from bottom left. // // Based on: // https://github.com/pimoroni/pimoroni-pico/blob/main/examples/pico_rgb_keypad @@ -34,12 +35,13 @@ class ButtonNote ButtonNote(uint8_t btn_num, NoteState note_state) : m_btn_num(btn_num), m_btn_state(note_state) {}; const uint8_t GetButtonNum(void) const { return m_btn_num; }; - const uint8_t GetButtonNote(void) const { return 60 + m_btn_num; }; const NoteState GetButtonState(void) const { return m_btn_state; }; + const uint8_t GetButtonNote(void) const { return 60 + m_btn_note_map[m_btn_num]; }; private: uint8_t m_btn_num; NoteState m_btn_state; + uint8_t m_btn_note_map[16] = {15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0}; }; // button_notes vector contains the note data to send over MIDI @@ -143,12 +145,12 @@ void midi_task(void) if(note.GetButtonState() == ButtonNote::nsOn) { // Send Note On at full velocity (127) on channel 1. - tudi_midi_write24(cable_num, 0x90 | channel, note.GetButtonNum(), 127); + tudi_midi_write24(cable_num, 0x90 | channel, note.GetButtonNote(), 127); } else { // Send Note Off - tudi_midi_write24(cable_num, 0x80 | channel, note.GetButtonNum(), 0); + tudi_midi_write24(cable_num, 0x80 | channel, note.GetButtonNote(), 0); } }