Hermetic Modular

09/Hardware

The Board, As Built

This page is the electrical ground truth for Alchemy Lab.

Ten jacks, three kinds

J1 and J2 are AC-coupled codec inputs. They carry audio and can detect fast positive transitions through RisingEdge(). AC coupling removes held DC levels, so these inputs cannot measure pitch CV or preserve a sustained gate level.

Start the audio path with hw.StartAudio() or hw.StartAudio(AudioCallback) to run detection. RisingEdge() returns whether a rise has occurred since the previous read and clears that latch. Multiple rises between reads coalesce. No threshold configuration is required.

J3–J8 are switchable CV jacks. Each starts as an input. EnableCvOutput() connects its backing DAC to the jack; DisableCvOutput() disconnects the output. The input path remains connected to the jack.

J9 and J10 are DC-coupled codec outputs. Claiming a channel with EnableCvOutput() makes the SDK fill it with the latest voltage target after the application’s audio callback. Releasing it returns the channel to ordinary audio processing.

JacksOutput implementationSDK update behavior
J3–J6Shared 12-bit MCP4728 DACBlocking I²C writes; stage several targets and flush once to batch them
J7/J8Two 12-bit STM32 DAC channelsDirect DAC writes; no automatic per-sample stream
J9/J10Two 24-bit codec channelsA claimed voltage target is held across each audio block

Nominal CV output targets span ±5 V. J3–J8 input normalization spans approximately ±10 V at nominal supply. These ranges are separate from electrical maximum ratings.

ProcessAllControls() refreshes the exposed CV input values. Calling Volts() repeatedly does not produce a history of fresh, synchronized audio-rate samples.

J3–J8 apply their stored calibration to voltage conversion. J9/J10 use the standard Seed2 DFM circuit’s nominal transfer:

sample = volts / -8.666667

Use V2CodecCvSample() when converting a voltage into samples for an application-owned codec channel. The conversion assumes the stock audio gain and bounds generated samples to the codec range.

The codec’s 24-bit sample format does not imply 24-bit voltage accuracy. J9/J10 have no calibration readback path through CvJack, and Volts() reports their requested target rather than a measured output.

Self-calibration

Every production board calibrates its own six CV jacks, DAC out and ADC in, using the built-in DAC→jack→ADC loopback against the MCU's factory voltage reference.

Unpatch the CV jacks, hold B1+B2 while the board resets, and the panel narrates the ~15 second sweep, flashing green on success. The result is a 120-byte record in its own QSPI sector that survives reflashing: calibrate once, and every firmware picks it up automatically. SetVolts() and Volts() apply it transparently; hw.IsCalibrated() tells you a record exists.

Bootloader and update mode

The board runs a board-specific fork of the Daisy bootloader that serves DFU on the front-panel USB-C and renders its state on the rings: a warm-white comet during the ~2 second boot window, a slow breathe once latched into update mode by B3. It is open source like everything else.

Firmware can also jump there without hands on the panel:

daisy::System::ResetToBootloader(
    daisy::System::BootloaderMode::DAISY_INFINITE_TIMEOUT);

HostLink's reboot command uses exactly this, which is how the browser can update a module end to end.

Expansion header

The rear header gives a custom expander direct access to eight digital pins on the Daisy Seed2 DFM, plus ground and power. Use it for extra controls, sensors, displays, or a link between two Alchemy Labs.

Raw logic, beside power rails

These are 3.3 V MCU signals, without the voltage scaling of the front-panel jacks. Do not connect Eurorack CV, gates, ±12 V, 5 V logic, etc. Power down before making or changing a cable. The 20-position expansion header is not a Eurorack power connector.

01 /Pinout

Scroll diagram ↔
REAR HEADER · 20 POSITIONS3.3 V logic on B1–B8−12 V1GND2B23B44GND5B66B57B38B19B710+12 V11GND12GND13GND143V3A15GND16GND17GND18GND19B820Daisy Seed side
SPIUART / I²C4Shared I²C1PowerGround
Board-side view of the rear header, with the marked end at left and the Daisy Seed below. Numbers are SDK drawing positions, not standard odd/even IDC contact numbers. Confirm the orientation against your board before wiring.

