The build targets the Cortex-M4F single-precision FPU (-mfpu=fpv4-sp-d16),
which has no double-precision hardware. goertzel_mag() used sqrt() (double):
the float result was promoted to double for the call and converted back, and
a software double-sqrt routine ran -- twice per decoded bit.
The bit decision only compares the two Goertzel outputs, and power is
monotonic with magnitude, so the sqrt is unnecessary. goertzel_power()
returns the squared magnitude and fsk_feed() compares that directly. This
also removes a latent sqrt(NaN): the magnitude argument can round slightly
negative near zero signal, which sqrt() turned into NaN (and NaN comparisons
make the bit decision unreliable); comparing the raw power is well-defined.
The old goertzel_mag() had no callers anywhere and was not declared in the
header, so it is removed rather than kept.
Shared by all FSK readers (HID Prox, ioProx, Pyramid): bit decisions are
identical, the soft-float double dependency is gone, and the time-sensitive
demod loop is slightly faster.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep SCAN(3019)/WRITE(3020) adjacent, matching the SCAN+WRITE
adjacency convention used by every other LF protocol
(EM410X 3000/3001, HIDPROX 3002/3003, VIKING 3004/3005,
IOPROX 3010/3011, PAC 3014/3015). Slot 3017 is now free for
future use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
LupusE requested in the PR #407 review (2026-05-13) to move IDTECK
command IDs since PR #404 (Jablotron) also uses 3017 / 5010 / 5011.
Both PRs are open with the feature-freeze label; Jablotron retains
the original slots and IDTECK shifts up:
DATA_CMD_IDTECK_WRITE_TO_T55XX: 3017 -> 3018
DATA_CMD_IDTECK_SET_EMU_ID: 5010 -> 5012
DATA_CMD_IDTECK_GET_EMU_ID: 5011 -> 5013
Files changed:
- firmware/application/src/data_cmd.h
- software/script/chameleon_enum.py
app_cmd.c references the constants by name only, no edit needed there.
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 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.
The fix in EVT_END_SEQ0:
1. ANT_NO_MOD() — silences LF_MOD so the local drive no longer charges the peak detector
2. bsp_delay_ms(2) — 2ms settle, slightly above the ~2ms time constant so the detector drains to reflect only the external field
3. is_lf_field_exists() — now sees the real field state
4. If field gone → nrfx_pwm_stop() → EVT_STOPPED → lf_field_lost() runs correctly
5. If field present → ANT_MOD() restores modulation for the next PWM sequence