SDK
KTD2026 LED driver
The chip behind the Kode Dot's one RGB LED: how the colours are wired, the registers the driver writes, the value that keeps it lit, and who may call it.
What it is
The Kode Dot has one RGB LED and no wire from the P4 to it. A KTD2026 current-sink driver on the shared I2C bus sets how much current flows through each of the three colours, and an 8-bit level per colour is all your code says. The driver turns a level into a current, remembers the last colour, and offers on, off and toggle.
The chip can also blink and breathe on its own. The firmware uses neither. A blink in kodeOS is a timer in the LED's owner, AO_LedMgr, that toggles the LED, and the chip only ever sees a steady colour.
How it is connected
flowchart LR
subgraph board["On the Kode Dot"]
ao["AO_LedMgr"] --> p4["ESP32-P4"]
p4 -- "I2C, 0x30" --> ktd["KTD2026 (U8)"]
subgraph d13["LED D13"]
g["green die"]
b["blue die"]
r["red die"]
end
ktd -- "OUT1" --> g
ktd -- "OUT2" --> b
ktd -- "OUT3" --> r
vsys["+VSYS"] --> g
vsys --> b
vsys --> r
end
class ao ao
class p4,ktd chip
class vsys storeThe chip is U8 and answers at 0x30 on the shared bus (KBSP_I2C_ADDR_LED). It has no identity register, so the driver confirms it by an acknowledge. D13 is a three-die LED with a common anode on +VSYS. The three cathodes go to the chip's current sinks. The chip is supplied from 3.3 V. It has no enable pin and no interrupt line: reset and enable are register writes. Only C411, 100 nF, decouples it.
Which sink is which colour
Which sink is which colour. OUT1 lights the green die, OUT2 the blue and OUT3 the red. The schematic does not settle this, and a bench test did. The mapping lives in one function, led_drive(), and nowhere else. test component=led is what catches a board that differs.
The owner
The owner. AO_LedMgr claims KBSP_RES_LED and is the only caller of the driver. That is why the driver needs no lock: an Active Object finishes one event before it takes the next. Everyone else publishes a signal, and the facade does it for apps: kode_led_set(), kode_led_toggle() and kode_led_rgb(). A call answers KODE_NO_RESOURCES when the event pool is dry.
AO_LedMgr runs at priority 7 with a queue of 16. It starts the LED dark and subscribes to five signals: LED_SET_SIG, LED_TOGGLE_SIG, LED_RGB_SIG, LED_BLINK_PERIOD_SIG and LED_HEARTBEAT_ENABLE_SIG.
The heartbeat is a periodic timer that toggles the LED, 500 ms by default, off until a request enables it. A period below 10 ms (KODE_LED_BLINK_MIN_MS) is refused with a warning. Disabling the heartbeat turns the LED off.
AO_Power publishes LED_RGB_SIG itself while a flash write runs, so the LED pulses blue for 500 ms and goes dark for 500 ms, screen on or off. Outside a write it publishes nothing: the battery has no colour.
The test row
The row. test component=led is a substate of AO_LedMgr. It lights red, green and blue in turn at level 96 (KODE_LED_TEST_LEVEL) and waits for the pad: the top button confirms the colour, the bottom button refuses it, and 10 s of silence is a timeout. The row ends at the first colour not confirmed.
For its whole length AO_LedMgr drops every signal that would put a colour on the chip, so the operator never answers for a colour the row did not ask about. A heartbeat or period request that arrives meanwhile is recorded and applied when the row exits. The exit turns the LED off.
Bring-up and a colour
Bring-up. kdrv_led_init() probes 0x30 for 100 ms and attaches the device. It writes 0x07 to register 0x00, a full reset, which the chip does not acknowledge, so the status is ignored. After 1 ms it writes 0x18 to the same register. If that write fails, the driver forgets the device and the next call runs the whole sequence again. Then it clears all channels, stores white as the default colour and puts the chip to sleep with 0x08. The first LED call of any kind runs init itself.
A colour. led_drive() wakes the chip with 0x18 if anything is to be lit, writes the three current registers, writes the channel-enable register, and, if everything is now dark, puts the chip back to sleep. Channels are switched off in the enable register before the sleep write, which is the datasheet's order. The wake write is the SDA toggling that wakes the chip, so it is retried once if the first is not acknowledged.
Specifications
The board
| Thing | Value |
|---|---|
| Part | KTD2026EWE-TR, U8 |
| Address | 0x30, found by acknowledge, because the chip has no identity register |
| LED | D13, three dies, common anode on +VSYS. Cathodes: OUT1 green, OUT2 blue, OUT3 red |
| Current | I = 0.125 mA × (code + 1) per channel. The chip reaches 24 mA (0xBF); the driver never sends more than 5.0 mA (KTD_FULL_SCALE_MA), because this is a status indicator inside a case |
| Levels | 0 to 255 per colour map linearly onto 0 to 5 mA. Level 255 is full drive; the production row uses 96, about 1.9 mA |
| Supply current | 190 µA with the chip on and every channel off; 0.1 µA asleep (datasheet) |
| Wake from sleep | By traffic on SDA. Other devices' bus traffic wakes it for as long as it lasts plus 600 µs; the registers keep their values |
| Bus | 400 kHz, shared with the IMU, expander, codec and the rest |
The registers
The driver writes registers 0x00, 0x04 and 0x06 to 0x08. The others are in the datasheet and used by the board's validation program for a breathing demo; the firmware never writes them.
| Reg | Name | Bits and meaning |
|---|---|---|
0x00 | Enable and reset | [2:0] reset and timer slot, [4:3] enable control, [6:5] ramp scale |
0x01 | Flash period | [6:0] period of the breathing curve, [7] linear ramp |
0x02 | PWM1 | Duty of PWM1, 256 steps |
0x03 | PWM2 | Duty of PWM2 |
0x04 | Channel enable | Two bits per channel, OUT1 in bits 1:0, OUT2 in bits 3:2, OUT3 in bits 5:4: 00 off, 01 on, 10 PWM1, 11 PWM2 |
0x05 | Ramp | [3:0] rise time, [7:4] fall time |
0x06 | ISET1 | OUT1 current, 0.125 mA per code, plus one step |
0x07 | ISET2 | OUT2 current |
0x08 | ISET3 | OUT3 current |
The values the driver writes to register 0x00:
| Value | Name | Meaning |
|---|---|---|
0x07 | KTD_RESET_FULL | Full register reset. The chip needs about 200 µs afterwards; the driver waits 1 ms |
0x18 | KTD_EN_NORMAL | Timer slot 1, enable control 11 (device always on), ramp scale 1x. Held while anything is lit |
0x08 | KTD_EN_SLEEP | Timer slot 1, enable control 01 (on while SDA toggles). Held while nothing is lit |
The driver
| Call | What it does |
|---|---|
kdrv_led_init() | Brings the chip up, dark and asleep. Safe to call again |
kdrv_led_rgb(r, g, b) | Sets the colour. (0, 0, 0) means off and keeps the last real colour |
kdrv_led_set(on) | On at the last colour set, or white if none; or off |
kdrv_led_toggle() | Inverts on and off |
The signals
| Signal | Event | What AO_LedMgr does |
|---|---|---|
LED_SET_SIG | LedEvt.on | kdrv_led_set(on) |
LED_TOGGLE_SIG | LedEvt | kdrv_led_toggle() |
LED_RGB_SIG | LedRgbEvt (r, g, b) | kdrv_led_rgb(r, g, b) |
LED_BLINK_PERIOD_SIG | LedBlinkEvt.period_ms | Stores the period, at least 10 ms, and re-arms a running heartbeat |
LED_HEARTBEAT_ENABLE_SIG | LedEvt.on | Starts the toggling timer, or stops it and turns the LED off |
| Console | led_on, led_off, led_toggle, led_blink, led_hb, led_rgb, test component=led |
Gotchas
Example code
Cycle the LED through red, green and blue, one second each. You see the colour change every second, dim enough to look at.
/*
* led_cycle.c - 2026-10-28 - from Kode with love
*
* Cycles the Kode Dot's RGB LED through red, green and blue, one second
* each, by asking AO_LedMgr, the owner of the KTD2026, for each colour. The
* level is 32 of 255 on purpose: this LED is bright. You see the colour
* change every second.
*/
#include "kode.h"
#define LEVEL 32U
/* Show one colour, then wait a second. */
static void show(uint8_t red, uint8_t green, uint8_t blue)
{
(void)kode_led_rgb(red, green, blue);
kode_delay_ms(1000);
}
/* Bring the system up, then cycle the three colours for ever. */
void app_main(void)
{
kode_init();
while (1) {
show(LEVEL, 0U, 0U);
show(0U, LEVEL, 0U);
show(0U, 0U, LEVEL);
}
}Compiled against the SDK with ESP-IDF v6.1 on 2026-10-28. TBD: a run on a Kode Dot.
In the SDK
components/kdrv/led: the driver,kdrv_led.c, and its header.kdrv_led.h: the four calls.components/kaos/led: AO_LedMgr, the owner, with the heartbeat and the production row.