B1 through B8 here are Seed connector names, not the front-panel buttons and not STM32 port numbers. This is confusing because we document the buttons as B1-3, and STM32 documents their ports as B1-N as well. But these are the ports on the SEED2DFM DATA SHEET. Don't mix them up. For example, header signal B3 is MCU pin PB6; the front-panel B3 button is on the onboard I/O expander. Use the signal names and the drawing together when making a harness.

Pins and alternate functions

All eight signals are digital GPIO-capable. The table shows useful peripheral assignments;AF is the STM32 alternate-function selection, not another physical connection. A pad can serve one function at a time. The D names are the aliases in daisy::seed.

If you are new to hardware, the way to think about this is that every pin on the microcontroller has certain extra capabilities baked into the hardware. This table shows what all these pins can be configured to do. The most important are I2C, UART, and SPI, as digital communication methods.

SDK position / signalMCU / DaisyMain useOther useful functions
3 · B2PG11 / D8SPI1 SCK · AF5I2S1 CK (AF5), LPTIM1 IN2 (AF1), SPDIFRX1 IN1 (AF8)
4 · B4PB5 / D10SPI1 MOSI · AF5TIM3 CH2 (AF2), SPI3 MOSI (AF7), SPI6 MOSI (AF8), UART5 RX (AF14)
6 · B6PG10 / D7SPI1 NSS · AF5I2S1 WS (AF5), SAI2 SD_B (AF10)
7 · B5PB7 / D14USART1 RX · AF7I2C4 SDA (AF6), TIM4 CH2 (AF2), LPUART1 RX (AF8), I2C1 SDA (AF4)
8 · B3PB6 / D13USART1 TX · AF7I2C4 SCL (AF6), TIM4 CH1 (AF2), LPUART1 TX (AF8), I2C1 SCL (AF4)
9 · B1PB4 / D9SPI1 MISO · AF5TIM3 CH1 (AF2), SPI3 MISO (AF6), SPI6 MISO (AF8), UART7 TX (AF11)
10 · B7PB8 / D11I2C1 SCL · AF4 · in useI2C4 SCL (AF6), TIM4 CH3 (AF2), TIM16 CH1 (AF1), UART4 RX (AF8)
20 · B8PB9 / D12I2C1 SDA · AF4 · in useI2C4 SDA (AF6), TIM4 CH4 (AF2), TIM17 CH1 (AF1), UART4 TX (AF8), SPI2 NSS (AF5)

Timer channels can generate PWM or capture edges with suitable firmware. The SPI1 pins also expose I²S1 clock, word select, and data functions; they are not analog audio inputs or outputs. None of these eight header signals is an ADC or DAC output. Other alternate functions exist in the STM32 datasheet, but silicon capability does not guarantee a ready-made libDaisy driver or an unused timer, DMA stream, or peripheral in your firmware.

B7 and B8 already have a job

B7 / PB8 (position 10) and B8 / PB9 (position 20) carry the busy onboard I²C1 bus. Use the free I²C4 bus on B3 / PB6 and B5 / PB7 first, with external pull-ups. If you do not need the MCP4728 DAC outputs on J3–J6, custom firmware can stop their updates and repurpose that bandwidth for extra I²C peripherals. This frees bandwidth, not the pins or the DAC's address. The PCA9557 GPIO expander at 0x18 is essential: never conflict with it or disable its service. It reads the front-panel B3 button and controls critical jack routing. Keep I²C1 on these pins and preserve the expander's operation.

Power and return paths

Never join the power rails of two Alchemy Labs

