Kode Dot
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
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 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
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.
/*
* 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: the two card radios inkode-sdk. TBD: the 125 kHz reader.