# Status LED

The RGB LED on a Kode Dot: a KTD2026 driver on the shared I2C bus, why its brightness is a current and not a duty cycle, and the code to light it.

## What it is

One small RGB LED on the front, the kind that says "charging" or "message"
on a phone. It is three LEDs in one package, red, green and blue, and mixing
their brightness gives any colour.

The P4 does not drive it directly. A **KTD2026**, a three-channel LED driver,
sits between them on the I2C bus and sinks the current for each colour. You
tell it how many milliamps to put through each LED, and it holds that current
steady. That is different from a bare LED on a pin, where brightness is how
fast you switch it: here brightness is a number you write, and the driver
has two engines of its own for blinking and breathing without your code
doing anything.

## How it is connected

```mermaid
flowchart LR
  p4["ESP32-P4"] -- "I2C, 0x30" --> drv["KTD2026"]
  drv -- "sink 1, 2, 3" --> led["RGB LED, common anode on VSYS"]
```

*The LED's three colours are three current sinks on one I2C driver*

The KTD2026 answers at `0x30` on the shared bus. The LED's common anode is
on the system rail and its three cathodes go to the driver's three sinks.
There is no enable pin, no reset pin and no interrupt: everything is a
register write, including turning the chip on.

## Specifications

| Thing | Value |
| --- | --- |
| Driver | KTD2026, U8, at `0x30` on the shared I2C bus. No identity register: presence is an ACK |
| LED | One RGB package, common anode, three current sinks |
| Brightness | A current per channel, 0.125 mA per step: `I = 0.125 × (code + 1)` mA, up to 24 mA |
| Per channel | Off, on, or one of two PWM engines for blink and breathe |
| Enable | Register `0x00`: `0x18` keeps the chip on whatever the bus does. Off out of reset |
| Kode driver | `kode_led_init`, `kode_led_set_rgb(r, g, b)`, `kode_led_off` in the `kode` component |
| Which sink is which | **TBD**: channels 1, 2, 3 map to red, green, blue by the schematic; confirming it takes a look at the LED |
| Cost | About 71 mA with all three at maximum; 5 mA per channel is plainly visible |
| Validated | `Kode-Dot-Val/08-LED`: solid colours and breathing on a board |

## Gotchas

> **WARNING**
>
> **The LED dies the moment anything else talks on the bus.** Out of reset the
> chip shuts down whenever the bus lines go low, and the bus is shared, so a
> sensor read puts the LED out. Write `0x18` to register `0x00` first, "always
> on"; `kode_led_init` does.

> **NOTE**
>
> **A colour that looks right on the screen looks wrong on the LED.** The screen
> is gamma-encoded and the LED is a linear current, so the same byte lands
> about five times brighter on the LED and washes a red out to pink.
> `kode_led_set_rgb` corrects for that; if you write registers yourself, expect
> to.

## Example code

Cycle the LED through red, green, blue and white, one second each, then turn
it off. You see the four colours, then nothing.

```c
/*
 * led_cycle.c - 2026-09-18 - from Kode with love
 *
 * Lights the Kode Dot's status LED red, green, blue and white for a second
 * each, then turns it off. Brings the board up, wakes the KTD2026 through
 * the Kode driver, and writes one colour per second.
 */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "kode.h"

/* Board, driver, four colours, off. */
void app_main(void)
{
    ESP_ERROR_CHECK(kode_bsp_init());
    ESP_ERROR_CHECK(kode_led_init());

    const uint8_t colours[4][3] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 255} };
    for (int i = 0; i < 4; i++) {
        ESP_ERROR_CHECK(kode_led_set_rgb(colours[i][0], colours[i][1], colours[i][2]));
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
    ESP_ERROR_CHECK(kode_led_off());
}
```

Compiled on the date in the header against the `kode` component with
ESP-IDF v6.1. **TBD**: a run on a V1 board.

## In the SDK

- [`kdrv_led.c`](https://github.com/kodediy/kode-sdk/blob/main/components/kdrv/kdrv_led.c): the KTD2026 driver in `kode-sdk`.