Do not connect position 1 / −12 V, position 11 / +12 V, or position 15 / 3V3A to the other Lab's supply rails. Power each module through its normal Eurorack power input. Ground is different: connect GND to GND for a direct logic link. The pull-up examples below use a local 3.3 V rail through resistors, never a direct 15-to-15 wire.
SDK positionNetUse with care
1 / 11−12 V / +12 VSupply rails for a designed expander. Account for the case supply, board path, connector, and cable current limits. Never connect to a signal pin.
153V3AThe board's 3.3 V analog rail. Do not backfeed it or treat it as an unlimited supply for displays, LEDs, or another processor. Added loads can disturb analog performance.
2, 5, 12–14, 16–19GNDCommon signal reference and return. Keep returns close to the signals they accompany.

Connecting two Alchemy Labs

Choose the link around what the modules need to exchange: UART for independent messages, SPI for clocked blocks, I²C4 for addressed control, or GPIO for clocks and handshakes. Each option below uses available signals and leaves both Labs' onboard I²C1 buses separate.

Before connecting: never join these pins between Labs

  • Do not join the power rails: 1 / −12 V, 11 / +12 V, or 15 / 3V3A.
  • Do not join the onboard I²C1 buses: 10 / B7 / SCL or 20 / B8 / SDA. Both boards have critical onboard devices at conflicting addresses.
  • Never use a full straight-through ribbon. Wire only the chosen signals and a common ground, such as 2 / GND to 2 / GND. Never connect two push-pull outputs, including UART TX to TX.
LinkGood forAdvantagesTradeoffs
UART / USART1Notes, parameters, settings, and status messages.Either Lab can send independently. Two signal wires plus ground.Needs packet framing and recovery. No shared clock. Uses B3/B5, so cannot share that pair with I²C4.
SPI1Fast blocks of samples or control data with explicit timing.Full-duplex transfers clocked by one controller. Leaves B3/B5 available.Four signals plus ground. The target must be ready before the controller clocks; needs buffering and a readiness protocol.
I²C4Small addressed commands, register-style data, or several targets.Two signal wires plus ground. B3/B5 are independent of the onboard DAC and GPIO expander.External pull-ups required. The controller initiates transfers. Shares pins with USART1; no I²C4 DMA in bundled libDaisy.
GPIO / timerClock, reset, trigger, and ready/busy signals at 3.3 V.Simple edges; hardware timer capture or PWM where supported. Can accompany a data link.One function per wire, with one output driving an input. Uses spare B1–B6 signals; no byte protocol by itself.

For an I²C link, I²C4 on B3/B5 is the first choice. Do not join the crowded B7/B8 buses between Labs, even if you do not use their DAC outputs: the essential expanders still collide at 0x18. All examples need matching custom firmware, short wiring, and a common ground. Header positions use the SDK drawing convention above.

Wire with both modules off and power each through its normal input. Do not drive an unpowered peer; independent power switching needs a buffer with specified powered-off protection or isolation. Series resistors alone do not prevent back-powering. Use a differential or isolated interface for longer links outside a common-ground case.

For audio or precisely timed events, design buffering, clock ownership, and recovery explicitly. A faster bus alone does not synchronize two audio engines. I²S1 is an advanced alternative on the SPI1 pads, requiring custom driver and clock setup; it cannot share those pads with SPI1 at the same time.

UART between two Labs

This USART1 example exchanges independent messages over a 3.3 V UART, with two crossed signal wires and a ground. Both ends use 115200 baud, 8 data bits, no parity, and 1 stop bit. This is a logic-level connection, not a MIDI DIN or RS-232 electrical interface.

02 / Two Labs over UART

