# 125 kHz RFID

The low-frequency front end on the antenna PCB: a discrete 125 kHz reader that decodes EM4100 tags, writes them onto a T5577, and measures a foreign field.

## What it is

Access cards, key fobs and pet tags often work at 125 kHz, the low-frequency (LF) band of RFID. A Kode Dot reads and copies the common ones with a circuit on the antenna PCB. There is **no reader chip**: the circuit is discrete, built from separate transistors, a comparator and op-amps, and the ESP32-P4 does the rest in software.

The driver does three things:

1. It **reads** an EM4100 or EM4102 tag. The P4 drives a 125 kHz carrier onto a printed loop antenna, the passive tag answers by changing the load it puts on the field, and the P4 samples the demodulated signal and decodes the 40-bit ID.
2. It **clones** an EM4100 ID onto a rewritable T5577 tag, by opening timed gaps in the carrier.
3. It **captures** a window of the demodulated line with the carrier off, to see what another 125 kHz field puts on the coil, such as a reader or a cloner.

The decoder works on the raw envelope and reads weakly coupled tags only if they are flat and close to the antenna. Other LF formats are outside this driver.

## How it is connected

```mermaid
flowchart LR
  subgraph p4["ESP32-P4"]
    ao["AO_Rfid<br/>rfid_worker"] --> drv["kdrv_rfid_lf"]
  end
  subgraph pcb["Antenna PCB"]
    hb["MOSFET<br/>half-bridge"] --> coil["loop antenna<br/>ANT1"]
    coil --> det["envelope detector<br/>op-amps"]
  end
  drv -- "LEDC 125 kHz, GPIO47" --> hb
  det -- "RFID_RF_IN, GPIO54, ADC2" --> drv
  drv -- "GPIO45, RFID_PULL" --> det
  class ao ao
  class hb,coil,det chip
```

*The 125 kHz front end*

**AO_Rfid** (`sdk/kaos/rfid`) owns the front end and its ADC (`KBSP_RES_RFID`), and is the only caller. It runs at priority `KODE_PRIO_RFID` (23), high on purpose, so nothing interrupts a T5577 write in the middle of a bit. Blocking work leaves the AO for its worker, `rfid_worker` (8192-byte stack, in PSRAM, since no job touches internal flash), which calls `kdrv_rfid_lf_run()`.

### The pins

Four pins carry the circuit (`KBSP_PIN_RFID_*`):

| Pin | Name | What it does |
| --- | --- | --- |
| GPIO47 | `RFID_OUT` | The carrier. It switches Q8, an inverter with a 510 Ω pull-up to the system rail, which drives the complementary pair Q7 and Q9 into the coil through two 4.7 Ω resistors |
| GPIO46 | `RFID_CARRIER` | An input only. A TL331 comparator squares the coil's zero crossings. The driver leaves it a plain input |
| GPIO54 | `RFID_RF_IN` | The demodulated envelope, read by the ADC. It idles at 0.62 V and swings 0 to 1.25 V |
| GPIO45 | `RFID_PULL` | Gate of Q2, which shunts the envelope detector to ground. High means the receiver hears nothing |

The coil and a 3.3 nF capacitor form a series-resonant tank, and the whole stage runs from the system rail (`SYS` of the BQ25896, see `sdk/kdrv/bq25896`), so the swing on the coil follows the system rail. The receiver is a diode envelope detector followed by gain stages (5.7, then two inverting low-pass stages of gain -10 and about 73 kHz). Those values come from the DVT schematic and nobody has measured them.

### Rest, ADC, carrier and rail

**`RFID_PULL` rests high.** An unconnected pin is high anyway, because R11 on the main PCB pulls it up. The driver drives it low for every read, capture and clone, and `kdrv_rfid_lf_rest()` raises it again. Held low it costs about 330 µA through R11.

**The demod line is read on ADC2.** GPIO54 is `ADC2_CH5`. GPIO23 is the C5's enable line and also `ADC1_CH7`, so a read on ADC1 would reconfigure the enable line and drop the radio. The analog positions of the expansion header are on ADC1, a unit of their own.

**The carrier** is a 125 kHz square wave from one LEDC channel, taken through khal's PWM service with a timer of its own: 8-bit resolution, duty 128 of 256, drive strength at maximum. "Off" means no channel is held. If the expansion header holds all four LEDC timers, `kdrv_rfid_lf_field_on()` returns `KODE_NO_RESOURCES` and the read or clone ends at once.

**VO4, the IO rail.** GPIO45 to GPIO47 sit in the `VDD_IO_5` bank, fed by LDO channel 4 of the P4. A clone needs the rail at 3.3 V so the T5577 harvests enough energy to program its memory, and a read leaves it at its boot level.

With `boost` true the driver tries to acquire the channel at 3300 mV. When a mounted microSD card already holds that channel, the acquire fails and the driver shares the rail as it stands, which is already 3.3 V for a standard card.

