# Infrared

The infrared emitter and receiver on a Kode Dot, both on the RMT peripheral: what a remote's frame looks like, how the Kode Dot hears itself, and the code to send one.

## What it is

Infrared is how a TV remote talks: a light you cannot see, switched on and
off in a pattern, at a carrier the receiver is tuned to. The Kode Dot has
both halves. Two emitters send, so it can be the remote; a receiver listens,
so it can learn a remote's codes or take commands from one.

The P4's RMT peripheral is built for exactly this. You hand it a list of on
and off durations in microseconds and it adds the 38 kHz carrier on the way
out; the receiver hands it back marks and spaces with the carrier already
stripped. Because both halves are on board, the Kode Dot can hear what it
sends, which is the fastest test you have.

## How it is connected

```mermaid
flowchart LR
  p4["ESP32-P4"] -- "GPIO45, RMT TX" --> q["MOSFET"] --> tx["2× SFH4045N, 950 nm"]
  rx["TSOP75338, 38 kHz"] -- "GPIO46, RMT RX" --> p4
```

*Emitters and receiver on two GPIOs, both driven by the RMT peripheral*

The two **SFH4045N** emitters are switched through a MOSFET from `GPIO45`,
active high, with 39 Ω each to the battery rail. The **TSOP75338** receiver
demodulates 38 kHz and drives `GPIO46` low for each mark. No enable, no
reset, no interrupt: two pins and nothing else.

## Specifications

| Thing | Value |
| --- | --- |
| Emitter | Two SFH4045N, 950 nm, 39 Ω each, switched by a MOSFET from `GPIO45`. Active high, idle low |
| Receiver | TSOP75338, 38 kHz demodulator, output on `GPIO46`. Active low, already demodulated |
| Peripheral | RMT, transmit and receive, 1 MHz resolution: one tick is one microsecond |
| Carrier | A property of the transmit channel: 38 kHz, 33 % duty, on the mark. Sony wants 40 kHz |
| NEC timing | Leader 9000/4500 µs, bit 0 560/560, bit 1 560/1690, repeat 9000/2250. Decode with ±300 µs of margin |
| Protocols run | NEC standard and extended, Samsung32, Sony SIRC-12 on a board. RC5, RC6 and Kaseikyo are not written |
| Current | 147 mA peak while a mark is lit, about 12 mA averaged over a frame |
| Kode driver | None yet. Kode's `nec_ir` in the validation repository sends NEC |
| Validated | `Kode-Dot-Val/10-IR`: self-echo on the board and a Flipper Zero cross-check |

## Gotchas

> **WARNING**
>
> **Your Sony code does nothing after a Samsung one worked.** The carrier
> belongs to the RMT channel, so going from 38 to 40 kHz means applying the
> carrier again with the channel idle in between. Group sends by frequency.

> **WARNING**
>
> **The receiver reports your own frames.** A decoded frame arriving right
> after you sent one is yours, bounced off the room. Ignore the receiver while
> the transmitter is busy.

## Apps that use it

On the marketplace, apps that put this part to work. Install one from
`start/first-app` and see it before you write a line.

- https://apps.kode.diy/app/87b43a63-5881-4b90-9c47-a1511a5bdfd5
- https://apps.kode.diy/app/5b864b5a-8d84-48c6-aba9-636b12c34aae
- https://apps.kode.diy/app/bfb1fb03-516a-4379-8b5b-101df6758524
- https://apps.kode.diy/app/5d4012e8-74c6-4542-ab47-4b42a1927ef4
- https://apps.kode.diy/app/963762ed-8005-4342-94ec-4e8253e48bf3
- https://apps.kode.diy/app/236f65e9-55e6-4eed-946f-bd3051513957
- https://apps.kode.diy/app/bb67aa95-94a5-486a-9e26-1f6b88d17470
- https://apps.kode.diy/app/db6f7781-9d9c-4240-87cb-8f4f9bf1e796

## Example code

Send one NEC frame, the format most TV remotes use. Point a phone camera at
the front of the Kode Dot and the emitters flash faintly; a TV that speaks
NEC address `0x00` reacts to command `0x15`.

```c
/*
 * infrared_nec.c - 2026-09-18 - from Kode with love
 *
 * Sends one NEC frame from the Kode Dot's infrared emitter: address 0x00,
 * command 0x15, the way most TV remotes frame a key press. Point a phone
 * camera at the front of the Kode Dot and the emitters flash faintly; the
 * receiver on the same board decodes the frame straight back.
 */
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"

#define IR_TX_GPIO 45

/* One NEC bit: a 560 µs mark, then a 560 µs space for 0 or 1690 µs for 1. */
static rmt_symbol_word_t nec_bit(bool one)
{
    return (rmt_symbol_word_t){ .level0 = 1, .duration0 = 560, .level1 = 0, .duration1 = one ? 1690 : 560 };
}

/* The 34-symbol frame: leader, address, ~address, command, ~command, stop. */
static void nec_frame(rmt_symbol_word_t *out, uint8_t address, uint8_t command)
{
    *out++ = (rmt_symbol_word_t){ .level0 = 1, .duration0 = 9000, .level1 = 0, .duration1 = 4500 };
    uint32_t payload = address | (uint32_t)(uint8_t)~address << 8
                     | (uint32_t)command << 16 | (uint32_t)(uint8_t)~command << 24;
    for (int i = 0; i < 32; i++) *out++ = nec_bit((payload >> i) & 1);
    *out = (rmt_symbol_word_t){ .level0 = 1, .duration0 = 560, .level1 = 0, .duration1 = 0x7FFF };
}

/* A 1 MHz RMT channel with a 38 kHz carrier on the mark, then one frame. */
void app_main(void)
{
    rmt_channel_handle_t channel;
    rmt_tx_channel_config_t cfg = { .clk_src = RMT_CLK_SRC_DEFAULT, .gpio_num = IR_TX_GPIO,
                                    .mem_block_symbols = 48, .resolution_hz = 1000000, .trans_queue_depth = 1 };
    ESP_ERROR_CHECK(rmt_new_tx_channel(&cfg, &channel));
    rmt_carrier_config_t carrier = { .frequency_hz = 38000, .duty_cycle = 0.33 };
    ESP_ERROR_CHECK(rmt_apply_carrier(channel, &carrier));
    rmt_encoder_handle_t copy;
    ESP_ERROR_CHECK(rmt_new_copy_encoder(&(rmt_copy_encoder_config_t){}, &copy));
    ESP_ERROR_CHECK(rmt_enable(channel));

    rmt_symbol_word_t frame[34];
    nec_frame(frame, 0x00, 0x15);
    ESP_ERROR_CHECK(rmt_transmit(channel, copy, frame, sizeof frame, &(rmt_transmit_config_t){}));
}
```

Compiled on the date in the header with ESP-IDF v6.1. The timings and the
carrier are the ones Kode's validation ran on a board, where a Flipper Zero
read the frame as `NEC A:00 C:15`.

## In the SDK

- [`kdrv_ir`](https://github.com/kodediy/kode-sdk/tree/main/components/kdrv/kdrv_ir): the infrared pair in `kode-sdk`.