Scroll diagram ↔
3 WIRES · 3.3 V LOGIC · SHORT IN-CASE LINKALCHEMY LAB AALCHEMY LAB BTX8 · B3 · PB6RX7 · B5 · PB72 · GNDTX8 · B3 · PB6RX7 · B5 · PB72 · GND100 Ω100 Ωcommon referenceNormal Eurorack powerNormal Eurorack powerLeave 1 / 11 / 15 (power) and 10 / 20 (I²C1) unconnected.
Signal schematic, not a connector view. Connect A position 8 to B position 7, B position 8 to A position 7, and GND to GND. The optional 100 Ω resistors sit near each transmitter to damp edges; they are not level shifters or protection against an unpowered peer.
  1. 1

    Wire with both modules off

    Confirm orientation, then connect A 8 / TX to B 7 / RX, B 8 / TX to A 7 / RX, and 2 / GND to 2 / GND. Check continuity and shorts before applying power. The two crossing signal lines in the drawing are not joined.
  2. 2

    Agree on a protocol

    Start at 115200 baud, 8 data bits, no parity, 1 stop bit on both ends. Add message framing, a version, a length, and a checksum as needed. Handle missing packets, peer resets, and buffer overflow. UART only moves bytes; it does not define what a parameter or clock message means.
UART setup · call after hw.Init() on both Labs
static daisy::UartHandler expansion_uart;

bool InitExpansionUart()
{
    daisy::UartHandler::Config cfg;
    cfg.periph = daisy::UartHandler::Config::Peripheral::USART_1;
    cfg.mode = daisy::UartHandler::Config::Mode::TX_RX;
    cfg.pin_config.tx = daisy::seed::D13; // PB6, B3, header position 8
    cfg.pin_config.rx = daisy::seed::D14; // PB7, B5, header position 7
    cfg.baudrate = 115200;
    cfg.wordlength = daisy::UartHandler::Config::WordLength::BITS_8;
    cfg.parity = daisy::UartHandler::Config::Parity::NONE;
    cfg.stopbits = daisy::UartHandler::Config::StopBits::BITS_1;
    return expansion_uart.Init(cfg) == daisy::UartHandler::Result::OK;
}

Check the return value before starting communication. This initializes the port only; add reception, buffering, and your message parser in your firmware. Keep blocking transmit and receive calls out of the audio callback. Queue control messages, and use the driver's asynchronous facilities where appropriate.

SPI between two Labs

Use SPI1 for clocked blocks of data: one Lab is the controller and the other is the target. Connect matching signal names, including MOSI → MOSI and MISO → MISO. Unlike UART, these wires do not cross. The controller drives SCK, MOSI, and active-low chip select; the target drives MISO. B3/B5 remain available for UART or a separate I²C4 link.

03 / Two Labs over SPI

Scroll diagram ↔
SPI1 · MODE 0 · 8 BIT · MSB FIRSTAlchemy Lab AController · owns the clockAlchemy Lab BTarget · ready before SCKSCK · output3 · B2 · PG11SCK · input3 · B2 · PG11MOSI · output4 · B4 · PB5MOSI · input4 · B4 · PB5MISO · input9 · B1 · PB4MISO · output9 · B1 · PB4NSS / CS · output6 · B6 · PG10NSS / CS · input6 · B6 · PG10B 15 · 3V3A10 kΩGND2 · groundGND2 · groundEach Lab uses its normal power input · no power-rail or I²C1 links
Signal schematic, using SDK header positions. All four signals connect to the same name on the other Lab. Arrows show who drives each line. The 10 kΩ resistor is a local pull-up at Lab B, from NSS to B's own position 15 / 3V3A. Never join the two 3V3A rails.

Add a 10 kΩ pull-up from the target's NSS to its own 3V3A so it stays deselected while the controller resets. This is a local resistor connection at Lab B, not a wire between the two supply rails. Use the same short, common-ground, jointly powered arrangement described above. Leave positions 1, 11, 10, and 20 out of the harness.

Mode 0, one selected block at a time

Start with 8-bit words, MSB first, clock idle low, and sampling on the first rising edge (mode 0) on both ends. The example uses the slowest SPI prescaler, PS_256; SCK is the SPI peripheral clock divided by 256, not the CPU clock. Check the actual rate and edges at the receiver before speeding up. The controller uses a GPIO for chip select, keeping NSS low across the whole block; the target uses hardware NSS input.

SPI1 setup · after hw.Init()
#include "daisy_seed.h"
#include "per/spi.h"

