Resolved conflicts from upstream feature additions (IDTECK PR #407,
LF_T55XX_WRITE PR #413, ISO14443-4 T=CL emulation, HF14A scan-keep,
etc.) by keeping both sides where independent.
Command ID note for reviewer:
- Maintainer's IDTECK shift commit (1e78976) stated "Jablotron retains
the original slots 3016 and 3017", but upstream had already taken
3016 for DATA_CMD_LF_T55XX_WRITE. Only 3017 was actually free.
- Resolution: JABLOTRON_WRITE_TO_T55XX kept at 3017 (honors intent).
JABLOTRON_SCAN moved 3016 -> 3019 (next free slot).
- 5xxx range: JABLOTRON_SET/GET_EMU_ID = 5010/5011 unchanged
(IDTECK shifted to 5012/5013 as planned).
Python files parse cleanly. No duplicate command IDs in data_cmd.h
(3xxx and 5xxx ranges checked).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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).
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.
Jablotron uses differential biphase (inverted) at RF/64, 64-bit frames:
bits 0-15: 0xFFFF preamble
bits 16-55: 40-bit data (5 bytes), bit 16 must be 0
bits 56-63: 8-bit checksum = (sum of data bytes) XOR 0x3A
Firmware:
- rfid/nfctag/lf/protocols/jablotron.c - encoder/decoder codec
- rfid/nfctag/lf/utils/diphase.c - inverted-biphase state machine
(shared util, reusable by other diphase protocols)
- rfid/reader/lf/lf_jablotron_data.c - GPIO-interval reader path
- app_cmd.c: JABLOTRON_SCAN, JABLOTRON_WRITE_TO_T55XX,
JABLOTRON_SET_EMU_ID, JABLOTRON_GET_EMU_ID
- tag_base_type.h: TAG_TYPE_JABLOTRON enum
- t55xx.h: T5577_JABLOTRON_CONFIG (DIPHASE modulation, RF/64)
- lf_tag_em.c: load callback, factory-default data, save callback
Python CLI (software/script/):
- lf jablotron read - scan a real tag
- lf jablotron write --id - clone onto T55xx
- lf jablotron econfig -s N --id - set emulator ID on a slot
- hw slot list shows Jablotron ID and decimal card number
Python test (software/script/tests/test_jablotron_modulator.py):
Pure-Python round-trip validator that reimplements the modulator and
diphase decoder, expands PWM entries to an edge stream, and confirms
the decoded data matches the input. Regression guard for both the
firmware's double-frame encoding and the single-frame variant.
Notable PWM design choices:
- Constant-level diphase encoding uses the same PAC pattern:
CC=0 for LOW, CC=counter_top+1 for HIGH. counter_top=31 gives
exactly 32 carrier cycles per half-bit at NRF_PWM_CLK_125kHz.
- The 64-bit frame is encoded twice in the 256-entry PWM buffer with
the internal level variable persisting between the two passes.
This is required for clean PWM looping: a single 64-bit diphase
frame with an odd number of zero bits ends at a level opposite the
starting level, leaving no transition at the loop boundary where
the reader expects one. Encoding twice guarantees a continuous
diphase stream regardless of the data's zero-count parity.
Reference: Proxmark3 cmdlfjablotron.c
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements NRZ/Direct modulation decoder for PAC/Stanley 125kHz cards
using SAADC ADC sampling with spike-aware threshold calibration.
The LC antenna produces brief high-amplitude transients at NRZ transitions
which are clipped before the moving-average filter to isolate the actual
data levels.
* feat: add lf HIDProx read, t55xx write, emulate function
code quaility:
- consistance: simulation -> emulation, label -> tag
- machine translated unreadable comments are made native
logic:
- newly added cli command includes: `lf hid prox read`, `lf hid prox write`, `lf hid prox econfig`
- machester demodulator is simplified
- various wiegand formats of HIDProx are supported
- goertzel algorithm is used in HIDProx FSK demod
- lf read is refactored using stream/feed pattern to boost scan speed
- t55xx write is refactored to share same logic between em410x & HIDProx
- lf emulating is refactored to use PWM peripheral, allowing more card type to be added
closes: #212, #210
* chore: remove not implemented wiegand format comments
* fix: build ci
* fix: build ci
* fix: build ci
* fix: build ci