### The decoder

`krfid_em4100_decode()` thresholds the raw samples halfway between the lowest and highest and computes run lengths. It estimates the Manchester half-bit as the smallest run many others confirm, rebuilds the half-bit stream and decodes it over both pairing phases and both polarities. Then it checks the frame: nine header ones, row parity, column parity and a zero stop bit.

It returns:

- 0 with a 5-byte ID (most-significant nibble first);
- -1 when the window is flat (peak-to-peak under 120 counts);
- -2 when a tag is present but no clean frame came out.

It rejects an all-zero ID.

A diagnostics structure reports the peak-to-peak, the half-bit width, the bit rate (RF/16, 32 or 64), the phase, the polarity and where the frame starts, even on failure. The thresholding stays on the raw envelope on purpose, because smoothing breaks a read with weak coupling.

`krfid_em4100_frame()` is the inverse: it turns a 5-byte ID into the 64-bit frame a T5577 holds. The decoder's scratch space (`krfid_em4100_work_t`, 84 KB) is lent by the caller for one decode, so the codec holds no large static and names no allocator.

### The read

`kdrv_rfid_lf_read_window()` fills one window of 12000 samples at 80 kS/s, which is 150 ms and well over two 64-bit frames, and decodes it. The read buffers (the window plus the scratch, 108 KB in all) are taken from PSRAM on first use, and the continuous ADC handle on ADC2 channel 5 is created on first use.

A READ job raises the field without the boost and reads windows until one decodes or the time asked for runs out. It stops at once on any failure but `KODE_TIMEOUT`, and then drops the field.

### The T5577 downlink

`kdrv_rfid_lf_clone()` writes three blocks of page 0. Block 0 gets the config word `0x00148040` (Manchester, RF/64, two data blocks), block 1 the top 32 bits of the EM4100 frame, and block 2 the low 32.

Each block is sent bit by bit as on-off keying on the carrier, in field clocks of 8 µs: a start gap of 30, a `0` is 24 clocks of carrier and a `1` is 56, a write gap of 18 between bits, and 700 clocks for the EEPROM to program. A gap is the carrier's timer paused, a busy wait, and the timer resumed, so a retune on the header cannot stretch it.

The driver waits one tick between blocks, and the CLONE job then reads a window back to verify the write. The write starts at once and does not wait for a tag, so the tag must already be on the antenna.

### The capture and the certification carrier

`kdrv_rfid_lf_field_capture()` is the read window with no carrier and no decoder. It keeps only valid conversions of the demod line, then describes the samples (`krfid_wave_stats()`) and squeezes them into a waveform (`krfid_wave_decimate()`). It runs on the worker only, never under the driver's own carrier.

A build with `CONFIG_KODEOS_NFC_CERT` also has `kdrv_rfid_lf_cert_carrier()`, a continuous maximum-drive carrier, plain or on-off modulated, for the FCC emission measurement. The front end cannot sense its own antenna, so success means the carrier was commanded. It does not prove that RF radiated.

### The files

The files of the component:

| File | What it does |
| --- | --- |
| `include/kdrv_rfid_lf.h`, `src/kdrv_rfid_lf.c` | The front end: pins, carrier, VO4, ADC window, read, capture, T5577 clone, the jobs, the certification carrier |
| `include/krfid_em4100.h`, `src/krfid_em4100.c` | The EM4100 decoder and frame encoder. Pure C, no ESP-IDF |
| `include/krfid_wave.h`, `src/krfid_wave.c` | Window statistics and peak-per-bucket decimation. Pure C |
| `include/krfid_env.h`, `src/krfid_env.c` | Folds RMT comparator pulse widths into one envelope sample per carrier period. Pure C. Only the host tests call it |

## Specifications

### The front end

| Thing | Value |
| --- | --- |
| Carrier | 125 kHz (`RFID_CARRIER_HZ`), 8-bit LEDC, 50 % duty |
| Demod input | GPIO54, `ADC2_CH5`, continuous mode, 80 kS/s (`RFID_SAMPLE_HZ`) |
| Window | 12000 samples (`KRFID_EM4100_MAX_SAMPLES`) = 150 ms. A capture is cut to `KDRV_RFID_LF_CAPTURE_MS_MAX` (150) |
| ADC read | DMA wait up to 1000 ms; the fill gives up after 512 reads, so a dead ADC cannot hang the worker |
| Buffers | 108 KB in PSRAM, taken on use and returned at `field_off()` |
| Boost | VO4 at 3300 mV for a clone and for a held certification carrier, never for a read |
| T5577 config word | `0x00148040` |
| T5577 timing | Field clock 8 µs: start gap 30, write gap 18, `0` 24, `1` 56, program 700 |
| Tags read | EM4100 and EM4102 at RF/16, RF/32 and RF/64 |