// Build Lab A with true; build Lab B with false.
static constexpr bool kSpiController = true;
static daisy::SpiHandle expansion_spi;
static daisy::GPIO expansion_cs;

bool InitExpansionSpi()
{
    using C = daisy::SpiHandle::Config;
    if(kSpiController)
    {
        // Preload HIGH before enabling the output, avoiding a low pulse.
        expansion_cs.Init(daisy::seed::D7, daisy::GPIO::Mode::INPUT);
        expansion_cs.Write(true);
        expansion_cs.Init(daisy::seed::D7, daisy::GPIO::Mode::OUTPUT);
    }
    C cfg;
    cfg.periph = C::Peripheral::SPI_1;
    cfg.mode = kSpiController ? C::Mode::MASTER : C::Mode::SLAVE;
    cfg.direction = C::Direction::TWO_LINES;
    cfg.nss = kSpiController ? C::NSS::SOFT : C::NSS::HARD_INPUT;
    cfg.pin_config.sclk = daisy::seed::D8;  // PG11, B2, position 3
    cfg.pin_config.mosi = daisy::seed::D10; // PB5,  B4, position 4
    cfg.pin_config.miso = daisy::seed::D9;  // PB4,  B1, position 9
    cfg.pin_config.nss = daisy::seed::D7;   // PG10, B6, position 6
    cfg.datasize = 8;
    cfg.clock_polarity = C::ClockPolarity::LOW;
    cfg.clock_phase = C::ClockPhase::ONE_EDGE;
    cfg.baud_prescaler = C::BaudPrescaler::PS_256;
    return expansion_spi.Init(cfg) == daisy::SpiHandle::Result::OK;
}

A first exchange

  1. 1

    Prepare the target before sending

    Check initialization on both ends. For a bench test, have Lab B enter the blocking exchange below before manually triggering Lab A within its 3 s receive window. Use four bytes on each end, for example A sends 01 02 03 04 and B preloads A1 A2 A3 A4. Each should receive the other's four bytes. Retry a missed window with both ends reset to the same block boundary.
  2. 2

    Define readiness and replies in your protocol

    SPI has no acknowledgment or automatic flow control. For a reliable application, arm target DMA with persistent, DMA-accessible buffers and correct cache handling, then signal readiness through a separate GPIO or UART handshake. Account for the driver's queued DMA transfers: requesting a transfer is not proof that it has started. A reply dependent on the current request normally belongs in the next transaction; the target cannot prepare it retroactively while those bits are clocked.
Blocking bring-up helper · main loop only
// Same byte count on both Labs. Target must be waiting first.
// Use separate tx and rx buffers. Call only after successful Init.
bool ExchangeExpansionBlock(uint8_t* tx, uint8_t* rx, size_t size)
{
    if(kSpiController)
    {
        expansion_cs.Write(false);
        daisy::System::DelayUs(1); // NSS setup time
    }
    const auto result = expansion_spi.BlockingTransmitAndReceive(
        tx, rx, size, kSpiController ? 100 : 3000); // Bench-test timeouts
    if(kSpiController)
    {
        daisy::System::DelayUs(1); // NSS hold time
        expansion_cs.Write(true); // Release on success AND failure
    }
    return result == daisy::SpiHandle::Result::OK;
}

A successful controller call only means it finished clocking; it does not prove that the target was ready or received valid data. Validate a known response, sequence number, and checksum. On a timeout or partial block, stop sending, release NSS, and re-arm both ends through your recovery protocol. Do not run this blocking bring-up helper inside an audio callback or use it as an unattended streaming protocol.

I2C between two Labs

Recommended I²C pair: B3 and B5

Use B3 / PB6 / position 8 for SCL and B5 / PB7 / position 7 for SDA, on I²C4 (AF6). These signals are unused by the onboard V2 hardware and BSP, so the link has no onboard expander or DAC traffic. Keep B7/B8 on each board's internal I²C1 bus. B3/B5 also carry USART1 in the UART example: choose UART or I²C4 on this pair, not both.

