MIDI over physical DIN5

This commit is contained in:
Dejvino 2026-02-16 01:48:03 +01:00
parent fcd4396d4a
commit 4c6a4bfbc1
2 changed files with 18 additions and 331 deletions

View File

@ -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.
* **MIDI**: The device sends MIDI notes via the Hardware Serial (DIN) port on GP0.

View File

@ -4,18 +4,6 @@
#include <Adafruit_SSD1306.h>
#include <Adafruit_NeoPixel.h>
// --- BLUETOOTH CONFIGURATION (Earle Philhower Core) ---
#if defined(ARDUINO_ARCH_RP2040) && defined(ARDUINO_RASPBERRY_PI_PICO_W)
#define ENABLE_BTSTACK
#include <btstack.h>
// 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<size; i++) {
Serial.print(packet[i], HEX); Serial.print(" ");
}
Serial.println();
// Scan packet for the MIDI Characteristic UUID
// The packet structure varies, so we search for the 128-bit UUID sequence.
for(int i=0; i <= size - 16; i++) {
if (memcmp(&packet[i], midi_char_uuid, 16) == 0 || memcmp(&packet[i], midi_char_uuid_be, 16) == 0) {
Serial.println(F("Found MIDI Char UUID!"));
// The Value Handle is typically located 2 bytes before the UUID (in this specific event format)
midi_char_value_handle = little_endian_read_16(packet, i - 2);
Serial.print(F("Handle: 0x")); Serial.println(midi_char_value_handle, HEX);
// Capture the characteristic struct ONLY if this is the correct one
gatt_event_characteristic_query_result_get_characteristic(packet, &target_characteristic);
}
}
}
break;
case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:
{
Serial.print(F("Desc Packet: "));
for(int i=0; i<size; i++) {
Serial.print(packet[i], HEX); Serial.print(" ");
}
Serial.println();
uint16_t handle = little_endian_read_16(packet, 8);
const uint8_t *uuid = &packet[10];
Serial.print(F("Desc Handle: 0x")); Serial.print(handle, HEX);
// Check for CCCD (0x2902) in standard 128-bit base UUID (Little Endian)
uint8_t cccd_uuid128[] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x02, 0x29, 0x00, 0x00};
if (memcmp(uuid, cccd_uuid128, 16) == 0) {
midi_cccd_handle = handle;
Serial.println(F(" -> 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<sizeof(midiPacket); i++) {
Serial.print(midiPacket[i], HEX); Serial.print(" ");
}
Serial.println();
}
}
#endif
Serial1.write(status);
Serial1.write(note);
Serial1.write(velocity);
}
void handleInput() {
@ -538,16 +242,6 @@ void drawUI() {
display.println();
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
// Bluetooth Icon
#ifdef ENABLE_BTSTACK
if (con_handle != HCI_CON_HANDLE_INVALID) {
display.drawBitmap(120, 0, bluetooth_icon, 8, 8, SSD1306_WHITE);
if (midi_ready) {
display.fillCircle(114, 4, 2, SSD1306_WHITE); // Small dot to indicate MIDI Ready
}
}
#endif
// Steps
int y = 10;
for (int i = scrollOffset; i < min(scrollOffset + 6, NUM_STEPS); i++) {
@ -625,17 +319,6 @@ void updateLeds() {
}
void loop() {
// BTStack runs in background, no poll needed.
#ifdef ENABLE_BTSTACK
bool connected = (con_handle != HCI_CON_HANDLE_INVALID);
if (connected != wasConnected) {
wasConnected = connected;
if (connected) Serial.println(F("BLE: Connected"));
else Serial.println(F("BLE: Disconnected"));
}
#endif
handleInput();
handlePlayback();
drawUI();