Feature/lf: add IDTECK PSK1 tag emulation and T55xx clone#407
Open
matteoscrugli wants to merge 6 commits intoRfidResearchGroup:mainfrom
Open
Feature/lf: add IDTECK PSK1 tag emulation and T55xx clone#407matteoscrugli wants to merge 6 commits intoRfidResearchGroup:mainfrom
matteoscrugli wants to merge 6 commits intoRfidResearchGroup:mainfrom
Conversation
Factors out the PSK1 subcarrier generator into utils/psk1.{c,h}.
The helper takes a frame (MSB-first bytes), a bit count and a
destination wave-form buffer, and fills the buffer with PWM entries
expressing differential PSK1 as polarity flips at bit transitions.
No protocol uses this helper yet; it is introduced alone so that
individual PSK1 protocol files (starting with IDTECK in the next
commit) can plug into the same timing and encoding logic without
each re-implementing it.
The helper targets the 1MHz PWM base clock that will be selected by
pwm_init for PSK1 tag types; counter_top and duty constants are
defined accordingly.
Adds IDTECK as a new LF protocol for tag emulation. IDTECK is a PSK1
encoding at RF/32 with a 64-bit frame: a 32-bit fixed preamble
0x4944544B ("IDTK") followed by a 32-bit card payload (one-byte
checksum + 24-bit card number in byte-reversed layout, matching the
format used by the Proxmark3 client).
The modulator drives LF_MOD (load-modulation, same hardware path used
for FSK protocols like HID Prox) via the shared utils/psk1 helper,
producing a 62.5kHz subcarrier with a 180-degree phase flip at every
differential bit transition. Because PSK1 is differential the reader
decodes phase transitions between consecutive bits rather than
absolute phase, so carrier phase-lock is not required — a free-running
subcarrier from HFXO (±40ppm) stays within the tolerance of consumer
readers.
The 16us subcarrier period is below the counter_top minimum of 3 at
the legacy 125kHz PWM base clock used for ASK/FSK protocols. To avoid
rescaling every existing protocol, pwm_init now selects the base
clock based on the active tag type (predicate IS_PSK1_TYPE): 1MHz for
PSK1, 125kHz otherwise. Legacy protocols are untouched.
The comment in lf_sense_enable is updated to reflect that the absence
of carrier phase-lock (envelope-only tag-mode antenna taps) rules out
coherent demod but does not preclude differential-phase encodings
like the one introduced here.
T5577 cloning configuration uses the existing T5577_MODULATION_PSK1
symbol combined with RF/32 bitrate and 2 data blocks. Emulation read
is not added: the tag-emulation ADC path is 125kHz envelope-filtered,
so PSK demod would need a dedicated edge-timing decoder (left as a
follow-up).
Exposes IDTECK to the host command protocol: - DATA_CMD_IDTECK_SET_EMU_ID (5010) / GET_EMU_ID (5011) / WRITE_TO_T55XX (3017) - Matching handlers in app_cmd.c for setting the emulated frame on the current LF slot, reading it back, and programming a T55xx tag Adds write_idteck_to_t55xx in lf_reader_main (modeled on the other per-protocol T55xx writers), wrapping idteck_t55xx_writer and the shared write_t55xx helper. After this commit the firmware is fully functional for IDTECK: a host can set an emulated frame, read it back, or clone it to a T55xx. The CLI wiring is added in the following commit.
Adds host-side CLI support for IDTECK:
- lf idteck econfig -s <slot> [--id <hex>] set or read the emulated frame
- lf idteck write --id <hex> clone to a T55xx tag in reader mode
- lf clone -t idteck --id <hex> same via the unified clone command
- hw slot list now renders Frame and Card ID
for IDTECK slots
Input accepts 16 hex characters for the full 64-bit frame, or 8 hex
for the 32-bit payload (the fixed preamble 4944544B is auto-prepended).
A non-blocking informational note is emitted when the payload checksum
does not match the value computed from the card number, since some
readers validate this field and some do not.
Private helpers in chameleon_cli_unit.py (_idteck_compute_checksum,
_idteck_compose_frame, _idteck_frame_info) parse and compose IDTECK
frames and expose card-number-driven composition for a future
`lf idteck compose` command.
ea5619c to
cf6319f
Compare
Built artifacts for commit a30cabbFirmwareClient |
This was referenced Apr 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lf: add IDTECK PSK1 tag emulation and T55xx clone
TL;DR
This PR adds IDTECK (a 125kHz PSK1 card format) to the supported LF protocols, covering tag emulation and T55xx cloning. Tested end-to-end against a real IDTECK reader.
Why this was worth implementing
The Chameleon Ultra already supports a handful of LF protocols (EM410x, HID Prox, ioProx, Viking, PAC), all of them ASK or FSK modulations. IDTECK belongs to the PSK1 family, which on first inspection seemed out of reach: the firmware comment in
lf_sense_enablenoted that the tag-mode antenna taps are envelope-only, which rules out carrier phase-lock and makes coherent demodulation (the approach Proxmark3 uses) unavailable.The insight that made this PR possible: PSK1 in practice is a differential encoding. What the reader decodes are 180° phase transitions between consecutive bits, not absolute phase. Other LF tooling (Flipper Zero, for example) chooses to phase-lock anyway for deterministic bit timing, typically by clocking a timer off the reader's carrier via a GPIO, a solution that presupposes a dedicated carrier-sense pin. The Chameleon Ultra's tag-mode front-end does not expose such a tap to the MCU, so that path isn't available; but given that PSK1 is differential, strict phase-lock turns out to be a convenience rather than a requirement. A free-running subcarrier generated from HFXO (±40 ppm) stays well within the PLL tolerance of consumer-grade readers, because the drift between one bit and the next is below 3°.
In other words: we can emit PSK1 from the same
LF_MOD→ load-modulation path the Chameleon already uses for FSK protocols, by simply flipping the PWM polarity at bit transitions, with no carrier sync, no active RF driving, and no hardware modifications.What this PR does
Shared PSK1 helper in
utils/psk1.{c,h}The subcarrier generator is factored into a reusable helper: given a frame (MSB-first bytes) and a bit count, it fills a
nrf_pwm_values_wave_form_tbuffer with PSK1-encoded entries. The helper lives inutils/psk1.{c,h}so future PSK1 protocols (Indala, Keri, NexWatch, ...) can plug in without reimplementing the timing and differential-encoding logic.PWM base clock selector
The 16µs subcarrier period (carrier/2 = 62.5 kHz) requires a
counter_topbelow 3 at the legacy 125kHz PWM base used by ASK/FSK protocols, which is out of spec for the nRF52 PWM peripheral. Rather than rescaling every existing protocol,pwm_initnow selects the base clock from the currently-loaded tag type:IS_PSK1_TYPE()is a macro intag_base_type.h; today it matches onlyTAG_TYPE_IDTECK, but extends trivially to other PSK1 types in future PRs. Legacy protocols are untouched, so there is zero regression risk on EM410x, HID Prox, ioProx, Viking, PAC.The comment in
lf_sense_enablethat previously implied PSK was out of reach is updated to reflect the actual constraint (no coherent demod) without overstating the limit for differential encodings.IDTECK protocol in
protocols/idteck.{c,h}The IDTECK frame layout (32-bit fixed preamble
0x4944544B"IDTK" + 32-bit card payload consisting of a checksum byte and a byte-reversed 24-bit card number) matches what the Proxmark3 client expects, so we emit byte-for-byte whatcmdlfidteck.cdecodes. Theidteck_t55xx_writerproduces the block layout for cloning onto a T55xx tag usingT5577_MODULATION_PSK1 | T5577_BITRATE_RF_32.Command layer and CLI
Three new
DATA_CMD_*codes, three firmware handlers, and CLI commands:lf idteck econfig -s <slot> --id <hex>lf idteck econfig -s <slot>lf idteck write --id <hex>lf clone -t idteck --id <hex>hw slot listFrame:andCard ID:lines for IDTECK slotsThe
--idargument accepts either 16 hex characters (the full 64-bit frame) or 8 hex (the 32-bit payload, with the preamble auto-prepended for the user's convenience).The CLI emits a non-blocking informational note when the payload checksum does not match the value computed from the card number, since some readers validate it and some do not. This turns out to matter for a common variant of IDTECK cards in the wild whose checksum byte differs from the simple sum-of-bytes formula.
As with the other per-protocol T55xx writers (
write_em410x_to_t55xx,write_hidprox_to_t55xx, etc.),write_idteck_to_t55xxlives inlf_reader_main.cand is therefore only compiled on Chameleon Ultra; the correspondingcmd_processor_idteck_write_to_t55xxis wrapped in#if defined(PROJECT_CHAMELEON_ULTRA)to match the existing convention. The IDTECK emulation path (set/get emu id, slot integration) is available on both Ultra and Lite.Not included (intentionally)
LF_OA_OUTis feasible purely in software, reusing the GPIOTE+TIMER edge-capture infrastructure already in place for the Viking reader, not unlike what comparator-plus-input-capture LF tools do. It's left as a follow-up PR to keep review size manageable.utils/psk1helper andIS_PSK1_TYPE()macro are designed to absorb these with a one-file-per-protocol pattern, but adding them here would inflate the PR. Follow-up PRs, one per protocol.How it was tested
lf idteck writeworks on the same reader as expected.pwm_initcorrectly switches the base clock in both directions, with emulation of the pre-existing cards unchanged.hw dfu+ a reflash of the same firmware image as a clean cold-boot proxy (FDS preserved), the slot configuration survives intact, with tag type, emulated frame and nick name all readable vialf idteck econfigafter reboot.--idonwriteis caught by the required-argument validator, and checksum-mismatch frames still go through with a clear warning.Commit structure
Intentionally split into logical commits (same style as PR #367 for ioProx):
feat(lf): add shared PSK1 wave-form helper for tag emulation: the reusable subcarrier generator, alone. Compiles but has no caller yet.feat(lf): add IDTECK tag emulation (PSK1 RF/32): the protocol file, the PWM base-clock switch, the tag-emulation handler map wiring, and the T5577 config constant.feat(lf): integrate IDTECK into firmware command and T55xx write paths:DATA_CMD_*,app_cmd.chandlers,write_idteck_to_t55xx. After this the firmware is functional end-to-end from the host protocol point of view.feat(cli): add lf idteck subgroup and extend lf clone with idteck type: the Python CLI layer andhw slot listrendering.docs(changelog): add IDTECK LF protocol entry.Happy to answer any questions during review, especially around the base-clock isolation choice and the decision to defer read support.