04 / A separate I²C bus on B3 + B5

Scroll diagram ↔
I²C4 · OPEN-DRAIN · START AT 100 kHzLab A · controllerLab B · target15 · 3V3A · local7-bit address 0x424.7 kΩ4.7 kΩSCL · open-drain8 · B3 · PB6SDA · open-drain7 · B5 · PB7GND2 · groundSCL · open-drain8 · B3 · PB6SDA · open-drain7 · B5 · PB7GND2 · groundPower both Labs together · never connect 15 directly to 15B7 / B8 stay on each board's internal I²C1 bus
Signal schematic, using SDK header positions. Add two 4.7 kΩ pull-ups, one per line, to Lab A's local 3V3A rail as a starting point for 100 kHz. Dots mark electrical joins; the gap where the SDA pull-up crosses SCL means no connection. Do not cross SCL and SDA.

Connect 8 ↔ 8 for SCL, 7 ↔ 7 for SDA, and 2 ↔ 2 for ground. Both signal lines must be open-drain. Assign Lab A as the I²C4 controller and Lab B as a target at a chosen, non-reserved 7-bit address, such as 0x42. Each Lab keeps its own I²C1 controller, PCA9557, and MCP4728 on a separate electrical bus; their duplicate addresses do not matter here. Do not move hw.i2c onto these pins.

Two external pull-ups are required

B3/B5 need their own pull-ups: the resistors on B7/B8 do not reach this separate bus, and libDaisy configures the I²C pins without internal pulls. Start with one 4.7 kΩ resistor from SCL to 3.3 V and one from SDA to 3.3 V for a short 100 kHz link. These are pull-ups, not series resistors and not resistors between SCL and SDA. Use one pair for the whole bus; extra pairs combine in parallel. Do not rely on weak MCU internal pull-ups.

The drawing takes both pull-ups to Lab A's position 15 / 3V3A locally. Do not connect A's position 15 to B's position 15. This small load biases the lines, it does not power the other Lab; it draws about 0.7 mA per low line and still adds switching load to the analog rail. Power both modules together and do not leave the bus pulled high into an unpowered peer. Independent power sequencing needs a buffer with powered-off protection or isolation.

4.7 kΩ is a starting value, not a cable-length guarantee. Measure the 30–70% rise time: allow at most 1,000 ns at 100 kHz or 300 ns at 400 kHz. The estimate tᵣ ≈ 0.8473 × R × C gives about 800 ns for 4.7 kΩ and 200 pF, suitable for the 100 kHz rise-time limit but too slow for 400 kHz. Include both boards and the cable in C. If reducing resistance, also check the weakest device's low-level sink-current limit.

Separate I²C4 · after hw.Init()
#include "daisy_seed.h"
#include "per/i2c.h"

static daisy::I2CHandle expansion_i2c;

// Lab A: InitExpansionI2c(true). Lab B: InitExpansionI2c(false).
bool InitExpansionI2c(bool controller)
{
    using C = daisy::I2CHandle::Config;
    C cfg;
    cfg.periph = C::Peripheral::I2C_4; // Keep hw.i2c on I2C1!
    cfg.mode = controller ? C::Mode::I2C_MASTER : C::Mode::I2C_SLAVE;
    cfg.speed = C::Speed::I2C_100KHZ;
    cfg.pin_config.scl = daisy::seed::D13; // PB6, B3, position 8
    cfg.pin_config.sda = daisy::seed::D14; // PB7, B5, position 7
    cfg.address = 0x42; // Target's 7-bit address, no left shift
    return expansion_i2c.Init(cfg) == daisy::I2CHandle::Result::OK;
}

Check initialization, then implement matching target receive/transmit handling before the controller starts transactions. Pass the unshifted 0x42 to libDaisy's controller transfer methods; the driver adds the address shift. Handle NACKs, timeouts, and peer resets in the main/control loop. The SDK's bundled libDaisy returns an error for I²C4 DMA calls, so its DMA API cannot make this link asynchronous. For low-latency continuous traffic, prefer UART/SPI or write a suitable interrupt-driven I²C4 driver.

