{
  "family": "Display",
  "name": "RGBW",
  "rev": "a",
  "tile_id": 58,
  "json_version": "0.3",
  "updated_at": "2026-05-07T05:50:40.220Z",
  "headline": "Smart RGBW LED",
  "description": "",
  "application_notes": [],
  "package": {
    "pads": 10,
    "type": "T44",
    "size_x": 4000,
    "size_y": 4000,
    "size_z": 0
  },
  "power": [
    {
      "max": 5.5,
      "min": 1.8,
      "type": "system",
      "notes": "",
      "gnd_pad": [
        "1"
      ],
      "function": "",
      "direction": "input",
      "is_required": true,
      "max_current": "",
      "positive_pad": [
        "10"
      ]
    }
  ],
  "components": [
    {
      "url": "https://www.ti.com/product/LP5811",
      "part": "LP5811",
      "datasheet": "https://mosaic-component-datasheets.s3.eu-north-1.amazonaws.com/58/Texas_Instruments-LP5811.pdf",
      "manufacturer": "Texas Instruments"
    }
  ],
  "pads": [
    {
      "pad": "1",
      "geometry": {
        "size_x": 1000,
        "size_y": 400,
        "center_x": -1500,
        "center_y": 1600
      },
      "functions": [
        {
          "note": "",
          "type": "power",
          "function": "GND",
          "direction": "input"
        }
      ]
    },
    {
      "pad": "2",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": 800
      },
      "functions": [
        {
          "note": "",
          "type": "other",
          "function": "",
          "direction": ""
        }
      ]
    },
    {
      "pad": "3",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": 0
      },
      "functions": [
        {
          "note": "",
          "type": "other",
          "function": "",
          "direction": ""
        }
      ]
    },
    {
      "pad": "4",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": -800
      },
      "functions": [
        {
          "note": "",
          "type": "digital",
          "function": "I2C.CLK",
          "direction": "bidirectional",
          "interface": "I2C"
        }
      ]
    },
    {
      "pad": "5",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": -1600,
        "center_y": -1600
      },
      "functions": [
        {
          "note": "",
          "type": "digital",
          "function": "I2C.DAT",
          "direction": "bidirectional",
          "interface": "I2C"
        }
      ]
    },
    {
      "pad": "6",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": -1600
      },
      "functions": [
        {
          "note": "",
          "type": "other",
          "function": "",
          "direction": ""
        }
      ]
    },
    {
      "pad": "7",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": -800
      },
      "functions": [
        {
          "note": "",
          "type": "other",
          "function": "",
          "direction": ""
        }
      ]
    },
    {
      "pad": "8",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": 0
      },
      "functions": [
        {
          "note": "pull enables by default; connect to GND to disable",
          "type": "digital",
          "function": "EN",
          "direction": "input"
        }
      ]
    },
    {
      "pad": "9",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": 800
      },
      "functions": [
        {
          "note": "",
          "type": "other",
          "function": "",
          "direction": ""
        }
      ]
    },
    {
      "pad": "10",
      "geometry": {
        "size_x": 800,
        "size_y": 400,
        "center_x": 1600,
        "center_y": 1600
      },
      "functions": [
        {
          "note": "",
          "type": "power",
          "function": "V+",
          "direction": "input"
        }
      ]
    }
  ],
  "interfaces": [
    {
      "name": "I2C",
      "type": "I2C",
      "parameters": {
        "modes": [
          "slave"
        ],
        "addresses": [
          {
            "address": "0x50"
          }
        ]
      },
      "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 Display.RGBW — TI LP5811 4-channel LED driver + on-tile RGBW LED.\n//\n// The LP5811 is a boost + 4 low-side current sinks (O0..O3). On this tile the\n// RGBW LED is ON-BOARD: VOUT (boost, ~4.5 V) drives the LED common anode and the\n// four sinks pull each cathode — O0=R, O1=B, O2=G, O3=W (per the tile schematic).\n// So the LED channels are internal, not edge pads; the tile exposes only I²C\n// (pads 4/5), EN (pad 8), V+ (pad 10), GND (pad 1). Address 0x50 (LP5811A).\n//\n// Two output paths, both modeled:\n//   • Manual: per-channel PWM brightness (0–255) × DC current limit (0–255),\n//     scaled by the max-current mode (25.5 / 51 mA full-scale).\n//   • Autonomous (AEU): the on-chip animation engine runs a channel with no MCU\n//     help. This twin models a single active autonomous breathe (set via\n//     breathe_auto / animate_start) and evolves it over time in deriveState, so\n//     the simulated LED actually breathes. Per-channel open/short + thermal faults.\n//\n// Brightness/current/fault/engine semantics are canonical (driver + LP5811 TRM);\n// the LED forward drop + boost efficiency in the power layer are modeled.\nimport type { TileSim } from '../tileSim';\n\nconst I2C_ADDR = 0x50;\nconst VOUT_MV = 4500; // boost output to the LED common anode\nconst BOOST_EFF = 0.85;\nconst IQ_ACTIVE_UA = 1000; // LP5811 active quiescent (boost running)\nconst IQ_STANDBY_UA = 10; // EN low / sleep\nconst TICK_MS = 100; // runner deriveState cadence\n\n// channel letters by index (O0=R, O1=B, O2=G, O3=W) — matches set_autonomous(ch)\nconst CH = ['r', 'b', 'g', 'w'] as const;\ntype Ch = (typeof CH)[number];\nconst FAULT_BIT: Record<Ch, number> = { r: 0, b: 1, g: 2, w: 3 };\n\ninterface State {\n  // manual PWM brightness 0-255\n  r: number;\n  g: number;\n  b: number;\n  w: number;\n  // per-channel DC current limit 0-255\n  ir: number;\n  ig: number;\n  ib: number;\n  iw: number;\n\n  max_current: number; // 0 = 25.5 mA, 1 = 51 mA full-scale\n  enabled: number;\n  sleeping: number;\n\n  // faults (per-channel bit: 0=R,1=B,2=G,3=W) + thermal\n  open_mask: number;\n  short_mask: number;\n  tsd: number;\n  short_shutdown: number;\n  open_shutdown: number;\n\n  // ── autonomous engine ──\n  auto_r: number;\n  auto_g: number;\n  auto_b: number;\n  auto_w: number; // per-channel autonomous-mode enable\n  anim_channel: number; // which channel the active breathe drives (0-3)\n  anim_peak: number; // breathe peak brightness 0-255\n  anim_period_ms: number; // full breathe period\n  animating: number; // engine running\n  anim_paused: number;\n  anim_clock: number; // ms since animate_start\n  exp_mask: number; // exponential-dimming enable bits\n  phase_align: number; // last-set phase-align mode (cosmetic)\n  latched: number; // CMD_Update issued\n  aeu_configured: number;\n\n  vplus_mv: number;\n\n  [field: string]: number;\n}\n\nconst clamp = (v: number, lo: number, hi: number) => Math.max(lo, Math.min(hi, v));\nconst pick = (args: number[], i: number, cur: number) =>\n  args.length > i && Number.isFinite(args[i]) ? args[i] : cur;\n\nconst maxMa = (s: State) => (s.max_current === 1 ? 51 : 25.5);\nconst autoOn = (s: State, ch: Ch) => s[('auto_' + ch) as keyof State] === 1;\n\n// Triangle breathe envelope 0..1 from the animation clock (0→1→0 over period).\nfunction animDuty(s: State): number {\n  if (s.anim_period_ms <= 0) return s.anim_peak / 255; // steady hold\n  const phase = (s.anim_clock % s.anim_period_ms) / s.anim_period_ms;\n  return (s.anim_peak / 255) * (1 - Math.abs(2 * phase - 1));\n}\n\n// A channel's PWM duty 0..1: the autonomous breathe when this channel is the\n// active animated one, otherwise its manual brightness register.\nfunction duty(s: State, ch: Ch): number {\n  if (autoOn(s, ch) && s.animating === 1 && s.anim_channel === FAULT_BIT[ch]) {\n    return s.anim_paused === 1 ? animDuty({ ...s }) : animDuty(s);\n  }\n  return (s[ch] as number) / 255;\n}\n\n// Channel output level 0..1 = duty × current fraction, zeroed when off/asleep or\n// the channel has a disabling fault (open, or short with shutdown enabled).\nfunction level(s: State, ch: Ch): number {\n  if (s.enabled !== 1 || s.sleeping === 1) return 0;\n  const bit = 1 << FAULT_BIT[ch];\n  if (s.open_mask & bit) return 0;\n  if (s.short_mask & bit && s.short_shutdown === 1) return 0;\n  const ilim = s[('i' + ch) as keyof State] as number;\n  return clamp(duty(s, ch) * (ilim / 255), 0, 1);\n}\n\nconst chMa = (s: State, ch: Ch) => level(s, ch) * maxMa(s);\n\n// ms → AEU slope/pause code (driver's lp_time_ms table), nearest match.\nconst SLOPE_MS = [\n  0, 90, 180, 360, 540, 800, 1070, 1520, 2060, 2500, 3040, 4020, 5010, 5990, 7060, 8050,\n];\nfunction msToSlope(ms: number): number {\n  let best = 0;\n  let bestErr = Infinity;\n  for (let i = 0; i < 16; i++) {\n    const err = Math.abs(ms - SLOPE_MS[i]);\n    if (err < bestErr) {\n      bestErr = err;\n      best = i;\n    }\n  }\n  return best;\n}\n\nconst sim: TileSim<State> = {\n  tile: 'Display.RGBW',\n\n  defaultState: {\n    r: 0,\n    g: 0,\n    b: 0,\n    w: 0,\n    ir: 128,\n    ig: 128,\n    ib: 128,\n    iw: 128,\n    max_current: 1,\n    enabled: 1,\n    sleeping: 0,\n    open_mask: 0,\n    short_mask: 0,\n    tsd: 0,\n    short_shutdown: 0,\n    open_shutdown: 1,\n    auto_r: 0,\n    auto_g: 0,\n    auto_b: 0,\n    auto_w: 0,\n    anim_channel: 0,\n    anim_peak: 255,\n    anim_period_ms: 2000,\n    animating: 0,\n    anim_paused: 0,\n    anim_clock: 0,\n    exp_mask: 0,\n    phase_align: 0,\n    latched: 0,\n    aeu_configured: 0,\n    vplus_mv: 3700,\n  },\n\n  controls: [\n    { type: 'slider', field: 'r', label: 'Red', min: 0, max: 255, step: 1 },\n    { type: 'slider', field: 'g', label: 'Green', min: 0, max: 255, step: 1 },\n    { type: 'slider', field: 'b', label: 'Blue', min: 0, max: 255, step: 1 },\n    { type: 'slider', field: 'w', label: 'White', min: 0, max: 255, step: 1 },\n    { type: 'toggle', field: 'animating', label: 'Autonomous run' },\n    { type: 'slider', field: 'anim_channel', label: 'Anim ch (0R1B2G3W)', min: 0, max: 3, step: 1 },\n    {\n      type: 'slider',\n      field: 'anim_period_ms',\n      label: 'Breathe period',\n      min: 200,\n      max: 8000,\n      step: 100,\n      unit: 'ms',\n    },\n    { type: 'toggle', field: 'enabled', label: 'Enabled' },\n  ],\n\n  // Evolve the autonomous breathe: advance the engine clock while running.\n  deriveState(state) {\n    if (state.animating !== 1 || state.anim_paused === 1) return {};\n    // Auto-enable the active channel so the envelope shows even if the user\n    // toggled `animating` directly rather than via breathe_auto.\n    const chKey = 'auto_' + CH[clamp(Math.round(state.anim_channel), 0, 3)];\n    return { anim_clock: state.anim_clock + TICK_MS, [chKey]: 1 };\n  },\n\n  hostCalls: {\n    // ── lifecycle ──\n    tile_display_rgbw_find: () => ({ scalar: I2C_ADDR }),\n    tile_display_rgbw_init: () => ({\n      scalar: 0,\n      nextState: { enabled: 1, max_current: 1, r: 0, g: 0, b: 0, w: 0, animating: 0 },\n    }),\n    tile_display_rgbw_sleep: () => ({ nextState: { sleeping: 1 } }),\n    tile_display_rgbw_wake: () => ({ nextState: { sleeping: 0 } }),\n    tile_display_rgbw_reset: () => ({\n      nextState: {\n        r: 0,\n        g: 0,\n        b: 0,\n        w: 0,\n        open_mask: 0,\n        short_mask: 0,\n        tsd: 0,\n        enabled: 1,\n        animating: 0,\n      },\n    }),\n\n    // ── manual output ──\n    tile_display_rgbw_set: ({ args }) => ({\n      nextState: {\n        r: clamp(pick(args, 0, 0), 0, 255),\n        g: clamp(pick(args, 1, 0), 0, 255),\n        b: clamp(pick(args, 2, 0), 0, 255),\n        w: clamp(pick(args, 3, 0), 0, 255),\n      },\n    }),\n    tile_display_rgbw_set_color: ({ args }) => ({\n      nextState: {\n        r: clamp(pick(args, 0, 0), 0, 255),\n        g: clamp(pick(args, 1, 0), 0, 255),\n        b: clamp(pick(args, 2, 0), 0, 255),\n      },\n    }),\n    tile_display_rgbw_off: () => ({ nextState: { r: 0, g: 0, b: 0, w: 0, animating: 0 } }),\n    tile_display_rgbw_set_current: ({ args }) => ({\n      nextState: {\n        ir: clamp(pick(args, 0, 128), 0, 255),\n        ig: clamp(pick(args, 1, 128), 0, 255),\n        ib: clamp(pick(args, 2, 128), 0, 255),\n        iw: clamp(pick(args, 3, 128), 0, 255),\n      },\n    }),\n    tile_display_rgbw_set_max_current: ({ args }) => ({\n      nextState: { max_current: pick(args, 0, 1) ? 1 : 0 },\n    }),\n\n    // ── host-side animation helpers (steady target modeled, timing not simulated) ──\n    tile_display_rgbw_pulse: ({ args }) => ({\n      nextState: {\n        r: clamp(pick(args, 0, 0), 0, 255),\n        g: clamp(pick(args, 1, 0), 0, 255),\n        b: clamp(pick(args, 2, 0), 0, 255),\n      },\n    }),\n    tile_display_rgbw_breathe: ({ args }) => ({\n      nextState: {\n        r: clamp(pick(args, 0, 0), 0, 255),\n        g: clamp(pick(args, 1, 0), 0, 255),\n        b: clamp(pick(args, 2, 0), 0, 255),\n      },\n    }),\n    tile_display_rgbw_flash: ({ args }) => ({\n      nextState: {\n        r: clamp(pick(args, 0, 0), 0, 255),\n        g: clamp(pick(args, 1, 0), 0, 255),\n        b: clamp(pick(args, 2, 0), 0, 255),\n      },\n    }),\n\n    // ── faults ──\n    tile_display_rgbw_read_faults: ({ state }) => ({\n      scalar:\n        (state.tsd ? 1 << 8 : 0) | ((state.short_mask & 0x0f) << 4) | (state.open_mask & 0x0f),\n    }),\n    tile_display_rgbw_clear_faults: () => ({ nextState: { open_mask: 0, short_mask: 0, tsd: 0 } }),\n    tile_display_rgbw_is_faulted: ({ state }) => ({\n      scalar: state.open_mask || state.short_mask || state.tsd ? 1 : 0,\n    }),\n    tile_display_rgbw_set_short_threshold: () => ({ nextState: {} }),\n    tile_display_rgbw_set_short_shutdown: ({ args }) => ({\n      nextState: { short_shutdown: pick(args, 0, 0) ? 1 : 0 },\n    }),\n    tile_display_rgbw_set_open_shutdown: ({ args }) => ({\n      nextState: { open_shutdown: pick(args, 0, 1) ? 1 : 0 },\n    }),\n\n    // ── autonomous animation engine (AEU) ──\n    tile_display_rgbw_ms_to_slope: ({ args }) => ({ scalar: msToSlope(pick(args, 0, 0)) }),\n    tile_display_rgbw_set_autonomous: ({ args }) => {\n      const ch = clamp(pick(args, 0, 0), 0, 3);\n      const on = pick(args, 1, 1) ? 1 : 0;\n      return { nextState: { ['auto_' + CH[ch]]: on } };\n    },\n    tile_display_rgbw_set_aeu: ({ args }) => ({\n      // Struct arg can't pass through flat args; capture the channel + (best-effort)\n      // a peak/period so the engine demo has something to play.\n      nextState: { aeu_configured: 1, anim_channel: clamp(pick(args, 0, 0), 0, 3) },\n    }),\n    tile_display_rgbw_set_animation: ({ args }) => ({\n      nextState: { anim_channel: clamp(pick(args, 0, 0), 0, 3) },\n    }),\n    tile_display_rgbw_set_exp_dimming: ({ state, args }) => {\n      const bit = 1 << clamp(pick(args, 0, 0), 0, 3);\n      const on = pick(args, 1, 1) ? 1 : 0;\n      return { nextState: { exp_mask: on ? state.exp_mask | bit : state.exp_mask & ~bit } };\n    },\n    tile_display_rgbw_set_phase_align: ({ args }) => ({\n      nextState: { phase_align: clamp(pick(args, 1, 0), 0, 3) },\n    }),\n    tile_display_rgbw_update: () => ({ nextState: { latched: 1 } }),\n    tile_display_rgbw_animate_start: () => ({\n      nextState: { animating: 1, anim_paused: 0, anim_clock: 0 },\n    }),\n    tile_display_rgbw_animate_stop: () => ({\n      nextState: { animating: 0, anim_paused: 0, anim_clock: 0 },\n    }),\n    tile_display_rgbw_animate_pause: () => ({ nextState: { anim_paused: 1 } }),\n    tile_display_rgbw_animate_continue: () => ({ nextState: { anim_paused: 0 } }),\n    tile_display_rgbw_breathe_auto: ({ args }) => {\n      const ch = clamp(pick(args, 0, 0), 0, 3);\n      return {\n        nextState: {\n          anim_channel: ch,\n          ['auto_' + CH[ch]]: 1,\n          anim_peak: clamp(pick(args, 1, 255), 0, 255),\n          anim_period_ms: pick(args, 2, 2000),\n          animating: 1,\n          anim_paused: 0,\n          anim_clock: 0,\n          exp_mask: 1 << ch,\n        },\n      };\n    },\n  },\n\n  provenance: {\n    tile_display_rgbw_find: 'canonical',\n    tile_display_rgbw_set: 'canonical',\n    tile_display_rgbw_set_current: 'canonical',\n    tile_display_rgbw_set_max_current: 'canonical',\n    tile_display_rgbw_read_faults: 'canonical',\n    tile_display_rgbw_clear_faults: 'canonical',\n    tile_display_rgbw_off: 'canonical',\n    // autonomous engine — register-accurate command/config semantics\n    tile_display_rgbw_set_autonomous: 'canonical', // Dev_Config_3 auto_en\n    tile_display_rgbw_ms_to_slope: 'canonical', // lp_time_ms table\n    tile_display_rgbw_animate_start: 'canonical', // CMD 0xFF\n    tile_display_rgbw_animate_stop: 'canonical', // CMD 0xAA\n    tile_display_rgbw_animate_pause: 'canonical', // CMD 0x33\n    tile_display_rgbw_animate_continue: 'canonical', // CMD 0xCC\n    tile_display_rgbw_update: 'canonical', // CMD 0x55 latch\n    tile_display_rgbw_set_exp_dimming: 'canonical', // Dev_Config_5\n    tile_display_rgbw_set_phase_align: 'canonical', // Dev_Config_7\n    // inferred / partially modeled\n    tile_display_rgbw_set_color: 'inferred',\n    tile_display_rgbw_init: 'inferred',\n    tile_display_rgbw_sleep: 'inferred',\n    tile_display_rgbw_wake: 'inferred',\n    tile_display_rgbw_reset: 'inferred',\n    tile_display_rgbw_is_faulted: 'inferred',\n    tile_display_rgbw_set_short_shutdown: 'inferred',\n    tile_display_rgbw_set_open_shutdown: 'inferred',\n    tile_display_rgbw_set_animation: 'inferred', // chains AEUs; only channel captured\n    tile_display_rgbw_breathe_auto: 'inferred', // builds a breathe AEU program\n    // hallucinated — can't model from flat args / not simulated\n    tile_display_rgbw_set_aeu: 'hallucinated', // 5-keyframe struct arg can't pass through\n    tile_display_rgbw_pulse: 'hallucinated',\n    tile_display_rgbw_breathe: 'hallucinated',\n    tile_display_rgbw_flash: 'hallucinated',\n    tile_display_rgbw_set_short_threshold: 'hallucinated',\n    power: 'inferred',\n  },\n\n  // The on-tile RGBW LED — per-channel intensity 0..1 (manual or live breathe).\n  padOutputs(state) {\n    return {\n      R: level(state, 'r'),\n      G: level(state, 'g'),\n      B: level(state, 'b'),\n      W: level(state, 'w'),\n    };\n  },\n\n  power(state) {\n    if (state.enabled !== 1 || state.sleeping === 1) {\n      return {\n        draw_ua: IQ_STANDBY_UA,\n        rails: [\n          {\n            name: 'V+',\n            role: 'supply',\n            v_mv: state.vplus_mv,\n            i_ua: IQ_STANDBY_UA,\n            pads: ['10'],\n            note: 'standby',\n          },\n        ],\n      };\n    }\n    const ledMa = chMa(state, 'r') + chMa(state, 'g') + chMa(state, 'b') + chMa(state, 'w');\n    const inUa = (ledMa * 1000 * VOUT_MV) / (BOOST_EFF * state.vplus_mv);\n    const drawUa = Math.round(IQ_ACTIVE_UA + inUa);\n    return {\n      draw_ua: drawUa,\n      rails: [\n        {\n          name: 'V+',\n          role: 'supply',\n          v_mv: state.vplus_mv,\n          i_ua: drawUa,\n          pads: ['10'],\n          note: 'boost input + LED current',\n        },\n        {\n          name: 'VLED',\n          role: 'output',\n          v_mv: ledMa > 0 ? VOUT_MV : 0,\n          note: 'boosted LED common-anode (on-tile)',\n        },\n      ],\n    };\n  },\n};\n\nexport default sim;\n",
    "status": "validated",
    "updated_at": "2026-06-22 03:40:21"
  }
}