Audio (the Scroll Pack has no speaker, so MIDI is the only path):
- usb-device 0.3 + usbd-midi 0.5 on the rp2040-hal UsbBus; enumerates as a
class-compliant MIDI device 'PM_G-1 Grid'.
- tick() emits a GM note-on per lane hit on channel 10 (note from the ported
SOUND_GM map, velocity by level) via send_bytes([0x09,0x99,note,vel]) — raw
4-byte packets, so arbitrary GM drum notes work without the named Note enum.
- USB polled every loop iteration AND during the boot splash (so the host can
enumerate during the ~2.5s animation).
Debug: defmt/defmt-rtt + panic-probe + flip-link; runner probe-rs run --chip
RP2040 (Pi Debug Probe). build.sh emits pm-grid.uf2 + pm-grid.elf; deploy serves
both; key info! log points + 1Hz heartbeat.
Web: drop CircuitPython from the PM_G-1 product. info-grid.html features the
Rust .uf2 download + accurate controls/views (X/Y swap, Ticker); build.sh +
deploy.sh no longer bundle/serve pm_g1_circuitpy.zip or pico-scroll-app.{py,mpy}.
pico-scroll/ stays as the reference port; editor FW_PATHS.G left for graceful
degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
168 lines
11 KiB
HTML
168 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>VARASYS PM_G-1 Grid - wiring, parts & firmware (Pimoroni Pico Scroll Pack / RP2040)</title>
|
|
<meta name="description" content="PM_G-1 Grid - the Pimoroni Pico Scroll Pack (PIM545, 17x7 LED matrix + 4 buttons) on a Raspberry Pi Pico as a polymeter metronome. Pinout, parts list, and the native Rust firmware (flash the .uf2)." />
|
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
|
|
<script>
|
|
/* ?embed=1 -> strip site chrome (base.css [data-embed]) + auto-size to the host iframe */
|
|
(function(){ if(!/[?&]embed=1/.test(location.search)) return;
|
|
document.documentElement.dataset.embed="1";
|
|
function ph(){ try{ parent.postMessage({type:"varasys-h",h:Math.ceil(document.documentElement.getBoundingClientRect().height)},"*"); }catch(e){} }
|
|
addEventListener("load",ph); addEventListener("resize",ph); setTimeout(ph,300); setTimeout(ph,1000);
|
|
})();
|
|
</script>
|
|
<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_G-1 Grid</h1>
|
|
<p class="sub">The off-the-shelf <b>Pimoroni Pico Scroll Pack</b> (a 17×7 white LED matrix + 4 buttons) on a plain <b>Raspberry Pi Pico</b> - sibling to the PM_K-1 Kit and PM_X-1 Explorer, 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>RP2040 (Raspberry Pi Pico)</span><span>Pimoroni Pico Scroll Pack PIM545</span><span>~$30</span></div>
|
|
<p>The <a href="https://shop.pimoroni.com/products/pico-scroll-pack" target="_blank" rel="noopener">Pico Scroll Pack (PIM545)</a>
|
|
plugs straight onto a Raspberry Pi Pico's headers: <b>119 white LEDs in a 17×7 matrix</b> driven by an
|
|
<b>IS31FL3731</b> over I²C (individually brightness-controlled), and <b>4 buttons</b> (A / B / X / Y).
|
|
No soldering, no touchscreen, no joystick, no RGB - and <b>no onboard speaker</b>. About 65 × 25 × 10 mm
|
|
on top of the Pico. Power is over VSYS (USB or battery).</p>
|
|
<p>It runs the <b>native Rust firmware</b> (<code>rust/pm-grid</code>), built on the same <b>polymeter engine</b>
|
|
(the shared <code>track-format</code> crate) and <b>program strings</b> as the web editor. The 7-row × 17-column
|
|
matrix maps directly onto the editor's <b>lane × step</b> pad grid: each lane is a row, each step a column, and
|
|
LED brightness encodes accent / normal / ghost. <b>Audio is over USB-MIDI</b> - turn on the editor's
|
|
<b>🎹 Device audio</b> to hear the clicks through your computer (the Scroll Pack has no speaker of its own).</p>
|
|
</section>
|
|
|
|
<details class="spec" open>
|
|
<summary>Wiring - the Pico Scroll Pack fixed pinout (just press it onto the Pico)</summary>
|
|
<div class="spec-body">
|
|
<p class="sub">Everything is wired through the header; this is what the firmware reads. Pins verified against Pimoroni's <code>pico_scroll</code> library.</p>
|
|
<table class="bom">
|
|
<thead><tr><th>Component</th><th>Pico pins</th></tr></thead>
|
|
<tbody>
|
|
<tr class="grp"><td colspan="2">LED matrix - 17×7 white, IS31FL3731 (I²C @ 0x74)</td></tr>
|
|
<tr><td class="part">SDA / SCL</td><td>GP4 / GP5</td></tr>
|
|
<tr class="grp"><td colspan="2">Buttons (digital, pull-up)</td></tr>
|
|
<tr><td class="part">A (play/stop) / B (track)</td><td>GP12 / GP13</td></tr>
|
|
<tr><td class="part">X (+bpm) / Y (-bpm)</td><td>GP14 / GP15</td></tr>
|
|
<tr class="grp"><td colspan="2">Audio - over USB-MIDI (the Scroll Pack has no speaker)</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="spec" open>
|
|
<summary>Controls & views</summary>
|
|
<div class="spec-body">
|
|
<table class="bom">
|
|
<thead><tr><th>Button</th><th>Tap</th><th>Hold</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="part">A</td><td>play / stop</td><td>cycle view (Ticker → Grid → Pendulum)</td></tr>
|
|
<tr><td class="part">B</td><td>next track</td><td>next set list</td></tr>
|
|
<tr><td class="part">X</td><td>tempo +1</td><td>repeat (+5 after ~1.5 s)</td></tr>
|
|
<tr><td class="part">Y</td><td>tempo -1</td><td>repeat (-5 after ~1.5 s)</td></tr>
|
|
</tbody>
|
|
</table>
|
|
<table class="bom" style="margin-top:10px">
|
|
<thead><tr><th>View</th><th>What the 17×7 matrix shows</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="part">Ticker</td><td>the track name infinite-scrolls along the left; a beat strip on the top row marks the beats with a bright playhead; the BPM is pinned right, rotated 90° CCW (a hundreds dot-bar + the last two digits). The whole matrix flashes on the downbeat.</td></tr>
|
|
<tr><td class="part">Grid</td><td>lanes as rows, steps as columns; brightness = accent / normal / ghost; a bright playhead column tracks the beat (bars > 17 steps scale to fit - no steps dropped)</td></tr>
|
|
<tr><td class="part">Pendulum</td><td>a column bounces across the bar like a metronome arm, full-height flash on each beat</td></tr>
|
|
</tbody>
|
|
</table>
|
|
<p class="sub" style="margin-top:8px">The button mapping is deliberately simple (this is a UI prototype) and easy to re-bind in <code>rust/pm-grid/src/main.rs</code>.</p>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="spec" open>
|
|
<summary>Parts</summary>
|
|
<div class="spec-body">
|
|
<p class="sub">Two off-the-shelf parts, no soldering - 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 Pico Scroll Pack (PIM545) <span class="spec">- 17×7 white LED matrix (IS31FL3731) + 4 buttons</span></td><td class="q">1</td><td class="c">22</td></tr>
|
|
<tr><td class="part">Raspberry Pi Pico <span class="spec">- RP2040; pre-soldered headers so the pack presses on</span></td><td class="q">1</td><td class="c">5</td></tr>
|
|
<tr><td class="part">USB cable <span class="spec">- power + flashing (micro-USB)</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">≈ $29</td></tr>
|
|
</tbody>
|
|
</table>
|
|
<p class="sub" style="margin-top:10px">Reference: <a href="https://shop.pimoroni.com/products/pico-scroll-pack" target="_blank" rel="noopener">Pico Scroll Pack product page</a>
|
|
· <a href="https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_scroll" target="_blank" rel="noopener">vendor code (pico_scroll)</a>.</p>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="spec" open>
|
|
<summary>Firmware - native Rust (flash the <code>.uf2</code>)</summary>
|
|
<div class="spec-body">
|
|
<p class="sub">The Grid runs <b>native Rust firmware</b> (<code>rust/pm-grid</code>), sharing the same
|
|
<code>track-format</code> engine crate as the web editor and the other devices. It's a button-driven LED
|
|
metronome: <b>three views</b> (Ticker, Grid, Pendulum), the <b>built-in set lists</b>, tempo <b>ramp</b> +
|
|
<b>gap-trainer</b>, and a whole-matrix flash on the downbeat. <b>Audio plays through your computer over
|
|
USB-MIDI</b> - the Scroll Pack has no speaker. Flashing is a one-step <code>.uf2</code> drag (no CircuitPython,
|
|
no drive bundle).</p>
|
|
<p>
|
|
<a class="dl" href="/pm-grid.uf2" download>Download firmware (pm-grid.uf2) ↓</a>
|
|
<a class="dl alt" href="https://codeberg.org/VARASYS/metronome/src/branch/main/rust/pm-grid" target="_blank" rel="noopener">Source ↗</a>
|
|
</p>
|
|
<ol class="steps">
|
|
<li><b>Flash it:</b> hold <b>BOOTSEL</b> on the Pico, plug in USB (it appears as the <code>RPI-RP2</code> drive),
|
|
and drag <b>pm-grid.uf2</b> onto it. It flashes and reboots - "PM-G1 GRID" scrolls once, then the Ticker view.</li>
|
|
<li><b>Play through your computer:</b> plug the Pico into your computer, open the
|
|
<a href="/editor.html">editor</a> in Chrome / Edge / Firefox, click <b>🎹 Device audio</b>, and press
|
|
<b>A</b> on the device - each click sounds through your speakers over USB-MIDI (GM channel 10).</li>
|
|
<li><b>Controls:</b> <b>A</b> tap = play/stop, hold = cycle view; <b>B</b> tap = next track, hold = next set list;
|
|
<b>X / Y</b> = tempo up / down (auto-repeats while held).</li>
|
|
</ol>
|
|
<p class="sub" style="margin-top:8px">Built from <code>rust/pm-grid</code> via its <code>build.sh</code> (RP2040 /
|
|
<code>thumbv6m</code>). For development, a Raspberry Pi Debug Probe flashes it and streams logs over
|
|
<code>probe-rs</code> + <code>defmt</code> (the <code>pm-grid.elf</code> is served alongside for log decoding).
|
|
Live-sync editing, on-device practice log and editor firmware-push are on the roadmap (not in the Rust build yet).</p>
|
|
</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> and the button-driven <a href="/info-explorer.html">PM_X-1 Explorer</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>
|