Compare commits
11 commits
8726f42d05
...
3192f3debc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3192f3debc | ||
|
|
617bb5a8b2 | ||
|
|
5a75dbbbdc | ||
|
|
6b6a58fa56 | ||
|
|
2f44be6f63 | ||
|
|
e6f425ee6f | ||
|
|
075c1786af | ||
|
|
3f7f4b94d9 | ||
|
|
d51c9f1011 | ||
|
|
bcfa5dd7f0 | ||
|
|
87caa933ea |
56 changed files with 3987 additions and 23 deletions
21
build.sh
21
build.sh
|
|
@ -19,6 +19,10 @@ MPYC="$PWD/tools/mpy-cross"; ROOT="$PWD"
|
||||||
[[ -x "$MPYC" ]] || { echo "error: $MPYC missing (Adafruit mpy-cross for CircuitPython 10.2.1)" >&2; exit 1; }
|
[[ -x "$MPYC" ]] || { echo "error: $MPYC missing (Adafruit mpy-cross for CircuitPython 10.2.1)" >&2; exit 1; }
|
||||||
( cd pico-cp && "$MPYC" app.py -o "$ROOT/dist/app.mpy" ) # compile from pico-cp/ so tracebacks read "app.py"
|
( cd pico-cp && "$MPYC" app.py -o "$ROOT/dist/app.mpy" ) # compile from pico-cp/ so tracebacks read "app.py"
|
||||||
echo "precompiled dist/app.mpy ($(stat -c%s dist/app.mpy) bytes <- $(stat -c%s pico-cp/app.py) source)"
|
echo "precompiled dist/app.mpy ($(stat -c%s dist/app.mpy) bytes <- $(stat -c%s pico-cp/app.py) source)"
|
||||||
|
# PM_X-1 Explorer firmware uses the same mpy-cross. Output as dist/explorer-app.mpy so the Kit + Explorer
|
||||||
|
# bundles each ship their own precompiled binary; the served URLs follow the same one-target-per-file rule.
|
||||||
|
( cd pico-explorer && "$MPYC" app.py -o "$ROOT/dist/explorer-app.mpy" )
|
||||||
|
echo "precompiled dist/explorer-app.mpy ($(stat -c%s dist/explorer-app.mpy) bytes <- $(stat -c%s pico-explorer/app.py) source)"
|
||||||
|
|
||||||
python3 - <<'PY'
|
python3 - <<'PY'
|
||||||
import os, pathlib, re
|
import os, pathlib, re
|
||||||
|
|
@ -40,7 +44,7 @@ def build(name):
|
||||||
|
|
||||||
for name in ("index.html","editor.html","editor-beta.html","player.html","teacher.html","stage.html","micro.html","showcase.html","kit.html",
|
for name in ("index.html","editor.html","editor-beta.html","player.html","teacher.html","stage.html","micro.html","showcase.html","kit.html",
|
||||||
"embed.html",
|
"embed.html",
|
||||||
"info-editor.html","info-player.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html","info-kit.html"):
|
"info-editor.html","info-player.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html","info-kit.html","info-explorer.html"):
|
||||||
print("built %s (%dKB)" % (name, build(name) // 1024))
|
print("built %s (%dKB)" % (name, build(name) // 1024))
|
||||||
pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text()) # loader, served as-is
|
pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text()) # loader, served as-is
|
||||||
print("copied embed.js")
|
print("copied embed.js")
|
||||||
|
|
@ -55,6 +59,12 @@ pathlib.Path("dist/pico-cp-app.py").write_text(_appsrc) # served for version r
|
||||||
# the editor pushes the PRECOMPILED .mpy (base64); serve it next to the source
|
# the editor pushes the PRECOMPILED .mpy (base64); serve it next to the source
|
||||||
pathlib.Path("dist/pico-cp-app.mpy").write_bytes(pathlib.Path("dist/app.mpy").read_bytes())
|
pathlib.Path("dist/pico-cp-app.mpy").write_bytes(pathlib.Path("dist/app.mpy").read_bytes())
|
||||||
print("copied pico-cp-app.py + pico-cp-app.mpy")
|
print("copied pico-cp-app.py + pico-cp-app.mpy")
|
||||||
|
_xsrc = pathlib.Path("pico-explorer/app.py").read_text() # PM_X-1 Explorer firmware (sibling to the Kit)
|
||||||
|
_xbad = [(i, c) for i, c in enumerate(_xsrc) if ord(c) > 0x7F]
|
||||||
|
assert not _xbad, "pico-explorer/app.py has non-ASCII at %r -- keep it ASCII (version regex + clean source)" % (_xbad[:5],)
|
||||||
|
pathlib.Path("dist/pico-explorer-app.py").write_text(_xsrc) # editor reads APP_VERSION from here
|
||||||
|
pathlib.Path("dist/pico-explorer-app.mpy").write_bytes(pathlib.Path("dist/explorer-app.mpy").read_bytes())
|
||||||
|
print("copied pico-explorer-app.py + pico-explorer-app.mpy")
|
||||||
import zipfile # PM_K-1 CircuitPython drive bundle (download → unzip onto CIRCUITPY)
|
import zipfile # PM_K-1 CircuitPython drive bundle (download → unzip onto CIRCUITPY)
|
||||||
with zipfile.ZipFile("dist/pm_k1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z:
|
with zipfile.ZipFile("dist/pm_k1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z:
|
||||||
for f in ("code.py", "boot.py", "programs.json", "font_s.bin", "font_m.bin", "font_l.bin",
|
for f in ("code.py", "boot.py", "programs.json", "font_s.bin", "font_m.bin", "font_l.bin",
|
||||||
|
|
@ -63,4 +73,13 @@ with zipfile.ZipFile("dist/pm_k1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z
|
||||||
z.write("dist/app.mpy", "app.mpy") # the precompiled firmware (NOT app.py - too big to compile on-device)
|
z.write("dist/app.mpy", "app.mpy") # the precompiled firmware (NOT app.py - too big to compile on-device)
|
||||||
z.write("dist/editor.html", "editor.html") # offline copy of the editor, on the drive
|
z.write("dist/editor.html", "editor.html") # offline copy of the editor, on the drive
|
||||||
print("zipped pm_k1_circuitpy.zip")
|
print("zipped pm_k1_circuitpy.zip")
|
||||||
|
# PM_X-1 Explorer drive bundle (download → unzip onto CIRCUITPY on the Pimoroni Explorer with CircuitPython for Pico 2)
|
||||||
|
with zipfile.ZipFile("dist/pm_x1_circuitpy.zip", "w", zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for f in ("code.py", "boot.py", "programs.json", "README.md"):
|
||||||
|
z.write("pico-explorer/" + f, f)
|
||||||
|
for f in ("font_s.bin", "font_m.bin", "font_l.bin", "logo.bin", "midi.bin", "usb.bin"):
|
||||||
|
z.write("pico-cp/" + f, f) # fonts + icons are resolution-agnostic; reuse the Kit's baked blobs
|
||||||
|
z.write("dist/explorer-app.mpy", "app.mpy")
|
||||||
|
z.write("dist/editor.html", "editor.html")
|
||||||
|
print("zipped pm_x1_circuitpy.zip")
|
||||||
PY
|
PY
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ fi
|
||||||
echo "deployed v$BUILD -> $DEST_DIR"
|
echo "deployed v$BUILD -> $DEST_DIR"
|
||||||
for f in index.html editor.html editor-beta.html player.html teacher.html stage.html micro.html showcase.html kit.html \
|
for f in index.html editor.html editor-beta.html player.html teacher.html stage.html micro.html showcase.html kit.html \
|
||||||
embed.html \
|
embed.html \
|
||||||
info-editor.html info-player.html info-teacher.html info-stage.html info-micro.html info-showcase.html info-kit.html; do
|
info-editor.html info-player.html info-teacher.html info-stage.html info-micro.html info-showcase.html info-kit.html info-explorer.html; do
|
||||||
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f"
|
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f"
|
||||||
echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)"
|
echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)"
|
||||||
done
|
done
|
||||||
|
|
@ -51,6 +51,9 @@ cp "$DIST_DIR/pico-main.py" "$DEST_DIR/pico-main.py"; echo " pico-main.py ($(st
|
||||||
cp "$DIST_DIR/pm_k1_circuitpy.zip" "$DEST_DIR/pm_k1_circuitpy.zip"; echo " pm_k1_circuitpy.zip ($(stat -c '%s' "$DEST_DIR/pm_k1_circuitpy.zip") bytes)" # PM_K-1 CircuitPython bundle
|
cp "$DIST_DIR/pm_k1_circuitpy.zip" "$DEST_DIR/pm_k1_circuitpy.zip"; echo " pm_k1_circuitpy.zip ($(stat -c '%s' "$DEST_DIR/pm_k1_circuitpy.zip") bytes)" # PM_K-1 CircuitPython bundle
|
||||||
cp "$DIST_DIR/pico-cp-app.py" "$DEST_DIR/pico-cp-app.py"; echo " pico-cp-app.py ($(stat -c '%s' "$DEST_DIR/pico-cp-app.py") bytes)" # served for version reading + reference
|
cp "$DIST_DIR/pico-cp-app.py" "$DEST_DIR/pico-cp-app.py"; echo " pico-cp-app.py ($(stat -c '%s' "$DEST_DIR/pico-cp-app.py") bytes)" # served for version reading + reference
|
||||||
cp "$DIST_DIR/pico-cp-app.mpy" "$DEST_DIR/pico-cp-app.mpy"; echo " pico-cp-app.mpy ($(stat -c '%s' "$DEST_DIR/pico-cp-app.mpy") bytes)" # precompiled firmware the editor pushes (base64)
|
cp "$DIST_DIR/pico-cp-app.mpy" "$DEST_DIR/pico-cp-app.mpy"; echo " pico-cp-app.mpy ($(stat -c '%s' "$DEST_DIR/pico-cp-app.mpy") bytes)" # precompiled firmware the editor pushes (base64)
|
||||||
|
cp "$DIST_DIR/pm_x1_circuitpy.zip" "$DEST_DIR/pm_x1_circuitpy.zip"; echo " pm_x1_circuitpy.zip ($(stat -c '%s' "$DEST_DIR/pm_x1_circuitpy.zip") bytes)" # PM_X-1 Explorer CircuitPython bundle
|
||||||
|
cp "$DIST_DIR/pico-explorer-app.py" "$DEST_DIR/pico-explorer-app.py"; echo " pico-explorer-app.py ($(stat -c '%s' "$DEST_DIR/pico-explorer-app.py") bytes)" # served for version reading
|
||||||
|
cp "$DIST_DIR/pico-explorer-app.mpy" "$DEST_DIR/pico-explorer-app.mpy"; echo " pico-explorer-app.mpy ($(stat -c '%s' "$DEST_DIR/pico-explorer-app.mpy") bytes)" # PM_X-1 firmware (the editor pushes this when device id = X)
|
||||||
rm -f "$DEST_DIR/player-asbuilt.html" # renamed to teacher.html
|
rm -f "$DEST_DIR/player-asbuilt.html" # renamed to teacher.html
|
||||||
rm -f "$DEST_DIR/concepts.html" # Concepts is now the landing (/)
|
rm -f "$DEST_DIR/concepts.html" # Concepts is now the landing (/)
|
||||||
# info-*.html are first-class pages again: each form factor has a lean widget page
|
# info-*.html are first-class pages again: each form factor has a lean widget page
|
||||||
|
|
|
||||||
|
|
@ -193,3 +193,20 @@ target.
|
||||||
- Streaming the device practice log (`history.json`) up to the browser.
|
- Streaming the device practice log (`history.json`) up to the browser.
|
||||||
- Mirroring device `settings.json` (LED brightness, MIDI config, etc.).
|
- Mirroring device `settings.json` (LED brightness, MIDI config, etc.).
|
||||||
- Multi‑peer / multi‑editor arbitration beyond last‑writer‑wins.
|
- Multi‑peer / multi‑editor arbitration beyond last‑writer‑wins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Per‑device emit/apply matrix
|
||||||
|
|
||||||
|
Both targets implement the **full apply path** for every verb. They differ in what
|
||||||
|
they **emit**, because on‑device editing differs:
|
||||||
|
|
||||||
|
| Device | Emits | Applies |
|
||||||
|
|-------------|----------------------------------------------------|---------------------------------------------|
|
||||||
|
| **PM_K‑1** Kit (touchscreen + joystick) | `play` / `stop` / `bpm` / `sel` / `beat` / `lane` (FULL on structural lane edits) | all of the above |
|
||||||
|
| **PM_X‑1** Explorer (6 buttons, read‑only beats) | `play` / `stop` / `bpm` / `sel` only (no on‑device beat/lane editing) | all of the above |
|
||||||
|
|
||||||
|
Editors don't need to special‑case the source — both DELTA streams look identical on
|
||||||
|
the wire, and the **device id is only exposed on the version query** (SysEx `0x02`
|
||||||
|
→ `0x03` reply, `<id>;<version>`; pre‑0.0.23 firmware sends bare version → assume
|
||||||
|
`K`).
|
||||||
|
|
|
||||||
|
|
@ -1211,34 +1211,42 @@ async function toggleDeviceAudio() {
|
||||||
function _queryDeviceVersion() { // ask the device its firmware version (SysEx 0x02 -> reply 0x03)
|
function _queryDeviceVersion() { // ask the device its firmware version (SysEx 0x02 -> reply 0x03)
|
||||||
return new Promise((res) => { _verCb = res; _send([0xF0, 0x7D, 0x02, 0xF7]); setTimeout(() => { if (_verCb) { _verCb = null; res(null); } }, 1500); });
|
return new Promise((res) => { _verCb = res; _send([0xF0, 0x7D, 0x02, 0xF7]); setTimeout(() => { if (_verCb) { _verCb = null; res(null); } }, 1500); });
|
||||||
}
|
}
|
||||||
|
// 0.0.23+ devices reply "<id>;<version>" (e.g. "K;0.0.23", "X;0.0.1"); pre-0.0.23 send bare version -> assume K.
|
||||||
|
function _parseDeviceReply(s) {
|
||||||
|
if (!s) return { id: null, version: null };
|
||||||
|
const i = s.indexOf(";");
|
||||||
|
return i >= 0 ? { id: s.slice(0, i), version: s.slice(i + 1) } : { id: "K", version: s };
|
||||||
|
}
|
||||||
|
const FW_PATHS = { K: { py: "/pico-cp-app.py", mpy: "/pico-cp-app.mpy", label: "PM_K-1 Kit" },
|
||||||
|
X: { py: "/pico-explorer-app.py", mpy: "/pico-explorer-app.mpy", label: "PM_X-1 Explorer" } };
|
||||||
async function updateFirmware() { // A/B firmware update over USB-MIDI: push the precompiled .mpy
|
async function updateFirmware() { // A/B firmware update over USB-MIDI: push the precompiled .mpy
|
||||||
console.log("[fw] update start");
|
console.log("[fw] update start");
|
||||||
if (!(await _ensureMidi()) || !_midiOutputs().length) {
|
if (!(await _ensureMidi()) || !_midiOutputs().length) {
|
||||||
console.log("[fw] no MIDI output (outputs:", _midiOutputs().length, ")");
|
console.log("[fw] no MIDI output (outputs:", _midiOutputs().length, ")");
|
||||||
return alert("Connect the PM_K-1 (Chrome/Edge/Firefox), then try again.");
|
return alert("Connect the device (Chrome/Edge/Firefox), then try again.");
|
||||||
}
|
}
|
||||||
console.log("[fw] MIDI ok; outputs:", _midiOutputs().map((o) => o.name));
|
console.log("[fw] MIDI ok; outputs:", _midiOutputs().map((o) => o.name));
|
||||||
const dev = await _queryDeviceVersion();
|
const reply = await _queryDeviceVersion();
|
||||||
console.log("[fw] device version reply:", dev);
|
const { id: devId, version: dev } = _parseDeviceReply(reply);
|
||||||
|
const paths = FW_PATHS[devId] || FW_PATHS.K; // unknown id -> assume Kit (pre-0.0.23 firmware)
|
||||||
|
console.log("[fw] device reply:", reply, "-> id:", devId || "(none)", "version:", dev, "paths:", paths.label);
|
||||||
let latest = null, b64 = null;
|
let latest = null, b64 = null;
|
||||||
// version comes from the (text) source; the payload is the precompiled .mpy bytecode — CircuitPython
|
|
||||||
// compiles a big .py at boot, which OOMs the RP2040, so we ship + push compiled bytecode instead.
|
|
||||||
for (const base of ["", "https://metronome.varasys.io"]) {
|
for (const base of ["", "https://metronome.varasys.io"]) {
|
||||||
try { const t = await (await fetch(base + "/pico-cp-app.py", { cache: "no-store" })).text();
|
try { const t = await (await fetch(base + paths.py, { cache: "no-store" })).text();
|
||||||
const m = t.match(/APP_VERSION\s*=\s*["']([^"']+)["']/); if (m) latest = m[1]; } catch (_) {}
|
const m = t.match(/APP_VERSION\s*=\s*["']([^"']+)["']/); if (m) latest = m[1]; } catch (_) {}
|
||||||
try { const r = await fetch(base + "/pico-cp-app.mpy", { cache: "no-store" });
|
try { const r = await fetch(base + paths.mpy, { cache: "no-store" });
|
||||||
if (r.ok) { b64 = _b64(new Uint8Array(await r.arrayBuffer())); break; } } catch (_) {}
|
if (r.ok) { b64 = _b64(new Uint8Array(await r.arrayBuffer())); break; } } catch (_) {}
|
||||||
}
|
}
|
||||||
if (!b64) { // offline: let the user pick app.mpy
|
if (!b64) { // offline: let the user pick app.mpy
|
||||||
alert("Can't reach the site.\n\nPick the firmware file (app.mpy) to flash — download it from\n" +
|
alert("Can't reach the site.\n\nPick the firmware file (app.mpy) to flash — download it from\n" +
|
||||||
"metronome.varasys.io/pico-cp-app.mpy, or use the online editor at metronome.varasys.io/editor.html.");
|
"metronome.varasys.io" + paths.mpy + ", or use the online editor at metronome.varasys.io/editor.html.");
|
||||||
const u8 = await _pickBinary(); if (!u8) return;
|
const u8 = await _pickBinary(); if (!u8) return;
|
||||||
b64 = _b64(u8); if (!latest) latest = "(picked .mpy)";
|
b64 = _b64(u8); if (!latest) latest = "(picked .mpy)";
|
||||||
}
|
}
|
||||||
if (!latest) latest = "?";
|
if (!latest) latest = "?";
|
||||||
console.log("[fw] latest:", latest, "| .mpy base64 length:", b64 && b64.length);
|
console.log("[fw] latest:", latest, "| .mpy base64 length:", b64 && b64.length);
|
||||||
const upToDate = dev && dev === latest;
|
const upToDate = dev && dev === latest;
|
||||||
if (!confirm("Device firmware: " + (dev || "unknown") + "\nNew build: " + latest +
|
if (!confirm(paths.label + " firmware: " + (dev || "unknown") + "\nNew build: " + latest +
|
||||||
(upToDate ? "\n\nSame version. Re-install anyway?"
|
(upToDate ? "\n\nSame version. Re-install anyway?"
|
||||||
: "\n\nUpdate now? The device reboots, runs the new build, and auto-rolls-back if it fails to start."))) {
|
: "\n\nUpdate now? The device reboots, runs the new build, and auto-rolls-back if it fails to start."))) {
|
||||||
console.log("[fw] confirm returned false (cancelled, OR the browser is suppressing dialogs -> reload the page)");
|
console.log("[fw] confirm returned false (cancelled, OR the browser is suppressing dialogs -> reload the page)");
|
||||||
|
|
|
||||||
26
editor.html
26
editor.html
|
|
@ -1204,34 +1204,42 @@ async function toggleDeviceAudio() {
|
||||||
function _queryDeviceVersion() { // ask the device its firmware version (SysEx 0x02 -> reply 0x03)
|
function _queryDeviceVersion() { // ask the device its firmware version (SysEx 0x02 -> reply 0x03)
|
||||||
return new Promise((res) => { _verCb = res; _send([0xF0, 0x7D, 0x02, 0xF7]); setTimeout(() => { if (_verCb) { _verCb = null; res(null); } }, 1500); });
|
return new Promise((res) => { _verCb = res; _send([0xF0, 0x7D, 0x02, 0xF7]); setTimeout(() => { if (_verCb) { _verCb = null; res(null); } }, 1500); });
|
||||||
}
|
}
|
||||||
|
// 0.0.23+ devices reply "<id>;<version>" (e.g. "K;0.0.23", "X;0.0.1"); pre-0.0.23 send bare version -> assume K.
|
||||||
|
function _parseDeviceReply(s) {
|
||||||
|
if (!s) return { id: null, version: null };
|
||||||
|
const i = s.indexOf(";");
|
||||||
|
return i >= 0 ? { id: s.slice(0, i), version: s.slice(i + 1) } : { id: "K", version: s };
|
||||||
|
}
|
||||||
|
const FW_PATHS = { K: { py: "/pico-cp-app.py", mpy: "/pico-cp-app.mpy", label: "PM_K-1 Kit" },
|
||||||
|
X: { py: "/pico-explorer-app.py", mpy: "/pico-explorer-app.mpy", label: "PM_X-1 Explorer" } };
|
||||||
async function updateFirmware() { // A/B firmware update over USB-MIDI: push the precompiled .mpy
|
async function updateFirmware() { // A/B firmware update over USB-MIDI: push the precompiled .mpy
|
||||||
console.log("[fw] update start");
|
console.log("[fw] update start");
|
||||||
if (!(await _ensureMidi()) || !_midiOutputs().length) {
|
if (!(await _ensureMidi()) || !_midiOutputs().length) {
|
||||||
console.log("[fw] no MIDI output (outputs:", _midiOutputs().length, ")");
|
console.log("[fw] no MIDI output (outputs:", _midiOutputs().length, ")");
|
||||||
return alert("Connect the PM_K-1 (Chrome/Edge/Firefox), then try again.");
|
return alert("Connect the device (Chrome/Edge/Firefox), then try again.");
|
||||||
}
|
}
|
||||||
console.log("[fw] MIDI ok; outputs:", _midiOutputs().map((o) => o.name));
|
console.log("[fw] MIDI ok; outputs:", _midiOutputs().map((o) => o.name));
|
||||||
const dev = await _queryDeviceVersion();
|
const reply = await _queryDeviceVersion();
|
||||||
console.log("[fw] device version reply:", dev);
|
const { id: devId, version: dev } = _parseDeviceReply(reply);
|
||||||
|
const paths = FW_PATHS[devId] || FW_PATHS.K; // unknown id -> assume Kit (pre-0.0.23 firmware)
|
||||||
|
console.log("[fw] device reply:", reply, "-> id:", devId || "(none)", "version:", dev, "paths:", paths.label);
|
||||||
let latest = null, b64 = null;
|
let latest = null, b64 = null;
|
||||||
// version comes from the (text) source; the payload is the precompiled .mpy bytecode — CircuitPython
|
|
||||||
// compiles a big .py at boot, which OOMs the RP2040, so we ship + push compiled bytecode instead.
|
|
||||||
for (const base of ["", "https://metronome.varasys.io"]) {
|
for (const base of ["", "https://metronome.varasys.io"]) {
|
||||||
try { const t = await (await fetch(base + "/pico-cp-app.py", { cache: "no-store" })).text();
|
try { const t = await (await fetch(base + paths.py, { cache: "no-store" })).text();
|
||||||
const m = t.match(/APP_VERSION\s*=\s*["']([^"']+)["']/); if (m) latest = m[1]; } catch (_) {}
|
const m = t.match(/APP_VERSION\s*=\s*["']([^"']+)["']/); if (m) latest = m[1]; } catch (_) {}
|
||||||
try { const r = await fetch(base + "/pico-cp-app.mpy", { cache: "no-store" });
|
try { const r = await fetch(base + paths.mpy, { cache: "no-store" });
|
||||||
if (r.ok) { b64 = _b64(new Uint8Array(await r.arrayBuffer())); break; } } catch (_) {}
|
if (r.ok) { b64 = _b64(new Uint8Array(await r.arrayBuffer())); break; } } catch (_) {}
|
||||||
}
|
}
|
||||||
if (!b64) { // offline: let the user pick app.mpy
|
if (!b64) { // offline: let the user pick app.mpy
|
||||||
alert("Can't reach the site.\n\nPick the firmware file (app.mpy) to flash — download it from\n" +
|
alert("Can't reach the site.\n\nPick the firmware file (app.mpy) to flash — download it from\n" +
|
||||||
"metronome.varasys.io/pico-cp-app.mpy, or use the online editor at metronome.varasys.io/editor.html.");
|
"metronome.varasys.io" + paths.mpy + ", or use the online editor at metronome.varasys.io/editor.html.");
|
||||||
const u8 = await _pickBinary(); if (!u8) return;
|
const u8 = await _pickBinary(); if (!u8) return;
|
||||||
b64 = _b64(u8); if (!latest) latest = "(picked .mpy)";
|
b64 = _b64(u8); if (!latest) latest = "(picked .mpy)";
|
||||||
}
|
}
|
||||||
if (!latest) latest = "?";
|
if (!latest) latest = "?";
|
||||||
console.log("[fw] latest:", latest, "| .mpy base64 length:", b64 && b64.length);
|
console.log("[fw] latest:", latest, "| .mpy base64 length:", b64 && b64.length);
|
||||||
const upToDate = dev && dev === latest;
|
const upToDate = dev && dev === latest;
|
||||||
if (!confirm("Device firmware: " + (dev || "unknown") + "\nNew build: " + latest +
|
if (!confirm(paths.label + " firmware: " + (dev || "unknown") + "\nNew build: " + latest +
|
||||||
(upToDate ? "\n\nSame version. Re-install anyway?"
|
(upToDate ? "\n\nSame version. Re-install anyway?"
|
||||||
: "\n\nUpdate now? The device reboots, runs the new build, and auto-rolls-back if it fails to start."))) {
|
: "\n\nUpdate now? The device reboots, runs the new build, and auto-rolls-back if it fails to start."))) {
|
||||||
console.log("[fw] confirm returned false (cancelled, OR the browser is suppressing dialogs -> reload the page)");
|
console.log("[fw] confirm returned false (cancelled, OR the browser is suppressing dialogs -> reload the page)");
|
||||||
|
|
|
||||||
36
hardware/BOM.csv
Normal file
36
hardware/BOM.csv
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
Ref,Block,Part,Manufacturer,MPN,Qty,Approx_USD_ea,Notes
|
||||||
|
U1,MCU,RP2350A microcontroller,Raspberry Pi,RP2350A,1,1.10,QFN-60; mind E9 input-latch erratum (external pulldowns)
|
||||||
|
U2,MCU,16MB QSPI flash,Winbond,W25Q128JVSIQ,1,1.20,genuine part; firmware wear-levels history.json
|
||||||
|
Y1,MCU,12MHz crystal,Abracon,ABM8-272-12.000MHZ,1,0.30,+/-30ppm
|
||||||
|
U3,Power,3V3 IO LDO,Diodes Inc,AP2112K-3.3TRG1,1,0.25,digital domain
|
||||||
|
L1,Power,RP2350 core SMPS inductor,TDK,VLS3012,1,0.15,per RP2350 reference
|
||||||
|
U4,Power,Dual boost/inverter +/-18V,Texas Instruments,TPS65131RGER,1,2.50,raw +/-18V from 5V; guarded corner
|
||||||
|
U5,Power,Ultra-low-noise +15V LDO,Texas Instruments,TPS7A4901DGNR,1,1.80,post-regulates +18 to clean +15
|
||||||
|
U6,Power,Ultra-low-noise -15V LDO,Texas Instruments,TPS7A3001DGNR,1,1.90,post-regulates -18 to clean -15
|
||||||
|
J1,Power/USB,USB-C receptacle (TH-anchored),GCT,USB4085-GF-A,1,0.60,through-hole anchor tabs for strain
|
||||||
|
U7,Power/USB,USB ESD protection array,STMicroelectronics,USBLC6-2SC6,1,0.30,D+/D-/CC/VBUS
|
||||||
|
FL1,Power/USB,USB common-mode choke,Wurth,744232090,1,0.35,data-pair EMI
|
||||||
|
U8,Click,I2S audio DAC,Texas Instruments,PCM5102APWR,1,2.20,Burr-Brown; reliability-first
|
||||||
|
X1,Click,Low-jitter audio oscillator,Abracon,ASEM1-24.576MHZ-LR-T,1,1.40,dedicated MCLK; not PIO-jittered
|
||||||
|
U9,Audio-in,Balanced line receiver,THAT Corp,THAT1240S08-U,1,3.20,0dB unity; pinout verified doc600035 rev05; 2nd-src INA134/SSM2141
|
||||||
|
U10,Audio-in,JFET Hi-Z instrument buffer,Texas Instruments,OPA1641AID,1,1.60,>=1Mohm DI buffer + gain
|
||||||
|
U11,Audio-mix,Dual audio op-amp (sum/filter),Texas Instruments,OPA1612AIDR,1,2.40,signal-path low noise
|
||||||
|
U12,Audio-out,Balanced line driver,THAT Corp,THAT1646S08-U,1,3.30,near-zero source; 47ohm build-out per leg; pinout TBV at output stage
|
||||||
|
RV1,Audio-out,Output level cal trimmer 25-turn,Bourns,3296W-1-103LF,1,0.70,factory-set DAC FS -> +4dBu
|
||||||
|
U13,Indicator,Dual comparator (sig/clip),Texas Instruments,LM393DR,1,0.15,peak-detect -> RP2350 GPIO + LED lines
|
||||||
|
K1,Audio-in,Signal relay line/inst (DPDT gold),Panasonic,TQ2SA-5V,1,1.30,sealed gold bifurcated contacts
|
||||||
|
K2,Audio-out,Mute relay (fail-safe DPDT gold),Panasonic,TQ2SA-5V,1,1.30,de-energized=muted
|
||||||
|
K3,Audio-out,Ground-lift relay (gold),Panasonic,TQ2SA-5V,1,1.30,series with face panel switch; soft-lift 100R||10nF
|
||||||
|
U14,Control,Relay driver array,Texas Instruments,ULN2003ADR,1,0.20,drives K1-K3 with flyback
|
||||||
|
U15,RTC,Real-time clock (integrated xtal),Micro Crystal,RV-8803-C7,1,1.50,I2C on touch bus; drift irrelevant
|
||||||
|
BT1,RTC,CR2032 holder (socketed),Keystone,1066,1,0.30,user-replaceable
|
||||||
|
U16,MIDI(DNP),Opto-isolator MIDI IN,Vishay,H11L1M,1,0.55,DNP populate-option
|
||||||
|
U17,MIDI(DNP),Hex Schmitt buffer MIDI OUT/THRU,Nexperia,74LVC14APW,1,0.20,DNP populate-option
|
||||||
|
U18,Speaker(DNP),Class-D mono amp,Diodes Inc,PAM8302AASCR,1,0.35,DNP monitor option per face
|
||||||
|
J2,Interconnect,Digital ribbon header 2x13 shrouded keyed,Wurth,61303421821,1,0.45,Pico-pinout-compatible
|
||||||
|
J3,Interconnect,Analog audio header 2x5 shrouded keyed,Wurth,61301021821,1,0.35,twisted/shielded; away from digital
|
||||||
|
J4,Interconnect,MIDI header 1x6,Wurth,61300611121,1,0.20,used only if DNP MIDI populated
|
||||||
|
J5,Debug,SWD Cortex-Debug 2x5 1.27mm,Samtec,FTSH-105-01-L-DV-K,1,0.50,service header
|
||||||
|
D-arr,ESD,Interconnect ESD clamp arrays,Texas Instruments,TPD2E2U06DCKR,3,0.10,on user-touchable lines
|
||||||
|
PASS,Passives,Film caps / 0.1% thin-film R / clamp diodes / ferrites,various,various,1,4.00,WIMA film signal caps; Panasonic ERA 0.1%; no electrolytics in signal path
|
||||||
|
PCB,Fabrication,4-layer PCB ENIG finish,JLCPCB/PCBWay,custom,1,3.00,gold finish; qty-dependent
|
||||||
|
211
hardware/DESIGN.md
Normal file
211
hardware/DESIGN.md
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
# PM_K-1 Core Board ("brain") — design-of-record
|
||||||
|
|
||||||
|
VARASYS PolyMeter · heirloom pro-audio · modular brain/face architecture
|
||||||
|
Status: **design-of-record / pre-layout.** Component selection complete; PCB routing is the
|
||||||
|
remaining interactive step (see [Open items](#open-items)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Philosophy
|
||||||
|
|
||||||
|
This is meant to be a device people hand down to their great-grandkids. The core board carries
|
||||||
|
**all the active electronics** and is specced to pro/audiophile tier with longevity and
|
||||||
|
serviceability as first-class goals. We do **not** value-engineer the audio path.
|
||||||
|
|
||||||
|
The user selects the face/enclosure/connector components; this document specs only the **core**.
|
||||||
|
|
||||||
|
## 2. Architecture — modular brain/face
|
||||||
|
|
||||||
|
- **Core ("brain"):** RP2350 + power + RTC + the full pro-audio analog chain + control logic.
|
||||||
|
One core design is reused across every form factor.
|
||||||
|
- **Face ("form factor"):** display, touch, joystick, buttons, LED, speaker, the physical
|
||||||
|
audio/MIDI connectors, panel switches, enclosure. **The core never decides connector type.**
|
||||||
|
- **Two interconnects, deliberately separate** (§7):
|
||||||
|
1. a **digital ribbon** whose pinout mirrors the Raspberry Pi Pico, so a stock Pico/Pico 2 on a
|
||||||
|
test adapter can drive any face board for bring-up;
|
||||||
|
2. an **analog interconnect** (+ a small MIDI interconnect) kept physically away from the fast
|
||||||
|
digital ribbon, because a balanced audio signal must never run parallel to the 24 MHz display SPI.
|
||||||
|
|
||||||
|
## 3. Block diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
USB-C 5V ─┬─► 3V3 LDO ──────────────────────► RP2350 (core reg + ext L) + W25Q128 16MB + RV-8803 RTC(+CR2032)
|
||||||
|
│ │ I²S + low-jitter MCLK
|
||||||
|
└─► TPS65131 ±18V ─► TPS7A49/30 LDO ─► clean ±15V ▼
|
||||||
|
PCM5102A DAC ──► [summing: click + input]
|
||||||
|
│
|
||||||
|
bal IN ─[ESD/DC-block/clamp/series-R]─► [LINE/INST relay] ─┬─ THAT1240 receiver ──┘
|
||||||
|
(analog interconnect) └─ OPA1641 Hi-Z DI buffer (+gain)
|
||||||
|
│
|
||||||
|
THAT1646 balanced driver ─[47Ω build-out]─► [MUTE relay]─► bal OUT
|
||||||
|
shield ─[panel SW]─[GND-LIFT relay]─ gnd
|
||||||
|
sig/clip peak detect ─► LM393 ─► RP2350 GPIO (UI) + LED lines (interconnect)
|
||||||
|
RP2350 UART ─► [DNP: H11L1 opto IN + 74LVC14 buffer OUT] ─► MIDI interconnect (USB-MIDI = default)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Functional blocks
|
||||||
|
|
||||||
|
### 4.1 MCU + digital
|
||||||
|
- **RP2350A** (QFN-60, 30 GPIO). Chosen over RP2040 as the newest in-house part: dual M33+RISC-V,
|
||||||
|
520 KB RAM, secure boot, longest production runway. Firmware runs unchanged.
|
||||||
|
- **Erratum E9:** high-impedance inputs can latch — every input we read (face switches, buttons)
|
||||||
|
gets an **external pulldown**, never relies on the internal pad alone.
|
||||||
|
- Core supply via the RP2350 on-chip switched-mode regulator (external inductor); 3V3 IO from an
|
||||||
|
external LDO (AP2112K-3.3 / TLV75533).
|
||||||
|
- **Flash:** Winbond **W25Q128JV** (16 MB) — genuine part; the CircuitPython appliance bundles the
|
||||||
|
editor + tracks on a USB drive. Firmware does wear-leveling for `history.json`.
|
||||||
|
- **Crystal:** 12 MHz ±30 ppm.
|
||||||
|
- **Debug/service:** SWD 2×5 Cortex-Debug header + labeled test points (rails, I²S, audio nodes).
|
||||||
|
|
||||||
|
### 4.2 Click source (DAC)
|
||||||
|
- **TI/Burr-Brown PCM5102A** I²S DAC — reliability-first, widely stocked for future repair.
|
||||||
|
- Fed by a **dedicated low-jitter audio oscillator** (22.5792/24.576 MHz MEMS XO), **not** an MCLK
|
||||||
|
jittered out of the RP2350 PIO — jitter is audible as a raised noise floor.
|
||||||
|
|
||||||
|
### 4.3 Analog audio chain (the heart)
|
||||||
|
- **Input (balanced, switchable line/instrument):**
|
||||||
|
- **Line mode:** THAT1240 laser-trimmed balanced receiver (~high CMRR, no hand-matched resistors).
|
||||||
|
- **Instrument mode:** OPA1641 JFET Hi-Z buffer (≥1 MΩ) + ~+10–15 dB gain (active DI).
|
||||||
|
- Selected by a **gold-contact signal relay** on the core (1 GPIO; touchscreen toggle, optional
|
||||||
|
face panel switch on a separate GPIO input).
|
||||||
|
- **Protection (non-negotiable):** series DC-blocking film cap (blocks +48 V phantom — the real
|
||||||
|
input-killer), clamp diodes/TVS to the rails, series current-limit resistor. A wrong-mode plug
|
||||||
|
then only *sounds* wrong; nothing is damaged.
|
||||||
|
- **Mix:** digital/firmware (touchscreen). Analog stage at unity; click level set via the DAC.
|
||||||
|
- **Output driver:** THAT1646 balanced line driver, **47 Ω build-out per leg** for
|
||||||
|
cable-capacitance stability and short-circuit tolerance (§5.1).
|
||||||
|
- **No electrolytics in the signal path** — film coupling caps (WIMA). 0.1 % thin-film resistors.
|
||||||
|
|
||||||
|
### 4.4 Output protection / conditioning
|
||||||
|
- **Power-up/down mute relay** — fail-safe, **de-energized = muted** (shorts hot+cold to gnd). A
|
||||||
|
hardware rail supervisor + RC turn-on delay un-mutes only after ±15 V settles; on power loss the
|
||||||
|
coil drops and mutes *faster than the rails can thump*. **Not MCU-dependent.** The MCU can *also*
|
||||||
|
assert mute (for clean line/inst flips and DAC reconfig).
|
||||||
|
- **Ground-lift** — both a **face panel switch** and a **core GPIO relay**, wired in **series** in
|
||||||
|
the shield-ground path: bonded only when both closed, either opening lifts it. **Soft lift =
|
||||||
|
100 Ω ∥ 10 nF** (not a hard open) so RF/safety keeps a path.
|
||||||
|
|
||||||
|
### 4.5 RTC
|
||||||
|
- **Micro Crystal RV-8803** (integrated 32.768 kHz crystal → no second crystal near the RP2350's
|
||||||
|
own) + **CR2032 in a socketed holder**. Shares the touch I²C bus (no extra GPIO). Drift is
|
||||||
|
irrelevant for a practice-log timestamp; reliability and zero-fuss layout win.
|
||||||
|
|
||||||
|
### 4.6 MIDI (default USB, hardware optional)
|
||||||
|
- **USB-MIDI is the default** and already in firmware — IN/OUT/THRU are software routing to a
|
||||||
|
computer/tablet host. Zero extra parts.
|
||||||
|
- **Dedicated DIN/TRS MIDI is a DNP populate-option:** the RP2350 UART lines route to the MIDI
|
||||||
|
interconnect with **H11L1 opto IN + 74LVC14 buffered OUT/THRU footprints left unpopulated**.
|
||||||
|
A "stage" face can populate them for laptop-free hardware sync. (USB-MIDI can't peer-to-peer with
|
||||||
|
standalone DIN gear — the device is a USB *peripheral*, not a host — which is why the hardware
|
||||||
|
option stays available.) Analog pulse/clock sync: **not included** (MIDI only).
|
||||||
|
|
||||||
|
### 4.7 Monitor speaker
|
||||||
|
- **DNP-optional** class-D amp (PAM8302) footprint on the core; speaker +/- routed on the analog
|
||||||
|
interconnect. Populated only for form factors that want a built-in monitor.
|
||||||
|
|
||||||
|
## 5. The five remaining pro details (decided)
|
||||||
|
|
||||||
|
### 5.1 Output impedance & level calibration
|
||||||
|
- THAT1646 source impedance is near-zero; **47 Ω build-out resistors** per leg give stable driving
|
||||||
|
into long/capacitive cables and survive a shorted output.
|
||||||
|
- **Level calibration:** a 25-turn precision trimmer (Bourns 3296W) in the driver gain network,
|
||||||
|
factory-set so DAC full-scale → **+4 dBu nominal**, leaving ~**+24 dBu** peak headroom on ±15 V.
|
||||||
|
Set-and-forget on the core; not a face control.
|
||||||
|
|
||||||
|
### 5.2 Signal / clip indication
|
||||||
|
- A peak detector (Schottky + hold cap) on the input (signal-present, ~−40 dBu) and on the
|
||||||
|
driver-input/summing node (clip, within ~3 dB of rail) feeds an **LM393** dual comparator.
|
||||||
|
- Comparator outputs go to **RP2350 GPIOs** (clip/signal shown on the touchscreen) **and** are
|
||||||
|
mirrored to **SIG/CLIP LED drive lines on the digital interconnect** so a face can fit discrete LEDs.
|
||||||
|
|
||||||
|
### 5.3 ESD / EMI hardening
|
||||||
|
- **USB-C:** USBLC6-2SC6 (or TPD4E05U06) ESD array on D±/CC/VBUS; common-mode choke on the data
|
||||||
|
pair; shell bonded to chassis via RC; ferrite + TVS + bulk on VBUS.
|
||||||
|
- **Interconnects:** ~33 Ω series on fast SPI lines for edge-rate control; ESD clamp arrays on any
|
||||||
|
line reaching a user-touchable cable; interleaved ground pins; ferrite beads where 3V3/5V cross
|
||||||
|
into the analog domain.
|
||||||
|
- **Board:** full ground planes + stitching vias; the boost/inverter switcher lives in a guarded
|
||||||
|
corner away from the analog section; analog/digital grounds meet at a single **star point**.
|
||||||
|
- **Heirloom option:** conformal coating for humidity/longevity (build-time choice).
|
||||||
|
|
||||||
|
### 5.4 Chassis / strain-relief (core-side)
|
||||||
|
- 4× **M3 mounting holes** with keep-outs; a dedicated chassis-ground pad/pin.
|
||||||
|
- **Through-hole-anchored USB-C** jack (SMD-only tabs shear off with cable wiggle — unacceptable for
|
||||||
|
a 50-year device).
|
||||||
|
- **Shrouded, keyed, latching** interconnect headers (can't insert backward or vibrate loose).
|
||||||
|
- Panel strain-relief and connector mounting live on the face/enclosure.
|
||||||
|
|
||||||
|
### 5.5 Interconnect pinout — see §7.
|
||||||
|
|
||||||
|
## 6. Power tree
|
||||||
|
|
||||||
|
| Rail | Source | Part | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| +5 V | USB-C VBUS | — | ferrite + TVS + bulk |
|
||||||
|
| +3V3 (IO) | LDO from 5 V | AP2112K-3.3 / TLV75533 | digital domain |
|
||||||
|
| +1.1 V core | RP2350 internal SMPS | (external inductor) | per RP2350 ref |
|
||||||
|
| ±18 V (raw) | dual boost/inverter from 5 V | **TPS65131** | switcher, guarded corner |
|
||||||
|
| **±15 V (clean)** | ultra-low-noise LDO | **TPS7A4901 (+) / TPS7A3001 (−)** | feeds all audio op-amps |
|
||||||
|
|
||||||
|
## 7. Interconnect pinouts
|
||||||
|
|
||||||
|
### 7.1 Digital ribbon — 2×13 (26-pin) IDC, Pico-pinout-compatible
|
||||||
|
Grounds interleaved around SPI. A Pico/Pico 2 test adapter maps these to the listed GP.
|
||||||
|
|
||||||
|
| Pin | Signal | GP | | Pin | Signal | GP |
|
||||||
|
|----|--------|----|--|----|--------|----|
|
||||||
|
| 1 | +5V | — | | 2 | GND | — |
|
||||||
|
| 3 | +3V3 | — | | 4 | GND | — |
|
||||||
|
| 5 | SPI_SCK | GP2 | | 6 | GND | — |
|
||||||
|
| 7 | SPI_MOSI | GP3 | | 8 | LCD_CS | GP5 |
|
||||||
|
| 9 | LCD_DC | GP6 | | 10 | LCD_RST | GP7 |
|
||||||
|
| 11 | GND | — | | 12 | I2C_SDA | GP8 |
|
||||||
|
| 13 | I2C_SCL | GP9 | | 14 | GND | — |
|
||||||
|
| 15 | JOY_X (ADC0) | GP26 | | 16 | JOY_Y (ADC1) | GP27 |
|
||||||
|
| 17 | BTN_A | GP15 | | 18 | BTN_B | GP14 |
|
||||||
|
| 19 | WS2812 | GP12 | | 20 | GND | — |
|
||||||
|
| 21 | GNDLIFT_SW (in) | GP21 | | 22 | LINEINST_SW (in) | GP22 |
|
||||||
|
| 23 | SIG_LED | GP19 | | 24 | CLIP_LED | GP20 |
|
||||||
|
| 25 | GND | — | | 26 | GND | — |
|
||||||
|
|
||||||
|
*I²S (BCK/LRCK/DOUT), the relays (line/inst route GP16, mute GP18, gnd-lift GP17), and MCLK stay
|
||||||
|
on-core — they are not on the ribbon. A Pico test brain drives the digital/face I/O above but
|
||||||
|
**cannot** drive the analog chain (DAC/op-amps are core-only).*
|
||||||
|
|
||||||
|
### 7.2 Analog audio interconnect — 2×5 (10-pin), twisted/shielded, away from digital
|
||||||
|
| Pin | Signal | | Pin | Signal |
|
||||||
|
|----|--------|--|----|--------|
|
||||||
|
| 1 | AOUT_HOT | | 2 | AGND |
|
||||||
|
| 3 | AOUT_COLD | | 4 | CHASSIS/SHIELD (face side of ground-lift) |
|
||||||
|
| 5 | AIN_HOT | | 6 | AGND |
|
||||||
|
| 7 | AIN_COLD | | 8 | SPK+ (DNP) |
|
||||||
|
| 9 | AGND | | 10 | SPK− (DNP) |
|
||||||
|
|
||||||
|
### 7.3 MIDI interconnect — 1×6, only if DNP MIDI populated
|
||||||
|
| Pin | Signal |
|
||||||
|
|----|--------|
|
||||||
|
| 1 | MIDI_OUT_A (TRS-A tip/ring leg) |
|
||||||
|
| 2 | MIDI_OUT_B |
|
||||||
|
| 3 | MIDI_IN_A (to opto) |
|
||||||
|
| 4 | MIDI_IN_B |
|
||||||
|
| 5 | +5V (OUT drive) |
|
||||||
|
| 6 | GND / shield |
|
||||||
|
|
||||||
|
## 8. BOM
|
||||||
|
Full part list with manufacturer numbers and rough costs in **`hardware/BOM.csv`**. Headline parts:
|
||||||
|
RP2350A · W25Q128JV · PCM5102A · THAT1240 + THAT1646 · OPA1641 · OPA1612 · TPS65131 + TPS7A4901/3001 ·
|
||||||
|
RV-8803 · USBLC6-2SC6 · 3× Panasonic TQ2SA gold-contact relays · H11L1 (DNP).
|
||||||
|
|
||||||
|
## 9. Manufacturing
|
||||||
|
- **PCB:** ENIG (gold) finish — non-negotiable for decades of reliable contacts/solderability.
|
||||||
|
- **Assembly:** JLCPCB/PCBWay PCBA, ~5-board prototype minimum; ~$80–200 first run. Core parts cost
|
||||||
|
~$25–40/board one-off (pro op-amps + relays dominate), trending toward ~$15–20 at qty 100.
|
||||||
|
- Most expensive items are the THAT audio ICs and the relays — that's where "heirloom" lives.
|
||||||
|
|
||||||
|
## 10. Open items
|
||||||
|
- **PCB layout/routing is the interactive next step** — placement, controlled-impedance USB pair,
|
||||||
|
star ground, switcher isolation, copper pours, DRC. The KiCad project under `hardware/kicad/`
|
||||||
|
is a documented schematic canvas + design-of-record; symbol placement and wiring happen in
|
||||||
|
Eeschema. `kicad-cli` is used here for ERC and PDF export verification.
|
||||||
|
- Confirm the exact 3.5" ST7796/GT911 panel connector (FPC vs header) before finalizing the face.
|
||||||
|
- Decide MIDI connector (TRS-MIDI Type-A vs DIN-5) per form factor — face decision.
|
||||||
36
hardware/eda/Containerfile
Normal file
36
hardware/eda/Containerfile
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Reproducible EDA toolchain for the PM_K-1 core board.
|
||||||
|
#
|
||||||
|
# Why this exists: the system KiCad is 7.0 (no CLI ERC). This pins a known,
|
||||||
|
# rebuildable environment so the design can be checked/simulated identically
|
||||||
|
# years from now — fitting for a device meant to outlive its tools.
|
||||||
|
#
|
||||||
|
# KiCad 9 -> schematic capture, CLI ERC/DRC, netlist/PDF/Gerber export
|
||||||
|
# ngspice -> SPICE simulation of the analog audio circuits
|
||||||
|
# python3 -> scripting / BOM / skidl-style helpers
|
||||||
|
#
|
||||||
|
# Build/run via ../eda/run.sh (or: podman build -t pmk1-eda:9.0 .)
|
||||||
|
FROM docker.io/library/ubuntu:24.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
software-properties-common ca-certificates gnupg && \
|
||||||
|
add-apt-repository -y ppa:kicad/kicad-9.0-releases && \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
kicad \
|
||||||
|
kicad-symbols \
|
||||||
|
kicad-footprints \
|
||||||
|
ngspice \
|
||||||
|
python3 python3-pip python3-venv \
|
||||||
|
git make && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||||
|
pip3 install --no-cache-dir --break-system-packages skidl
|
||||||
|
|
||||||
|
# Point SKiDL / KiCad CLI at the installed libraries (reproducible, not ad-hoc).
|
||||||
|
ENV KICAD9_SYMBOL_DIR=/usr/share/kicad/symbols \
|
||||||
|
KICAD9_FOOTPRINT_DIR=/usr/share/kicad/footprints \
|
||||||
|
KICAD_SYMBOL_DIR=/usr/share/kicad/symbols
|
||||||
|
|
||||||
|
WORKDIR /work
|
||||||
|
CMD ["bash"]
|
||||||
36
hardware/eda/README.md
Normal file
36
hardware/eda/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# PM_K-1 EDA environment
|
||||||
|
|
||||||
|
A reproducible container with the tools to design, check, and simulate the core board —
|
||||||
|
so the work doesn't depend on whatever happens to be installed on a given machine, now or
|
||||||
|
in 50 years.
|
||||||
|
|
||||||
|
## What's inside
|
||||||
|
- **KiCad 9** — schematic capture + PCB layout, and a CLI (`kicad-cli`) that can run
|
||||||
|
**ERC** (Electrical Rules Check) and DRC, and export netlists/PDF/Gerbers.
|
||||||
|
- **ngspice** — SPICE simulator for validating the analog audio circuits before we commit
|
||||||
|
copper (op-amp stages, filters, input loading, etc.).
|
||||||
|
- **python3** — scripting, BOM munging, optional code-defined-schematic helpers.
|
||||||
|
|
||||||
|
## Why a container?
|
||||||
|
The system KiCad here is 7.0, whose CLI can't run ERC (that arrived in KiCad 8). Rather than
|
||||||
|
fight the host, we pin a known toolchain. Anyone — including future-you — rebuilds the exact
|
||||||
|
environment with one command.
|
||||||
|
|
||||||
|
## Use it
|
||||||
|
```bash
|
||||||
|
cd hardware/eda
|
||||||
|
./run.sh # interactive shell, lands in hardware/kicad/
|
||||||
|
./run.sh kicad-cli version # confirm KiCad 9
|
||||||
|
./run.sh kicad-cli sch erc pm_k1_core.kicad_sch # run ERC on the schematic
|
||||||
|
./run.sh ngspice -b ../eda/sim/input_loading.cir # run a simulation (cwd is kicad/)
|
||||||
|
```
|
||||||
|
`run.sh` builds the image on first use, then mounts the whole repo at `/work` (so KiCad sees
|
||||||
|
`hardware/`). Use `RUNTIME=docker ./run.sh …` to use Docker instead of Podman.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
```
|
||||||
|
eda/
|
||||||
|
Containerfile # the pinned toolchain (KiCad 9 + ngspice + python)
|
||||||
|
run.sh # build-if-needed + run with the repo mounted
|
||||||
|
sim/ # ngspice decks (SPICE simulations of the analog circuits)
|
||||||
|
```
|
||||||
195
hardware/eda/circuits/audio_chain.py
Normal file
195
hardware/eda/circuits/audio_chain.py
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - INTEGRATED board netlist (SKiDL).
|
||||||
|
|
||||||
|
Wires stages 1, 1b, 2, 3, 4 into one netlist with shared nets and DEDUPLICATED parts:
|
||||||
|
* the Stage-2 reconstruction filter and the Stage-3 summer share ONE OPA1612 dual
|
||||||
|
(U4: section A = filter, section B = summer) -- no parked half.
|
||||||
|
* the three relays (K1 select, K2 mute, K3 ground-lift) share ONE ULN2003 (U6).
|
||||||
|
This is the source of truth for the integrated audio netlist; the per-stage circuits/
|
||||||
|
files remain as the documented, individually-simulated building blocks.
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/audio_chain.py
|
||||||
|
Outputs ERC + hardware/kicad/audio_chain.net.
|
||||||
|
|
||||||
|
All IC pinouts datasheet-verified (see the per-stage files for the citations):
|
||||||
|
THAT1240 1=Ref 2=In- 3=In+ 4=Vee 5=Sns 6=Vout 7=Vcc 8=NC
|
||||||
|
OPA1641 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC
|
||||||
|
OPA1612 1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+
|
||||||
|
PCM5102A (TSSOP-20) per TI SLAS859C
|
||||||
|
THAT1646 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee 6=Vcc 7=Sns+ 8=Out+
|
||||||
|
TQ2SA coil 1/10; pole1 COM3/NC4/NO2; pole2 COM8/NC7/NO9
|
||||||
|
ULN2003A in 1B-7B=1..7, out 1C-7C=16..10, GND=8, COM=9
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from skidl import *
|
||||||
|
|
||||||
|
set_default_tool(KICAD9)
|
||||||
|
|
||||||
|
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
|
||||||
|
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
|
||||||
|
|
||||||
|
# ---------------- nets ----------------
|
||||||
|
p15, n15, p3v3, p5, gnd = Net("+15V"), Net("-15V"), Net("+3V3"), Net("+5V"), Net("GND")
|
||||||
|
for n in (p15, n15, p3v3, p5, gnd):
|
||||||
|
n.drive = POWER
|
||||||
|
# audio signal flow
|
||||||
|
ain_hot, ain_cold = Net("AIN_HOT"), Net("AIN_COLD") # input jack tip/ring
|
||||||
|
rx_hot_in = Net("RX_HOT_IN") # relay-routed hot -> receiver
|
||||||
|
rx_out, di_in, di_out = Net("RX_OUT"), Net("DI_IN"), Net("DI_OUT")
|
||||||
|
stage1_out, click_out, mix_out = Net("STAGE1_OUT"), Net("CLICK_OUT"), Net("MIX_OUT")
|
||||||
|
aout_hot, aout_cold, chassis = Net("AOUT_HOT"), Net("AOUT_COLD"), Net("CHASSIS")
|
||||||
|
# DAC clocks/data + control
|
||||||
|
mclk, i2s_bck, i2s_din, i2s_lrck = Net("MCLK"), Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK")
|
||||||
|
dac_xsmt = Net("DAC_XSMT")
|
||||||
|
sel_linst, mute_en, gndlift_en = Net("SEL_LINST"), Net("MUTE_EN"), Net("GNDLIFT_EN")
|
||||||
|
k1_drv, k2_drv, k3_drv = Net("K1_DRV"), Net("K2_DRV"), Net("K3_DRV")
|
||||||
|
|
||||||
|
# ---------------- part definitions ----------------
|
||||||
|
def mk(name, pins, ref_prefix="U", fp="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"):
|
||||||
|
return Part(name=name, tool=SKIDL, dest=TEMPLATE, ref_prefix=ref_prefix, footprint=fp, pins=pins)
|
||||||
|
|
||||||
|
P = Pin.types
|
||||||
|
THAT1240 = mk("THAT1240", [Pin(num=1,name="REF",func=P.INPUT),Pin(num=2,name="IN-",func=P.INPUT),
|
||||||
|
Pin(num=3,name="IN+",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="SENSE",func=P.PASSIVE),
|
||||||
|
Pin(num=6,name="OUT",func=P.OUTPUT),Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC",func=P.NOCONNECT)])
|
||||||
|
OPA1641 = mk("OPA1641", [Pin(num=1,name="NC1",func=P.NOCONNECT),Pin(num=2,name="-IN",func=P.INPUT),
|
||||||
|
Pin(num=3,name="+IN",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="NC5",func=P.NOCONNECT),
|
||||||
|
Pin(num=6,name="OUT",func=P.OUTPUT),Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC8",func=P.NOCONNECT)])
|
||||||
|
OPA1612 = mk("OPA1612", [Pin(num=1,name="OUTA",func=P.OUTPUT),Pin(num=2,name="-INA",func=P.INPUT),
|
||||||
|
Pin(num=3,name="+INA",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="+INB",func=P.INPUT),
|
||||||
|
Pin(num=6,name="-INB",func=P.INPUT),Pin(num=7,name="OUTB",func=P.OUTPUT),Pin(num=8,name="V+",func=P.PWRIN)])
|
||||||
|
THAT1646 = mk("THAT1646", [Pin(num=1,name="OUT-",func=P.OUTPUT),Pin(num=2,name="SNS-",func=P.INPUT),
|
||||||
|
Pin(num=3,name="GND",func=P.PWRIN),Pin(num=4,name="IN",func=P.INPUT),Pin(num=5,name="V-",func=P.PWRIN),
|
||||||
|
Pin(num=6,name="V+",func=P.PWRIN),Pin(num=7,name="SNS+",func=P.INPUT),Pin(num=8,name="OUT+",func=P.OUTPUT)])
|
||||||
|
PCM5102A = mk("PCM5102A", [Pin(num=1,name="CPVDD",func=P.PWRIN),Pin(num=2,name="CAPP",func=P.PASSIVE),
|
||||||
|
Pin(num=3,name="CPGND",func=P.PWRIN),Pin(num=4,name="CAPM",func=P.PASSIVE),Pin(num=5,name="VNEG",func=P.PASSIVE),
|
||||||
|
Pin(num=6,name="OUTL",func=P.OUTPUT),Pin(num=7,name="OUTR",func=P.OUTPUT),Pin(num=8,name="AVDD",func=P.PWRIN),
|
||||||
|
Pin(num=9,name="AGND",func=P.PWRIN),Pin(num=10,name="DEMP",func=P.INPUT),Pin(num=11,name="FLT",func=P.INPUT),
|
||||||
|
Pin(num=12,name="SCK",func=P.INPUT),Pin(num=13,name="BCK",func=P.INPUT),Pin(num=14,name="DIN",func=P.INPUT),
|
||||||
|
Pin(num=15,name="LRCK",func=P.INPUT),Pin(num=16,name="FMT",func=P.INPUT),Pin(num=17,name="XSMT",func=P.INPUT),
|
||||||
|
Pin(num=18,name="LDOO",func=P.PWROUT),Pin(num=19,name="DGND",func=P.PWRIN),Pin(num=20,name="DVDD",func=P.PWRIN)],
|
||||||
|
fp="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm")
|
||||||
|
TQ2SA = mk("TQ2SA-5V", [Pin(num=1,name="COIL_A",func=P.PASSIVE),Pin(num=10,name="COIL_B",func=P.PASSIVE),
|
||||||
|
Pin(num=3,name="P1_COM",func=P.PASSIVE),Pin(num=4,name="P1_NC",func=P.PASSIVE),Pin(num=2,name="P1_NO",func=P.PASSIVE),
|
||||||
|
Pin(num=8,name="P2_COM",func=P.PASSIVE),Pin(num=7,name="P2_NC",func=P.PASSIVE),Pin(num=9,name="P2_NO",func=P.PASSIVE),
|
||||||
|
Pin(num=5,name="NC5",func=P.NOCONNECT),Pin(num=6,name="NC6",func=P.NOCONNECT)],
|
||||||
|
ref_prefix="K", fp="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA")
|
||||||
|
ULN2003 = mk("ULN2003A", [Pin(num=1,name="1B",func=P.INPUT),Pin(num=2,name="2B",func=P.INPUT),
|
||||||
|
Pin(num=3,name="3B",func=P.INPUT),Pin(num=4,name="4B",func=P.INPUT),Pin(num=5,name="5B",func=P.INPUT),
|
||||||
|
Pin(num=6,name="6B",func=P.INPUT),Pin(num=7,name="7B",func=P.INPUT),Pin(num=8,name="GND",func=P.PWRIN),
|
||||||
|
Pin(num=9,name="COM",func=P.PWRIN),Pin(num=10,name="7C",func=P.OPENCOLL),Pin(num=11,name="6C",func=P.OPENCOLL),
|
||||||
|
Pin(num=12,name="5C",func=P.OPENCOLL),Pin(num=13,name="4C",func=P.OPENCOLL),Pin(num=14,name="3C",func=P.OPENCOLL),
|
||||||
|
Pin(num=15,name="2C",func=P.OPENCOLL),Pin(num=16,name="1C",func=P.OPENCOLL)],
|
||||||
|
fp="Package_SO:SOIC-16_3.9x9.9mm_P1.27mm")
|
||||||
|
|
||||||
|
# instances (deduplicated)
|
||||||
|
u1 = THAT1240(ref="U1") # line receiver
|
||||||
|
u2 = OPA1641(ref="U2") # Hi-Z DI buffer
|
||||||
|
u3 = PCM5102A(ref="U3") # DAC
|
||||||
|
u4 = OPA1612(ref="U4") # A = recon filter, B = summer (shared dual)
|
||||||
|
u5 = THAT1646(ref="U5") # balanced driver
|
||||||
|
u6 = ULN2003(ref="U6") # shared relay driver
|
||||||
|
k1, k2, k3 = TQ2SA(ref="K1"), TQ2SA(ref="K2"), TQ2SA(ref="K3")
|
||||||
|
|
||||||
|
def dcp(rail): # 100nF decoupling helper
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
|
||||||
|
# ---------------- Stage 1: balanced line receiver + protection ----------------
|
||||||
|
def protect_bal(src, tag):
|
||||||
|
cblk = C(value="2.2uF", footprint="Capacitor_SMD:C_1206_3216Metric")
|
||||||
|
rs, rb = R(value="1k"), R(value="1Meg")
|
||||||
|
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
|
||||||
|
node = Net(tag)
|
||||||
|
src += cblk[1]; cblk[2] += rs[1]; rs[2] += node
|
||||||
|
rb[1] += node; rb[2] += gnd
|
||||||
|
dp[1] += p15; dp[2] += node # Device:D pin1=K,pin2=A : high clamp
|
||||||
|
dn[1] += node; dn[2] += n15 # low clamp
|
||||||
|
return node
|
||||||
|
u1["IN+"] += protect_bal(rx_hot_in, "RXIN_P") # hot comes from the select relay (NC)
|
||||||
|
u1["IN-"] += protect_bal(ain_cold, "RXIN_N") # cold is the ring (direct)
|
||||||
|
u1["REF"] += gnd; u1["SENSE"] += rx_out; u1["OUT"] += rx_out
|
||||||
|
u1["V+"] += p15; u1["V-"] += n15; dcp(p15); dcp(n15)
|
||||||
|
|
||||||
|
# ---------------- Stage 1b: Hi-Z DI buffer (OPA1641) ----------------
|
||||||
|
cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric")
|
||||||
|
rbias = R(value="1Meg"); dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
|
||||||
|
di_node = Net("DI_NODE")
|
||||||
|
di_in += cblk[1]; cblk[2] += di_node
|
||||||
|
rbias[1] += di_node; rbias[2] += gnd
|
||||||
|
dp[1] += p15; dp[2] += di_node
|
||||||
|
dn[1] += di_node; dn[2] += n15
|
||||||
|
u2["+IN"] += di_node
|
||||||
|
rf2, rg2 = R(value="3k"), R(value="1k") # +12 dB
|
||||||
|
u2["OUT"] += di_out
|
||||||
|
rf2[1] += di_out; rf2[2] += u2["-IN"]
|
||||||
|
rg2[1] += u2["-IN"]; rg2[2] += gnd
|
||||||
|
u2["V+"] += p15; u2["V-"] += n15; dcp(p15); dcp(n15)
|
||||||
|
|
||||||
|
# ---------------- Stage 1b: select relay K1 ----------------
|
||||||
|
k1["P1_COM"] += ain_hot; k1["P1_NC"] += rx_hot_in; k1["P1_NO"] += di_in # route tip
|
||||||
|
k1["P2_COM"] += stage1_out; k1["P2_NC"] += rx_out; k1["P2_NO"] += di_out # select output
|
||||||
|
k1["COIL_A"] += p5; k1["COIL_B"] += k1_drv
|
||||||
|
|
||||||
|
# ---------------- Stage 2: PCM5102A DAC ----------------
|
||||||
|
for pin in ("AVDD","CPVDD","DVDD"): u3[pin] += p3v3
|
||||||
|
for pin in ("AGND","DGND","CPGND"): u3[pin] += gnd
|
||||||
|
for pin in ("AVDD","CPVDD","DVDD"):
|
||||||
|
c = C(value="100nF"); u3[pin] += c[1]; c[2] += gnd
|
||||||
|
cb = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric"); u3["AVDD"] += cb[1]; cb[2] += gnd
|
||||||
|
cfly = C(value="2.2uF"); u3["CAPP"] += cfly[1]; u3["CAPM"] += cfly[2]
|
||||||
|
cvneg = C(value="2.2uF"); u3["VNEG"] += cvneg[1]; cvneg[2] += gnd
|
||||||
|
cldoo = C(value="1uF"); u3["LDOO"] += cldoo[1]; cldoo[2] += gnd
|
||||||
|
u3["DEMP"] += gnd; u3["FLT"] += gnd; u3["FMT"] += gnd
|
||||||
|
u3["XSMT"] += dac_xsmt; rpu = R(value="10k"); dac_xsmt += rpu[1]; rpu[2] += p3v3
|
||||||
|
u3["SCK"] += mclk; u3["BCK"] += i2s_bck; u3["DIN"] += i2s_din; u3["LRCK"] += i2s_lrck
|
||||||
|
rload = R(value="2.2k"); u3["OUTR"] += rload[1]; rload[2] += gnd
|
||||||
|
|
||||||
|
# ---------------- Stage 2: reconstruction filter = OPA1612 section A ----------------
|
||||||
|
r1, r2 = R(value="1.5k"), R(value="1.5k"); ca = C(value="2.2nF"); cbq = C(value="1nF")
|
||||||
|
rcA = Net("RC_A")
|
||||||
|
u3["OUTL"] += r1[1]; r1[2] += rcA
|
||||||
|
r2[1] += rcA; r2[2] += u4["+INA"]
|
||||||
|
ca[1] += rcA; ca[2] += u4["OUTA"]
|
||||||
|
cbq[1] += u4["+INA"]; cbq[2] += gnd
|
||||||
|
u4["-INA"] += u4["OUTA"]; u4["OUTA"] += click_out
|
||||||
|
u4["V+"] += p15; u4["V-"] += n15; dcp(p15); dcp(n15)
|
||||||
|
|
||||||
|
# ---------------- Stage 3: summer = OPA1612 section B (shared chip) ----------------
|
||||||
|
ri_in, ri_clk, rf3 = R(value="10k"), R(value="10k"), R(value="10k")
|
||||||
|
stage1_out += ri_in[1]; ri_in[2] += u4["-INB"]
|
||||||
|
click_out += ri_clk[1]; ri_clk[2] += u4["-INB"]
|
||||||
|
rf3[1] += u4["-INB"]; rf3[2] += u4["OUTB"]
|
||||||
|
u4["+INB"] += gnd; u4["OUTB"] += mix_out
|
||||||
|
|
||||||
|
# ---------------- Stage 4: level trim + THAT1646 + build-out ----------------
|
||||||
|
RV = Part("Device","R_Potentiometer", dest=TEMPLATE,
|
||||||
|
footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
|
||||||
|
rv1 = RV(value="10k", ref="RV1")
|
||||||
|
mix_out += rv1[1]; rv1[3] += gnd; rv1[2] += u5["IN"]
|
||||||
|
u5["SNS-"] += u5["OUT-"]; u5["SNS+"] += u5["OUT+"]; u5["GND"] += gnd
|
||||||
|
u5["V+"] += p15; u5["V-"] += n15; dcp(p15); dcp(n15)
|
||||||
|
rbo_h, rbo_c = R(value="47"), R(value="47")
|
||||||
|
u5["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot # phase-corrected: HOT<-OUT-
|
||||||
|
u5["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold # COLD<-OUT+
|
||||||
|
|
||||||
|
# ---------------- Stage 4: mute relay K2 + ground-lift relay K3 ----------------
|
||||||
|
k2["P1_COM"] += aout_hot; k2["P1_NC"] += gnd # de-energized = muted (short to gnd)
|
||||||
|
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd
|
||||||
|
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv
|
||||||
|
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis # de-energized = bonded
|
||||||
|
rlift = R(value="100"); clift = C(value="10nF")
|
||||||
|
rlift[1] += gnd; rlift[2] += chassis; clift[1] += gnd; clift[2] += chassis
|
||||||
|
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv
|
||||||
|
|
||||||
|
# ---------------- shared relay driver U6 (ULN2003): 3 channels ----------------
|
||||||
|
u6["1B"] += sel_linst; u6["1C"] += k1_drv
|
||||||
|
u6["2B"] += mute_en; u6["2C"] += k2_drv
|
||||||
|
u6["3B"] += gndlift_en; u6["3C"] += k3_drv
|
||||||
|
u6["GND"] += gnd; u6["COM"] += p5 # flyback common to coil supply
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "audio_chain.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Integrated audio netlist ->", out)
|
||||||
82
hardware/eda/circuits/stage1_input.py
Normal file
82
hardware/eda/circuits/stage1_input.py
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 1: balanced LINE input receiver + protection (SKiDL).
|
||||||
|
|
||||||
|
Code-defined schematic. Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage1_input.py
|
||||||
|
Outputs ERC results + a KiCad netlist at hardware/kicad/stage1_input.net,
|
||||||
|
which imports into Pcbnew for layout.
|
||||||
|
|
||||||
|
VERIFIED against datasheets:
|
||||||
|
* THAT1240 = 0 dB (unity-gain) line receiver; SO-8 pinout per THAT doc 600035
|
||||||
|
rev 05: 1=Ref 2=In- 3=In+ 4=Vee 5=Sense 6=Vout 7=Vcc 8=NC. Supply 6-36V
|
||||||
|
(our +/-15V is in range). Pin-compatible 2nd sources: INA134 / SSM2141.
|
||||||
|
* clamp-diode orientation per KiCad Device:D (pin1=K cathode, pin2=A anode).
|
||||||
|
Open at BOM time only: exact clamp-diode part (fast low-leakage small-signal;
|
||||||
|
1N4148WS is a placeholder).
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from skidl import *
|
||||||
|
|
||||||
|
set_default_tool(KICAD9)
|
||||||
|
|
||||||
|
# ---------- passive templates ----------
|
||||||
|
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
|
||||||
|
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
|
||||||
|
|
||||||
|
# ---------- power rails (global nets; marked POWER-driven for ERC) ----------
|
||||||
|
# No power-flag *symbols* -- they carry no footprint and SKiDL errors on that.
|
||||||
|
# The rails arrive from the power block; here we just declare + drive them.
|
||||||
|
p15, n15, gnd = Net("+15V"), Net("-15V"), Net("GND")
|
||||||
|
for n in (p15, n15, gnd):
|
||||||
|
n.drive = POWER
|
||||||
|
|
||||||
|
# ---------- THAT1240 balanced line receiver (0 dB) : pinout VERIFIED ----------
|
||||||
|
RX = Part(name="THAT1240", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="REF", func=Pin.types.INPUT),
|
||||||
|
Pin(num=2, name="IN-", func=Pin.types.INPUT),
|
||||||
|
Pin(num=3, name="IN+", func=Pin.types.INPUT),
|
||||||
|
Pin(num=4, name="V-", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=5, name="SENSE", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=6, name="OUT", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=7, name="V+", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=8, name="NC", func=Pin.types.NOCONNECT),
|
||||||
|
])
|
||||||
|
rx = RX(ref="U1")
|
||||||
|
|
||||||
|
# ---------- nets ----------
|
||||||
|
ain_hot, ain_cold, rxout = Net("AIN_HOT"), Net("AIN_COLD"), Net("RX_OUT")
|
||||||
|
|
||||||
|
def protected_input(src, node_name):
|
||||||
|
"""series DC-block cap (blocks +48V phantom) -> series R -> node;
|
||||||
|
bias R to gnd; clamp diodes to the +/-15V rails (orientation TODO-VERIFY)."""
|
||||||
|
cblk = C(value="2.2uF", footprint="Capacitor_SMD:C_1206_3216Metric") # film
|
||||||
|
rs, rb = R(value="1k"), R(value="1Meg")
|
||||||
|
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
|
||||||
|
node = Net(node_name)
|
||||||
|
src += cblk[1]; cblk[2] += rs[1]; rs[2] += node
|
||||||
|
rb[1] += node; rb[2] += gnd
|
||||||
|
# clamp diodes: KiCad Device:D is pin1=K (cathode), pin2=A (anode).
|
||||||
|
dp[1] += p15; dp[2] += node # high clamp: conducts when node > +15
|
||||||
|
dn[1] += node; dn[2] += n15 # low clamp: conducts when node < -15
|
||||||
|
return node
|
||||||
|
|
||||||
|
rx["IN+"] += protected_input(ain_hot, "RXIN_P")
|
||||||
|
rx["IN-"] += protected_input(ain_cold, "RXIN_N")
|
||||||
|
rx["REF"] += gnd
|
||||||
|
rx["SENSE"] += rxout # sense tied to output (local feedback)
|
||||||
|
rx["OUT"] += rxout
|
||||||
|
rx["V+"] += p15
|
||||||
|
rx["V-"] += n15
|
||||||
|
|
||||||
|
# supply decoupling at the receiver
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF")
|
||||||
|
rail += c[1]; c[2] += gnd
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage1_input.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 1 netlist ->", out)
|
||||||
123
hardware/eda/circuits/stage1b_select.py
Normal file
123
hardware/eda/circuits/stage1b_select.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 1b: Hi-Z instrument DI buffer + line/instrument select (SKiDL).
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage1b_select.py
|
||||||
|
Outputs ERC + a KiCad netlist at hardware/kicad/stage1b_select.net.
|
||||||
|
|
||||||
|
WHAT THIS STAGE DOES
|
||||||
|
The same input jack feeds either the balanced LINE receiver (Stage 1) or a Hi-Z
|
||||||
|
INSTRUMENT buffer (here). One DPDT relay (K1) does two jobs at once:
|
||||||
|
pole 1 routes the jack TIP to the line receiver (default) OR the DI buffer
|
||||||
|
pole 2 selects which OUTPUT (RX_OUT or DI_OUT) feeds the summing stage
|
||||||
|
De-energized (relay OFF) = LINE = the common case (saves coil power; fail to line).
|
||||||
|
The DI buffer = OPA1641 non-inverting, gain +12 dB (1+Rf/Rg), 1 Mohm input.
|
||||||
|
|
||||||
|
PINOUTS VERIFIED FROM DATASHEETS
|
||||||
|
* OPA1641 (TI SBOS484D): 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC. Supply +/-18V max.
|
||||||
|
* TQ2SA (Panasonic TQ-SMD): 2 Form C. pole1 COM=3 (throws 2,4); pole2 COM=8 (throws 7,9)
|
||||||
|
-- confirmed from the contact-resistance terminal pairs (2-3,3-4,7-8,8-9).
|
||||||
|
RESOLVED from the Panasonic TQ-SMD connection diagram (single-side-stable, top view)
|
||||||
|
+ the contact-resistance terminal pairs (2-3,3-4,7-8,8-9):
|
||||||
|
* coil = pins 1 & 10 (polarity irrelevant -- non-latching single coil, no diode).
|
||||||
|
* pole1 COM=3, NC=4, NO=2 ; pole2 COM=8, NC=7, NO=9 ; pins 5,6 unused.
|
||||||
|
NC/NO sense is also firmware-correctable: the relay is GPIO-driven, so if a physical
|
||||||
|
unit reads opposite, invert SEL_LINST in firmware (no board change).
|
||||||
|
Relay coil is driven by the SHARED ULN2003 (U14) -- represented here as net K1_DRV
|
||||||
|
(sinks the coil) + SEL_LINST (the RP2350 GPIO). The ULN2003 is instantiated once in
|
||||||
|
the power/control block, not per-relay.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from skidl import *
|
||||||
|
|
||||||
|
set_default_tool(KICAD9)
|
||||||
|
|
||||||
|
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
|
||||||
|
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
|
||||||
|
|
||||||
|
# ---- rails ----
|
||||||
|
p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
|
||||||
|
for n in (p15, n15, gnd, p5):
|
||||||
|
n.drive = POWER
|
||||||
|
|
||||||
|
# ---- OPA1641 JFET Hi-Z buffer (pinout verified) ----
|
||||||
|
OPA = Part(name="OPA1641", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="NC1", func=Pin.types.NOCONNECT),
|
||||||
|
Pin(num=2, name="-IN", func=Pin.types.INPUT),
|
||||||
|
Pin(num=3, name="+IN", func=Pin.types.INPUT),
|
||||||
|
Pin(num=4, name="V-", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
|
||||||
|
Pin(num=6, name="OUT", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=7, name="V+", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=8, name="NC8", func=Pin.types.NOCONNECT),
|
||||||
|
])
|
||||||
|
u = OPA(ref="U2")
|
||||||
|
|
||||||
|
# ---- TQ2SA DPDT select relay (contacts verified; NC/NO + coil pins flagged) ----
|
||||||
|
RLY = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
|
||||||
|
footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA", # footprint: confirm at layout
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="COIL_A", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=3, name="P1_COM", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=4, name="P1_NC", func=Pin.types.PASSIVE), # de-energized = LINE
|
||||||
|
Pin(num=2, name="P1_NO", func=Pin.types.PASSIVE), # energized = INSTRUMENT
|
||||||
|
Pin(num=8, name="P2_COM", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=7, name="P2_NC", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=9, name="P2_NO", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
|
||||||
|
Pin(num=6, name="NC6", func=Pin.types.NOCONNECT),
|
||||||
|
])
|
||||||
|
k1 = RLY(ref="K1")
|
||||||
|
|
||||||
|
# ---- nets (shared with Stage 1 / Stage 3 by name) ----
|
||||||
|
ain_hot = Net("AIN_HOT") # jack TIP
|
||||||
|
rx_hot_in = Net("RX_HOT_IN") # -> Stage 1 line-receiver hot protection (was AIN_HOT there)
|
||||||
|
di_in = Net("DI_IN") # -> this DI buffer input
|
||||||
|
rx_out = Net("RX_OUT") # <- Stage 1 line receiver output
|
||||||
|
di_out = Net("DI_OUT") # this DI buffer output
|
||||||
|
stage1_out = Net("STAGE1_OUT") # selected -> Stage 3 summing
|
||||||
|
sel = Net("SEL_LINST") # RP2350 GPIO: low=LINE(default), high=INSTRUMENT
|
||||||
|
k1_drv = Net("K1_DRV") # shared ULN2003 output sinks the coil
|
||||||
|
|
||||||
|
# pole 1: route jack tip
|
||||||
|
k1["P1_COM"] += ain_hot
|
||||||
|
k1["P1_NC"] += rx_hot_in # default -> line receiver
|
||||||
|
k1["P1_NO"] += di_in # energized -> DI buffer
|
||||||
|
# pole 2: select output
|
||||||
|
k1["P2_COM"] += stage1_out
|
||||||
|
k1["P2_NC"] += rx_out # default -> line receiver output
|
||||||
|
k1["P2_NO"] += di_out # energized -> DI buffer output
|
||||||
|
# coil: +5V -- coil -- K1_DRV (ULN2003 sinks to gnd when SEL_LINST high)
|
||||||
|
k1["COIL_A"] += p5
|
||||||
|
k1["COIL_B"] += k1_drv
|
||||||
|
|
||||||
|
# ---- DI buffer input: DC-block, 1M bias, rail clamps ----
|
||||||
|
cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric") # film
|
||||||
|
rbias = R(value="1Meg")
|
||||||
|
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
|
||||||
|
node = Net("DI_NODE")
|
||||||
|
di_in += cblk[1]; cblk[2] += node
|
||||||
|
rbias[1] += node; rbias[2] += gnd
|
||||||
|
dp[1] += p15; dp[2] += node # Device:D pin1=K, pin2=A -> high clamp (>+15)
|
||||||
|
dn[1] += node; dn[2] += n15 # low clamp (< -15)
|
||||||
|
u["+IN"] += node
|
||||||
|
|
||||||
|
# ---- non-inverting gain: Av = 1 + Rf/Rg = 1 + 3k/1k = 4 (+12 dB) ----
|
||||||
|
rf, rg = R(value="3k"), R(value="1k")
|
||||||
|
u["OUT"] += di_out
|
||||||
|
rf[1] += di_out; rf[2] += u["-IN"]
|
||||||
|
rg[1] += u["-IN"]; rg[2] += gnd
|
||||||
|
|
||||||
|
# ---- supplies + decoupling ----
|
||||||
|
u["V+"] += p15; u["V-"] += n15
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage1b_select.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 1b netlist ->", out)
|
||||||
137
hardware/eda/circuits/stage2_dac.py
Normal file
137
hardware/eda/circuits/stage2_dac.py
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 2: PCM5102A DAC + reconstruction filter (SKiDL).
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage2_dac.py
|
||||||
|
Outputs ERC + a KiCad netlist at hardware/kicad/stage2_dac.net.
|
||||||
|
|
||||||
|
WHAT THIS STAGE DOES
|
||||||
|
The RP2350 streams I2S audio (the click, and later sampled sounds) to a PCM5102A
|
||||||
|
DAC clocked by the dedicated low-jitter oscillator (SCK=MCLK). The PCM5102A puts
|
||||||
|
out 2.1 Vrms GROUND-CENTERED analog (no DC-block cap needed). A 2nd-order
|
||||||
|
Sallen-Key low-pass (~75 kHz, one OPA1612 section) removes the delta-sigma HF
|
||||||
|
residue before the click reaches the summing/output stages -> CLICK_OUT.
|
||||||
|
|
||||||
|
PINOUTS VERIFIED FROM DATASHEETS
|
||||||
|
* PCM5102A (TI SLAS859C, PW/TSSOP-20): pin map below matches the datasheet table.
|
||||||
|
Output 2.1Vrms GND-centered; 3.3V supplies; charge pump (CAPP/CAPM flying cap +
|
||||||
|
VNEG) makes the negative rail for the ground-centered swing.
|
||||||
|
* OPA1612 (dual): standard JEDEC dual-opamp SOIC-8 pinout
|
||||||
|
(1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+), confirmed against the
|
||||||
|
OPA164x dual on its datasheet (identical layout). Confirm OPA1612 sheet at layout.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from skidl import *
|
||||||
|
|
||||||
|
set_default_tool(KICAD9)
|
||||||
|
|
||||||
|
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
|
||||||
|
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
|
||||||
|
# ---- rails / signals ----
|
||||||
|
p3v3, gnd, p15, n15 = Net("+3V3"), Net("GND"), Net("+15V"), Net("-15V")
|
||||||
|
for n in (p3v3, gnd, p15, n15):
|
||||||
|
n.drive = POWER
|
||||||
|
mclk = Net("MCLK") # low-jitter audio clock -> SCK
|
||||||
|
i2s_bck, i2s_din, i2s_lrck = Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK") # from RP2350
|
||||||
|
dac_xsmt = Net("DAC_XSMT") # GPIO soft-mute (high=unmute), pulled up
|
||||||
|
click_out = Net("CLICK_OUT")# filtered click -> Stage 3 summing
|
||||||
|
|
||||||
|
# ---- PCM5102A (pinout verified, TI SLAS859C) ----
|
||||||
|
DAC = Part(name="PCM5102A", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="CPVDD", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=2, name="CAPP", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=3, name="CPGND", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=4, name="CAPM", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=5, name="VNEG", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=6, name="OUTL", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=7, name="OUTR", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=8, name="AVDD", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=9, name="AGND", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=10, name="DEMP", func=Pin.types.INPUT),
|
||||||
|
Pin(num=11, name="FLT", func=Pin.types.INPUT),
|
||||||
|
Pin(num=12, name="SCK", func=Pin.types.INPUT),
|
||||||
|
Pin(num=13, name="BCK", func=Pin.types.INPUT),
|
||||||
|
Pin(num=14, name="DIN", func=Pin.types.INPUT),
|
||||||
|
Pin(num=15, name="LRCK", func=Pin.types.INPUT),
|
||||||
|
Pin(num=16, name="FMT", func=Pin.types.INPUT),
|
||||||
|
Pin(num=17, name="XSMT", func=Pin.types.INPUT),
|
||||||
|
Pin(num=18, name="LDOO", func=Pin.types.PWROUT),
|
||||||
|
Pin(num=19, name="DGND", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=20, name="DVDD", func=Pin.types.PWRIN),
|
||||||
|
])
|
||||||
|
dac = DAC(ref="U3")
|
||||||
|
|
||||||
|
# ---- OPA1612 dual op-amp (standard dual pinout) ----
|
||||||
|
OPA2 = Part(name="OPA1612", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="OUTA", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=2, name="-INA", func=Pin.types.INPUT),
|
||||||
|
Pin(num=3, name="+INA", func=Pin.types.INPUT),
|
||||||
|
Pin(num=4, name="V-", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=5, name="+INB", func=Pin.types.INPUT),
|
||||||
|
Pin(num=6, name="-INB", func=Pin.types.INPUT),
|
||||||
|
Pin(num=7, name="OUTB", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=8, name="V+", func=Pin.types.PWRIN),
|
||||||
|
])
|
||||||
|
flt = OPA2(ref="U4")
|
||||||
|
|
||||||
|
# ---- PCM5102A supplies + grounds (all grounds tied; <0.2V per datasheet) ----
|
||||||
|
dac["AVDD"] += p3v3
|
||||||
|
dac["CPVDD"] += p3v3
|
||||||
|
dac["DVDD"] += p3v3
|
||||||
|
dac["AGND"] += gnd
|
||||||
|
dac["DGND"] += gnd
|
||||||
|
dac["CPGND"] += gnd
|
||||||
|
for pin in ("AVDD", "CPVDD", "DVDD"):
|
||||||
|
c = C(value="100nF"); dac[pin] += c[1]; c[2] += gnd
|
||||||
|
cbulk = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric")
|
||||||
|
dac["AVDD"] += cbulk[1]; cbulk[2] += gnd
|
||||||
|
|
||||||
|
# charge pump: flying cap across CAPP/CAPM, VNEG + LDOO decoupling
|
||||||
|
cfly = C(value="2.2uF", footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
dac["CAPP"] += cfly[1]; dac["CAPM"] += cfly[2]
|
||||||
|
cvneg = C(value="2.2uF"); dac["VNEG"] += cvneg[1]; cvneg[2] += gnd
|
||||||
|
cldoo = C(value="1uF"); dac["LDOO"] += cldoo[1]; cldoo[2] += gnd
|
||||||
|
|
||||||
|
# control-pin tie-offs: no de-emphasis, normal latency, I2S format
|
||||||
|
dac["DEMP"] += gnd
|
||||||
|
dac["FLT"] += gnd
|
||||||
|
dac["FMT"] += gnd
|
||||||
|
# soft-mute: GPIO with 10k pull-up to keep un-muted by default
|
||||||
|
dac["XSMT"] += dac_xsmt
|
||||||
|
rpull = R(value="10k"); dac_xsmt += rpull[1]; rpull[2] += p3v3
|
||||||
|
|
||||||
|
# clocks + data
|
||||||
|
dac["SCK"] += mclk
|
||||||
|
dac["BCK"] += i2s_bck
|
||||||
|
dac["DIN"] += i2s_din
|
||||||
|
dac["LRCK"] += i2s_lrck
|
||||||
|
|
||||||
|
# OUTR unused (mono click on L): give it the datasheet-recommended load
|
||||||
|
rload = R(value="2.2k"); dac["OUTR"] += rload[1]; rload[2] += gnd
|
||||||
|
|
||||||
|
# ---- Sallen-Key reconstruction LPF on OUTL (R=1.5k, Ca=2.2n, Cb=1n ~75kHz) ----
|
||||||
|
r1, r2 = R(value="1.5k"), R(value="1.5k")
|
||||||
|
ca = C(value="2.2nF"); cb = C(value="1nF")
|
||||||
|
nodeA = Net("RC_A")
|
||||||
|
dac["OUTL"] += r1[1]; r1[2] += nodeA
|
||||||
|
r2[1] += nodeA; r2[2] += flt["+INA"]
|
||||||
|
ca[1] += nodeA; ca[2] += flt["OUTA"] # feedback cap
|
||||||
|
cb[1] += flt["+INA"]; cb[2] += gnd
|
||||||
|
flt["-INA"] += flt["OUTA"] # unity-gain follower
|
||||||
|
flt["OUTA"] += click_out
|
||||||
|
|
||||||
|
# ---- OPA1612 supplies + decoupling; park unused section B ----
|
||||||
|
flt["V+"] += p15; flt["V-"] += n15
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
flt["+INB"] += gnd; flt["OUTB"] += flt["-INB"] # B = grounded follower (parked)
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage2_dac.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 2 netlist ->", out)
|
||||||
69
hardware/eda/circuits/stage3_sum.py
Normal file
69
hardware/eda/circuits/stage3_sum.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 3: summing node (selected input + click) (SKiDL).
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage3_sum.py
|
||||||
|
Outputs ERC + a KiCad netlist at hardware/kicad/stage3_sum.net.
|
||||||
|
|
||||||
|
WHAT THIS STAGE DOES
|
||||||
|
Inverting summing amp (OPA1612 section) mixes STAGE1_OUT (the line/instrument input,
|
||||||
|
unity) and CLICK_OUT (the filtered DAC click). Each source enters through its own
|
||||||
|
10k resistor into the op-amp's virtual-ground node, so the two never interact. The
|
||||||
|
"digital mix" lives upstream: click level is set by the DAC; the input passes at
|
||||||
|
unity. Output MIX_OUT -> Stage 4 balanced driver.
|
||||||
|
Vout = -(STAGE1_OUT + CLICK_OUT) (Rf = Ri = 10k)
|
||||||
|
|
||||||
|
POLARITY: an inverting summer flips phase. That is corrected for free at the Stage 4
|
||||||
|
balanced driver by assigning hot/cold accordingly (absolute polarity preserved).
|
||||||
|
|
||||||
|
OPA1612 dual: standard JEDEC pinout (1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB
|
||||||
|
8=V+). At INTEGRATION this summer can use the PARKED 2nd half of the Stage 2 filter's
|
||||||
|
OPA1612 (U4) instead of a separate package -- noted for the merge step.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from skidl import *
|
||||||
|
|
||||||
|
set_default_tool(KICAD9)
|
||||||
|
|
||||||
|
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
|
||||||
|
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
|
||||||
|
p15, n15, gnd = Net("+15V"), Net("-15V"), Net("GND")
|
||||||
|
for n in (p15, n15, gnd):
|
||||||
|
n.drive = POWER
|
||||||
|
stage1_out = Net("STAGE1_OUT") # from Stage 1b relay (selected input)
|
||||||
|
click_out = Net("CLICK_OUT") # from Stage 2 reconstruction filter
|
||||||
|
mix_out = Net("MIX_OUT") # -> Stage 4 balanced driver
|
||||||
|
|
||||||
|
OPA2 = Part(name="OPA1612", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="OUTA", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=2, name="-INA", func=Pin.types.INPUT),
|
||||||
|
Pin(num=3, name="+INA", func=Pin.types.INPUT),
|
||||||
|
Pin(num=4, name="V-", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=5, name="+INB", func=Pin.types.INPUT),
|
||||||
|
Pin(num=6, name="-INB", func=Pin.types.INPUT),
|
||||||
|
Pin(num=7, name="OUTB", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=8, name="V+", func=Pin.types.PWRIN),
|
||||||
|
])
|
||||||
|
u = OPA2(ref="U5")
|
||||||
|
|
||||||
|
# inverting summer: each source -> 10k -> virtual-ground (-INA); Rf 10k; +INA -> gnd
|
||||||
|
ri_in, ri_clk, rf = R(value="10k"), R(value="10k"), R(value="10k")
|
||||||
|
stage1_out += ri_in[1]; ri_in[2] += u["-INA"]
|
||||||
|
click_out += ri_clk[1]; ri_clk[2] += u["-INA"]
|
||||||
|
rf[1] += u["-INA"]; rf[2] += u["OUTA"]
|
||||||
|
u["+INA"] += gnd
|
||||||
|
u["OUTA"] += mix_out
|
||||||
|
|
||||||
|
# supplies + decoupling; park unused section B
|
||||||
|
u["V+"] += p15; u["V-"] += n15
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
u["+INB"] += gnd; u["OUTB"] += u["-INB"] # parked follower
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage3_sum.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 3 netlist ->", out)
|
||||||
113
hardware/eda/circuits/stage4_driver.py
Normal file
113
hardware/eda/circuits/stage4_driver.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 4: balanced output driver (THAT1646) + mute + ground-lift.
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage4_driver.py
|
||||||
|
Outputs ERC + a KiCad netlist at hardware/kicad/stage4_driver.net. This closes the
|
||||||
|
audio chain: MIX_OUT -> level trim -> THAT1646 -> build-out -> mute relay -> balanced
|
||||||
|
output on the analog interconnect; plus the ground-lift relay.
|
||||||
|
|
||||||
|
PINOUT VERIFIED (THAT doc 600078 rev 07, SO-8):
|
||||||
|
THAT1646: 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee(V-) 6=Vcc(V+) 7=Sns+ 8=Out+.
|
||||||
|
Fixed +6 dB differential gain; sense pins tie to the output pins (local sense) with
|
||||||
|
the 47 ohm build-out OUTSIDE the loop for cable stability. Supply 4-18V (we use +/-15).
|
||||||
|
TQ2SA relay pinout per stage1b (coil 1/10; pole1 COM=3 NC=4 NO=2; pole2 COM=8 NC=7 NO=9).
|
||||||
|
|
||||||
|
KEY CHOICES
|
||||||
|
* Level cal: the THAT1646 gain is FIXED (+6 dB), so the calibration trim is a 25-turn
|
||||||
|
pot ahead of it (DAC full-scale -> +4 dBu, accounting for summer + the +6 dB).
|
||||||
|
* Phase: Stage 3's summer inverted the signal; corrected here by taking AOUT_HOT from
|
||||||
|
Out- and AOUT_COLD from Out+ (absolute polarity preserved).
|
||||||
|
* Mute (K2): fail-safe -- de-energized shorts both legs to GND (after the 47 ohm
|
||||||
|
build-out, so the driver is current-limited). Energized = un-muted. Driven by the
|
||||||
|
hardware rail-supervisor via K2_DRV (in the power block); MCU can also assert.
|
||||||
|
* Ground-lift (K3): de-energized = bonded (GND<->CHASSIS); energize to LIFT. Soft-lift
|
||||||
|
100 ohm || 10 nF across the contact keeps an RF/safety path. A face panel switch sits
|
||||||
|
in series downstream (CHASSIS pin on the interconnect) -> both-must-close to bond.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from skidl import *
|
||||||
|
|
||||||
|
set_default_tool(KICAD9)
|
||||||
|
|
||||||
|
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
|
||||||
|
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
|
||||||
|
|
||||||
|
p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
|
||||||
|
for n in (p15, n15, gnd, p5):
|
||||||
|
n.drive = POWER
|
||||||
|
mix_out = Net("MIX_OUT") # from Stage 3 summer (inverted)
|
||||||
|
aout_hot, aout_cold = Net("AOUT_HOT"), Net("AOUT_COLD") # -> analog interconnect
|
||||||
|
chassis = Net("CHASSIS") # shield -> interconnect (face panel switch + chassis)
|
||||||
|
k2_drv, k3_drv = Net("K2_DRV"), Net("K3_DRV") # relay coil drives
|
||||||
|
|
||||||
|
# ---- THAT1646 balanced line driver (pinout verified) ----
|
||||||
|
DRV = Part(name="THAT1646", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="OUT-", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=2, name="SNS-", func=Pin.types.INPUT),
|
||||||
|
Pin(num=3, name="GND", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=4, name="IN", func=Pin.types.INPUT),
|
||||||
|
Pin(num=5, name="V-", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=6, name="V+", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=7, name="SNS+", func=Pin.types.INPUT),
|
||||||
|
Pin(num=8, name="OUT+", func=Pin.types.OUTPUT),
|
||||||
|
])
|
||||||
|
drv = DRV(ref="U6")
|
||||||
|
|
||||||
|
# ---- TQ2SA relay (verified pinout, reused def) ----
|
||||||
|
def tq2sa(ref):
|
||||||
|
p = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
|
||||||
|
footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="COIL_A", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=3, name="P1_COM", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=4, name="P1_NC", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=2, name="P1_NO", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=8, name="P2_COM", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=7, name="P2_NC", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=9, name="P2_NO", func=Pin.types.PASSIVE),
|
||||||
|
Pin(num=5, name="NC5", func=Pin.types.NOCONNECT),
|
||||||
|
Pin(num=6, name="NC6", func=Pin.types.NOCONNECT),
|
||||||
|
])
|
||||||
|
return p(ref=ref)
|
||||||
|
k2 = tq2sa("K2") # mute
|
||||||
|
k3 = tq2sa("K3") # ground-lift
|
||||||
|
|
||||||
|
# ---- level-cal trim (25-turn pot): MIX_OUT (top) / GND (bottom) / wiper -> THAT1646 IN ----
|
||||||
|
RV = Part("Device", "R_Potentiometer", dest=TEMPLATE,
|
||||||
|
footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
|
||||||
|
rv1 = RV(value="10k", ref="RV1")
|
||||||
|
mix_out += rv1[1]; rv1[3] += gnd; rv1[2] += drv["IN"]
|
||||||
|
|
||||||
|
# ---- THAT1646: sense ties (local) + supplies ----
|
||||||
|
drv["SNS-"] += drv["OUT-"]
|
||||||
|
drv["SNS+"] += drv["OUT+"]
|
||||||
|
drv["GND"] += gnd
|
||||||
|
drv["V+"] += p15; drv["V-"] += n15
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
|
||||||
|
# ---- 47 ohm build-out (phase-corrected: HOT<-OUT-, COLD<-OUT+) ----
|
||||||
|
rbo_h, rbo_c = R(value="47"), R(value="47")
|
||||||
|
drv["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot
|
||||||
|
drv["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold
|
||||||
|
|
||||||
|
# ---- mute relay K2: de-energized shorts both legs to GND (fail-safe) ----
|
||||||
|
k2["P1_COM"] += aout_hot; k2["P1_NC"] += gnd # de-energized: hot -> GND (muted)
|
||||||
|
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd # de-energized: cold -> GND
|
||||||
|
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv # energize (rails OK) = un-mute
|
||||||
|
|
||||||
|
# ---- ground-lift K3: de-energized bonds GND<->CHASSIS; energize to lift ----
|
||||||
|
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis # de-energized: bonded
|
||||||
|
rlift = R(value="100"); clift = C(value="10nF")
|
||||||
|
gnd += rlift[1]; rlift[2] += chassis # soft-lift: 100 ohm || 10 nF
|
||||||
|
clift[1] += gnd; clift[2] += chassis
|
||||||
|
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage4_driver.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 4 netlist ->", out)
|
||||||
24
hardware/eda/run.sh
Executable file
24
hardware/eda/run.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build (first time) and run the PM_K-1 EDA container with the repo mounted.
|
||||||
|
#
|
||||||
|
# ./run.sh # interactive shell in hardware/kicad/
|
||||||
|
# ./run.sh kicad-cli sch erc pm_k1_core.kicad_sch
|
||||||
|
# ./run.sh ngspice -b sim/some.cir
|
||||||
|
#
|
||||||
|
# Override the runtime with RUNTIME=docker ./run.sh ...
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMG="pmk1-eda:9.0"
|
||||||
|
EDA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # hardware/eda
|
||||||
|
REPO_DIR="$(cd "$EDA_DIR/../.." && pwd)" # repo root
|
||||||
|
RUNTIME="${RUNTIME:-podman}"
|
||||||
|
|
||||||
|
if ! "$RUNTIME" image inspect "$IMG" >/dev/null 2>&1; then
|
||||||
|
echo ">> building $IMG (first run, a few minutes)…" >&2
|
||||||
|
"$RUNTIME" build -t "$IMG" "$EDA_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount the whole repo so KiCad/ngspice see hardware/ ; land in hardware/kicad.
|
||||||
|
flags=(--rm -v "$REPO_DIR":/work:Z -w /work/hardware/kicad)
|
||||||
|
[[ -t 0 && $# -eq 0 ]] && flags+=(-it)
|
||||||
|
exec "$RUNTIME" run "${flags[@]}" "$IMG" "${@:-bash}"
|
||||||
1
hardware/eda/sim/.gitignore
vendored
Normal file
1
hardware/eda/sim/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.csv
|
||||||
47
hardware/eda/sim/input_loading.cir
Normal file
47
hardware/eda/sim/input_loading.cir
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
* PM_K-1 : input loading -- line receiver (25k) vs Hi-Z instrument buffer (1M)
|
||||||
|
*
|
||||||
|
* This is the circuit-level proof of a decision we made by hand: why a guitar/bass
|
||||||
|
* needs a HIGH-impedance input. A passive pickup is inductive, so the load impedance
|
||||||
|
* it sees shapes its tone. Plug it into a low impedance and you damp its resonance
|
||||||
|
* and lose treble + level; a high-Z buffer preserves it.
|
||||||
|
*
|
||||||
|
* Pickup model: open-circuit source + winding resistance + inductance + self-capacitance.
|
||||||
|
* Plus a typical ~300 pF guitar cable. We drive two identical pickup+cable networks
|
||||||
|
* from one ideal source and load them differently, then compare.
|
||||||
|
*
|
||||||
|
* Run: ngspice -b sim/input_loading.cir (from inside the EDA container)
|
||||||
|
|
||||||
|
.title PM_K-1 input loading: line 25k vs instrument 1M
|
||||||
|
|
||||||
|
Vpu src 0 AC 1
|
||||||
|
|
||||||
|
* --- Branch A: LINE input, ~25k receiver impedance ---
|
||||||
|
RdcA src a1 6k
|
||||||
|
LpuA a1 a2 3
|
||||||
|
CpuA a2 0 150p
|
||||||
|
CcabA a2 0 300p
|
||||||
|
RinA a2 0 25k
|
||||||
|
|
||||||
|
* --- Branch B: INSTRUMENT input, 1M Hi-Z buffer ---
|
||||||
|
RdcB src b1 6k
|
||||||
|
LpuB b1 b2 3
|
||||||
|
CpuB b2 0 150p
|
||||||
|
CcabB b2 0 300p
|
||||||
|
RinB b2 0 1meg
|
||||||
|
|
||||||
|
.ac dec 100 10 100k
|
||||||
|
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
meas ac a_1k find vdb(a2) at=1000
|
||||||
|
meas ac b_1k find vdb(b2) at=1000
|
||||||
|
meas ac a_peak max vdb(a2) from=1000 to=20000
|
||||||
|
meas ac b_peak max vdb(b2) from=1000 to=20000
|
||||||
|
echo
|
||||||
|
echo " Level @1kHz : line(25k)= $&a_1k dB inst(1M)= $&b_1k dB"
|
||||||
|
echo " Resonant peak : line(25k)= $&a_peak dB inst(1M)= $&b_peak dB"
|
||||||
|
echo " -> the 25k load drags the signal down and flattens the pickup's natural peak;"
|
||||||
|
echo " the 1M input preserves level and tone. Hence the switchable Hi-Z front end."
|
||||||
|
wrdata ../eda/sim/input_loading.csv vdb(a2) vdb(b2)
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
56
hardware/eda/sim/stage1_cmrr.cir
Normal file
56
hardware/eda/sim/stage1_cmrr.cir
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
* PM_K-1 Stage 1 : balanced line receiver -- why CMRR needs matched resistors
|
||||||
|
*
|
||||||
|
* A balanced receiver rejects "common-mode" noise -- hum/interference that appears
|
||||||
|
* equally on both signal wires -- by SUBTRACTING the two wires. How well it cancels
|
||||||
|
* (its Common-Mode Rejection Ratio, CMRR) depends entirely on how well its four
|
||||||
|
* resistors match. This is the circuit-level reason we chose the laser-trimmed
|
||||||
|
* THAT1240 instead of a discrete op-amp + four 1% resistors.
|
||||||
|
*
|
||||||
|
* Three identical unity-gain difference amplifiers, driven common-mode (same 1V on
|
||||||
|
* both inputs), with 0% / 0.1% / 1% mismatch on one resistor.
|
||||||
|
* CMRR(dB) = -20*log10(|Vout|) since differential gain = 1 and Vcm = 1V.
|
||||||
|
*
|
||||||
|
* Run: ngspice -b ../eda/sim/stage1_cmrr.cir
|
||||||
|
|
||||||
|
.title Stage1 line receiver CMRR vs resistor match
|
||||||
|
|
||||||
|
Vcm cm 0 AC 1 ; common-mode drive: both inputs tied to cm
|
||||||
|
|
||||||
|
* ---- A: perfectly matched ----
|
||||||
|
EA outa 0 vpa vma 1e6
|
||||||
|
R1a cm vma 10k
|
||||||
|
R2a outa vma 10k
|
||||||
|
R3a cm vpa 10k
|
||||||
|
R4a vpa 0 10k
|
||||||
|
|
||||||
|
* ---- B: 0.1% mismatch on one resistor ----
|
||||||
|
EB outb 0 vpb vmb 1e6
|
||||||
|
R1b cm vmb 10k
|
||||||
|
R2b outb vmb 10k
|
||||||
|
R3b cm vpb 10k
|
||||||
|
R4b vpb 0 10.01k
|
||||||
|
|
||||||
|
* ---- C: 1% mismatch on one resistor ----
|
||||||
|
EC outc 0 vpc vmc 1e6
|
||||||
|
R1c cm vmc 10k
|
||||||
|
R2c outc vmc 10k
|
||||||
|
R3c cm vpc 10k
|
||||||
|
R4c vpc 0 10.1k
|
||||||
|
|
||||||
|
.ac dec 10 100 10k
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
meas ac acm_a find vm(outa) at=1000
|
||||||
|
meas ac acm_b find vm(outb) at=1000
|
||||||
|
meas ac acm_c find vm(outc) at=1000
|
||||||
|
let cmrr_a = -20*log10(acm_a)
|
||||||
|
let cmrr_b = -20*log10(acm_b)
|
||||||
|
let cmrr_c = -20*log10(acm_c)
|
||||||
|
echo
|
||||||
|
echo " CMRR perfect match : $&cmrr_a dB (limited only by the amplifier)"
|
||||||
|
echo " CMRR 0.1% mismatch : $&cmrr_b dB"
|
||||||
|
echo " CMRR 1% mismatch : $&cmrr_c dB"
|
||||||
|
echo " -> hand-matched 1% resistors give poor rejection; the THAT1240's"
|
||||||
|
echo " laser-trimmed internal resistors are how we get >90 dB for free."
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
39
hardware/eda/sim/stage1_phantom.cir
Normal file
39
hardware/eda/sim/stage1_phantom.cir
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
* PM_K-1 Stage 1 : input protection -- a +48V phantom-power step at the jack
|
||||||
|
*
|
||||||
|
* "Phantom power" is +48V DC that mixers put on their MIC inputs. If it ever reaches
|
||||||
|
* our input (miswire, wrong cable, our OUT plugged into a phantom'd input), a bare
|
||||||
|
* op-amp input would die. Our protection makes it a non-event:
|
||||||
|
* - a series DC-BLOCKING film cap passes audio (AC) but blocks the +48V (DC),
|
||||||
|
* - a series resistor + clamp diodes to the +/-15V rails survive the turn-on EDGE
|
||||||
|
* (the cap passes the fast step before it charges).
|
||||||
|
*
|
||||||
|
* We hit the input with a +48V step at t=1ms and watch the OP-AMP INPUT node.
|
||||||
|
*
|
||||||
|
* Run: ngspice -b ../eda/sim/stage1_phantom.cir
|
||||||
|
|
||||||
|
.title Stage1 phantom protection
|
||||||
|
|
||||||
|
Vin in 0 PWL(0 0 1m 0 1.001m 48 5 48) ; +48V appears at t=1ms, stays
|
||||||
|
|
||||||
|
C1 in a 1u ; DC-blocking film cap (audio passes, DC blocked)
|
||||||
|
Rs a n 1k ; series current limit
|
||||||
|
Rb n 0 1meg ; bias / input impedance (audio high-pass corner ~0.16 Hz)
|
||||||
|
|
||||||
|
.model Dclamp D(Is=1e-14 N=1 Rs=10)
|
||||||
|
Dp n vp Dclamp ; clamp to +15
|
||||||
|
Dn vn n Dclamp ; clamp to -15
|
||||||
|
Vp vp 0 15
|
||||||
|
Vn vn 0 -15
|
||||||
|
|
||||||
|
.tran 2m 5
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
meas tran vn_peak max v(n) from=1m to=5
|
||||||
|
meas tran vn_final find v(n) at=4.9
|
||||||
|
echo
|
||||||
|
echo " op-amp input PEAK during the +48V step : $&vn_peak V (clamped near a +/-15 rail)"
|
||||||
|
echo " op-amp input STEADY-STATE : $&vn_final V (DC blocked by the cap)"
|
||||||
|
echo " -> +48V at the jack becomes a clamped blip that decays to ~0. Nothing is harmed;"
|
||||||
|
echo " a wrong patch only ever sounds wrong. (Audio passes -- the high-pass is ~0.16 Hz.)"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
28
hardware/eda/sim/stage1b_di.cir
Normal file
28
hardware/eda/sim/stage1b_di.cir
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
* PM_K-1 Stage 1b : Hi-Z instrument DI buffer -- gain + flatness check
|
||||||
|
*
|
||||||
|
* An OPA1641 (JFET input, ~1e12 ohm) as a non-inverting amp. The INPUT impedance
|
||||||
|
* the instrument sees is set by the bias resistor (1 Mohm) -- which the earlier
|
||||||
|
* input_loading.cir proved is what preserves a pickup's tone. Here we just confirm
|
||||||
|
* the voltage gain: Av = 1 + Rf/Rg. Target +12 dB (x4) with Rf=3k, Rg=1k.
|
||||||
|
*
|
||||||
|
* Run: ngspice -b ../eda/sim/stage1b_di.cir
|
||||||
|
|
||||||
|
.title Stage1b DI buffer gain
|
||||||
|
|
||||||
|
Vin in 0 AC 1
|
||||||
|
Eop out 0 in vm 1e6 ; ideal op-amp: +IN = in, -IN = vm
|
||||||
|
Rf out vm 3k ; feedback
|
||||||
|
Rg vm 0 1k ; gain set
|
||||||
|
|
||||||
|
.ac dec 20 10 100k
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
meas ac g_1k find vdb(out) at=1000
|
||||||
|
meas ac g_20k find vdb(out) at=20000
|
||||||
|
let av = pow(10, g_1k/20)
|
||||||
|
echo
|
||||||
|
echo " DI buffer gain @1kHz : $&g_1k dB ( x$&av ) target +12.04 dB (x4)"
|
||||||
|
echo " DI buffer gain @20kHz : $&g_20k dB (flat across audio band)"
|
||||||
|
echo " Input impedance is set by the 1Mohm bias R (JFET input ~1e12 ohm) -- see input_loading.cir"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
35
hardware/eda/sim/stage2_recon.cir
Normal file
35
hardware/eda/sim/stage2_recon.cir
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
* PM_K-1 Stage 2 : DAC reconstruction filter (2nd-order Sallen-Key low-pass)
|
||||||
|
*
|
||||||
|
* The PCM5102A is a delta-sigma DAC: its analog output carries the audio plus
|
||||||
|
* shaped high-frequency quantization noise WAY above the audio band. A gentle
|
||||||
|
* low-pass cleans that residue before it reaches the pro output stage. We want it
|
||||||
|
* dead flat to 20 kHz and rolling off above ~80 kHz (Butterworth, Q~0.7).
|
||||||
|
*
|
||||||
|
* Unity-gain Sallen-Key, equal R. Target fc ~75 kHz with R=1.5k, C1=2.2n, C2=1n.
|
||||||
|
* Run: ngspice -b ../eda/sim/stage2_recon.cir
|
||||||
|
|
||||||
|
.title Stage2 DAC reconstruction filter
|
||||||
|
|
||||||
|
.param R=1.5k
|
||||||
|
.param Ca=2.2n
|
||||||
|
.param Cb=1n
|
||||||
|
|
||||||
|
Vin in 0 AC 1
|
||||||
|
R1 in a {R}
|
||||||
|
R2 a vp {R}
|
||||||
|
Ca a out {Ca} ; feedback cap
|
||||||
|
Cb vp 0 {Cb} ; cap to ground
|
||||||
|
Eop out 0 vp out 1e6 ; unity-gain follower op-amp (-in tied to out)
|
||||||
|
|
||||||
|
.ac dec 100 100 2meg
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
meas ac g_1k find vdb(out) at=1k
|
||||||
|
meas ac g_20k find vdb(out) at=20k
|
||||||
|
meas ac f_3db when vdb(out)=-3
|
||||||
|
echo
|
||||||
|
echo " passband @1kHz : $&g_1k dB"
|
||||||
|
echo " @20kHz (audio edge) : $&g_20k dB (want ~0 dB = flat)"
|
||||||
|
echo " -3dB corner : $&f_3db Hz (well above audio; attenuates DAC HF residue)"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
40
hardware/eda/sim/stage3_sum.cir
Normal file
40
hardware/eda/sim/stage3_sum.cir
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
* PM_K-1 Stage 3 : summing node (selected input + click)
|
||||||
|
*
|
||||||
|
* An inverting summing amp mixes STAGE1_OUT (line/instrument) and CLICK_OUT (DAC).
|
||||||
|
* Each source feeds the op-amp's inverting input through its own 10k resistor; the
|
||||||
|
* feedback holds that node at a "virtual ground" (~0V). Because BOTH sources see 0V
|
||||||
|
* there, neither can load or pull on the other -- they sum with no interaction.
|
||||||
|
* Vout = -(Rf/Ri1)*V1 - (Rf/Ri2)*V2 ; with Rf=Ri=10k -> Vout = -(V1+V2).
|
||||||
|
*
|
||||||
|
* We confirm: each input alone = 0 dB (gain -1), both together = +6 dB (they add),
|
||||||
|
* and each input's gain is unchanged by the other (isolation).
|
||||||
|
* Run: ngspice -b ../eda/sim/stage3_sum.cir
|
||||||
|
|
||||||
|
.title Stage3 inverting summer
|
||||||
|
|
||||||
|
Vinp inp 0 AC 1 ; selected input (STAGE1_OUT)
|
||||||
|
Vclk clk 0 AC 1 ; filtered click (CLICK_OUT)
|
||||||
|
Ri1 inp vm 10k
|
||||||
|
Ri2 clk vm 10k
|
||||||
|
Rf vm out 10k
|
||||||
|
Eop out 0 0 vm 1e6 ; +in = gnd, -in = vm -> inverting; feedback makes vm a virtual ground
|
||||||
|
|
||||||
|
.ac dec 10 100 20k
|
||||||
|
.control
|
||||||
|
* both sources active -> they sum
|
||||||
|
run
|
||||||
|
meas ac g_both find vdb(out) at=1k
|
||||||
|
echo " both = $&g_both dB (+6 dB over each-alone = the two sum)"
|
||||||
|
* input alone (click muted)
|
||||||
|
alter @Vclk[acmag]=0
|
||||||
|
run
|
||||||
|
meas ac g_in find vdb(out) at=1k
|
||||||
|
echo " input alone = $&g_in dB (gain -1, i.e. 0 dB)"
|
||||||
|
* click alone (input muted)
|
||||||
|
alter @Vinp[acmag]=0
|
||||||
|
alter @Vclk[acmag]=1
|
||||||
|
run
|
||||||
|
meas ac g_clk find vdb(out) at=1k
|
||||||
|
echo " click alone = $&g_clk dB (gain -1; unchanged by the input = virtual-ground isolation)"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
38
hardware/eda/sim/stage4_driver.cir
Normal file
38
hardware/eda/sim/stage4_driver.cir
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
* PM_K-1 Stage 4 : balanced output driver -- antiphase + build-out into a cable
|
||||||
|
*
|
||||||
|
* The THAT1646 turns the single-ended mix into a balanced (differential) output:
|
||||||
|
* Out+ and Out- swing equal-and-opposite, so noise picked up equally on both wires
|
||||||
|
* cancels at the far end. It has a fixed +6 dB gain (differential = 2x input).
|
||||||
|
* We model it as two ideal sources (Out+ = +Vin, Out- = -Vin) and add the 47 ohm
|
||||||
|
* per-leg build-out resistors driving a 600 ohm load plus cable capacitance.
|
||||||
|
*
|
||||||
|
* Shows: audio band is dead flat, and the build-out + cable rolloff sits far above
|
||||||
|
* audio -- i.e. the build-out tames long/capacitive cables without dulling the sound.
|
||||||
|
* Run: ngspice -b ../eda/sim/stage4_driver.cir
|
||||||
|
|
||||||
|
.title Stage4 balanced driver
|
||||||
|
|
||||||
|
Vin in 0 AC 1
|
||||||
|
Eop op 0 in 0 1 ; Out+ = +Vin
|
||||||
|
Eon on 0 0 in 1 ; Out- = -Vin -> differential (op-on) = 2*Vin = +6 dB
|
||||||
|
Rbp op hot 47 ; build-out, hot leg
|
||||||
|
Rbn on cold 47 ; build-out, cold leg
|
||||||
|
Rl hot cold 600 ; receiver / load
|
||||||
|
Cch hot 0 1n ; ~cable capacitance per leg
|
||||||
|
Ccc cold 0 1n
|
||||||
|
|
||||||
|
.ac dec 20 10 10meg
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
let vddb = db(v(hot) - v(cold)) ; differential magnitude in dB
|
||||||
|
meas ac d_1k find vddb at=1k
|
||||||
|
meas ac d_20k find vddb at=20k
|
||||||
|
meas ac d_1meg find vddb at=1meg
|
||||||
|
meas ac ph_hot find vp(hot) at=1k
|
||||||
|
meas ac ph_cold find vp(cold) at=1k
|
||||||
|
echo
|
||||||
|
echo " differential @1kHz : $&d_1k dB @20kHz : $&d_20k dB (flat across audio)"
|
||||||
|
echo " hot phase: $&ph_hot rad ; cold phase: $&ph_cold rad (~pi rad = 180 deg apart = balanced/antiphase)"
|
||||||
|
echo " differential @1MHz : $&d_1meg dB (build-out+cable rolloff stays above audio)"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
10
hardware/kicad/.gitignore
vendored
Normal file
10
hardware/kicad/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# KiCad generated/derived outputs
|
||||||
|
*.pdf
|
||||||
|
*.net
|
||||||
|
*.svg
|
||||||
|
*.rpt
|
||||||
|
*-bak
|
||||||
|
*.kicad_prl
|
||||||
|
fp-info-cache
|
||||||
|
~*.lck
|
||||||
|
*.net
|
||||||
40
hardware/kicad/audio_chain.erc
Normal file
40
hardware/kicad/audio_chain.erc
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 3/3B of ULN2003A/U6) attached to net GNDLIFT_EN.
|
||||||
|
ERC WARNING: No drivers for net GNDLIFT_EN.
|
||||||
|
ERC WARNING: Insufficient drive current on net GNDLIFT_EN for pin INPUT pin 3/3B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 15/LRCK of PCM5102A/U3) attached to net I2S_LRCK.
|
||||||
|
ERC WARNING: No drivers for net I2S_LRCK.
|
||||||
|
ERC WARNING: Insufficient drive current on net I2S_LRCK for pin INPUT pin 15/LRCK of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 2/2B of ULN2003A/U6) attached to net MUTE_EN.
|
||||||
|
ERC WARNING: No drivers for net MUTE_EN.
|
||||||
|
ERC WARNING: Insufficient drive current on net MUTE_EN for pin INPUT pin 2/2B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 1/1B of ULN2003A/U6) attached to net SEL_LINST.
|
||||||
|
ERC WARNING: No drivers for net SEL_LINST.
|
||||||
|
ERC WARNING: Insufficient drive current on net SEL_LINST for pin INPUT pin 1/1B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of C/C2) attached to net AIN_COLD.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 14/DIN of PCM5102A/U3) attached to net I2S_DIN.
|
||||||
|
ERC WARNING: No drivers for net I2S_DIN.
|
||||||
|
ERC WARNING: Insufficient drive current on net I2S_DIN for pin INPUT pin 14/DIN of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 12/SCK of PCM5102A/U3) attached to net MCLK.
|
||||||
|
ERC WARNING: No drivers for net MCLK.
|
||||||
|
ERC WARNING: Insufficient drive current on net MCLK for pin INPUT pin 12/SCK of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 13/BCK of PCM5102A/U3) attached to net I2S_BCK.
|
||||||
|
ERC WARNING: No drivers for net I2S_BCK.
|
||||||
|
ERC WARNING: Insufficient drive current on net I2S_BCK for pin INPUT pin 13/BCK of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 3/P1_COM of TQ2SA-5V/K1) attached to net AIN_HOT.
|
||||||
|
ERC WARNING: Unconnected pin: INPUT pin 4/4B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: INPUT pin 5/5B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: INPUT pin 6/6B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: INPUT pin 7/7B of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: OPEN-COLLECTOR pin 10/7C of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: OPEN-COLLECTOR pin 11/6C of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: OPEN-COLLECTOR pin 12/5C of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: OPEN-COLLECTOR pin 13/4C of ULN2003A/U6.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 2/P1_NO of TQ2SA-5V/K2.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 9/P2_NO of TQ2SA-5V/K2.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 2/P1_NO of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 8/P2_COM of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 7/P2_NC of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 9/P2_NO of TQ2SA-5V/K3.
|
||||||
|
ERC INFO: 37 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
119
hardware/kicad/audio_chain.log
Normal file
119
hardware/kicad/audio_chain.log
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: Missing tag on THAT1240 instantiated at /work/hardware/eda/circuits/audio_chain.py:88.
|
||||||
|
WARNING: Random tag NaoCkmuv6F generated for THAT1240.
|
||||||
|
WARNING: Missing tag on OPA1641 instantiated at /work/hardware/eda/circuits/audio_chain.py:89.
|
||||||
|
WARNING: Random tag bhZEXwD6Ma generated for OPA1641.
|
||||||
|
WARNING: Missing tag on PCM5102A instantiated at /work/hardware/eda/circuits/audio_chain.py:90.
|
||||||
|
WARNING: Random tag GKJv8Mk9db generated for PCM5102A.
|
||||||
|
WARNING: Missing tag on OPA1612 instantiated at /work/hardware/eda/circuits/audio_chain.py:91.
|
||||||
|
WARNING: Random tag ZiUYdGNUpf generated for OPA1612.
|
||||||
|
WARNING: Missing tag on THAT1646 instantiated at /work/hardware/eda/circuits/audio_chain.py:92.
|
||||||
|
WARNING: Random tag PA94RBoDz5 generated for THAT1646.
|
||||||
|
WARNING: Missing tag on ULN2003A instantiated at /work/hardware/eda/circuits/audio_chain.py:93.
|
||||||
|
WARNING: Random tag 7az6rCV1b8 generated for ULN2003A.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/audio_chain.py:94.
|
||||||
|
WARNING: Random tag a5p3EKoZSh generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/audio_chain.py:94.
|
||||||
|
WARNING: Random tag nzmU2EFKQ5 generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/audio_chain.py:94.
|
||||||
|
WARNING: Random tag PvvXwCojLV generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:101.
|
||||||
|
WARNING: Random tag 1BQDYOk7Jk generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:102.
|
||||||
|
WARNING: Random tag VJCWjFmo15 generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:102.
|
||||||
|
WARNING: Random tag F3pmXe3Ukl generated for R.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/audio_chain.py:103.
|
||||||
|
WARNING: Random tag GcBAEBfRsm generated for D.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/audio_chain.py:103.
|
||||||
|
WARNING: Random tag DxTLvXTPXw generated for D.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:101.
|
||||||
|
WARNING: Random tag TbYYz7sRwg generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:102.
|
||||||
|
WARNING: Random tag q4ANsoJP6d generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:102.
|
||||||
|
WARNING: Random tag XafPhdShAz generated for R.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/audio_chain.py:103.
|
||||||
|
WARNING: Random tag 3Dy7kKhUuS generated for D.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/audio_chain.py:103.
|
||||||
|
WARNING: Random tag srbPiv0wDA generated for D.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag cywPzV0BP5 generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag ys82MNo87q generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:116.
|
||||||
|
WARNING: Random tag GjB9TiUkLV generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:117.
|
||||||
|
WARNING: Random tag 5d0DxRmiVK generated for R.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/audio_chain.py:117.
|
||||||
|
WARNING: Random tag UiNgHe5ZoI generated for D.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/audio_chain.py:117.
|
||||||
|
WARNING: Random tag dREXHB06LQ generated for D.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:124.
|
||||||
|
WARNING: Random tag IzL4dTk5KE generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:124.
|
||||||
|
WARNING: Random tag aecw3XjGu8 generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag ZkkukzL1dw generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag SiWPcikSTD generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:139.
|
||||||
|
WARNING: Random tag XkEyeGCXrV generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:139.
|
||||||
|
WARNING: Random tag KtTaLAKE12 generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:139.
|
||||||
|
WARNING: Random tag kxLUIeU4MG generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:140.
|
||||||
|
WARNING: Random tag G0gy3YpApr generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:141.
|
||||||
|
WARNING: Random tag 54BIrx4GwW generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:142.
|
||||||
|
WARNING: Random tag D2ift49ZDj generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:143.
|
||||||
|
WARNING: Random tag 2xrRFHfzVn generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:145.
|
||||||
|
WARNING: Random tag ciywLsI1BV generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:147.
|
||||||
|
WARNING: Random tag iHP45ECBTV generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:150.
|
||||||
|
WARNING: Random tag SpTEBfUSZQ generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:150.
|
||||||
|
WARNING: Random tag GANJXVVJhX generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:150.
|
||||||
|
WARNING: Random tag cTcBdxazGj generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:150.
|
||||||
|
WARNING: Random tag WltiwW7vNV generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag K76qTGXZDl generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag qNcbyMvj04 generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:160.
|
||||||
|
WARNING: Random tag 8Z8qBauuqJ generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:160.
|
||||||
|
WARNING: Random tag ZvbhQbNpVf generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:160.
|
||||||
|
WARNING: Random tag XI22X1n2tP generated for R.
|
||||||
|
WARNING: Missing tag on R_Potentiometer instantiated at /work/hardware/eda/circuits/audio_chain.py:169.
|
||||||
|
WARNING: Random tag joe6s3brR6 generated for R_Potentiometer.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag MjG60k1gb_ generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:97.
|
||||||
|
WARNING: Random tag JNrbq6C1BQ generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:173.
|
||||||
|
WARNING: Random tag IwhpZyA9LO generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:173.
|
||||||
|
WARNING: Random tag cWxhi7FDib generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/audio_chain.py:182.
|
||||||
|
WARNING: Random tag GS23xEE7xZ generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/audio_chain.py:182.
|
||||||
|
WARNING: Random tag fJUwwIBJ1g generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 113 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
106
hardware/kicad/audio_chain_sklib.py
Normal file
106
hardware/kicad/audio_chain_sklib.py
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
|
||||||
|
|
||||||
|
from skidl.pin import pin_types
|
||||||
|
|
||||||
|
SKIDL_lib_version = '0.0.1'
|
||||||
|
|
||||||
|
audio_chain = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'THAT1240', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'THAT1240'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='REF',func=pin_types.INPUT),
|
||||||
|
Pin(num='2',name='IN-',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='IN+',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='SENSE',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='6',name='OUT',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='7',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='8',name='NC',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'OPA1641', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1641'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='NC1',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='2',name='-IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='+IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='NC5',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='6',name='OUT',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='7',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='8',name='NC8',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'PCM5102A', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'PCM5102A'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='CPVDD',func=pin_types.PWRIN),
|
||||||
|
Pin(num='2',name='CAPP',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='3',name='CPGND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='4',name='CAPM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='5',name='VNEG',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='6',name='OUTL',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='7',name='OUTR',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='8',name='AVDD',func=pin_types.PWRIN),
|
||||||
|
Pin(num='9',name='AGND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='10',name='DEMP',func=pin_types.INPUT),
|
||||||
|
Pin(num='11',name='FLT',func=pin_types.INPUT),
|
||||||
|
Pin(num='12',name='SCK',func=pin_types.INPUT),
|
||||||
|
Pin(num='13',name='BCK',func=pin_types.INPUT),
|
||||||
|
Pin(num='14',name='DIN',func=pin_types.INPUT),
|
||||||
|
Pin(num='15',name='LRCK',func=pin_types.INPUT),
|
||||||
|
Pin(num='16',name='FMT',func=pin_types.INPUT),
|
||||||
|
Pin(num='17',name='XSMT',func=pin_types.INPUT),
|
||||||
|
Pin(num='18',name='LDOO',func=pin_types.PWROUT),
|
||||||
|
Pin(num='19',name='DGND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='20',name='DVDD',func=pin_types.PWRIN)] }),
|
||||||
|
Part(**{ 'name':'OPA1612', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1612'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='OUTA',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='2',name='-INA',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='+INA',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='+INB',func=pin_types.INPUT),
|
||||||
|
Pin(num='6',name='-INB',func=pin_types.INPUT),
|
||||||
|
Pin(num='7',name='OUTB',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='8',name='V+',func=pin_types.PWRIN)] }),
|
||||||
|
Part(**{ 'name':'THAT1646', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'THAT1646'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='OUT-',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='2',name='SNS-',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='GND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='4',name='IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='5',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='6',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='7',name='SNS+',func=pin_types.INPUT),
|
||||||
|
Pin(num='8',name='OUT+',func=pin_types.OUTPUT)] }),
|
||||||
|
Part(**{ 'name':'ULN2003A', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'ULN2003A'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-16_3.9x9.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='1B',func=pin_types.INPUT),
|
||||||
|
Pin(num='2',name='2B',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='3B',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='4B',func=pin_types.INPUT),
|
||||||
|
Pin(num='5',name='5B',func=pin_types.INPUT),
|
||||||
|
Pin(num='6',name='6B',func=pin_types.INPUT),
|
||||||
|
Pin(num='7',name='7B',func=pin_types.INPUT),
|
||||||
|
Pin(num='8',name='GND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='9',name='COM',func=pin_types.PWRIN),
|
||||||
|
Pin(num='10',name='7C',func=pin_types.OPENCOLL),
|
||||||
|
Pin(num='11',name='6C',func=pin_types.OPENCOLL),
|
||||||
|
Pin(num='12',name='5C',func=pin_types.OPENCOLL),
|
||||||
|
Pin(num='13',name='4C',func=pin_types.OPENCOLL),
|
||||||
|
Pin(num='14',name='3C',func=pin_types.OPENCOLL),
|
||||||
|
Pin(num='15',name='2C',func=pin_types.OPENCOLL),
|
||||||
|
Pin(num='16',name='1C',func=pin_types.OPENCOLL)] }),
|
||||||
|
Part(**{ 'name':'TQ2SA-5V', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TQ2SA-5V'}), 'ref_prefix':'K', 'fplist':None, 'footprint':'Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='COIL_A',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='10',name='COIL_B',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='3',name='P1_COM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='4',name='P1_NC',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='2',name='P1_NO',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='8',name='P2_COM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='7',name='P2_NC',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='9',name='P2_NO',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='5',name='NC5',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='6',name='NC6',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_1206_3216Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'D', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'D'}), 'ref_prefix':'D', 'fplist':[''], 'footprint':'Diode_SMD:D_SOD-323', 'keywords':'diode', 'description':'Diode', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='K',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='A',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'R_Potentiometer', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R_Potentiometer'}), 'ref_prefix':'RV', 'fplist':[''], 'footprint':'Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical', 'keywords':'resistor variable', 'description':'Potentiometer', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='1',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='3',name='3',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='2',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])
|
||||||
92
hardware/kicad/pm_k1_core.kicad_pro
Normal file
92
hardware/kicad/pm_k1_core.kicad_pro
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"design_settings": {
|
||||||
|
"defaults": {},
|
||||||
|
"rules": {},
|
||||||
|
"track_widths": [],
|
||||||
|
"via_dimensions": []
|
||||||
|
},
|
||||||
|
"layer_presets": [],
|
||||||
|
"viewports": []
|
||||||
|
},
|
||||||
|
"boards": [],
|
||||||
|
"cvpcb": {
|
||||||
|
"equivalence_files": []
|
||||||
|
},
|
||||||
|
"erc": {
|
||||||
|
"erc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"rule_severities": {},
|
||||||
|
"rule_severitiesV2": {}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"pinned_footprint_libs": [],
|
||||||
|
"pinned_symbol_libs": []
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"filename": "pm_k1_core.kicad_pro",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"net_settings": {
|
||||||
|
"classes": [
|
||||||
|
{
|
||||||
|
"bus_width": 12,
|
||||||
|
"clearance": 0.2,
|
||||||
|
"diff_pair_gap": 0.25,
|
||||||
|
"diff_pair_via_gap": 0.25,
|
||||||
|
"diff_pair_width": 0.2,
|
||||||
|
"line_style": 0,
|
||||||
|
"microvia_diameter": 0.3,
|
||||||
|
"microvia_drill": 0.1,
|
||||||
|
"name": "Default",
|
||||||
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"track_width": 0.25,
|
||||||
|
"via_diameter": 0.8,
|
||||||
|
"via_drill": 0.4,
|
||||||
|
"wire_width": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pcbnew": {
|
||||||
|
"last_paths": {
|
||||||
|
"gencad": "",
|
||||||
|
"idf": "",
|
||||||
|
"netlist": "",
|
||||||
|
"specctra_dsn": "",
|
||||||
|
"step": "",
|
||||||
|
"vrml": ""
|
||||||
|
},
|
||||||
|
"page_layout_descr_file": ""
|
||||||
|
},
|
||||||
|
"schematic": {
|
||||||
|
"annotate_start_num": 0,
|
||||||
|
"drawing": {},
|
||||||
|
"legacy_lib_dir": "",
|
||||||
|
"legacy_lib_list": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"net_format_name": "",
|
||||||
|
"page_layout_descr_file": "",
|
||||||
|
"plot_directory": "",
|
||||||
|
"spice_current_sheet_as_root": false,
|
||||||
|
"spice_external_command": "spice \"%I\"",
|
||||||
|
"spice_model_current_sheet_as_root": true,
|
||||||
|
"spice_save_all_currents": false,
|
||||||
|
"subpart_first_id": 65,
|
||||||
|
"subpart_id_separator": 0
|
||||||
|
},
|
||||||
|
"sheets": [
|
||||||
|
[
|
||||||
|
"743b8308-6a69-48de-a6ff-2336bc6c804f",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text_variables": {}
|
||||||
|
}
|
||||||
56
hardware/kicad/pm_k1_core.kicad_sch
Normal file
56
hardware/kicad/pm_k1_core.kicad_sch
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
(kicad_sch (version 20230121) (generator eeschema)
|
||||||
|
(uuid 743b8308-6a69-48de-a6ff-2336bc6c804f)
|
||||||
|
(paper "A3")
|
||||||
|
(title_block
|
||||||
|
(title "PM_K-1 Core Board (brain) - VARASYS PolyMeter")
|
||||||
|
(date "2026-05-30")
|
||||||
|
(rev "A")
|
||||||
|
(company "VARASYS")
|
||||||
|
(comment 1 "Heirloom pro-audio modular core: RP2350 + +/-15V studio audio + balanced click-injector")
|
||||||
|
(comment 2 "Design-of-record canvas - see hardware/DESIGN.md. Symbol placement + wiring = interactive TODO in Eeschema.")
|
||||||
|
)
|
||||||
|
(lib_symbols)
|
||||||
|
(text "PM_K-1 CORE BOARD (\"brain\")\nHeirloom pro-audio modular core - design-of-record canvas.\nFull spec, BOM and interconnect pinouts: hardware/DESIGN.md + hardware/BOM.csv."
|
||||||
|
(at 20 18 0)
|
||||||
|
(effects (font (size 2.2 2.2)) (justify left top))
|
||||||
|
(uuid 00109097-7760-4eb7-b299-bd3f64383aab)
|
||||||
|
)
|
||||||
|
(text "POWER TREE\nUSB-C 5V -> AP2112K 3V3 (digital IO)\nRP2350 internal SMPS -> 1.1V core (ext L)\nTPS65131 dual boost/inverter -> raw +/-18V\nTPS7A4901 / TPS7A3001 LDO -> CLEAN +/-15V (audio)\nStar ground; switcher in a guarded corner."
|
||||||
|
(at 20 45 0)
|
||||||
|
(effects (font (size 1.6 1.6)) (justify left top))
|
||||||
|
(uuid 4e184999-041e-4c11-9e86-faad487f2bad)
|
||||||
|
)
|
||||||
|
(text "MCU + DIGITAL\nRP2350A (QFN-60) - mind E9 erratum: external pulldowns on read inputs.\nW25Q128JV 16MB QSPI flash (wear-leveled).\n12MHz crystal. SWD 2x5 + labeled test points.\nI2S BCK/LRCK/DOUT + low-jitter 24.576MHz MCLK stay ON-CORE."
|
||||||
|
(at 110 45 0)
|
||||||
|
(effects (font (size 1.6 1.6)) (justify left top))
|
||||||
|
(uuid 43233868-23ee-43fa-b873-a47bffeacf5f)
|
||||||
|
)
|
||||||
|
(text "CLICK SOURCE\nPCM5102A I2S DAC (Burr-Brown).\nFed by dedicated low-jitter audio XO (ASEM1-24.576MHz),\nnot PIO-jittered MCLK."
|
||||||
|
(at 200 45 0)
|
||||||
|
(effects (font (size 1.6 1.6)) (justify left top))
|
||||||
|
(uuid a62ad51b-a1a9-4c34-b3d7-314c107afc7a)
|
||||||
|
)
|
||||||
|
(text "ANALOG INPUT (switchable line / instrument)\nProtection (non-negotiable): series DC-block film cap (blocks +48V phantom),\nclamp diodes/TVS to rails, series current-limit R.\nLINE: THAT1240 balanced receiver (laser-trimmed CMRR).\nINST: OPA1641 JFET Hi-Z buffer (>=1Mohm) + ~+10..15dB gain.\nK1 gold relay selects path (GPIO; touchscreen or optional face switch)."
|
||||||
|
(at 20 80 0)
|
||||||
|
(effects (font (size 1.6 1.6)) (justify left top))
|
||||||
|
(uuid 737ed14f-66db-4799-a337-21438143ce09)
|
||||||
|
)
|
||||||
|
(text "MIX + OUTPUT\nMix = digital/firmware (click level via DAC); analog stage at unity.\nTHAT1646 balanced driver + 47ohm build-out per leg.\nOutput level: Bourns 3296W 25-turn trim -> DAC FS = +4dBu (~+24dBu headroom).\nMUTE relay K2: fail-safe (de-energized = muted); HW supervisor, not MCU-dependent.\nGROUND-LIFT: face panel switch IN SERIES with core relay K3; soft-lift 100ohm||10nF.\nNO electrolytics in signal path - WIMA film caps, 0.1% thin-film R."
|
||||||
|
(at 110 80 0)
|
||||||
|
(effects (font (size 1.6 1.6)) (justify left top))
|
||||||
|
(uuid 99fe482f-1fde-4dd1-9579-56113dcb21bc)
|
||||||
|
)
|
||||||
|
(text "INDICATORS / MIDI / SPEAKER\nSIG/CLIP: peak detect -> LM393 -> RP2350 GPIO (UI) + LED lines on interconnect.\nMIDI: USB-MIDI default (firmware). DNP hardware option: H11L1 opto IN + 74LVC14 OUT/THRU.\nSpeaker: PAM8302 class-D, DNP per form factor.\nESD/EMI: USBLC6-2 on USB + CM choke; series R + ESD arrays on interconnect; ferrites at analog crossing."
|
||||||
|
(at 20 120 0)
|
||||||
|
(effects (font (size 1.6 1.6)) (justify left top))
|
||||||
|
(uuid 03eeefa8-7728-4e14-90c0-6d0496a0d207)
|
||||||
|
)
|
||||||
|
(text "INTERCONNECTS (see DESIGN.md s7)\nJ2 DIGITAL RIBBON 2x13 - Pico-pinout-compatible (SPI/I2C/ADC/buttons/LED + GNDLIFT_SW/LINEINST_SW/SIG_LED/CLIP_LED).\nJ3 ANALOG 2x5 - AOUT H/C, AIN H/C, AGND, CHASSIS/SHIELD, SPK+/- (DNP). Kept away from the fast digital ribbon.\nJ4 MIDI 1x6 - OUT A/B, IN A/B, +5V, GND (only if DNP MIDI populated).\nRelays + I2S + MCLK are core-only and NOT on the ribbon; a Pico test brain drives digital I/O but not the analog chain."
|
||||||
|
(at 110 120 0)
|
||||||
|
(effects (font (size 1.5 1.5)) (justify left top))
|
||||||
|
(uuid 0b1d2c3e-4f5a-6b7c-8d9e-0a1b2c3d4e5f)
|
||||||
|
)
|
||||||
|
(sheet_instances
|
||||||
|
(path "/" (page "1"))
|
||||||
|
)
|
||||||
|
)
|
||||||
0
hardware/kicad/skidl.erc
Normal file
0
hardware/kicad/skidl.erc
Normal file
9
hardware/kicad/skidl.log
Normal file
9
hardware/kicad/skidl.log
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD9_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
0
hardware/kicad/skidl_REPL.erc
Normal file
0
hardware/kicad/skidl_REPL.erc
Normal file
9
hardware/kicad/skidl_REPL.log
Normal file
9
hardware/kicad/skidl_REPL.log
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD9_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
5
hardware/kicad/stage1_input.erc
Normal file
5
hardware/kicad/stage1_input.erc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of C/C1) attached to net AIN_HOT.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of C/C2) attached to net AIN_COLD.
|
||||||
|
ERC INFO: 2 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
37
hardware/kicad/stage1_input.log
Normal file
37
hardware/kicad/stage1_input.log
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: Missing tag on THAT1240 instantiated at /work/hardware/eda/circuits/stage1_input.py:47.
|
||||||
|
WARNING: Random tag QgKieWmNoe generated for THAT1240.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1_input.py:55.
|
||||||
|
WARNING: Random tag iOc5_iqozn generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1_input.py:56.
|
||||||
|
WARNING: Random tag pZqcRwtR8R generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1_input.py:56.
|
||||||
|
WARNING: Random tag YRKF1YkzBd generated for R.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1_input.py:57.
|
||||||
|
WARNING: Random tag 9_z9ZD6kzY generated for D.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1_input.py:57.
|
||||||
|
WARNING: Random tag BmXsrS9O5J generated for D.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1_input.py:55.
|
||||||
|
WARNING: Random tag AA57mtziiu generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1_input.py:56.
|
||||||
|
WARNING: Random tag OlgSnmTOjl generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1_input.py:56.
|
||||||
|
WARNING: Random tag cs8OUXPRRM generated for R.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1_input.py:57.
|
||||||
|
WARNING: Random tag i9mJhNwc3K generated for D.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1_input.py:57.
|
||||||
|
WARNING: Random tag UUQtRJt6zT generated for D.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1_input.py:76.
|
||||||
|
WARNING: Random tag MU_6d6hLHv generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1_input.py:76.
|
||||||
|
WARNING: Random tag jSRPH3NSVb generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 31 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
26
hardware/kicad/stage1_input_sklib.py
Normal file
26
hardware/kicad/stage1_input_sklib.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
|
||||||
|
|
||||||
|
from skidl.pin import pin_types
|
||||||
|
|
||||||
|
SKIDL_lib_version = '0.0.1'
|
||||||
|
|
||||||
|
stage1_input = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'THAT1240', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'THAT1240'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='REF',func=pin_types.INPUT),
|
||||||
|
Pin(num='2',name='IN-',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='IN+',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='SENSE',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='6',name='OUT',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='7',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='8',name='NC',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_1206_3216Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'D', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'D'}), 'ref_prefix':'D', 'fplist':[''], 'footprint':'Diode_SMD:D_SOD-323', 'keywords':'diode', 'description':'Diode', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='K',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='A',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])
|
||||||
11
hardware/kicad/stage1b_select.erc
Normal file
11
hardware/kicad/stage1b_select.erc
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 10/COIL_B of TQ2SA-5V/K1) attached to net K1_DRV.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 3/P1_COM of TQ2SA-5V/K1) attached to net AIN_HOT.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/COIL_A of TQ2SA-5V/K1) attached to net +5V.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 4/P1_NC of TQ2SA-5V/K1) attached to net RX_HOT_IN.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 7/P2_NC of TQ2SA-5V/K1) attached to net RX_OUT.
|
||||||
|
ERC WARNING: No pins attached to net SEL_LINST.
|
||||||
|
ERC WARNING: No drivers for net SEL_LINST.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 8/P2_COM of TQ2SA-5V/K1) attached to net STAGE1_OUT.
|
||||||
|
ERC INFO: 8 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
31
hardware/kicad/stage1b_select.log
Normal file
31
hardware/kicad/stage1b_select.log
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: Missing tag on OPA1641 instantiated at /work/hardware/eda/circuits/stage1b_select.py:55.
|
||||||
|
WARNING: Random tag JMxNatPRjd generated for OPA1641.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/stage1b_select.py:72.
|
||||||
|
WARNING: Random tag CfzYAxfHjh generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1b_select.py:97.
|
||||||
|
WARNING: Random tag Sd0rgwy5pV generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1b_select.py:98.
|
||||||
|
WARNING: Random tag OoRZG4Rm3b generated for R.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1b_select.py:99.
|
||||||
|
WARNING: Random tag RSLeGReAd9 generated for D.
|
||||||
|
WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1b_select.py:99.
|
||||||
|
WARNING: Random tag UOfVnOhhVN generated for D.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1b_select.py:108.
|
||||||
|
WARNING: Random tag 3vZeT_Vysx generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1b_select.py:108.
|
||||||
|
WARNING: Random tag u1DrTpQXsB generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1b_select.py:116.
|
||||||
|
WARNING: Random tag UbVM1UVNF7 generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1b_select.py:116.
|
||||||
|
WARNING: Random tag 073687vTIh generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 25 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
37
hardware/kicad/stage1b_select_sklib.py
Normal file
37
hardware/kicad/stage1b_select_sklib.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
|
||||||
|
|
||||||
|
from skidl.pin import pin_types
|
||||||
|
|
||||||
|
SKIDL_lib_version = '0.0.1'
|
||||||
|
|
||||||
|
stage1b_select = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'OPA1641', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1641'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='NC1',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='2',name='-IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='+IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='NC5',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='6',name='OUT',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='7',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='8',name='NC8',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'TQ2SA-5V', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TQ2SA-5V'}), 'ref_prefix':'K', 'fplist':None, 'footprint':'Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='COIL_A',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='10',name='COIL_B',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='3',name='P1_COM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='4',name='P1_NC',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='2',name='P1_NO',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='8',name='P2_COM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='7',name='P2_NC',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='9',name='P2_NO',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='5',name='NC5',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='6',name='NC6',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_1206_3216Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'D', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'D'}), 'ref_prefix':'D', 'fplist':[''], 'footprint':'Diode_SMD:D_SOD-323', 'keywords':'diode', 'description':'Diode', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='K',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='A',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])
|
||||||
15
hardware/kicad/stage2_dac.erc
Normal file
15
hardware/kicad/stage2_dac.erc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 15/LRCK of PCM5102A/U3) attached to net I2S_LRCK.
|
||||||
|
ERC WARNING: No drivers for net I2S_LRCK.
|
||||||
|
ERC WARNING: Insufficient drive current on net I2S_LRCK for pin INPUT pin 15/LRCK of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 13/BCK of PCM5102A/U3) attached to net I2S_BCK.
|
||||||
|
ERC WARNING: No drivers for net I2S_BCK.
|
||||||
|
ERC WARNING: Insufficient drive current on net I2S_BCK for pin INPUT pin 13/BCK of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 12/SCK of PCM5102A/U3) attached to net MCLK.
|
||||||
|
ERC WARNING: No drivers for net MCLK.
|
||||||
|
ERC WARNING: Insufficient drive current on net MCLK for pin INPUT pin 12/SCK of PCM5102A/U3.
|
||||||
|
ERC WARNING: Only one pin (INPUT pin 14/DIN of PCM5102A/U3) attached to net I2S_DIN.
|
||||||
|
ERC WARNING: No drivers for net I2S_DIN.
|
||||||
|
ERC WARNING: Insufficient drive current on net I2S_DIN for pin INPUT pin 14/DIN of PCM5102A/U3.
|
||||||
|
ERC INFO: 12 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
45
hardware/kicad/stage2_dac.log
Normal file
45
hardware/kicad/stage2_dac.log
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: Missing tag on PCM5102A instantiated at /work/hardware/eda/circuits/stage2_dac.py:65.
|
||||||
|
WARNING: Random tag SnNljvRqpw generated for PCM5102A.
|
||||||
|
WARNING: Missing tag on OPA1612 instantiated at /work/hardware/eda/circuits/stage2_dac.py:80.
|
||||||
|
WARNING: Random tag xlt7MeMuUW generated for OPA1612.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:90.
|
||||||
|
WARNING: Random tag APxc1ZswyC generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:90.
|
||||||
|
WARNING: Random tag 2Lpho8vvNO generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:90.
|
||||||
|
WARNING: Random tag ckOnqrtEN_ generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:91.
|
||||||
|
WARNING: Random tag u0XfWM3NJZ generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:95.
|
||||||
|
WARNING: Random tag 0nDWAElKnx generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:97.
|
||||||
|
WARNING: Random tag enBI6FilVb generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:98.
|
||||||
|
WARNING: Random tag BsHVUTUgsl generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:106.
|
||||||
|
WARNING: Random tag MwfOtBOy0H generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:115.
|
||||||
|
WARNING: Random tag T191zb3J0X generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:118.
|
||||||
|
WARNING: Random tag PbFpsp3Kl4 generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:118.
|
||||||
|
WARNING: Random tag uQ27wvkIQL generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:119.
|
||||||
|
WARNING: Random tag xSn6HarfSW generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:119.
|
||||||
|
WARNING: Random tag mU_gTQB1ji generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:131.
|
||||||
|
WARNING: Random tag u20v3iP1Ti generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:131.
|
||||||
|
WARNING: Random tag ZBz2Y2o1hX generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 39 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
44
hardware/kicad/stage2_dac_sklib.py
Normal file
44
hardware/kicad/stage2_dac_sklib.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
|
||||||
|
|
||||||
|
from skidl.pin import pin_types
|
||||||
|
|
||||||
|
SKIDL_lib_version = '0.0.1'
|
||||||
|
|
||||||
|
stage2_dac = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'PCM5102A', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'PCM5102A'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='CPVDD',func=pin_types.PWRIN),
|
||||||
|
Pin(num='2',name='CAPP',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='3',name='CPGND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='4',name='CAPM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='5',name='VNEG',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='6',name='OUTL',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='7',name='OUTR',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='8',name='AVDD',func=pin_types.PWRIN),
|
||||||
|
Pin(num='9',name='AGND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='10',name='DEMP',func=pin_types.INPUT),
|
||||||
|
Pin(num='11',name='FLT',func=pin_types.INPUT),
|
||||||
|
Pin(num='12',name='SCK',func=pin_types.INPUT),
|
||||||
|
Pin(num='13',name='BCK',func=pin_types.INPUT),
|
||||||
|
Pin(num='14',name='DIN',func=pin_types.INPUT),
|
||||||
|
Pin(num='15',name='LRCK',func=pin_types.INPUT),
|
||||||
|
Pin(num='16',name='FMT',func=pin_types.INPUT),
|
||||||
|
Pin(num='17',name='XSMT',func=pin_types.INPUT),
|
||||||
|
Pin(num='18',name='LDOO',func=pin_types.PWROUT),
|
||||||
|
Pin(num='19',name='DGND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='20',name='DVDD',func=pin_types.PWRIN)] }),
|
||||||
|
Part(**{ 'name':'OPA1612', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1612'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='OUTA',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='2',name='-INA',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='+INA',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='+INB',func=pin_types.INPUT),
|
||||||
|
Pin(num='6',name='-INB',func=pin_types.INPUT),
|
||||||
|
Pin(num='7',name='OUTB',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='8',name='V+',func=pin_types.PWRIN)] }),
|
||||||
|
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_0805_2012Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])
|
||||||
5
hardware/kicad/stage3_sum.erc
Normal file
5
hardware/kicad/stage3_sum.erc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of R/R1) attached to net STAGE1_OUT.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of R/R2) attached to net CLICK_OUT.
|
||||||
|
ERC INFO: 2 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
23
hardware/kicad/stage3_sum.log
Normal file
23
hardware/kicad/stage3_sum.log
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: Missing tag on OPA1612 instantiated at /work/hardware/eda/circuits/stage3_sum.py:50.
|
||||||
|
WARNING: Random tag 5n2wh6xRRe generated for OPA1612.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage3_sum.py:53.
|
||||||
|
WARNING: Random tag PsTgUzSm6S generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage3_sum.py:53.
|
||||||
|
WARNING: Random tag MJ5MDnj_yt generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage3_sum.py:53.
|
||||||
|
WARNING: Random tag RyhIEBAL2O generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage3_sum.py:63.
|
||||||
|
WARNING: Random tag oOB0ftP80i generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage3_sum.py:63.
|
||||||
|
WARNING: Random tag NmK6U68Pr1 generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 17 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
23
hardware/kicad/stage3_sum_sklib.py
Normal file
23
hardware/kicad/stage3_sum_sklib.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
|
||||||
|
|
||||||
|
from skidl.pin import pin_types
|
||||||
|
|
||||||
|
SKIDL_lib_version = '0.0.1'
|
||||||
|
|
||||||
|
stage3_sum = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'OPA1612', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1612'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='OUTA',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='2',name='-INA',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='+INA',func=pin_types.INPUT),
|
||||||
|
Pin(num='4',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='5',name='+INB',func=pin_types.INPUT),
|
||||||
|
Pin(num='6',name='-INB',func=pin_types.INPUT),
|
||||||
|
Pin(num='7',name='OUTB',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='8',name='V+',func=pin_types.PWRIN)] }),
|
||||||
|
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_0805_2012Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])
|
||||||
12
hardware/kicad/stage4_driver.erc
Normal file
12
hardware/kicad/stage4_driver.erc
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 10/COIL_B of TQ2SA-5V/K2) attached to net K2_DRV.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/1 of R_Potentiometer/RV1) attached to net MIX_OUT.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 10/COIL_B of TQ2SA-5V/K3) attached to net K3_DRV.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 2/P1_NO of TQ2SA-5V/K2.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 9/P2_NO of TQ2SA-5V/K2.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 2/P1_NO of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 8/P2_COM of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 7/P2_NC of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 9/P2_NO of TQ2SA-5V/K3.
|
||||||
|
ERC INFO: 9 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
31
hardware/kicad/stage4_driver.log
Normal file
31
hardware/kicad/stage4_driver.log
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: fp-lib-table file was not found. Component footprints are not available.
|
||||||
|
WARNING: Missing tag on THAT1646 instantiated at /work/hardware/eda/circuits/stage4_driver.py:57.
|
||||||
|
WARNING: Random tag jOycpJtysP generated for THAT1646.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/stage4_driver.py:75.
|
||||||
|
WARNING: Random tag 8YvHjEQvvL generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/stage4_driver.py:75.
|
||||||
|
WARNING: Random tag gbZbaJRgHN generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on R_Potentiometer instantiated at /work/hardware/eda/circuits/stage4_driver.py:82.
|
||||||
|
WARNING: Random tag TtWL0siwIj generated for R_Potentiometer.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage4_driver.py:91.
|
||||||
|
WARNING: Random tag Il2xsDxaas generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage4_driver.py:91.
|
||||||
|
WARNING: Random tag h4JnvQCBxp generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage4_driver.py:94.
|
||||||
|
WARNING: Random tag 49MKQ_ERoo generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage4_driver.py:94.
|
||||||
|
WARNING: Random tag L4esGcufU5 generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage4_driver.py:105.
|
||||||
|
WARNING: Random tag xtw8oitG50 generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage4_driver.py:105.
|
||||||
|
WARNING: Random tag 5KGv1Z40Bi generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 25 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
38
hardware/kicad/stage4_driver_sklib.py
Normal file
38
hardware/kicad/stage4_driver_sklib.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE
|
||||||
|
|
||||||
|
from skidl.pin import pin_types
|
||||||
|
|
||||||
|
SKIDL_lib_version = '0.0.1'
|
||||||
|
|
||||||
|
stage4_driver = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'THAT1646', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'THAT1646'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='OUT-',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='2',name='SNS-',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='GND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='4',name='IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='5',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='6',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='7',name='SNS+',func=pin_types.INPUT),
|
||||||
|
Pin(num='8',name='OUT+',func=pin_types.OUTPUT)] }),
|
||||||
|
Part(**{ 'name':'TQ2SA-5V', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TQ2SA-5V'}), 'ref_prefix':'K', 'fplist':None, 'footprint':'Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[
|
||||||
|
Pin(num='1',name='COIL_A',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='10',name='COIL_B',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='3',name='P1_COM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='4',name='P1_NC',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='2',name='P1_NO',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='8',name='P2_COM',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='7',name='P2_NC',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='9',name='P2_NO',func=pin_types.PASSIVE),
|
||||||
|
Pin(num='5',name='NC5',func=pin_types.NOCONNECT),
|
||||||
|
Pin(num='6',name='NC6',func=pin_types.NOCONNECT)] }),
|
||||||
|
Part(**{ 'name':'R_Potentiometer', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R_Potentiometer'}), 'ref_prefix':'RV', 'fplist':[''], 'footprint':'Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical', 'keywords':'resistor variable', 'description':'Potentiometer', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='1',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='3',name='3',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='2',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_0805_2012Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })])
|
||||||
169
info-explorer.html
Normal file
169
info-explorer.html
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>VARASYS PM_X-1 Explorer - wiring, parts & firmware (Pimoroni Explorer / RP2350)</title>
|
||||||
|
<meta name="description" content="PM_X-1 Explorer - the Pimoroni Explorer (PIM744, RP2350) as a 6-button polymeter metronome with live-sync to the web editor. Pinout, parts list, and the precompiled CircuitPython firmware bundle." />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
|
||||||
|
<script>
|
||||||
|
(function(){ try{ var p = localStorage.getItem("metronome.theme");
|
||||||
|
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
|
||||||
|
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
|
||||||
|
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/*@BUILD:include:src/base.css@*/
|
||||||
|
:root{ --bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
|
||||||
|
--panel-bg:#161b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#2a313c; --silk:#aab2bc; }
|
||||||
|
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
|
||||||
|
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#d2dae4; }
|
||||||
|
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt);
|
||||||
|
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); }
|
||||||
|
a{ color:var(--link); }
|
||||||
|
main{ width:100%; max-width:980px; margin:0 auto; }
|
||||||
|
.info-hero{ text-align:center; padding:16px 8px 2px; }
|
||||||
|
.info-hero h1{ font-size:clamp(24px,5vw,36px); margin:0; letter-spacing:-.01em; }
|
||||||
|
.info-hero .sub{ margin:9px auto 0; max-width:64ch; font-size:14.5px; }
|
||||||
|
.steps{ width:100%; max-width:760px; margin:8px auto 0; color:var(--muted); font-size:14px; line-height:1.6; }
|
||||||
|
.steps li{ margin:5px 0; }
|
||||||
|
.steps code, .about code, .sub code { background:var(--field-bg); border:1px solid var(--field-bd); border-radius:5px; padding:1px 5px; font-size:12.5px; }
|
||||||
|
.dl{ display:inline-flex; align-items:center; gap:7px; margin:4px 10px 4px 0; padding:9px 14px; border-radius:10px;
|
||||||
|
background:linear-gradient(180deg,#34c6ff,var(--cyan)); color:#04121b; font-weight:700; text-decoration:none; font-size:13.5px; }
|
||||||
|
.dl.alt{ background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); font-weight:600; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
/*@BUILD:include:src/header.html@*/
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="info-hero">
|
||||||
|
<h1>PM_X-1 Explorer</h1>
|
||||||
|
<p class="sub">The off-the-shelf <b>Pimoroni Explorer Kit</b> (RP2350, 2.8" LCD, 6 buttons, piezo) as a polymeter metronome - sibling to the PM_K-1 Kit, sharing the engine, program-string grammar, and live-sync protocol with the web editor.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="about">
|
||||||
|
<h2>What it is</h2>
|
||||||
|
<div class="ff-tags"><span class="hw">Buildable now</span><span>RP2350 (Pico 2 class)</span><span>Pimoroni Explorer PIM744</span><span>~$60</span></div>
|
||||||
|
<p>The <a href="https://shop.pimoroni.com/products/explorer" target="_blank" rel="noopener">Pimoroni Explorer Kit (PIM744)</a> is a finished
|
||||||
|
development board: <b>RP2350B</b> built in, <b>2.8" ST7789V 320x240 IPS LCD</b>, <b>6 user buttons</b> (A/B/C
|
||||||
|
on the left of the screen, X/Y/Z on the right), a <b>piezo speaker</b>, USB-C, a JST-PH battery
|
||||||
|
connector, and a mini breadboard with 6 GPIOs / 3 ADCs broken out for sensor projects. No soldering;
|
||||||
|
you flash CircuitPython and drop the firmware on. <b>No touchscreen, no joystick, no RGB LED</b> -
|
||||||
|
everything is driven from the 6 buttons.</p>
|
||||||
|
<p>It runs the same <b>polymeter engine</b> and the same <b>program strings</b> as the web editor.
|
||||||
|
Beat editing is done in the browser; <b>Live sync</b> mirrors edits to the device in real time
|
||||||
|
(HELLO/FULL/DELTA over USB-MIDI), and the device mirrors play/stop/tempo/track changes back. The
|
||||||
|
piezo clicks; a tiny on-screen dot shows run state.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Wiring - the Pimoroni Explorer fixed pinout (no breadboarding required)</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">Everything is wired on the board; this is what the firmware reads. The display is driven by an 8-bit parallel bus initialised by CircuitPython's official board definition - we use <code>board.DISPLAY</code> directly.</p>
|
||||||
|
<table class="bom">
|
||||||
|
<thead><tr><th>Component</th><th>RP2350 pins</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="grp"><td colspan="2">Display - 2.8" ST7789V, 320x240 (8-bit parallel 8080)</td></tr>
|
||||||
|
<tr><td class="part">BL / CS / DC / WR / RD / D0-D7</td><td>GP26 / GP27 / GP28 / GP30 / GP31 / GP32-GP39 (board.c)</td></tr>
|
||||||
|
<tr class="grp"><td colspan="2">Buttons (digital, pull-up)</td></tr>
|
||||||
|
<tr><td class="part">A (play/stop) / B (tap tempo) / C (menu)</td><td>GP16 / GP15 / GP14 (left side, top to bottom)</td></tr>
|
||||||
|
<tr><td class="part">X (prev track) / Y (-bpm) / Z (next track)</td><td>GP17 / GP18 / GP19 (right side, top to bottom)</td></tr>
|
||||||
|
<tr class="grp"><td colspan="2">Audio</td></tr>
|
||||||
|
<tr><td class="part">Piezo PWM</td><td>GP12</td></tr>
|
||||||
|
<tr><td class="part">Amp enable</td><td>GP13</td></tr>
|
||||||
|
<tr class="grp"><td colspan="2">I2C (QwSTEMMA - unused by the firmware, free for sensors)</td></tr>
|
||||||
|
<tr><td class="part">SDA / SCL</td><td>GP20 / GP21</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Controls</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<table class="bom">
|
||||||
|
<thead><tr><th>Button</th><th>Action</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td class="part">A</td><td>play / stop</td></tr>
|
||||||
|
<tr><td class="part">B</td><td>tap tempo</td></tr>
|
||||||
|
<tr><td class="part">C</td><td>menu (Settings / Practice log / Help / About)</td></tr>
|
||||||
|
<tr><td class="part">X</td><td>prev track (hold to repeat)</td></tr>
|
||||||
|
<tr><td class="part">Z</td><td>next track (hold to repeat)</td></tr>
|
||||||
|
<tr><td class="part">Y</td><td>tempo -1 (hold = -5 after 1.5 s)</td></tr>
|
||||||
|
<tr><td class="part">X + Z (chord)</td><td>tempo +1 (same hold rule as Y)</td></tr>
|
||||||
|
<tr class="grp"><td colspan="2">In a menu</td></tr>
|
||||||
|
<tr><td class="part">X / Z</td><td>move cursor up / down (Help: prev / next page)</td></tr>
|
||||||
|
<tr><td class="part">Y</td><td>decrement the focused value</td></tr>
|
||||||
|
<tr><td class="part">A</td><td>cycle / increment / select</td></tr>
|
||||||
|
<tr><td class="part">B</td><td>back (cancel)</td></tr>
|
||||||
|
<tr><td class="part">C</td><td>close the menu</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Parts</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">A finished development board, not a custom build - ballpark one-off price (USD).</p>
|
||||||
|
<table class="bom">
|
||||||
|
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td class="part">Pimoroni Explorer Kit (PIM744) <span class="spec">- RP2350B, 2.8" ST7789V, 6 buttons, piezo + amp, USB-C</span></td><td class="q">1</td><td class="c">60</td></tr>
|
||||||
|
<tr><td class="part">USB-C cable <span class="spec">- power + flashing</span></td><td class="q">1</td><td class="c">2</td></tr>
|
||||||
|
<tr class="total"><td>Total (one-off)</td><td class="q"></td><td class="c">≈ $62</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="sub" style="margin-top:10px">Reference: <a href="https://shop.pimoroni.com/products/explorer" target="_blank" rel="noopener">Pimoroni Explorer product page</a>
|
||||||
|
· <a href="https://github.com/pimoroni/explorer" target="_blank" rel="noopener">vendor code</a>
|
||||||
|
· <a href="https://circuitpython.org/board/pimoroni_explorer2350/" target="_blank" rel="noopener">CircuitPython for Pimoroni Explorer (RP2350)</a>.</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="spec" open>
|
||||||
|
<summary>Firmware - self-contained appliance (USB drive · web-driven editing via Live sync · MIDI audio · practice log)</summary>
|
||||||
|
<div class="spec-body">
|
||||||
|
<p class="sub">The firmware turns the Explorer into a self-contained appliance: it mounts as a
|
||||||
|
<b>USB drive</b> carrying the (precompiled) firmware, your tracks and an offline copy of this editor;
|
||||||
|
drives a lanes/pads display with <b>web-driven editing</b> via <b>Live sync</b>; <b>logs your practice</b> to
|
||||||
|
<code>history.json</code>; takes new set lists <b>pushed from the editor over USB-MIDI</b>; and plays
|
||||||
|
out your <b>computer's speakers over USB-MIDI</b>. By default the firmware owns the drive (read-only to
|
||||||
|
the computer - so it can log and can't be accidentally erased); hold <b>button A</b> at power-on for
|
||||||
|
editor mode (drive writable).</p>
|
||||||
|
<p>
|
||||||
|
<a class="dl" href="/pm_x1_circuitpy.zip" download>Download CircuitPython bundle ↓</a>
|
||||||
|
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/pico-explorer" target="_blank" rel="noopener">Source + README ↗</a>
|
||||||
|
</p>
|
||||||
|
<ol class="steps">
|
||||||
|
<li>Flash <b>CircuitPython for Pimoroni Explorer (RP2350)</b>
|
||||||
|
(<a href="https://circuitpython.org/board/pimoroni_explorer2350/" target="_blank" rel="noopener">download</a>)
|
||||||
|
via BOOTSEL, unzip the bundle onto <code>CIRCUITPY</code>, and power-cycle. It boots into appliance mode.</li>
|
||||||
|
<li><b>Edit on the web:</b> open the <a href="/editor-beta.html">editor (beta)</a> in Chrome / Edge / Firefox,
|
||||||
|
click <b>🔗 Live sync</b>, and the Explorer mirrors your edits live (beats, tempo, track changes).</li>
|
||||||
|
<li><b>Save a set list to the device</b> for offline use: set-list <b>···</b> menu →
|
||||||
|
<b>📟 Save to device</b>. It's pushed over USB-MIDI; the device persists it to
|
||||||
|
<code>/programs.json</code>.</li>
|
||||||
|
<li><b>Play through your computer:</b> click <b>🎹 Device audio</b>, then press <b>A</b> on the device -
|
||||||
|
the full groove sounds through your speakers over USB-MIDI, in sync. A <b>MIDI</b> badge appears in the
|
||||||
|
header and the piezo auto-mutes.</li>
|
||||||
|
<li><b>Practice log:</b> press <b>C</b> → <b>Practice log</b>. Plays over 5 s appear (time · BPM · duration · bars).</li>
|
||||||
|
<li><b>Firmware updates:</b> ··· menu → <b>⬆ Update firmware</b> - the editor reads
|
||||||
|
the device id (X = Explorer), fetches the matching <code>pico-explorer-app.mpy</code>, and pushes it
|
||||||
|
over USB-MIDI. The device A/B-updates with automatic rollback if a build won't boot.</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<p class="sub" style="max-width:760px;margin:14px auto 0">Pairs with the touch-driven <a href="/info-kit.html">PM_K-1 Kit</a> - same engine, same programs.json, same web editor.</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
/*@BUILD:include:src/footer.html@*/
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const APP_VERSION = "v0.0.1-dev";
|
||||||
|
/*@BUILD:include:src/chrome.js@*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
|
|
||||||
import board, busio, digitalio, analogio, pwmio, displayio, vectorio, time, json, gc, os, supervisor
|
import board, busio, digitalio, analogio, pwmio, displayio, vectorio, time, json, gc, os, supervisor
|
||||||
supervisor.runtime.autoreload = False # we write our own files (log + pushed programs); never self-restart
|
supervisor.runtime.autoreload = False # we write our own files (log + pushed programs); never self-restart
|
||||||
APP_VERSION = "0.0.22" # firmware version (the A/B updater pushes/compares this)
|
APP_VERSION = "0.0.23" # firmware version (the A/B updater pushes/compares this)
|
||||||
|
DEVICE_ID = "K" # 'K' = 52Pi kit, 'X' = Pimoroni Explorer (per docs/livesync-protocol.md and the version reply)
|
||||||
try:
|
try:
|
||||||
import rtc # set from the editor's clock SysEx so the log has real timestamps
|
import rtc # set from the editor's clock SysEx so the log has real timestamps
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -1541,8 +1542,10 @@ class App:
|
||||||
if cmd == 0x01 and len(sx) >= 8 and rtc is not None: # set clock: yr-2000, mo, dd, hh, mm, ss
|
if cmd == 0x01 and len(sx) >= 8 and rtc is not None: # set clock: yr-2000, mo, dd, hh, mm, ss
|
||||||
try: rtc.RTC().datetime = time.struct_time((2000 + sx[2], sx[3], sx[4], sx[5], sx[6], sx[7], 0, -1, -1))
|
try: rtc.RTC().datetime = time.struct_time((2000 + sx[2], sx[3], sx[4], sx[5], sx[6], sx[7], 0, -1, -1))
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
elif cmd == 0x02: # version query -> reply 0x03 + APP_VERSION
|
elif cmd == 0x02: # version query -> reply 0x03 + "<device_id>;<APP_VERSION>"
|
||||||
if self.midi: self.midi.write(bytes([0xF0, 0x7D, 0x03]) + APP_VERSION.encode() + bytes([0xF7]))
|
if self.midi: # old firmware sent bare APP_VERSION; editor parses "contains ';'?" for back-compat
|
||||||
|
payload = DEVICE_ID + ";" + APP_VERSION
|
||||||
|
self.midi.write(bytes([0xF0, 0x7D, 0x03]) + payload.encode() + bytes([0xF7]))
|
||||||
elif cmd == 0x40 or cmd == 0x41 or cmd == 0x42 or cmd == 0x43: # Live sync (see src/livesync.js)
|
elif cmd == 0x40 or cmd == 0x41 or cmd == 0x42 or cmd == 0x43: # Live sync (see src/livesync.js)
|
||||||
try: text = "".join(chr(b) if 0x20 <= b < 0x7F else "" for b in sx[2:])
|
try: text = "".join(chr(b) if 0x20 <= b < 0x7F else "" for b in sx[2:])
|
||||||
except Exception: return
|
except Exception: return
|
||||||
|
|
|
||||||
74
pico-explorer/README.md
Normal file
74
pico-explorer/README.md
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# PM_X-1 "Explorer" — CircuitPython edition (Pimoroni Explorer · RP2350)
|
||||||
|
|
||||||
|
The **CircuitPython** firmware for the [Pimoroni Explorer Kit (PIM744)](https://shop.pimoroni.com/products/explorer),
|
||||||
|
set up as a self-contained appliance. Sibling to the PM_K-1 build in `../pico-cp/` (the 52Pi EP-0172
|
||||||
|
kit) — same engine, same program strings, same `programs.json`, same web editor.
|
||||||
|
|
||||||
|
This board is a **2.8″ ST7789V 320×240 LCD + 6 user buttons (A/B/C on the left, X/Y/Z on the right)
|
||||||
|
+ piezo speaker** built around an RP2350B (Pico 2 class chip). **No touchscreen, no joystick, no
|
||||||
|
RGB LED.** Editing is done in the web editor with **Live sync** on; the device mirrors changes in
|
||||||
|
real time and emits its own play/stop/bpm/sel deltas back.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
| ------ | --------------------------------------------------------------------- |
|
||||||
|
| **A** | play / stop |
|
||||||
|
| **B** | tap tempo |
|
||||||
|
| **C** | menu (Settings / Help / About / Practice log) |
|
||||||
|
| **X** | prev track (hold to repeat) |
|
||||||
|
| **Y** | tempo −1 (hold to repeat; after ~1.5 s the step grows to −5) |
|
||||||
|
| **Z** | next track (hold to repeat) |
|
||||||
|
| **X + Z** | tempo +1 (chord; same hold-repeat as Y) |
|
||||||
|
|
||||||
|
In a menu: **X / Z** move the cursor up / down, **Y** decrements the focused value, **A** commits or
|
||||||
|
cycles, **B** = back, **C** = close.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
1. **Flash CircuitPython for Pico 2 / RP2350.** Hold **BOOTSEL** on the Explorer, plug it in over
|
||||||
|
USB-C, drop the [Pimoroni Explorer (RP2350) CircuitPython `.uf2`](https://circuitpython.org/board/pimoroni_explorer2350/)
|
||||||
|
onto the `RP2350` drive. A `CIRCUITPY` drive appears.
|
||||||
|
2. **Copy the bundle onto `CIRCUITPY`** — `boot.py`, `code.py`, **`app.mpy`**, `programs.json`,
|
||||||
|
`font_s.bin` / `font_m.bin` / `font_l.bin`, `logo.bin` / `midi.bin` / `usb.bin`, `editor.html`
|
||||||
|
(offline editor). If an old `app.py` is on the drive, delete it.
|
||||||
|
3. **Power-cycle.** It boots into appliance mode and runs.
|
||||||
|
|
||||||
|
## Program it from the web
|
||||||
|
|
||||||
|
Open <https://metronome.varasys.io> in Chrome / Edge / Firefox. The set-list **⋯** menu →
|
||||||
|
**📟 Save to device** pushes a `programs.json` over USB-MIDI; the device persists it and reloads.
|
||||||
|
Click **🔗 Live sync** to mirror edits in real time.
|
||||||
|
|
||||||
|
## Pin reference
|
||||||
|
|
||||||
|
The display, buttons, and audio are wired into the board — no jumpers required. CircuitPython's
|
||||||
|
official board definition for `pimoroni_explorer2350` exposes `board.DISPLAY` pre-initialized, so
|
||||||
|
the firmware just uses it.
|
||||||
|
|
||||||
|
| Function | GPIO |
|
||||||
|
| ----------------- | -------- |
|
||||||
|
| Button A | GP16 |
|
||||||
|
| Button B | GP15 |
|
||||||
|
| Button C | GP14 |
|
||||||
|
| Button X | GP17 |
|
||||||
|
| Button Y | GP18 |
|
||||||
|
| Button Z | GP19 |
|
||||||
|
| Piezo audio (PWM) | GP12 |
|
||||||
|
| Piezo amp enable | GP13 |
|
||||||
|
| I²C SDA (QwSTEMMA) | GP20 |
|
||||||
|
| I²C SCL (QwSTEMMA) | GP21 |
|
||||||
|
| Display | `board.DISPLAY` (8080 parallel bus on GP26..GP39, initialized by board.c) |
|
||||||
|
|
||||||
|
## Calibration (flags at the top of `app.py`)
|
||||||
|
|
||||||
|
- **Speaker too loud / quiet:** the piezo + amp gain is fixed in hardware. `MUTE_SPEAKER`
|
||||||
|
silences the click; `SPEAKER_AUTO_MUTE` auto-mutes when a MIDI host is listening.
|
||||||
|
- **Buttons feel inverted:** the polarity is hard-coded to active-low (pull-up). If a button
|
||||||
|
fires on release instead of press, check the `BTN_*` pin map at the top of `app.py`.
|
||||||
|
- **Display orientation:** the board's CircuitPython init mounts the panel landscape
|
||||||
|
(320 × 240). If your screen looks rotated, that's a board.c-level thing — file a CircuitPython
|
||||||
|
bug, don't patch the firmware.
|
||||||
|
|
||||||
|
If `app.py` ever errors, CircuitPython prints the traceback **on the screen and over USB serial** —
|
||||||
|
send me that.
|
||||||
1444
pico-explorer/app.py
Normal file
1444
pico-explorer/app.py
Normal file
File diff suppressed because it is too large
Load diff
22
pico-explorer/boot.py
Normal file
22
pico-explorer/boot.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# boot.py - runs once at power-on (before USB connects); decides who owns the filesystem.
|
||||||
|
#
|
||||||
|
# DEFAULT = appliance mode: the FIRMWARE owns the drive, so it can save your practice log to
|
||||||
|
# /history.json and write /programs.json that the editor pushes over USB-MIDI. The drive is then
|
||||||
|
# READ-ONLY to the computer - which also protects the firmware from accidental deletion.
|
||||||
|
#
|
||||||
|
# HOLD BUTTON A (GP16 on the Pimoroni Explorer) WHILE PLUGGING IN = editor mode: the drive is
|
||||||
|
# writable by the computer, so you can drag programs.json / code.py on from any OS or browser
|
||||||
|
# (the universal fallback). Reset afterwards to return to appliance mode.
|
||||||
|
#
|
||||||
|
# Also frees a USB endpoint (disables unused HID) and makes sure USB-MIDI is available.
|
||||||
|
import board, digitalio, storage, usb_hid, usb_midi
|
||||||
|
try: usb_hid.disable()
|
||||||
|
except Exception: pass
|
||||||
|
usb_midi.enable()
|
||||||
|
a = digitalio.DigitalInOut(board.GP16)
|
||||||
|
a.switch_to_input(pull=digitalio.Pull.UP)
|
||||||
|
appliance = a.value # value True (pull-up, not pressed) -> appliance mode
|
||||||
|
a.deinit()
|
||||||
|
if appliance:
|
||||||
|
try: storage.remount("/", readonly=False) # writable by code, read-only to the computer
|
||||||
|
except Exception: pass
|
||||||
24
pico-explorer/code.py
Normal file
24
pico-explorer/code.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# code.py - PM_X-1 A/B firmware loader (stable; rarely changes).
|
||||||
|
#
|
||||||
|
# The real application is the PRECOMPILED app.mpy (CircuitPython compiles a big .py at boot, which
|
||||||
|
# fragments the heap and OOMs; a .mpy loads without compiling). app.bak holds the previous known-good
|
||||||
|
# build. The web editor pushes a new app.mpy to a "trial" slot over USB-MIDI; this loader runs it, and
|
||||||
|
# if it fails to boot it AUTOMATICALLY ROLLS BACK to app.bak. (Unbrickable: BOOTSEL -> drag a .uf2.)
|
||||||
|
# app.mpy clears the /trial marker once it has run healthily for ~5s.
|
||||||
|
import supervisor, os
|
||||||
|
supervisor.runtime.autoreload = False # updates reboot explicitly; never auto-restart on our own writes
|
||||||
|
|
||||||
|
def _trial():
|
||||||
|
try: os.stat("/trial"); return True
|
||||||
|
except OSError: return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import app # runs the application (app.mpy; ends with App().run())
|
||||||
|
except Exception:
|
||||||
|
if _trial(): # a freshly-pushed build crashed on startup -> roll back
|
||||||
|
try:
|
||||||
|
os.remove("/app.mpy"); os.rename("/app.bak", "/app.mpy"); os.remove("/trial")
|
||||||
|
except Exception: pass
|
||||||
|
supervisor.reload() # reboot into the restored known-good build
|
||||||
|
else:
|
||||||
|
raise # the active build failed unexpectedly (rare) -> on-screen traceback
|
||||||
3
pico-explorer/programs.json
Normal file
3
pico-explorer/programs.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"setlists": []
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue