{
  "family": "Drive",
  "name": "H",
  "rev": "a",
  "tile_id": 4,
  "json_version": "0.5",
  "updated_at": "2026-05-01T12:19:55.691Z",
  "headline": "haptic ERM/LRA driver",
  "description": "The Drive.H tile is designed for driving linear-resonant actuators (LRAs) or eccentric rotating-mass motors (ERMs) using the Texas Instruments DRV2605 IC.  Haptic signals can be generated either from an internal effects library, streamed in real time over I2C, or input as a high-speed pulse-width modulation (PWM) signal.  Additionally, an optional hardware trigger can be used to play back pre-configured effects.  Separate logic and motor supply connections allows for digital interfacing with logic down to 1.8V.",
  "application_notes": [],
  "package": {
    "pads": 10,
    "type": "T44",
    "size_x": 4000,
    "size_y": 4000,
    "size_z": 0
  },
  "power": [
    {
      "max": 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": 2.5,
      "type": "drive",
      "notes": "",
      "gnd_pad": [
        "1"
      ],
      "function": "",
      "direction": "input",
      "is_required": true,
      "max_current": "",
      "positive_pad": [
        "9"
      ]
    }
  ],
  "components": [
    {
      "url": "https://www.ti.com/product/DRV2605",
      "part": "DRV2605",
      "datasheet": "https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/4/Texas_Instruments-DRV2605.pdf",
      "manufacturer": "Texas Instruments"
    }
  ],
  "pads": [
    {
      "pad": "1",
      "geometry": {
        "size_x": 1000,
        "size_y": 400,
        "center_x": -1500,
        "center_y": 1600
      },
      "functions": [
        {
          "type": "power",
          "function": "GND",
          "direction": ""
        }
      ]
    },
    {
      "pad": "2",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": 800
      },
      "functions": [
        {
          "note": "",
          "type": "digital",
          "function": "TRIG",
          "direction": "input",
          "thresholds": {
            "logic_low": "0.3 * V+",
            "logic_high": "0.7 * V+"
          }
        }
      ]
    },
    {
      "pad": "3",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": 0
      },
      "functions": [
        {
          "note": "an internal pull-up resistor enables the output by default; connect to GND to disable",
          "type": "digital",
          "function": "EN",
          "direction": "input",
          "thresholds": {
            "logic_low": "0.3 * V+",
            "logic_high": "0.7 * V+"
          }
        }
      ]
    },
    {
      "pad": "4",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": -800
      },
      "functions": [
        {
          "note": "if using a non-Core processor, ensure adequate external pull-up resistance",
          "type": "digital",
          "function": "I2C.CLK",
          "direction": "bidirectional",
          "interface": "I2C",
          "is_default": true
        }
      ]
    },
    {
      "pad": "5",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": -1600
      },
      "functions": [
        {
          "note": "if using a non-Core processor, ensure adequate external pull-up resistance",
          "type": "digital",
          "function": "I2C.DAT",
          "direction": "bidirectional",
          "interface": "I2C",
          "is_default": true
        }
      ]
    },
    {
      "pad": "6",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": -1600
      },
      "functions": []
    },
    {
      "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": "2.5-5.5V supply for the motor driver",
          "type": "power",
          "function": "V_MOTOR",
          "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.0V",
          "type": "power",
          "function": "V+",
          "direction": "input"
        }
      ]
    }
  ],
  "interfaces": [
    {
      "name": "I2C",
      "type": "I2C",
      "parameters": {
        "modes": [
          "slave"
        ],
        "addresses": [
          {
            "address": "0x5A",
            "is_default": true
          }
        ],
        "data_bits": [
          8
        ],
        "address_bits": [
          7
        ],
        "max_clock_speed": "400kHz"
      },
      "pad_assignments": [
        {
          "pad": "4",
          "role": "bus",
          "function": "I2C.CLK",
          "is_required": true
        },
        {
          "pad": "5",
          "role": "bus",
          "function": "I2C.DAT",
          "is_required": true
        }
      ]
    }
  ],
  "twin": {
    "score": 1,
    "source": "// Digital twin for Drive.H — TI DRV2605 haptic driver for LRA/ERM actuators.\n// (DRV2605, not the DRV2605L: it has an integrated boost converter fed from a\n// separate drive supply, which is why the tile carries two rails — V+ VDD logic\n// and V_MOTOR boost/drive.) Output-centric: firmware selects a mode +\n// waveform/amplitude and the chip drives a differential haptic output\n// (OUT+/OUT-, boostable above V_MOTOR). This twin models the drive envelope\n// (0..1 strength) on the OUT pads plus the status/diagnostic/voltage reads,\n// with register-accurate conversions from the DRV2605 datasheet.\n//\n// Controls drive the same state a firmware program would set (RTP amplitude,\n// play/stop, supply, faults), so the actuator output can be exercised here.\nimport type { TileSim } from '../tileSim';\n\ninterface State {\n  // ── drive inputs (what makes it vibrate) ──\n  rtp_amplitude: number; // RTP register 0x02 value (0..255)\n  playing: number; // GO bit / sequence active\n  supply_mv: number; // V_MOTOR supply (for VBAT read)\n  resonance_hz: number; // actual LRA resonance (for LRA_PERIOD read)\n  fault_overtemp: number;\n  fault_overcurrent: number;\n\n  // ── configuration ──\n  mode: number; // 0 int-trig,1 ext-edge,2 ext-level,3 pwm/analog,4 audio,5 rtp,6 diag,7 cal\n  standby: number;\n  library: number; // 1..5 ERM A-E, 6 LRA\n  closed_loop: number;\n  rated_voltage: number;\n  od_clamp: number;\n  rtp_unsigned: number; // CONTROL3[3] DATA_FORMAT_RTP\n  bidir: number; // CONTROL2[7] BIDIR_INPUT\n  trigger_mode: number;\n  last_effect: number;\n  calibrated: number; // DIAG_RESULT after calibrate (1 = converged)\n  diag_pass: number; // DIAG_RESULT after diagnose\n  otp_programmed: number;\n\n  [field: string]: number;\n}\n\nconst DEVICE_ID = 0x60; // STATUS[7:5] = 3\nconst I2C_ADDR = 0x5a;\n\n// Current drive envelope, 0..1, from mode + amplitude. Models the magnitude of\n// the differential OUT+/OUT- drive (not the AC waveform itself).\nfunction driveStrength(s: State): number {\n  if (s.standby === 1) return 0;\n  if (s.mode === 5) {\n    // RTP: amplitude register 0x02, signed (default) or unsigned.\n    if (s.rtp_unsigned === 1) return Math.min(1, s.rtp_amplitude / 255);\n    const v = s.rtp_amplitude > 127 ? s.rtp_amplitude - 256 : s.rtp_amplitude; // int8\n    return Math.min(1, Math.abs(v) / 127);\n  }\n  if (s.mode <= 2 && s.playing === 1) return 0.8; // library effect playing (nominal)\n  return 0;\n}\n\nconst pick = (args: number[], i: number, cur: number) =>\n  args.length > i && Number.isFinite(args[i]) ? args[i] : cur;\n\nconst sim: TileSim<State> = {\n  tile: 'Drive.H',\n\n  defaultState: {\n    rtp_amplitude: 0,\n    playing: 0,\n    supply_mv: 3700,\n    resonance_hz: 175, // typical LRA\n    fault_overtemp: 0,\n    fault_overcurrent: 0,\n\n    mode: 0,\n    standby: 0,\n    library: 6, // LRA\n    closed_loop: 0,\n    rated_voltage: 0x1a, // 0.7 Vrms\n    od_clamp: 0x25,\n    rtp_unsigned: 0,\n    bidir: 1,\n    trigger_mode: 0,\n    last_effect: 0,\n    calibrated: 0,\n    diag_pass: 0,\n    otp_programmed: 0,\n  },\n\n  controls: [\n    { type: 'slider', field: 'rtp_amplitude', label: 'RTP amplitude', min: 0, max: 127, step: 1 },\n    { type: 'slider', field: 'playing', label: 'Playing (GO)', min: 0, max: 1, step: 1 },\n    { type: 'slider', field: 'mode', label: 'Mode (0-int 5-RTP)', min: 0, max: 7, step: 1 },\n    {\n      type: 'slider',\n      field: 'supply_mv',\n      label: 'Supply',\n      min: 2500,\n      max: 5500,\n      step: 50,\n      unit: 'mV',\n    },\n    {\n      type: 'slider',\n      field: 'resonance_hz',\n      label: 'LRA resonance',\n      min: 125,\n      max: 300,\n      step: 1,\n      unit: 'Hz',\n    },\n    { type: 'slider', field: 'fault_overtemp', label: 'Fault: over-temp', min: 0, max: 1, step: 1 },\n    {\n      type: 'slider',\n      field: 'fault_overcurrent',\n      label: 'Fault: over-current',\n      min: 0,\n      max: 1,\n      step: 1,\n    },\n  ],\n\n  hostCalls: {\n    // ── lifecycle ──\n    tile_drive_h_find: () => ({ scalar: I2C_ADDR }),\n    tile_drive_h_init: () => ({ scalar: 0, nextState: { mode: 0, standby: 0, library: 6 } }),\n    tile_drive_h_standby: () => ({ nextState: { standby: 1 } }),\n    tile_drive_h_wake: () => ({ nextState: { standby: 0 } }),\n\n    // ── sequence / library playback ──\n    tile_drive_h_play: ({ args }) => ({\n      nextState: { last_effect: pick(args, 0, 1), playing: 1, mode: 0 },\n    }),\n    tile_drive_h_play_sequence: () => ({ nextState: { playing: 1, mode: 0 } }),\n    tile_drive_h_load_sequence: () => ({ nextState: {} }),\n    tile_drive_h_set_sequence_wait: () => ({ nextState: {} }),\n    tile_drive_h_set_trigger: ({ state, args }) => ({\n      nextState: {\n        trigger_mode: pick(args, 0, state.trigger_mode),\n        mode: pick(args, 0, state.mode),\n      },\n    }),\n    tile_drive_h_is_playing: ({ state }) => ({ scalar: state.playing }),\n    tile_drive_h_stop: () => ({ nextState: { playing: 0, rtp_amplitude: 0 } }),\n\n    // ── library + actuator tuning ──\n    tile_drive_h_set_library: ({ state, args }) => ({\n      nextState: { library: pick(args, 0, state.library) },\n    }),\n    tile_drive_h_set_actuator_params: ({ state, args }) => ({\n      nextState: {\n        rated_voltage: pick(args, 0, state.rated_voltage),\n        od_clamp: pick(args, 1, state.od_clamp),\n      },\n    }),\n    tile_drive_h_set_resonance_params: () => ({ nextState: {} }),\n    tile_drive_h_set_waveform_timing: () => ({ nextState: {} }),\n\n    // ── real-time playback ──\n    tile_drive_h_rtp_start: () => ({ nextState: { mode: 5, rtp_amplitude: 0 } }),\n    tile_drive_h_rtp_write: ({ state, args }) => ({\n      nextState: { rtp_amplitude: pick(args, 0, state.rtp_amplitude) },\n    }),\n    tile_drive_h_set_rtp_format: ({ state, args }) => ({\n      nextState: {\n        rtp_unsigned: pick(args, 0, state.rtp_unsigned),\n        bidir: pick(args, 1, state.bidir),\n      },\n    }),\n    tile_drive_h_rtp_stop: () => ({ nextState: { rtp_amplitude: 0, mode: 0 } }),\n\n    // ── PWM / analog / audio input ──\n    tile_drive_h_pwm_input_start: () => ({ nextState: { mode: 3 } }),\n    tile_drive_h_analog_input_start: () => ({ nextState: { mode: 3 } }),\n    tile_drive_h_pwm_input_stop: () => ({ nextState: { mode: 0 } }),\n    tile_drive_h_audio_start: () => ({ nextState: { mode: 4 } }),\n    tile_drive_h_set_audio_params: () => ({ nextState: {} }),\n    tile_drive_h_audio_stop: () => ({ nextState: { mode: 0 } }),\n\n    // ── status / diagnostics ──\n    // STATUS 0x00: DEVICE_ID[7:5]=3, OVER_TEMP[1], OC_DETECT[0].\n    tile_drive_h_get_status: ({ state }) => ({\n      scalar: DEVICE_ID | (state.fault_overtemp ? 0x02 : 0) | (state.fault_overcurrent ? 0x01 : 0),\n    }),\n    tile_drive_h_diagnose: ({ state }) => ({\n      scalar: state.fault_overcurrent ? 0 : 1,\n      nextState: { diag_pass: state.fault_overcurrent ? 0 : 1 },\n    }),\n    tile_drive_h_calibrate: ({ state }) => ({\n      scalar: state.fault_overcurrent ? 0 : 1,\n      nextState: { calibrated: state.fault_overcurrent ? 0 : 1 },\n    }),\n    tile_drive_h_is_calibrated: ({ state }) => ({ scalar: state.calibrated }),\n    // VBAT (0x21): mV = raw*5600/255; valid only while driving.\n    tile_drive_h_get_vbat_mv: ({ state }) => ({\n      scalar: driveStrength(state) > 0 ? state.supply_mv : 0,\n    }),\n    // LRA_PERIOD (0x22): Hz ~= 10156/raw; valid only while driving.\n    tile_drive_h_get_resonance_hz: ({ state }) => ({\n      scalar: driveStrength(state) > 0 ? state.resonance_hz : 0,\n    }),\n\n    // ── OTP (one-time, destructive) ──\n    tile_drive_h_get_otp_status: ({ state }) => ({ scalar: state.otp_programmed }),\n    tile_drive_h_program_otp: () => ({ scalar: 0 }),\n\n    // ── idiomatic helpers ──\n    tile_drive_h_play_click: () => ({ nextState: { last_effect: 1, playing: 1, mode: 0 } }),\n    tile_drive_h_play_double_tap: () => ({ nextState: { last_effect: 10, playing: 1, mode: 0 } }),\n    tile_drive_h_play_alert: () => ({ nextState: { playing: 1, mode: 0 } }),\n    tile_drive_h_play_buzz: () => ({ nextState: { playing: 1, mode: 5, rtp_amplitude: 127 } }),\n  },\n\n  provenance: {\n    // canonical — datasheet register/bit/conversion accurate\n    tile_drive_h_find: 'canonical', // I2C 0x5A\n    tile_drive_h_get_status: 'canonical', // STATUS bits\n    tile_drive_h_get_vbat_mv: 'canonical', // raw*5600/255\n    tile_drive_h_get_resonance_hz: 'canonical', // 10156/raw\n    tile_drive_h_set_trigger: 'canonical', // MODE[2:0]\n    tile_drive_h_set_library: 'canonical', // LIBRARY_SEL / N_ERM_LRA\n    tile_drive_h_rtp_write: 'canonical', // RTP 0x02 encoding\n    tile_drive_h_set_rtp_format: 'canonical', // CONTROL2[7]/CONTROL3[3]\n    tile_drive_h_is_playing: 'canonical', // GO bit\n    tile_drive_h_stop: 'canonical', // GO=0\n    tile_drive_h_standby: 'canonical', // STANDBY bit\n    tile_drive_h_wake: 'canonical',\n    tile_drive_h_play: 'canonical', // SEQ + GO\n    tile_drive_h_get_otp_status: 'canonical', // CONTROL4[2]\n    // hallucinated — stubbed / not really modeled\n    tile_drive_h_program_otp: 'hallucinated', // destructive; always reports \"already/failed\"\n    tile_drive_h_set_audio_params: 'hallucinated',\n    tile_drive_h_set_resonance_params: 'hallucinated',\n    tile_drive_h_set_waveform_timing: 'hallucinated',\n    tile_drive_h_set_sequence_wait: 'hallucinated',\n    power: 'inferred', // base currents are datasheet; rail split + actuator load are modeled\n    // (everything else defaults to \"inferred\")\n  },\n\n  // Drive output envelope on the differential OUT pads (magnitude 0..1).\n  padOutputs(state) {\n    const s = driveStrength(state);\n    return { 'OUT+': s, 'OUT-': s };\n  },\n\n  // Electrical. Two tile rails (Drive-H-a.json), matching the DRV2605's split\n  // architecture: V+ VDD logic (1.8-5 V, pad 10) and V_MOTOR boost/drive supply\n  // (2.5-5.5 V, pad 9). VDD draws the logic/control current (standby 1.9 µA,\n  // quiescent 0.6 mA — datasheet); V_MOTOR feeds the integrated boost + H-bridge\n  // (~2.9 mA average operation + the actuator load) only while driving. The\n  // actuator load itself isn't datasheet-specified, so power = \"inferred\".\n  power(state) {\n    if (state.standby === 1) {\n      return {\n        draw_ua: 1.9,\n        rails: [\n          {\n            name: 'V+',\n            role: 'supply',\n            v_mv: 3300,\n            i_ua: 1.9,\n            pads: ['10'],\n            note: 'VDD logic — standby',\n          },\n          {\n            name: 'V_MOTOR',\n            role: 'supply',\n            v_mv: state.supply_mv,\n            i_ua: 0,\n            pads: ['9'],\n            note: 'boost/drive supply — idle',\n          },\n        ],\n      };\n    }\n    const s = driveStrength(state);\n    const driving = s > 0;\n    const logic_ua = 600; // VDD quiescent, enabled\n    const drive_ua = driving ? 2900 + s * 100000 : 0; // boost operation + actuator load (est.)\n    return {\n      draw_ua: logic_ua + drive_ua,\n      rails: [\n        { name: 'V+', role: 'supply', v_mv: 3300, i_ua: logic_ua, pads: ['10'], note: 'VDD logic' },\n        {\n          name: 'V_MOTOR',\n          role: 'supply',\n          v_mv: state.supply_mv,\n          i_ua: drive_ua,\n          pads: ['9'],\n          note: 'boost/drive supply + actuator (load est.)',\n        },\n        {\n          name: 'OUT±',\n          role: 'output',\n          v_mv: Math.round(s * 2400),\n          i_ua: driving ? Math.round(s * 100000) : 0,\n          pads: ['7', '8'],\n          note: 'boosted actuator drive',\n        },\n      ],\n    };\n  },\n};\n\nexport default sim;\n",
    "status": "validated",
    "updated_at": "2026-06-21T09:30:24.881Z"
  },
  "config": {
    "enable": {
      "kind": "boolean",
      "group": "Drive",
      "label": "Enable haptic output",
      "binding": {
        "pad": "3",
        "kind": "strap",
        "states": {
          "low": "disabled",
          "high": "enabled (forced)",
          "open": "enabled (default, via internal PU)"
        }
      },
      "default": true,
      "options": [
        {
          "value": true,
          "states": {
            "3": "open"
          }
        },
        {
          "value": false,
          "states": {
            "3": "low"
          },
          "netlist": {
            "requires": [
              {
                "to": {
                  "kind": "rail",
                  "rail": "GND"
                },
                "tag": "enable.disabled.connection",
                "from": {
                  "pad": "3",
                  "kind": "tile"
                },
                "role": "power"
              }
            ]
          }
        }
      ]
    },
    "trigger": {
      "kind": "boolean",
      "group": "Sidebands",
      "label": "Use external trigger input",
      "binding": {
        "pad": "2",
        "kind": "intent"
      },
      "default": false,
      "options": [
        {
          "value": false,
          "firmware_contract": [
            {
              "via": "i2c",
              "type": "register",
              "value": "internal",
              "register": "MODE.TRIG_SRC"
            }
          ]
        },
        {
          "value": true,
          "netlist": {
            "expects": [
              {
                "to": {
                  "kind": "matchFunction",
                  "function": "GPIO",
                  "capabilities": [
                    "output"
                  ]
                },
                "tag": "trigger.attached",
                "from": {
                  "pad": "2",
                  "kind": "tile"
                },
                "role": "data"
              }
            ]
          },
          "firmware_contract": [
            {
              "via": "i2c",
              "type": "register",
              "value": "external",
              "register": "MODE.TRIG_SRC"
            }
          ]
        }
      ],
      "description": "When enabled, pad 2 (TRIG) becomes an external pulse input that the chip uses as a haptic-effect trigger; firmware switches the DRV2605 into external-trigger mode."
    }
  }
}