Sharing the onboard I2C bus

This is a fallback for an extra peripheral, not the recommended inter-module link. hw.Init() starts hw.i2c as the I²C1 controller at 400 kHz on B7/PB8 and B8/PB9, already busy with the GPIO expander and DAC. It is a poor place for continuous display refreshes, audio streams, or a device that holds the clock for long periods. Use the free I²C4 bus on B3/PB6 and B5/PB7 instead, with external pull-ups.

05 / Onboard I²C1 · fallback only

Scroll diagram ↔
BUSY ONBOARD BUS · USE B3 / B5 I²C4 FIRSTExtra peripheral only if needed. Never join two Alchemy Labs here.STM32 · I²C1 controllerhw.i2c · 400 kHz10 · B7 · SCL20 · B8 · SDAPCA95570x18B3 button · jack routingEssential · do not conflictMCP47280x60–0x67 · detectedCV DAC outputs · J3–J6Optional peripheralOnly if I²C4 cannot be usedShares the crowded bus
Prefer the separate B3/B5 I²C4 bus. This branch shares critical board traffic: the GPIO expander must keep working, including the front-panel B3 button and jack routing. Stopping DAC updates frees bandwidth, but its address stays occupied. Do not join two Labs here.
7-bit addressDevicePreserve
0x18PCA9557 GPIO expanderEssential: front-panel B3, J3–J8 routing, and DAC LDAC. Never conflict with this address or interrupt its operation.
0x60–0x67MCP4728 quad DACOne address is occupied; reserve the range because the SDK probes it with real DAC writes. Stopping DAC updates does not disconnect it.

If J3–J6 DAC outputs are not needed, custom firmware can stop their updates and use that bandwidth for additional I²C traffic while keeping the expander serviced. Check hw.dac.Ready() before reading hw.dac.Address() for the detected DAC address. Never directly join two stock I²C1 buses: their expanders still conflict even without DAC traffic.

Adding a peripheral without taking over the board

Use the existing hw.i2c handle, a unique address outside 0x18 and 0x60–0x67, and 3.3 V open-drain signals with checked combined pull-up resistance. Serialize short, bounded transfers with the BSP in the control loop, never in the audio callback. Keep I²C1's pins, controller mode, and expander service intact. Do not send general-call resets, address-programming commands, or arbitrary writes to onboard devices.

Electrical limits and bring-up

Keep header logic between ground and 3.3 V. Some MCU pads have conditional 5 V tolerance, but this header is not specified as a 5 V interface. Add level shifting for other logic families and appropriate input/output conditioning for any connection exposed at a panel. Never connect two push-pull outputs together. Give input and chip-select lines defined startup states, and account for reset, reflashing, and either end losing power.

Keep cables short, route a nearby ground return, and start with conservative bus speeds. Check edges and voltage levels at the receiver before raising the rate. Pull-up resistance, cable capacitance, and loading set the usable I²C speed; an arbitrary cable length or clock rate is not guaranteed here. Provide decoupling and a reviewed power budget for a powered expander. Never assume the header can power a second Alchemy Lab.

A useful first test

Exchange a counter over UART while audio runs. Reset each peer and unplug/reconnect only with power off. Then confirm B3 still responds and CV output switching on J3–J8 still works. Test the actual cable, power sequence, and firmware together before building the feature around it. The wiring above is a design reference, not a report of a bench-tested assembly.

Source references

Pin positions follow the Alchemy SDK header reference. Board ownership and addresses follow the V2 initialization, V2 layout constants, and DAC probe implementation. Seed/MCU mapping is documented in the Seed2 DFM datasheet, table 2; alternate functions are in STM32H750 DS12556, table 8. The examples follow the SDK's bundled SPI driver and I²C driver. Pull-up sizing follows TI's I²C Bus Pull-Up Resistor Calculation. Recheck these against your board revision and SDK when designing hardware.