memlnaut-nisps/firmware/useq-celium/shared/protocol.h
monkey-w1n5t0n 4656568d4f feat(manifold,firmware): restore uSEQ CV/gate as an Outputs backend
Restores the April-2026 "uSEQ-Celium" functionality (browser → uSEQ
hardware + CV expander over USB Web Serial) as a first-class Manifold
Outputs backend, and re-vendors the RP2040 firmware into the repo.

- protocol v2 (uSEQ-CV): firmware/useq-celium/shared/protocol.h is the
  single source of truth, mirrored by manifold/src/backends/useq-protocol.ts.
  26-byte OUTPUT frame, 11×u16 CV (12-bit) + 3-gate bitfield + XOR; fixed
  topology; host-agnostic so the MEMLNaut RP2350 can emit identical bytes.
  Spec in docs/useq-celium/protocol.md.
- firmware/useq-celium/{main,expander}: PlatformIO RP2040 firmware rewritten
  to v2 from the real April pin maps (expander I2C addr 0x10).
- UseqCvBackend (id cvgate): Web Serial connect/identify/disconnect, 100 Hz
  stream, per-channel dead-zone, gate thresholding; modeled on midi-backend.
  Per-output CvSpec (channel + gateThreshold) on MFParam; config UI in
  OutputsBackendConfig + BackendAdvanced; new "CV / uSEQ" top-dock mode.
- bun-test for the protocol frame layout; MAP.md updated.
2026-06-28 22:30:54 +02:00

73 lines
4.7 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// uSEQ-CV wire protocol v2 — single source of truth (C/C++ side).
//
// Mirrored byte-for-byte by manifold/src/backends/useq-protocol.ts. Any change
// here MUST be reflected there (a unit test asserts the frame sizes match).
//
// Topology (fixed): 3 CV + 3 gate on the uSEQ main module, 8 CV on the expander
// = 11 CV (CV1..CV11) + 3 gate (GATE1..GATE3).
// CV1..CV3 → main module PWM (a1,a2,a3)
// CV4..CV11 → expander module PWM (e1..e8, forwarded over I2C)
// GATE1..3 → main module digital (d1,d2,d3)
//
// Host-agnostic: the sender may be the browser (Web Serial) or the MEMLNaut
// RP2350 firmware (USB/UART) — identical bytes either way.
//
// Transport: USB CDC / UART @ 115200, little-endian, XOR checksum, fixed-length
// frames keyed by a sync byte + type so a stream parser can resync after a drop.
#pragma once
#include <stdint.h>
// ─── Sync bytes ──────────────────────────────────────────────────────────────
static const uint8_t USEQ_SYNC_HOST = 0xAA; // host → uSEQ
static const uint8_t USEQ_SYNC_DEV = 0xBB; // uSEQ → host
static const uint8_t USEQ_SYNC_I2C = 0xCC; // main → expander
static const uint8_t USEQ_SYNC_I2C_IDENTIFY = 0xDD; // main → expander, identify
// ─── Message types ───────────────────────────────────────────────────────────
static const uint8_t USEQ_MSG_OUTPUT = 0x01; // host → uSEQ : CV + gate values
static const uint8_t USEQ_MSG_IDENTIFY = 0x03; // host → uSEQ : flash LEDs, ack
static const uint8_t USEQ_MSG_INPUT = 0x01; // uSEQ → host : i1,i2,ai1,ai2
// (0x02 reserved — the old CONFIG frame; topology is fixed in v2, so unused.)
// ─── Topology ────────────────────────────────────────────────────────────────
static const uint8_t USEQ_NUM_CV = 11; // CV1..CV11
static const uint8_t USEQ_NUM_GATE = 3; // GATE1..GATE3
static const uint8_t USEQ_NUM_MAIN_CV = 3; // CV1..CV3 live on the main board
static const uint8_t USEQ_NUM_EXP_CV = 8; // CV4..CV11 live on the expander
static const uint8_t USEQ_I2C_ADDR = 0x10;// expander I2C slave address
// ─── Value ranges ────────────────────────────────────────────────────────────
static const uint16_t USEQ_CV_MAX = 4095; // 12-bit canonical wire value
static const uint16_t USEQ_PWM_MAX = 2047; // 11-bit hardware PWM (RP2040 analogWrite)
// ─── Frame sizes ─────────────────────────────────────────────────────────────
// OUTPUT: sync(1) + type(1) + 11×u16 CV(22) + gate-bits(1) + xor(1)
static const uint8_t USEQ_FRAME_OUTPUT_LEN = 2 + USEQ_NUM_CV * 2 + 1 + 1; // 26
static const uint8_t USEQ_FRAME_IDENTIFY_LEN = 3; // sync + type + xor
static const uint8_t USEQ_FRAME_ACK_LEN = 4; // sync + type + status + xor
static const uint8_t USEQ_FRAME_INPUT_LEN = 11; // sync + type + 4×u16 + xor
// I2C (main → expander): sync(1) + 8×u16(16) + xor(1)
static const uint8_t USEQ_FRAME_I2C_LEN = 1 + USEQ_NUM_EXP_CV * 2 + 1; // 18
// ─── Byte offsets (OUTPUT frame) ─────────────────────────────────────────────
static const uint8_t USEQ_OFF_CV0 = 2; // first CV u16
static const uint8_t USEQ_OFF_GATES = 2 + USEQ_NUM_CV * 2; // 24
static const uint8_t USEQ_OFF_OXSUM = USEQ_FRAME_OUTPUT_LEN - 1; // 25
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint8_t useq_xor(const uint8_t* buf, uint8_t start, uint8_t end) {
uint8_t cs = 0;
for (uint8_t i = start; i <= end; i++) cs ^= buf[i];
return cs;
}
static inline uint16_t useq_read_u16le(const uint8_t* p) {
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static inline void useq_write_u16le(uint8_t* p, uint16_t v) {
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
}
// Scale a 12-bit wire CV (0..USEQ_CV_MAX) down to 11-bit hardware PWM.
static inline uint16_t useq_cv_to_pwm(uint16_t cv) {
return (cv > USEQ_CV_MAX ? USEQ_CV_MAX : cv) >> 1;
}