# RFID

The 125 kHz block on a Kode Dot: no reader chip, a carrier the P4 makes itself, an envelope on an ADC pin, the LDO to raise before a write, and the code to start the field.

## What it is

Low-frequency RFID is the white card that opens an office door or a hotel
room: a 125 kHz field from the reader powers a tiny chip in the card, and
the chip answers by loading that field in a pattern. The Kode Dot reads
those cards and, with a blank T5577 card, writes one, which is how a 125 kHz
card gets cloned.

There is no reader chip. The P4 makes the 125 kHz carrier itself, a
MOSFET half-bridge pushes it into a loop etched on the antenna board, and a
diode detector and three op-amp stages turn the card's answer into an
envelope on an ADC pin that your code decodes. It cannot pretend to be a
card: it drives its own field, and a card has to load somebody else's.

## How it is connected

```mermaid
flowchart LR
  p4["ESP32-P4"] -- "GPIO47, 125 kHz" --> bridge["Half-bridge"] --> ant["Loop antenna + tank"]
  ant --> det["Detector + 3× LMV358"] -- "GPIO23, ADC1 ch 7" --> p4
```

*The P4 makes the carrier, the antenna board demodulates the answer, the ADC reads it*

The carrier leaves on `GPIO47`, the envelope comes back on `GPIO23`, and a
comparator on `GPIO46` senses the carrier, unused by any example. `GPIO47`
lives in an IO bank fed by the P4's internal LDO channel 4, which starts off,
so the pin reaches only about 1.2 V until firmware raises it.

## Specifications

| Thing | Value |
| --- | --- |
| Carrier out | `GPIO47`, 125 kHz from LEDC, through a MOSFET half-bridge into the loop and its tank |
| Envelope in | `GPIO23`, ADC1 channel 7. Diode detector, three LMV358 stages at about ×470, biased at 1.65 V |
| Carrier sense | `GPIO46`, a TL331 comparator on the antenna board. **TBD**: what it reads on a V1 board. No example uses it |
| Reader | Continuous ADC at 80 kSPS, EM4100 Manchester at RF/64 and RF/32 |
| Writer | T5577, page 0: block 0 `0x00148040`, then the 64-bit EM4100 frame in blocks 1 and 2, by gapping the field. Timings from the Flipper Zero |
| The LDO | Channel 4 at 3.3 V through `esp_ldo` before a write. Reads work at the 1.2 V it starts at; writes do not |
| Also written | Indala26 to a T5577. Reading Indala is not possible: its 62.5 kHz subcarrier is beyond the receive filter |
| Emulation | No |
| Kode driver | None yet. `rfid_reader`, `rfid_write` and `rfid_tool` in the validation repository |
| Validated | `Kode-Dot-Val/13-RFID`: an EM4100 read and a clone to a T5577 re-read by the same board |

## Gotchas

> **WARNING**
>
> **Reads work and writes fail.** You did not raise the LDO. The carrier pin
> reaches 1.2 V out of reset, enough to read but not to give a card the energy
> to burn its EEPROM. Switch LDO channel 4 to 3.3 V before a write.

> **WARNING**
>
> **The decoder locks onto the wrong bit rate.** You filtered the ADC samples.
> With weak coupling the front end turns the levels into narrow spikes, and a
> moving average widens them past the glitch threshold. Threshold the raw
> samples and drop the sub-glitch spikes, as Kode's decoder does.

> **NOTE**
>
> **A phone or a transport card does nothing here.** Those are 13.56 MHz, the
> NFC reader's job, see `device/nfc`. White access and hotel cards are usually
> 125 kHz.

## Example code

Raise the field and log the envelope's peak-to-peak ten times a second. It
reads about 40 mV with nothing near and jumps to about 1 V as a 125 kHz card
reaches the back of the Kode Dot.

```c
/*
 * rfid_field.c - 2026-09-18 - from Kode with love
 *
 * Raises the Kode Dot's 125 kHz field and watches a card arrive. Puts a
 * 125 kHz square wave on GPIO47 with LEDC, then samples GPIO23 two hundred
 * times a loop and logs the peak-to-peak of the envelope: about 40 mV with
 * nothing near, about 1 V once a card is on the antenna.
 */
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

/* A 50 % square wave at 125 kHz on the carrier pin. */
static void carrier_on(void)
{
    ledc_timer_config_t timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .timer_num = LEDC_TIMER_0,
                                  .duty_resolution = LEDC_TIMER_8_BIT, .freq_hz = 125000, .clk_cfg = LEDC_AUTO_CLK };
    ledc_timer_config(&timer);
    ledc_channel_config_t channel = { .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0,
                                      .timer_sel = LEDC_TIMER_0, .gpio_num = GPIO_NUM_47, .duty = 128 };
    ledc_channel_config(&channel);
}

/* Start the field, then measure the envelope's swing over and over. */
void app_main(void)
{
    carrier_on();

    adc_oneshot_unit_handle_t adc;
    adc_oneshot_unit_init_cfg_t unit = { .unit_id = ADC_UNIT_1 };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&unit, &adc));
    adc_oneshot_chan_cfg_t chan = { .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc, ADC_CHANNEL_7, &chan));   /* GPIO23 */

    while (1) {
        int lo = 4095, hi = 0, v;
        for (int i = 0; i < 200; i++) {
            adc_oneshot_read(adc, ADC_CHANNEL_7, &v);
            if (v < lo) lo = v;
            if (v > hi) hi = v;
        }
        ESP_LOGI("rfid", "envelope pk-pk %4d mV", (hi - lo) * 3300 / 4095);
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}
```

Compiled on the date in the header with ESP-IDF v6.1. The carrier and the
envelope check ran on a board in `13-RFID/rfid_lf`. **TBD**: a run on a V1
board.

## In the SDK

- [`kdrv_nfc`](https://github.com/kodediy/kode-sdk/tree/main/components/kdrv/kdrv_nfc): the two card radios in `kode-sdk`. **TBD**: the 125 kHz reader.
