{
  "family": "Drive",
  "name": "P",
  "rev": "a",
  "tile_id": 5,
  "json_version": "0.6",
  "updated_at": "2026-05-01T12:42:45.189Z",
  "headline": "piezoelectric sensor/actuator driver",
  "description": "Built around the Boréas Technologies BOS1921 driver, the advanced DRIVE.P tile provides an ultra-compact, fully-integrated solution for interfacing with piezoelectric sensors and actuators. With a maximum peak-to-peak differential output amplitude of 190V, the ability to drive one or two capacitive loads up to a total of 820nF, a built-in waveform synthesizer and high-speed 1024-sample continuous-playback FIFO, as well as a sensing resolution of just 7.6mV, this tile provides an unparalleled level of integration for next-generation haptics and other piezoactuator solutions.",
  "application_notes": [
    {
      "details": "A single piezo connected across the OUT terminals can be used for both sensing and output, including bipolar output",
      "heading": "Single Piezo"
    },
    {
      "details": "Two piezos can be driven by in a unipolar configuration by connecting the positive lead of each piezo to each of the OUT pads, and the negative terminals to GND.  Sensing is not possible in this configuration.",
      "heading": "Dual Piezo"
    }
  ],
  "package": {
    "pads": 10,
    "type": "T44",
    "size_x": 4000,
    "size_y": 4000,
    "size_z": 1800
  },
  "power": [
    {
      "max": 5.5,
      "min": 1.8,
      "type": "system",
      "notes": "",
      "gnd_pad": [
        "1"
      ],
      "function": "",
      "direction": "input",
      "is_required": true,
      "max_current": "",
      "positive_pad": [
        "10"
      ]
    },
    {
      "max": 5.5,
      "min": 3,
      "type": "drive",
      "notes": "",
      "gnd_pad": [
        "1"
      ],
      "function": "",
      "direction": "input",
      "is_required": true,
      "max_current": "",
      "positive_pad": [
        "9"
      ]
    }
  ],
  "components": [
    {
      "url": "https://www.boreas.ca/products/bos1921-piezo-driver",
      "part": "BOS1921",
      "datasheet": "https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/5/Bor_as_Technologies-BOS1921.pdf",
      "manufacturer": "Boréas Technologies"
    }
  ],
  "pads": [
    {
      "pad": "1",
      "geometry": {
        "size_x": 1000,
        "size_y": 400,
        "center_x": -1500,
        "center_y": 1600
      },
      "functions": [
        {
          "type": "power",
          "function": "GND",
          "direction": ""
        }
      ]
    },
    {
      "pad": "3",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": 0
      },
      "functions": [
        {
          "type": "digital",
          "function": "GPIO",
          "direction": "bidirectional"
        }
      ]
    },
    {
      "pad": "4",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": -800
      },
      "functions": [
        {
          "note": "when using a non-Core processor, ensure adequate external pull-up resistance.",
          "type": "digital",
          "function": "I2C.CLK",
          "direction": "bidirectional",
          "interface": "I2C",
          "is_default": true
        },
        {
          "type": "digital",
          "function": "I3C.CLK",
          "direction": "bidirectional",
          "interface": "I3C"
        }
      ]
    },
    {
      "pad": "5",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": -1600
      },
      "functions": [
        {
          "note": "when using a non-Core processor, ensure adequate external pull-up resistance.",
          "type": "digital",
          "function": "I2C.DAT",
          "direction": "bidirectional",
          "interface": "I2C",
          "is_default": true
        },
        {
          "type": "digital",
          "function": "I3C.DAT",
          "direction": "bidirectional",
          "interface": "I3C"
        }
      ]
    },
    {
      "pad": "7",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": -800
      },
      "functions": [
        {
          "note": "",
          "type": "drive",
          "function": "OUT+",
          "direction": "output",
          "is_default": true
        }
      ]
    },
    {
      "pad": "8",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": 0
      },
      "functions": [
        {
          "note": "",
          "type": "drive",
          "function": "OUT-",
          "direction": "output",
          "is_default": true
        }
      ]
    },
    {
      "pad": "9",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": 800
      },
      "functions": [
        {
          "note": "3.0-5.5V supply for the power stage",
          "type": "power",
          "function": "V_DRIVE",
          "direction": "input",
          "is_default": true
        }
      ]
    },
    {
      "pad": "10",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": 1600
      },
      "functions": [
        {
          "note": "1.8-5.5V",
          "type": "power",
          "function": "V+",
          "direction": "input"
        }
      ]
    },
    {
      "pad": "2",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": 800
      },
      "functions": []
    },
    {
      "pad": "6",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": -1600
      },
      "functions": []
    }
  ],
  "interfaces": [
    {
      "name": "I2C",
      "type": "I2C",
      "parameters": {
        "modes": [
          "slave"
        ],
        "addresses": [
          {
            "address": "0x5A",
            "is_default": true
          }
        ],
        "data_bits": [
          8
        ],
        "address_bits": [
          7
        ],
        "max_clock_speed": "1MHz"
      },
      "pad_assignments": [
        {
          "pad": "4",
          "role": "bus",
          "function": "I2C.CLK",
          "is_required": true
        },
        {
          "pad": "5",
          "role": "bus",
          "function": "I2C.DAT",
          "is_required": true
        }
      ],
      "mutually_exclusive": [
        "I3C"
      ]
    },
    {
      "name": "I3C",
      "type": "I3C",
      "parameters": {
        "modes": [
          "slave"
        ]
      },
      "pad_assignments": [
        {
          "pad": "4",
          "role": "bus",
          "function": "I3C.CLK",
          "is_required": true
        },
        {
          "pad": "5",
          "role": "bus",
          "function": "I3C.DAT",
          "is_required": true
        }
      ],
      "mutually_exclusive": [
        "I2C"
      ]
    }
  ],
  "twin": {
    "score": 1,
    "source": "// Digital twin for Drive.P — Boréas BOS1921 piezoelectric haptic driver.\n//\n// A high-voltage piezo driver: an integrated boost converter (fed from V_DRIVE,\n// pad 9) swings a differential output OUT+/OUT- (pads 7/8) up to 190 Vpp (±95 V)\n// to drive a piezo actuator, and the same pins sense the piezo's voltage for\n// touch detection. Logic runs off V+ (pad 10). I²C on pads 4/5, GPIO on pad 3.\n//\n// Output-centric like Drive.H: firmware picks a mode + writes samples (FIFO) or\n// reads the sense channel. This twin models the drive envelope (0..1) on OUT±,\n// the sense/touch readback, status/fault, and the two-supply power layer.\n//\n// Conversions are datasheet/driver-accurate (canonical): full-scale 190 Vpp\n// (±95 V high-V, ±13.28 V low-V), sense 7.6 mV/LSB fine · 54.5 mV/LSB coarse,\n// 12-bit signed samples, IC_STATUS state/fault bits. The V_DRIVE power layer is\n// a CV²f + HV-hold model (see the constants block) fit to the datasheet's\n// average-supply-current anchors — it tracks amplitude, tone frequency, output\n// range, piezo load and supply voltage — but keeps an inferred loss/efficiency\n// factor, so the power layer is tagged \"inferred\".\nimport type { TileSim } from '../tileSim';\n\nconst I2C_ADDR = 0x44;\nconst CHIP_ID = 0x0781;\n\n// return-register selector (COMM.RDADDR) that read() reflects\nconst REG_IC_STATUS = 0x10;\nconst REG_SENSE_VAL = 0x18;\nconst REG_CHIP_ID = 0x1e;\n\n// modes (drive_p_mode_t)\nconst MODE_IDLE = 0;\nconst MODE_SENSE_FINE = 1;\nconst MODE_SENSE_COARSE = 2;\nconst MODE_PLAY_DIRECT = 3;\nconst MODE_PLAY_FIFO = 4;\nconst MODE_PLAY_RAM_SYNTH = 5;\n\n// IC_STATUS fields\nconst STATE_IDLE = 0x0000;\nconst STATE_RUNNING = 0x0200;\nconst STATE_ERROR = 0x0300;\nconst FAULT_BITS = 0x0004; // a representative fault bit within FAULT_MASK 0x00FC\n\nconst SAMPLE_FS = 2047; // 12-bit signed full-scale\nconst UVLO_MV = 3000; // V_DRIVE minimum (boost won't run below this)\n\n// Power model — V_DRIVE (VBUS) draw, anchored on BOS1921 datasheet Table 7\n// (IBUS,AVG; conditions Vbus = 3.6 V). A piezo is a capacitor, so the boost\n// supply current is dominated by reactive energy ≈ C·V²·f, plus a static cost to\n// hold the high-voltage rail. While driving:\n//     I_VDRIVE = IDLE + K_STATIC·Vpk            (HV-hold; fit to DC anchor)\n//                     + K_DYN·C·Vpp²·f / Vbus   (reactive CV²f; fit to 90 mA)\n// Datasheet anchors: sleep 0.6 µA / 2.4 µA w/ retention · idle 530 µA ·\n//   DC-hold 3.7 mA @ 95 V · 90 mA @ 190 Vpp / 300 Hz / 100 nF.\n// Validation point (190 Vpp / 200 Hz / 10 nF) predicts ~9.5 mA vs 14.5 mA spec\n// (~1.5× low — small-cap fixed/switching losses aren't separated out), so the\n// power layer stays \"inferred\" despite the datasheet-anchored shape.\nconst IQ_SLEEP_RET_UA = 2.4;\nconst IQ_SLEEP_NORET_UA = 0.6;\nconst IDLE_UA = 530;\nconst VPLUS_ACTIVE_UA = 50; // V+ logic/IO (small; datasheet lumps Iq under V_DRIVE)\nconst K_STATIC_UA_PER_V = 33.4; // HV-hold: 95 V → +3.17 mA over idle (DC anchor)\nconst K_DYN = 0.287; // reactive coefficient (A·V / F·V²·Hz), fit to the 90 mA anchor\nconst DEFAULT_LOAD_NF = 100; // datasheet characterization load (actuator is external)\n\ninterface State {\n  // ── inputs (controls) ──\n  supply_mv: number; // V_DRIVE (boost supply)\n  amplitude: number; // last FIFO/reference sample magnitude, 0..2047\n  freq_hz: number; // waveform/tone frequency (drives reactive power)\n  load_nf: number; // external piezo capacitance\n  mode: number;\n  sense_mv: number; // externally applied piezo voltage (for sense/touch)\n  fault: number; // chip fault/error\n  sleeping: number; // SLEEP state\n\n  // ── config ──\n  output_range: number; // 0 = ±95 V (high-V), 1 = ±13.28 V (low-V)\n  sense_gain: number; // 1 = fine 7.6 mV/LSB, 0 = coarse 54.5 mV/LSB\n  retain: number; // RET: retain RAM/regs in sleep\n  auto_sleep: number; // TOUT\n  upi: number; // unidirectional power input\n  return_reg: number; // which register read() reflects\n\n  [field: string]: number;\n}\n\nconst pick = (args: number[], i: number, cur: number) =>\n  args.length > i && Number.isFinite(args[i]) ? args[i] : cur;\nconst clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\n\nconst isPlayMode = (m: number) =>\n  m === MODE_PLAY_DIRECT || m === MODE_PLAY_FIFO || m === MODE_PLAY_RAM_SYNTH;\nconst powered = (s: State) => s.sleeping !== 1 && s.supply_mv >= UVLO_MV;\n\n// Differential drive envelope magnitude 0..1 (the |OUT± swing| / full-scale).\nfunction driveStrength(s: State): number {\n  if (!powered(s) || !isPlayMode(s.mode)) return 0;\n  return clamp(s.amplitude / SAMPLE_FS, 0, 1);\n}\n\n// Full-scale peak output for the active range, in mV (±95 V or ±13.28 V).\nconst outFsMv = (s: State) => (s.output_range === 1 ? 13_280 : 95_000);\n\n// Sense channel: applied piezo mV → signed 12-bit raw at the active LSB.\nfunction senseRaw(s: State): number {\n  const lsbTenthsMv = s.sense_gain === 1 ? 76 : 545; // 7.6 / 54.5 mV in 0.1 mV\n  const raw = Math.round((s.sense_mv * 10) / lsbTenthsMv);\n  return clamp(raw, -2048, 2047);\n}\n\nfunction statusWord(s: State): number {\n  if (s.fault === 1) return STATE_ERROR | FAULT_BITS;\n  return driveStrength(s) > 0 ? STATE_RUNNING : STATE_IDLE;\n}\n\n// What a generic read() returns, per the selected return register.\nfunction readReturn(s: State): number {\n  if (s.return_reg === REG_SENSE_VAL) return senseRaw(s) & 0x0fff;\n  if (s.return_reg === REG_CHIP_ID) return CHIP_ID;\n  return statusWord(s); // IC_STATUS default\n}\n\nconst sim: TileSim<State> = {\n  tile: 'Drive.P',\n\n  defaultState: {\n    supply_mv: 3700,\n    amplitude: 0,\n    freq_hz: 200,\n    load_nf: DEFAULT_LOAD_NF,\n    mode: MODE_IDLE,\n    sense_mv: 0,\n    fault: 0,\n    sleeping: 0,\n\n    output_range: 0, // ±95 V\n    sense_gain: 1, // fine\n    retain: 1,\n    auto_sleep: 0,\n    upi: 0,\n    return_reg: REG_CHIP_ID, // post-reset default\n  },\n\n  controls: [\n    { type: 'slider', field: 'amplitude', label: 'Drive amplitude', min: 0, max: 2047, step: 1 },\n    {\n      type: 'slider',\n      field: 'freq_hz',\n      label: 'Tone frequency',\n      min: 0,\n      max: 1000,\n      step: 5,\n      unit: 'Hz',\n    },\n    {\n      type: 'slider',\n      field: 'load_nf',\n      label: 'Piezo load',\n      min: 1,\n      max: 470,\n      step: 1,\n      unit: 'nF',\n    },\n    { type: 'slider', field: 'mode', label: 'Mode (0 idle…5)', min: 0, max: 5, step: 1 },\n    {\n      type: 'slider',\n      field: 'supply_mv',\n      label: 'V_DRIVE supply',\n      min: 2500,\n      max: 5500,\n      step: 50,\n      unit: 'mV',\n    },\n    {\n      type: 'slider',\n      field: 'sense_mv',\n      label: 'Sensed piezo V',\n      min: -1000,\n      max: 1000,\n      step: 5,\n      unit: 'mV',\n    },\n    { type: 'toggle', field: 'output_range', label: 'Low-V range (±13.28 V)' },\n    { type: 'toggle', field: 'sense_gain', label: 'Fine sense (7.6 mV)' },\n    { type: 'toggle', field: 'fault', label: 'Fault' },\n    { type: 'toggle', field: 'sleeping', label: 'Sleep' },\n  ],\n\n  hostCalls: {\n    // ── lifecycle ──\n    tile_drive_p_find: () => ({ scalar: I2C_ADDR }),\n    tile_drive_p_init: () => ({\n      scalar: 0,\n      nextState: {\n        mode: MODE_IDLE,\n        amplitude: 0,\n        sleeping: 0,\n        sense_gain: 1,\n        output_range: 0,\n        return_reg: REG_CHIP_ID,\n      },\n    }),\n    tile_drive_p_reset: () => ({\n      nextState: { mode: MODE_IDLE, amplitude: 0, fault: 0, sleeping: 0, return_reg: REG_CHIP_ID },\n    }),\n    tile_drive_p_sleep: () => ({ nextState: { sleeping: 1 } }),\n\n    // ── mode + raw access ──\n    tile_drive_p_set_mode: ({ args }) => {\n      const mode = pick(args, 0, MODE_IDLE);\n      const sense = mode === MODE_SENSE_FINE || mode === MODE_SENSE_COARSE;\n      return {\n        nextState: {\n          mode,\n          sleeping: 0,\n          // SENSE_FINE/COARSE also pin the sense-gain bit\n          ...(mode === MODE_SENSE_FINE ? { sense_gain: 1 } : {}),\n          ...(mode === MODE_SENSE_COARSE ? { sense_gain: 0 } : {}),\n          return_reg: sense ? REG_SENSE_VAL : REG_IC_STATUS,\n        },\n      };\n    },\n    tile_drive_p_read: ({ state }) => ({ scalar: readReturn(state) }),\n    tile_drive_p_read_sense: ({ state }) => ({\n      scalar: senseRaw(state),\n      nextState: { return_reg: REG_SENSE_VAL },\n    }),\n    tile_drive_p_read_status: ({ state }) => ({\n      scalar: statusWord(state),\n      nextState: { return_reg: REG_IC_STATUS },\n    }),\n    tile_drive_p_write_fifo: ({ args }) => {\n      const sample = pick(args, 0, 0);\n      return { nextState: { amplitude: clamp(Math.abs(sample), 0, SAMPLE_FS) } };\n    },\n    tile_drive_p_write_reg: () => ({ nextState: {} }), // opaque register poke\n    tile_drive_p_wfs_write: () => ({ nextState: {} }), // array arg not modeled\n    tile_drive_p_check_and_recover: ({ state, args }) => {\n      const needs = state.fault === 1 ? 1 : 0;\n      const restore = pick(args, 0, state.mode);\n      return { scalar: needs, nextState: needs ? { fault: 0, mode: restore } : {} };\n    },\n\n    // ── config setters ──\n    tile_drive_p_set_output_range: ({ args }) => ({\n      nextState: { output_range: pick(args, 0, 0) ? 1 : 0 },\n    }),\n    tile_drive_p_set_sense_gain: ({ args }) => ({\n      nextState: { sense_gain: pick(args, 0, 1) ? 1 : 0 },\n    }),\n    tile_drive_p_set_sleep_retention: ({ args }) => ({\n      nextState: { retain: pick(args, 0, 1) ? 1 : 0 },\n    }),\n    tile_drive_p_set_auto_sleep: ({ args }) => ({\n      nextState: { auto_sleep: pick(args, 0, 0) ? 1 : 0 },\n    }),\n    tile_drive_p_set_upi: ({ args }) => ({ nextState: { upi: pick(args, 0, 0) ? 1 : 0 } }),\n\n    // ── idiomatic playback helpers (set FIFO mode + a drive amplitude) ──\n    tile_drive_p_play_click: ({ args }) => ({\n      nextState: {\n        mode: MODE_PLAY_FIFO,\n        sleeping: 0,\n        amplitude: Math.round((clamp(pick(args, 0, 100), 0, 100) / 100) * SAMPLE_FS),\n      },\n    }),\n    tile_drive_p_play_sine: ({ state, args }) => ({\n      nextState: {\n        mode: MODE_PLAY_FIFO,\n        sleeping: 0,\n        freq_hz: pick(args, 0, state.freq_hz),\n        amplitude: Math.round((clamp(pick(args, 1, 100), 0, 100) / 100) * SAMPLE_FS),\n      },\n    }),\n    tile_drive_p_play_buzz: ({ args }) => ({\n      nextState: {\n        mode: MODE_PLAY_FIFO,\n        sleeping: 0,\n        freq_hz: 150, // driver's play_buzz uses a 150 Hz tone\n        amplitude: Math.round((clamp(pick(args, 0, 100), 0, 100) / 100) * SAMPLE_FS),\n      },\n    }),\n    tile_drive_p_play_pulse_train: ({ args }) => ({\n      nextState: {\n        mode: MODE_PLAY_FIFO,\n        sleeping: 0,\n        amplitude: Math.round((clamp(pick(args, 0, 100), 0, 100) / 100) * SAMPLE_FS),\n      },\n    }),\n    tile_drive_p_play_samples: () => ({\n      nextState: { mode: MODE_PLAY_FIFO, sleeping: 0, amplitude: SAMPLE_FS },\n    }),\n\n    // ── sensing helpers ──\n    tile_drive_p_is_touched: ({ state, args }) => {\n      const threshold = pick(args, 0, 0);\n      const mv = Math.abs((senseRaw(state) * 76) / 10); // fine 7.6 mV/LSB → mV\n      return {\n        scalar: mv >= threshold ? 1 : 0,\n        nextState: { mode: MODE_SENSE_FINE, sense_gain: 1, return_reg: REG_SENSE_VAL },\n      };\n    },\n    tile_drive_p_play_on_touch: ({ state, args }) => {\n      const threshold = pick(args, 1, 0);\n      const mv = Math.abs((senseRaw(state) * 76) / 10);\n      const touched = mv >= threshold ? 1 : 0;\n      return {\n        scalar: touched,\n        nextState: touched\n          ? {\n              mode: MODE_PLAY_FIFO,\n              amplitude: Math.round((clamp(pick(args, 0, 100), 0, 100) / 100) * SAMPLE_FS),\n            }\n          : { mode: MODE_SENSE_FINE, sense_gain: 1, return_reg: REG_SENSE_VAL },\n      };\n    },\n    tile_drive_p_read_sense_samples: () => ({\n      nextState: { mode: MODE_SENSE_FINE, sense_gain: 1, return_reg: REG_SENSE_VAL },\n    }),\n  },\n\n  provenance: {\n    // canonical — datasheet/driver-accurate\n    tile_drive_p_find: 'canonical', // I2C 0x44\n    tile_drive_p_read_sense: 'canonical', // signed 12-bit, 7.6 / 54.5 mV LSB\n    tile_drive_p_read_status: 'canonical', // IC_STATUS state/fault bits\n    tile_drive_p_set_mode: 'canonical', // CONFIG mode encoding\n    tile_drive_p_set_output_range: 'canonical', // ±95 / ±13.28 V (GAIND)\n    tile_drive_p_set_sense_gain: 'canonical', // GAINS\n    tile_drive_p_write_fifo: 'canonical', // 12-bit signed reference sample\n    tile_drive_p_is_touched: 'canonical', // 7.6 mV/LSB threshold compare\n    // inferred — behavior obvious from the driver, not a datasheet number\n    tile_drive_p_init: 'inferred',\n    tile_drive_p_reset: 'inferred',\n    tile_drive_p_sleep: 'inferred',\n    tile_drive_p_check_and_recover: 'inferred',\n    tile_drive_p_set_sleep_retention: 'inferred',\n    tile_drive_p_set_auto_sleep: 'inferred',\n    tile_drive_p_set_upi: 'inferred',\n    tile_drive_p_play_click: 'inferred',\n    tile_drive_p_play_sine: 'inferred',\n    tile_drive_p_play_buzz: 'inferred',\n    tile_drive_p_play_pulse_train: 'inferred',\n    tile_drive_p_play_on_touch: 'inferred',\n    tile_drive_p_read: 'inferred', // return-register multiplexing\n    // hallucinated — array/opaque args not modeled\n    tile_drive_p_write_reg: 'hallucinated',\n    tile_drive_p_wfs_write: 'hallucinated',\n    tile_drive_p_play_samples: 'hallucinated',\n    tile_drive_p_read_sense_samples: 'hallucinated',\n    power: 'inferred', // CV²f + HV-hold model fit to datasheet anchors; loss/efficiency factor inferred\n  },\n\n  // Differential drive envelope (magnitude 0..1) on the OUT+/OUT- pads.\n  padOutputs(state) {\n    const s = driveStrength(state);\n    return { 'OUT+': s, 'OUT-': s };\n  },\n\n  // Electrical layer. Two supplies: V+ logic (pad 10) + V_DRIVE boost (pad 9);\n  // the boosted differential drive appears on OUT± (pads 7/8) up to 190 Vpp.\n  // V_DRIVE draw uses the CV²f + HV-hold model above (see the constants block),\n  // anchored on datasheet Table 7 — so it responds to amplitude (Vpk), tone\n  // frequency, output range (Vpk²), piezo load, and supply voltage.\n  power(state) {\n    const on = powered(state);\n    const s = driveStrength(state);\n\n    let driveUa: number;\n    if (state.sleeping === 1) {\n      driveUa = state.retain === 1 ? IQ_SLEEP_RET_UA : IQ_SLEEP_NORET_UA;\n    } else if (state.supply_mv < UVLO_MV) {\n      driveUa = 0;\n    } else if (s <= 0) {\n      driveUa = IDLE_UA; // output off — quiescent only\n    } else {\n      const vpk_v = (s * outFsMv(state)) / 1000; // peak output voltage (V)\n      const vpp_v = 2 * vpk_v; // peak-to-peak swing (V)\n      const c_f = (state.load_nf > 0 ? state.load_nf : DEFAULT_LOAD_NF) * 1e-9;\n      const vbus_v = state.supply_mv / 1000;\n      const staticUa = K_STATIC_UA_PER_V * vpk_v; // HV-hold (∝ Vpk)\n      const dynUa = ((K_DYN * c_f * vpp_v * vpp_v * state.freq_hz) / vbus_v) * 1e6; // reactive CV²f\n      driveUa = IDLE_UA + staticUa + dynUa;\n    }\n    const vplusUa = on ? VPLUS_ACTIVE_UA : 0;\n\n    return {\n      draw_ua: Math.round(driveUa + vplusUa),\n      rails: [\n        {\n          name: 'V+',\n          role: 'supply',\n          v_mv: 1800,\n          i_ua: vplusUa,\n          pads: ['10'],\n          note: 'logic/IO supply',\n        },\n        {\n          name: 'V_DRIVE',\n          role: 'supply',\n          v_mv: state.supply_mv,\n          i_ua: Math.round(driveUa),\n          pads: ['9'],\n          note:\n            state.sleeping === 1\n              ? 'boost supply — sleep'\n              : s > 0\n                ? 'boost supply + piezo drive'\n                : 'boost supply — idle',\n        },\n        {\n          name: 'OUT±',\n          role: 'output',\n          v_mv: Math.round(s * outFsMv(state)),\n          pads: ['7', '8'],\n          note: 'boosted differential piezo drive (190 Vpp full-scale)',\n        },\n      ],\n    };\n  },\n};\n\nexport default sim;\n",
    "status": "validated",
    "updated_at": "2026-06-21 18:29:39"
  },
  "config": {
    "interfaceMode": {
      "kind": "select",
      "group": "Interfaces",
      "label": "Interface mode",
      "default": "i2c",
      "options": [
        {
          "label": "I2C",
          "value": "i2c",
          "activates": {
            "interface": "I2C"
          }
        },
        {
          "label": "I3C",
          "value": "i3c",
          "activates": {
            "interface": "I3C"
          }
        }
      ],
      "description": "Selects which bus this tile sits on. The chip's mode is set by firmware via register write at init (no boot-strap pad on this tile), so the catalog choice expresses *wiring intent* and which interface's pad_assignments are active."
    }
  }
}