diff --git a/README.md b/README.md index 0656ec6..ba3b60b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RP2040 MIDI Tracker -A simple MIDI step sequencer using a Raspberry Pi Pico W, an OLED display, a rotary encoder, and an 8x8 NeoPixel matrix. +A simple MIDI step sequencer using a Raspberry Pi Pico (or Pico W), an OLED display, a rotary encoder, and an 8x8 NeoPixel matrix. ## Software Requirements @@ -8,10 +8,9 @@ A simple MIDI step sequencer using a Raspberry Pi Pico W, an OLED display, a rot 1. Add this URL to your Additional Boards Manager URLs in Preferences: `https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json` 2. Go to **Tools > Board > Boards Manager**, search for **"pico"**, and install **Raspberry Pi Pico/RP2040**. - 3. Select **Raspberry Pi Pico W** from the board menu. + 3. Select **Raspberry Pi Pico** (or **Pico W**) from the board menu. * **Libraries**: Install via Library Manager: * `Adafruit GFX Library`, `Adafruit SSD1306`, `Adafruit NeoPixel` - * *(Note: `ArduinoBLE` is NO LONGER required. We use the built-in BTStack)* ## Connections & Wiring @@ -47,6 +46,11 @@ Make sure to establish a **common ground** by connecting the ground from your ex | 5V / VCC | External 5V Supply `+` | **External 5V Power** | | GND | External 5V Supply `-` | **External Power Ground** | | | GND (Pin 18) | **Common Ground with Pico** | +| **MIDI DIN (Serial)** | | | +| TX (MIDI OUT) | GP0 (Pin 1) | To DIN Pin 5 (via 220Ω) | + +**MIDI Hardware Note**: +* **MIDI OUT**: Connect **GP0** to DIN Pin 5 via a 220Ω resistor. Connect DIN Pin 4 to +5V via a 220Ω resistor. Connect DIN Pin 2 to GND. Once everything is wired up, you can upload the code and your tracker should be ready to go! @@ -87,4 +91,4 @@ For a more integrated build that can fit inside an enclosure, you can use a Lith * **Navigation**: Rotate the encoder to move between steps. * **Edit Mode**: Short press the encoder button to toggle Edit Mode. Rotate to change the note. * **Playback**: Long press the encoder button (> 0.6s) to Start/Stop playback. -* **Bluetooth**: The device advertises as "RP2040 Tracker". Connect to it from your computer or MIDI Bluetooth dongle. \ No newline at end of file +* **MIDI**: The device sends MIDI notes via the Hardware Serial (DIN) port on GP0. \ No newline at end of file diff --git a/RP2040_Tracker.ino b/RP2040_Tracker.ino index 9efefe5..969973f 100644 --- a/RP2040_Tracker.ino +++ b/RP2040_Tracker.ino @@ -4,18 +4,6 @@ #include #include -// --- BLUETOOTH CONFIGURATION (Earle Philhower Core) --- -#if defined(ARDUINO_ARCH_RP2040) && defined(ARDUINO_RASPBERRY_PI_PICO_W) - #define ENABLE_BTSTACK - #include - - // BTStack System Locks (provided by the core) - extern "C" { - void __lockBluetooth(); - void __unlockBluetooth(); - } -#endif - // --- HARDWARE CONFIGURATION --- #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 @@ -30,6 +18,9 @@ #define ENC_DT 13 #define ENC_SW 14 +// MIDI UART Pins (GP0/GP1) -- OUT only so far +#define PIN_MIDI_TX 0 + // NeoPixel Pin (any GPIO is fine, I've chosen 16) #define PIN_NEOPIXEL 16 #define NUM_PIXELS 64 // For 8x8 WS2812B matrix @@ -47,44 +38,6 @@ Step sequence[NUM_STEPS]; Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); Adafruit_NeoPixel pixels(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); -#ifdef ENABLE_BTSTACK -// BLE State -#define TARGET_DEVICE_NAME "WIDI Thru6" - -static hci_con_handle_t con_handle = HCI_CON_HANDLE_INVALID; -static uint16_t midi_char_value_handle = 0; // Handle to write to on remote device -static btstack_packet_callback_registration_t hci_event_callback_registration; -bool wasConnected = false; - -// UUIDs (Big Endian to match stack return values) -// Service: 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 -static const uint8_t midi_service_uuid[] = {0x03, 0xB8, 0x0E, 0x5A, 0xED, 0xE8, 0x4B, 0x33, 0xA7, 0x51, 0x6C, 0xE3, 0x4E, 0xC4, 0xC7, 0x00}; -// Char: 7772E5DB-3868-4112-A1A9-F2669D106BF3 (Little Endian for packet search) -static const uint8_t midi_char_uuid[] = {0xF3, 0x6B, 0x10, 0x9D, 0x66, 0xF2, 0xA9, 0xA1, 0x12, 0x41, 0x68, 0x38, 0xDB, 0xE5, 0x72, 0x77}; -// Char: 7772E5DB-3868-4112-A1A9-F2669D106BF3 (Big Endian fallback) -static const uint8_t midi_char_uuid_be[] = {0x77, 0x72, 0xE5, 0xDB, 0x38, 0x68, 0x41, 0x12, 0xA1, 0xA9, 0xF2, 0x66, 0x9D, 0x10, 0x6B, 0xF3}; - -static gatt_client_service_t found_service; -static gatt_client_service_t target_service; -static gatt_client_characteristic_t target_characteristic; -static uint16_t midi_cccd_handle = 0; - -enum DiscoveryState { - DISCOVERY_IDLE, - DISCOVERY_FINDING_SERVICE, - DISCOVERY_FINDING_CHARACTERISTIC, - DISCOVERY_FINDING_CCCD, - DISCOVERY_ENABLING_NOTIFICATIONS, - DISCOVERY_COMPLETE -}; -static DiscoveryState discovery_state = DISCOVERY_IDLE; -static bool service_found = false; -static bool midi_ready = false; - -// Uncomment to use Write Request (slower, reliable) instead of Write Command (faster, fire-and-forget) -// #define BLE_USE_WRITE_RESPONSE -#endif - int currentStep = 0; bool isEditing = false; int scrollOffset = 0; @@ -105,18 +58,6 @@ bool buttonActive = false; bool buttonConsumed = false; unsigned long buttonPressTime = 0; -// Bluetooth Icon (8x8) -const unsigned char bluetooth_icon[] PROGMEM = { - 0x10, // ...1.... - 0x18, // ...11... - 0x14, // ...1.1.. - 0x52, // .1.1..1. - 0x38, // ..111... - 0x52, // .1.1..1. - 0x14, // ...1.1.. - 0x18 // ...11... -}; - // --- ENCODER INTERRUPT --- // Robust Rotary Encoder reading void readEncoder() { @@ -136,215 +77,6 @@ void readEncoder() { } } -#ifdef ENABLE_BTSTACK - -// GATT Client Event Handler -void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - switch(hci_event_packet_get_type(packet)) { - case GATT_EVENT_SERVICE_QUERY_RESULT: - gatt_event_service_query_result_get_service(packet, &found_service); - Serial.print(F("Svc UUID: ")); - if (found_service.uuid16) { - Serial.println(found_service.uuid16, HEX); - } else { - for(int i=0; i<16; i++) { Serial.print(found_service.uuid128[i], HEX); Serial.print(" "); } - Serial.println(); - if (memcmp(found_service.uuid128, midi_service_uuid, 16) == 0) { - target_service = found_service; - service_found = true; - } - } - break; - case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: - { - Serial.print(F("Char Packet: ")); - for(int i=0; i Found CCCD!")); - } else { - Serial.println(); - } - } - break; - case GATT_EVENT_NOTIFICATION: - { - uint16_t value_length = gatt_event_notification_get_value_length(packet); - const uint8_t *value = gatt_event_notification_get_value(packet); - Serial.print(F("RX MIDI: ")); - for (int i = 0; i < value_length; i++) { - Serial.print(value[i], HEX); Serial.print(" "); - } - Serial.println(); - } - break; - case GATT_EVENT_QUERY_COMPLETE: - if (discovery_state == DISCOVERY_FINDING_SERVICE) { - if (service_found) { - Serial.println(F("Service found, searching for characteristic...")); - discovery_state = DISCOVERY_FINDING_CHARACTERISTIC; - // Discover ALL characteristics to avoid endianness issues in filters - gatt_client_discover_characteristics_for_service(handle_gatt_client_event, con_handle, &target_service); - } else { - Serial.println(F("MIDI Service not found on device.")); - discovery_state = DISCOVERY_IDLE; - } - } else if (discovery_state == DISCOVERY_FINDING_CHARACTERISTIC) { - if (midi_char_value_handle != 0) { - Serial.println(F("MIDI Char found. Searching for CCCD...")); - discovery_state = DISCOVERY_FINDING_CCCD; - gatt_client_discover_characteristic_descriptors(handle_gatt_client_event, con_handle, &target_characteristic); - } else { - Serial.println(F("MIDI Characteristic not found.")); - discovery_state = DISCOVERY_IDLE; - } - } else if (discovery_state == DISCOVERY_FINDING_CCCD) { - if (midi_cccd_handle != 0) { - Serial.println(F("CCCD found. Enabling Notifications...")); - discovery_state = DISCOVERY_ENABLING_NOTIFICATIONS; - uint8_t data[] = {0x01, 0x00}; // Enable Notification (Little Endian) - gatt_client_write_characteristic_descriptor_using_descriptor_handle(handle_gatt_client_event, con_handle, midi_cccd_handle, 2, data); - } else { - Serial.println(F("CCCD not found. Sending without notifications.")); - discovery_state = DISCOVERY_COMPLETE; - midi_ready = true; - } - } else if (discovery_state == DISCOVERY_ENABLING_NOTIFICATIONS) { - Serial.println(F("Notifications Enabled. BLE MIDI Ready!")); - discovery_state = DISCOVERY_COMPLETE; - midi_ready = true; - } - break; - } -} - -// BTStack Packet Handler -void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - if (packet_type != HCI_EVENT_PACKET) return; - - switch (hci_event_packet_get_type(packet)) { - case GAP_EVENT_ADVERTISING_REPORT: { - // Check if this is the target device - uint8_t event_type = gap_event_advertising_report_get_advertising_event_type(packet); - uint8_t length = gap_event_advertising_report_get_data_length(packet); - const uint8_t *data = gap_event_advertising_report_get_data(packet); - bd_addr_t addr; - gap_event_advertising_report_get_address(packet, addr); - - Serial.print(F("Adv [Type ")); Serial.print(event_type); - Serial.print(F("] from ")); Serial.print(bd_addr_to_str(addr)); - Serial.print(F(" len=")); Serial.println(length); - - // Parser to find Local Name (0x09 Complete, 0x08 Shortened) - int i = 0; - while (i < length) { - uint8_t len = data[i]; - if (len == 0) break; - uint8_t type = data[i+1]; - if (type == 0x09 || type == 0x08) { // Complete or Shortened Local Name - Serial.print(F(" Name: ")); - Serial.write(&data[i+2], len - 1); - Serial.println(); - - // Check for match (ensure length matches to avoid partial matches) - if ((len - 1) == strlen(TARGET_DEVICE_NAME) && - memcmp(&data[i+2], TARGET_DEVICE_NAME, len - 1) == 0) { - Serial.println(F("Found WIDI Thru6! Connecting...")); - gap_stop_scan(); - gap_connect(addr, (bd_addr_type_t)gap_event_advertising_report_get_address_type(packet)); - return; - } - } - i += len + 1; - } - break; - } - case HCI_EVENT_LE_META: - if (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE) { - con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); - Serial.println(F("Connected. Searching for MIDI Service...")); - - discovery_state = DISCOVERY_FINDING_SERVICE; - service_found = false; - midi_char_value_handle = 0; - midi_cccd_handle = 0; - midi_ready = false; - - // Discover ALL Services to debug/find MIDI - gatt_client_discover_primary_services(handle_gatt_client_event, con_handle); - - // Optimize Connection - gatt_client_send_mtu_negotiation(handle_gatt_client_event, con_handle); - gap_request_connection_parameter_update(con_handle, 12, 24, 0, 3000); // 15ms - 30ms interval - } - break; - case HCI_EVENT_DISCONNECTION_COMPLETE: - con_handle = HCI_CON_HANDLE_INVALID; - midi_char_value_handle = 0; - discovery_state = DISCOVERY_IDLE; - midi_ready = false; - Serial.println(F("Disconnected. Restarting scan...")); - gap_start_scan(); - break; - } -} - -void setupBluetooth() { - __lockBluetooth(); - - l2cap_init(); - gatt_client_init(); - sm_init(); - sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); - - // Register Callback & Start - hci_event_callback_registration.callback = &packet_handler; - hci_add_event_handler(&hci_event_callback_registration); - - // Start Scanning - gap_set_scan_parameters(1, 0x0030, 0x0030); // 1 = Active Scanning (requests Scan Response) - gap_start_scan(); - Serial.println(F("Scanning started...")); - - hci_power_control(HCI_POWER_ON); - - __unlockBluetooth(); -} -#endif - void setup() { Serial.begin(115200); delay(5000); @@ -390,46 +122,18 @@ void setup() { display.clearDisplay(); display.display(); - // 5. Setup BLE -#ifdef ENABLE_BTSTACK - setupBluetooth(); - Serial.println(F("BTStack initialized.")); -#else - Serial.println(F("BLE Disabled (Requires Earle Philhower Core + Pico W)")); -#endif + // 5. Setup MIDI Serial + Serial1.setTX(PIN_MIDI_TX); + Serial1.begin(31250); + Serial.println(F("MIDI Serial initialized on GP0/GP1")); Serial.println(F("Started.")); } void sendMidi(uint8_t status, uint8_t note, uint8_t velocity) { -#ifdef ENABLE_BTSTACK - if (con_handle != HCI_CON_HANDLE_INVALID && midi_char_value_handle != 0) { - // Use 0 timestamp for "immediate" execution to rule out timing/sync issues - // Header: 10xxxxxx (Bit 7=1, Bit 6=0 for MIDI, Bits 5-0 = high 6 bits of timestamp) - // Timestamp: 1xxxxxxx (Bit 7=1, Bits 6-0 = low 7 bits of timestamp) - uint8_t header = 0x80; - uint8_t timestamp = 0x80; - uint8_t midiPacket[] = { header, timestamp, status, note, velocity }; - - __lockBluetooth(); -#ifdef BLE_USE_WRITE_RESPONSE - uint8_t err = gatt_client_write_value_of_characteristic(handle_gatt_client_event, con_handle, midi_char_value_handle, sizeof(midiPacket), midiPacket); -#else - uint8_t err = gatt_client_write_value_of_characteristic_without_response(con_handle, midi_char_value_handle, sizeof(midiPacket), midiPacket); -#endif - __unlockBluetooth(); - - if (err) { - Serial.print(F("BLE Write Error: 0x")); Serial.println(err, HEX); - } else { - Serial.print(F("MIDI TX: ")); - for(int i=0; i