### The driver

| Call | What it does |
| --- | --- |
| `kdrv_rfid_lf_init()` | Configures `RFID_PULL` (rest, high) and `RFID_CARRIER` (input). Always `KODE_OK`: a board without the antenna PCB still boots |
| `kdrv_rfid_lf_field_on(boost)`, `_field_off()` | Carrier up (and VO4 with `boost`); down, with the ADC handle, the buffers and VO4 released |
| `kdrv_rfid_lf_rest()` | Raise `RFID_PULL`. Refused while a job runs or the carrier is up |
| `kdrv_rfid_lf_read_window(id, &diag)` | Fill one window and decode it. The field must be up. `KODE_OK`; `KODE_TIMEOUT` for a flat or undecodable window; `KODE_ERROR` for a NULL `id` or an ADC that would not configure; `KODE_NO_RESOURCES` when PSRAM is out |
| `kdrv_rfid_lf_clone(id)` | Write the three T5577 blocks. `KODE_DENIED` with the field down, `KODE_ERROR` for a NULL `id` |
| `kdrv_rfid_lf_field_capture(ms, &stats, wave, cap, &n)` | The carrier-off window. `KODE_DENIED` off the worker or under the carrier, `KODE_ERROR` for `ms` 0 or an ADC that delivered nothing. Outputs are written only on `KODE_OK`, and each may be NULL |
| `kdrv_rfid_lf_run(job, id, &rf_div)` | Run a `READ`, `CLONE` or `CAPTURE` job. Worker only |
| `kdrv_rfid_lf_set_worker_check(fn)` | The owner tells the driver how to recognise the worker's task, so a capture from any other task is refused |
| `kdrv_rfid_lf_cert_carrier(modulated)` | Hold a maximum-drive carrier (needs `CONFIG_KODEOS_NFC_CERT`) |

## Gotchas

> **WARNING**
>
> **A read works only when the tag is flat and close.** LF coupling depends on the tag and on where it sits, so a tag held at an angle returns `KODE_TIMEOUT` with a window that has signal but no clean frame. That is the antenna's limit, with the firmware working as designed. The decoder works on the raw envelope on purpose, so do not smooth the samples to improve it.

> **WARNING**
>
> **A clone needs the T5577 on the antenna before it starts.** The write does not wait for a tag, and a `ms` passed with a CLONE job does not reach the downlink. The job reads a window back to verify the write, so a failed read-back means the tag did not take the ID.

> **NOTE**
>
> **The blocking calls belong to the worker.** An ADC-continuous read from `app_main` returns zeros on this silicon, and a T5577 write is a burst of busy waits. Ask AO_Rfid through `kode_rfid_read()` and `kode_rfid_clone()`, which block a task and must never run on an Active Object.

> **NOTE**
>
> **Nothing here can tell that the front end is missing.** On a Kode Dot with no antenna PCB, the demod line floats and a capture answers `KODE_OK` over it. `KODE_DENIED` never means a missing circuit.

## Example code

Hold an EM4100 key fob against the antenna and this example reads its ID. Each call keeps the field up for up to three seconds. With a fob on the antenna the log reads like `ID 1A2B3C4D5E at RF/64`.

```c
/*
 * rfid_read.c - 2026-10-28 - from Kode with love
 *
 * Reads a 125 kHz EM4100 tag, three seconds at a time, and logs its 40-bit
 * ID. Hold a key fob flat against the antenna of the Kode Dot. The log
 * shows a line such as "ID 1A2B3C4D5E at RF/64" when a tag is read.
 */
#include <stdio.h>

#include "kode.h"

#define TAG "rfid_read"

/* Bring the system up, then read tags forever. */
void app_main(void)
{
    uint8_t id[5];
    int     rf_div;

    kode_init();

    while (1) {
        if (kode_rfid_read(id, &rf_div, 3000) == KODE_OK) {
            KLOG_I(TAG, "ID %02X%02X%02X%02X%02X at RF/%d", (unsigned)id[0],
                   (unsigned)id[1], (unsigned)id[2], (unsigned)id[3],
                   (unsigned)id[4], rf_div);
        }

        /* A busy front end answers at once, so pause between asks. */
        kode_delay_ms(500);
    }
}
```

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/rfid`](https://github.com/kodediy/kode-sdk/tree/main/components/kdrv/rfid): the front end, the decoder and the capture arithmetic.
- [`kdrv_rfid_lf.h`](https://github.com/kodediy/kode-sdk/blob/main/components/kdrv/rfid/include/kdrv_rfid_lf.h): the calls, the job type and the limits.
- [`components/kaos/rfid`](https://github.com/kodediy/kode-sdk/tree/main/components/kaos/rfid): AO_Rfid, the owner (`sdk/kaos/rfid`).
