Roland K-25m USB MIDI via Raspberry Pi Pico
Published: March 7, 2026
I was missing a good small keyboard for playing synths and I came across the Roland K-25m. It's meant to be used with the Roland boutique series and connects via a 16 pin IDC connector (the same that Eurorack also uses). I found some documentation about it here and decided to pick one up and wire it to a Raspberry Pi Pico:
Why not get a cheap USB keyboard? This way I can run my own code and add a sequencer later.
For some synths, I can also implement bug fixes (the MFB Nanozwerg triggers on note on and off in some cases).
Looking directly at the cable connector, I counted pins from top to bottom, right to left.
notch
15 13 11 9 7 5 3 1
○ ○ ○ ○ ○ ○ ○ ○
○ ○ ○ ○ ○ ○ ○ ○
16 14 12 10 8 6 4 2
import time
import board
import digitalio
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
# ── Config ───────────────────────────────────────────────
BASE_NOTE = 48 # C3
VEL_MIN_MS = 2.0 # fastest strike (ms) -> vel 127
VEL_MAX_MS = 60.0 # slowest strike (ms) -> vel 1
SCAN_DELAY = 0.0003 # 300us row settle time
TIMEOUT_S = 0.08 # 80ms max wait for second contact
# ── Pins ─────────────────────────────────────────────────
colA_pins = [board.GP3, board.GP5, board.GP7, board.GP9]
colB_pins = [board.GP2, board.GP4, board.GP6, board.GP8]
row_pins = [
board.GP22, board.GP21, board.GP20, board.GP15,
board.GP14, board.GP12, board.GP11, board.GP10,
]
NUM_ROWS = 8
NUM_COLS = 4
# ── Init pins ────────────────────────────────────────────
colA = []
colB = []
rows = []
for p in colA_pins:
pin = digitalio.DigitalInOut(p)
pin.direction = digitalio.Direction.INPUT
pin.pull = digitalio.Pull.UP
colA.append(pin)
for p in colB_pins:
pin = digitalio.DigitalInOut(p)
pin.direction = digitalio.Direction.INPUT
pin.pull = digitalio.Pull.UP
colB.append(pin)
for p in row_pins:
pin = digitalio.DigitalInOut(p)
pin.direction = digitalio.Direction.OUTPUT
pin.value = True
rows.append(pin)
# ── MIDI ─────────────────────────────────────────────────
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
# ── Key state ────────────────────────────────────────────
# States: 0=idle, 1=B pressed (waiting for A), 2=note on
KEY_IDLE = 0
KEY_WAIT_A = 1
KEY_ON = 2
state = [KEY_IDLE] * 32 # max 8*4=32 slots
b_time = [0.0] * 32 # when B was first detected
note_vel = [0] * 32 # velocity sent with note_on
def key_index(row, col):
return col * 8 + row
def key_to_note(row, col):
return BASE_NOTE + col * 8 + (row ^ 1)
def calc_velocity(delta_s):
ms = delta_s * 1000.0
if ms <= VEL_MIN_MS:
return 127
if ms >= VEL_MAX_MS:
return 1
# linear map
frac = (ms - VEL_MIN_MS) / (VEL_MAX_MS - VEL_MIN_MS)
return int(127 - frac * 126)
# ── Main loop ────────────────────────────────────────────
print("K-25m MIDI ready. Base note:", BASE_NOTE)
while True:
now = time.monotonic()
for r in range(NUM_ROWS):
rows[r].value = False
time.sleep(SCAN_DELAY)
for c in range(NUM_COLS):
a = not colA[c].value
b = not colB[c].value
ki = key_index(r, c)
s = state[ki]
note = key_to_note(r, c)
if s == KEY_IDLE:
if b:
# B contact closed first (normal)
if a:
# both at once = very fast strike
midi.send(NoteOn(note, 127))
note_vel[ki] = 127
state[ki] = KEY_ON
else:
# B only, start timing
b_time[ki] = now
state[ki] = KEY_WAIT_A
elif a:
# A first (rare but handle it)
b_time[ki] = now
state[ki] = KEY_WAIT_A
elif s == KEY_WAIT_A:
if a and b:
# second contact arrived
delta = now - b_time[ki]
vel = calc_velocity(delta)
midi.send(NoteOn(note, vel))
note_vel[ki] = vel
state[ki] = KEY_ON
elif now - b_time[ki] > TIMEOUT_S:
# timeout, send with minimum velocity
midi.send(NoteOn(note, 1))
note_vel[ki] = 1
state[ki] = KEY_ON
elif s == KEY_ON:
if not a and not b:
# fully released
midi.send(NoteOff(note, 0))
state[ki] = KEY_IDLE
rows[r].value = True