From 8f662598e499cec36b0cafde61118ce664bc13cf Mon Sep 17 00:00:00 2001 From: Me Here Date: Sat, 30 May 2026 23:28:59 -0500 Subject: [PATCH] PM_K-1 hardware: MIDI block (DNP) -- opto IN + buffered OUT/THRU circuits/midi.py, a do-not-populate option (USB-MIDI is the default). H11L1 opto-isolated IN (breaks the MIDI ground loop), 74LVC14-buffered OUT (TX through two inverters) and THRU (re-buffered IN). Connector is a face choice; core exposes the loop nets MIDI_IN/OUT/THRU_A/B plus MIDI_TX/RX to the RP2350 UART. ERC 0 errors; netlist 0 errors. CONFIRM: H11L1 pinout (standard; datasheet fetch timed out) + 3.3V-MIDI series-R values. Co-Authored-By: Claude Opus 4.8 (1M context) --- hardware/eda/circuits/midi.py | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 hardware/eda/circuits/midi.py diff --git a/hardware/eda/circuits/midi.py b/hardware/eda/circuits/midi.py new file mode 100644 index 0000000..d540a95 --- /dev/null +++ b/hardware/eda/circuits/midi.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""PM_K-1 MIDI block (SKiDL) -- DNP populate-option: opto-isolated IN + buffered OUT + THRU. + +Run INSIDE the EDA container: + cd hardware/eda && ./run.sh python3 ../eda/circuits/midi.py +Outputs ERC + hardware/kicad/midi.net. + +USB-MIDI is the DEFAULT (firmware). This hardware DIN/TRS MIDI is a DO-NOT-POPULATE +option for laptop-free sync to standalone gear (a "stage" face fits the connector + parts). + + IN : opto-isolated (H11L1) -- breaks the ground loop the MIDI spec requires. + OUT : RP2350 UART TX -> 74LVC14 buffer (2 inverters = non-inverting) -> series R. + THRU: a buffered copy of the received IN signal (re-transmit), same buffer chip. + +PINOUTS + * 74LVC14 hex Schmitt inverter: standard 14-pin (1A/1Y..6A/6Y, GND=7, VCC=14). + * H11L1 6-pin Schmitt opto: standard 1=Anode 2=Cathode 3=NC 4=GND 5=VO 6=VCC + (universal across makers of this part; datasheet fetch timed out -- reconfirm at layout). +CONFIRM: the series-resistor values for 3.3V MIDI per the MIDI Association spec (placeholders +below). Connector (TRS-MIDI Type-A vs DIN-5) is a FACE choice; the core exposes the loop nets. +""" +import os +from skidl import * +set_default_tool(KICAD9) +P = Pin.types +R = Part("Device","R", dest=TEMPLATE, footprint="Resistor_SMD:R_0402_1005Metric") +def C(v): return Part("Device","C", value=v, footprint="Capacitor_SMD:C_0402_1005Metric") +D = Part("Device","D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323") + +p3v3, gnd = Net("+3V3"), Net("GND") +p3v3.drive = POWER; gnd.drive = POWER +midi_tx, midi_rx = Net("MIDI_TX"), Net("MIDI_RX") # to/from RP2350 UART (assign free GPIO at integ.) +in_a, in_b = Net("MIDI_IN_A"), Net("MIDI_IN_B") # isolated current loop -> face connector +out_a, out_b = Net("MIDI_OUT_A"), Net("MIDI_OUT_B") +thru_a, thru_b = Net("MIDI_THRU_A"), Net("MIDI_THRU_B") + +OPTO = Part(name="H11L1", tool=SKIDL, dest=TEMPLATE, ref_prefix="U", + footprint="Package_DIP:DIP-6_W7.62mm", + pins=[Pin(num=1,name="A",func=P.PASSIVE),Pin(num=2,name="C",func=P.PASSIVE),Pin(num=3,name="NC",func=P.NOCONNECT), + Pin(num=4,name="GND",func=P.PWRIN),Pin(num=5,name="VO",func=P.OPENCOLL),Pin(num=6,name="VCC",func=P.PWRIN)]) +BUF = Part(name="74LVC14", tool=SKIDL, dest=TEMPLATE, ref_prefix="U", + footprint="Package_SO:TSSOP-14_4.4x5mm_P0.65mm", + pins=[Pin(num=1,name="1A",func=P.INPUT),Pin(num=2,name="1Y",func=P.OUTPUT),Pin(num=3,name="2A",func=P.INPUT), + Pin(num=4,name="2Y",func=P.OUTPUT),Pin(num=5,name="3A",func=P.INPUT),Pin(num=6,name="3Y",func=P.OUTPUT), + Pin(num=7,name="GND",func=P.PWRIN),Pin(num=8,name="4Y",func=P.OUTPUT),Pin(num=9,name="4A",func=P.INPUT), + Pin(num=10,name="5Y",func=P.OUTPUT),Pin(num=11,name="5A",func=P.INPUT),Pin(num=12,name="6Y",func=P.OUTPUT), + Pin(num=13,name="6A",func=P.INPUT),Pin(num=14,name="VCC",func=P.PWRIN)]) +opto = OPTO(ref="U8"); buf = BUF(ref="U9") + +# ---- MIDI IN (opto-isolated) ---- +rin = R(value="220"); dprot = D(value="1N4148WS") +in_a += rin[1]; rin[2] += opto["A"] # current-limit into LED +opto["C"] += in_b # LED return (isolated side) +dprot[2] += opto["C"]; dprot[1] += opto["A"] # reverse-protection across the LED (pin1=K,2=A) +opto["VCC"] += p3v3; opto["GND"] += gnd +rvo = R(value="10k"); opto["VO"] += midi_rx; midi_rx += rvo[1]; rvo[2] += p3v3 # pull-up; received data +copt = C("100nF"); p3v3 += copt[1]; copt[2] += gnd + +# ---- MIDI OUT: TX -> two inverters -> series R ---- +buf["1A"] += midi_tx; buf["1Y"] += Net("MIDI_OUT_N") # first invert +buf["2A"] += buf["1Y"] # second invert -> non-inverted +ro_b = R(value="33"); buf["2Y"] += ro_b[1]; ro_b[2] += out_b +ro_a = R(value="33"); p3v3 += ro_a[1]; ro_a[2] += out_a # current-source leg + +# ---- MIDI THRU: re-buffer the received signal ---- +buf["3A"] += midi_rx; buf["3Y"] += Net("MIDI_THRU_N") +buf["4A"] += buf["3Y"] +rt_b = R(value="33"); buf["4Y"] += rt_b[1]; rt_b[2] += thru_b +rt_a = R(value="33"); p3v3 += rt_a[1]; rt_a[2] += thru_a + +# unused inverters parked, supply +buf["5A"] += gnd; buf["6A"] += gnd +buf["VCC"] += p3v3; buf["GND"] += gnd +cbuf = C("100nF"); p3v3 += cbuf[1]; cbuf[2] += gnd + +ERC() +out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "midi.net")) +generate_netlist(file_=out) +print("MIDI netlist ->", out)