mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
Merge remote-tracking branch 'origin/main' into pr/nfcimport-v2
This commit is contained in:
@@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added PAC/Stanley LF protocol support: read, emulate and T55xx clone (@kevihiiin, @danieltwagner)
|
||||
- Fix firmware application USB serial number (@taichunmin)
|
||||
- Added ioProx LF protocol support (read, emulate and T55xx clone)
|
||||
- Added `hf mfu nfcimport` to import Flipper Zero `.nfc` files into MFU/NTAG emulator slots, with `--amiibo` flag for automatic PWD/PACK derivation (@fmuk)
|
||||
- Added commands to dump and clone Mifare tags
|
||||
|
||||
@@ -35,8 +35,9 @@ SRC_FILES += \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/circular_buffer.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/utils/manchester.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/em410x.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/hidprox.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/pac.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/ioprox.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/viking.c \
|
||||
$(PROJ_DIR)/rfid/nfctag/lf/protocols/wiegand.c \
|
||||
$(PROJ_DIR)/utils/dataframe.c \
|
||||
@@ -341,13 +342,16 @@ ifeq (${CURRENT_DEVICE_TYPE}, ${CHAMELEON_ULTRA})
|
||||
$(PROJ_DIR)/rfid/reader/hf/rc522.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_125khz_radio.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_em410x_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_em4x05_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_gap.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_main.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_t55xx_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_ioprox_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_hidprox_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_pac_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_ioprox_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_viking_data.c \
|
||||
$(PROJ_DIR)/rfid/reader/lf/lf_reader_generic.c \
|
||||
|
||||
INC_FOLDERS +=\
|
||||
${PROJ_DIR}/rfid/reader/ \
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
#include "settings.h"
|
||||
#include "delayed_reset.h"
|
||||
#include "netdata.h"
|
||||
#if defined(PROJECT_CHAMELEON_ULTRA)
|
||||
#include "bsp_wdt.h"
|
||||
#include "lf_reader_generic.h"
|
||||
#include "lf_em4x05_data.h"
|
||||
#endif
|
||||
#include "nfc_14a.h"
|
||||
|
||||
|
||||
#define NRF_LOG_MODULE_NAME app_cmd
|
||||
@@ -802,6 +808,15 @@ static data_frame_tx_t *cmd_processor_viking_scan(uint16_t cmd, uint16_t status,
|
||||
return data_frame_make(cmd, STATUS_LF_TAG_OK, sizeof(card_buffer), card_buffer);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_pac_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
uint8_t card_id[8] = {0x00};
|
||||
status = scan_pac(card_id);
|
||||
if (status != STATUS_LF_TAG_OK) {
|
||||
return data_frame_make(cmd, status, 0, NULL);
|
||||
}
|
||||
return data_frame_make(cmd, STATUS_LF_TAG_OK, sizeof(card_id), card_id);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_viking_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
typedef struct {
|
||||
uint8_t id[4];
|
||||
@@ -817,6 +832,20 @@ static data_frame_tx_t *cmd_processor_viking_write_to_t55xx(uint16_t cmd, uint16
|
||||
return data_frame_make(cmd, status, 0, NULL);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_pac_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
typedef struct {
|
||||
uint8_t id[LF_PAC_TAG_ID_SIZE];
|
||||
uint8_t new_key[4];
|
||||
uint8_t old_keys[4];
|
||||
} PACKED payload_t;
|
||||
payload_t *payload = (payload_t *)data;
|
||||
if (length < sizeof(payload_t) || (length - offsetof(payload_t, old_keys)) % sizeof(payload->old_keys) != 0) {
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
|
||||
}
|
||||
status = write_pac_to_t55xx(payload->id, payload->new_key, payload->old_keys, (length - offsetof(payload_t, old_keys)) / sizeof(payload->old_keys));
|
||||
return data_frame_make(cmd, status, 0, NULL);
|
||||
}
|
||||
|
||||
#define GENERIC_READ_LEN 800
|
||||
#define GENERIC_READ_TIMEOUT_MS 500
|
||||
static data_frame_tx_t *cmd_processor_generic_read(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
@@ -1066,6 +1095,26 @@ static data_frame_tx_t *cmd_processor_viking_get_emu_id(uint16_t cmd, uint16_t s
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, LF_VIKING_TAG_ID_SIZE, buffer->buffer);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_pac_set_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
if (length != LF_PAC_TAG_ID_SIZE) {
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
|
||||
}
|
||||
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_PAC);
|
||||
memcpy(buffer->buffer, data, LF_PAC_TAG_ID_SIZE);
|
||||
tag_emulation_load_by_buffer(TAG_TYPE_PAC, false);
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, 0, NULL);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_pac_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
tag_slot_specific_type_t tag_types;
|
||||
tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types);
|
||||
if (tag_types.tag_lf != TAG_TYPE_PAC) {
|
||||
return data_frame_make(cmd, STATUS_PAR_ERR, 0, data);
|
||||
}
|
||||
tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_PAC);
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, LF_PAC_TAG_ID_SIZE, buffer->buffer);
|
||||
}
|
||||
|
||||
static nfc_tag_14a_coll_res_reference_t *get_coll_res_data(bool write) {
|
||||
nfc_tag_14a_coll_res_reference_t *info;
|
||||
tag_slot_specific_type_t tag_types;
|
||||
@@ -1729,6 +1778,117 @@ static data_frame_tx_t *cmd_processor_mf0_get_emulator_config(uint16_t cmd, uint
|
||||
* (cmd -> processor) function map, the map struct is:
|
||||
* cmd code before process cmd processor after process
|
||||
*/
|
||||
#if defined(PROJECT_CHAMELEON_ULTRA)
|
||||
static data_frame_tx_t *cmd_processor_em4x05_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
em4x05_data_t tag = {0};
|
||||
status = scan_em4x05(&tag);
|
||||
if (status != STATUS_LF_TAG_OK) {
|
||||
return data_frame_make(cmd, status, 0, NULL);
|
||||
}
|
||||
struct {
|
||||
uint32_t config;
|
||||
uint32_t uid;
|
||||
uint32_t uid_hi;
|
||||
uint8_t is_em4x69;
|
||||
} PACKED payload;
|
||||
payload.config = U32HTONL(tag.config);
|
||||
payload.uid = U32HTONL(tag.uid);
|
||||
payload.uid_hi = U32HTONL(tag.uid_hi);
|
||||
payload.is_em4x69 = tag.is_em4x69 ? 1 : 0;
|
||||
return data_frame_make(cmd, STATUS_LF_TAG_OK, sizeof(payload), (uint8_t *)&payload);
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_lf_sniff(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
/* Optional 2-byte big-endian timeout in ms from host (default 2000ms) */
|
||||
uint32_t timeout_ms = 2000;
|
||||
if (length >= 2) {
|
||||
timeout_ms = ((uint32_t)data[0] << 8) | data[1];
|
||||
if (timeout_ms == 0 || timeout_ms > 10000) timeout_ms = 2000;
|
||||
}
|
||||
|
||||
static uint8_t sniff_buf[LF_SNIFF_MAX_SAMPLES];
|
||||
size_t outlen = 0;
|
||||
raw_read_to_buffer(sniff_buf, LF_SNIFF_MAX_SAMPLES, timeout_ms, &outlen);
|
||||
|
||||
if (outlen == 0) {
|
||||
return data_frame_make(cmd, STATUS_LF_TAG_NO_FOUND, 0, NULL);
|
||||
}
|
||||
return data_frame_make(cmd, STATUS_LF_TAG_OK, (uint16_t)outlen, sniff_buf);
|
||||
}
|
||||
#define HF_SNIFF_BUF_SIZE 3800 /* leave room for USB framing */
|
||||
#define HF_SNIFF_MAX_FRAMES 200
|
||||
|
||||
static uint8_t m_sniff_buf[HF_SNIFF_BUF_SIZE];
|
||||
static uint16_t m_sniff_buf_len = 0;
|
||||
static bool m_sniff_active = false;
|
||||
static uint16_t m_sniff_cb_count = 0; /* debug: total callback invocations */
|
||||
|
||||
static void hf14a_sniff_frame_cb(const uint8_t *data, uint16_t szBits) {
|
||||
m_sniff_cb_count++; /* count even if buffer full or inactive */
|
||||
if (!m_sniff_active) return;
|
||||
uint16_t szBytes = (szBits + 7) / 8;
|
||||
/* Check space: 2 bytes header + data */
|
||||
if (m_sniff_buf_len + 2 + szBytes > HF_SNIFF_BUF_SIZE) return;
|
||||
/* Write bit count big-endian */
|
||||
m_sniff_buf[m_sniff_buf_len++] = (szBits >> 8) & 0xFF;
|
||||
m_sniff_buf[m_sniff_buf_len++] = szBits & 0xFF;
|
||||
/* Write frame bytes */
|
||||
memcpy(&m_sniff_buf[m_sniff_buf_len], data, szBytes);
|
||||
m_sniff_buf_len += szBytes;
|
||||
}
|
||||
|
||||
static data_frame_tx_t *cmd_processor_hf14a_sniff(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
/* Optional 2-byte big-endian timeout in ms (default 5000ms) */
|
||||
uint32_t timeout_ms = 5000;
|
||||
if (length >= 2) {
|
||||
timeout_ms = ((uint32_t)data[0] << 8) | data[1];
|
||||
if (timeout_ms == 0 || timeout_ms > 30000) timeout_ms = 5000;
|
||||
}
|
||||
|
||||
/* Reload active slot data before sniffing.
|
||||
* The NFCT anti-collision response is built from m_tag_information which
|
||||
* points into the shared tag data buffer. After a slot switch the buffer
|
||||
* may still contain the previous slot's UID if the FDS async load has not
|
||||
* completed. A forced reload here ensures the correct UID is presented
|
||||
* during the sniff session.
|
||||
* A short settle delay follows to allow the reload to complete before
|
||||
* the first field detection can trigger the anti-collision path. */
|
||||
tag_emulation_load_data();
|
||||
bsp_delay_ms(100);
|
||||
|
||||
/* Install sniff callback into the already-running tag emulation stack.
|
||||
* Do NOT call tag_mode_enter() or sense_switch() here — those reinit
|
||||
* NFCT and wipe the anti-collision data, breaking the emulation.
|
||||
* The device must already be in emulator mode (hw mode --emulator)
|
||||
* with a slot active before running this command. */
|
||||
m_sniff_buf_len = 0;
|
||||
m_sniff_cb_count = 0;
|
||||
m_sniff_active = true;
|
||||
nfc_tag_14a_set_sniff_cb(hf14a_sniff_frame_cb);
|
||||
|
||||
/* Wait for duration, yielding each ms so USB stack stays alive.
|
||||
* Feed watchdog every iteration — WDT timeout is 5000ms and the
|
||||
* main loop cannot feed it while we are blocking here. */
|
||||
autotimer *p_at = bsp_obtain_timer(0);
|
||||
while (NO_TIMEOUT_1MS(p_at, timeout_ms)) {
|
||||
bsp_delay_ms(1);
|
||||
bsp_wdt_feed();
|
||||
}
|
||||
bsp_return_timer(p_at);
|
||||
|
||||
/* Remove callback and restore normal sense state */
|
||||
m_sniff_active = false;
|
||||
nfc_tag_14a_clear_sniff_cb();
|
||||
tag_emulation_sense_run(); /* restore slot-based sense state */
|
||||
|
||||
if (m_sniff_buf_len == 0) {
|
||||
return data_frame_make(cmd, STATUS_HF_TAG_NO, 0, NULL);
|
||||
}
|
||||
return data_frame_make(cmd, STATUS_SUCCESS, m_sniff_buf_len, m_sniff_buf);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static cmd_data_map_t m_data_cmd_map[] = {
|
||||
{ DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_app_version, NULL },
|
||||
{ DATA_CMD_CHANGE_DEVICE_MODE, NULL, cmd_processor_change_device_mode, NULL },
|
||||
@@ -1798,6 +1958,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
|
||||
{ DATA_CMD_VIKING_WRITE_TO_T55XX, before_reader_run, cmd_processor_viking_write_to_t55xx, NULL },
|
||||
{ DATA_CMD_IOPROX_SCAN, before_reader_run, cmd_processor_ioprox_scan, NULL },
|
||||
{ DATA_CMD_IOPROX_WRITE_TO_T55XX, before_reader_run, cmd_processor_ioprox_write_to_t55xx, NULL },
|
||||
{ DATA_CMD_PAC_SCAN, before_reader_run, cmd_processor_pac_scan, NULL },
|
||||
{ DATA_CMD_PAC_WRITE_TO_T55XX, before_reader_run, cmd_processor_pac_write_to_t55xx, NULL },
|
||||
{ DATA_CMD_ADC_GENERIC_READ, before_reader_run, cmd_processor_generic_read, NULL },
|
||||
|
||||
{ DATA_CMD_HF14A_SET_FIELD_ON, before_reader_run, cmd_processor_hf14a_set_field_on, NULL },
|
||||
@@ -1808,6 +1970,9 @@ static cmd_data_map_t m_data_cmd_map[] = {
|
||||
|
||||
{ DATA_CMD_IOPROX_DECODE_RAW, NULL, cmd_processor_ioprox_decode_raw, NULL },
|
||||
{ DATA_CMD_IOPROX_COMPOSE_ID, NULL, cmd_processor_ioprox_compose_id, NULL },
|
||||
{ DATA_CMD_EM4X05_SCAN, before_reader_run, cmd_processor_em4x05_scan, NULL },
|
||||
{ DATA_CMD_LF_SNIFF, before_reader_run, cmd_processor_lf_sniff, NULL },
|
||||
{ DATA_CMD_HF14A_SNIFF, NULL, cmd_processor_hf14a_sniff, NULL },
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1860,6 +2025,8 @@ static cmd_data_map_t m_data_cmd_map[] = {
|
||||
{ DATA_CMD_IOPROX_GET_EMU_ID, NULL, cmd_processor_ioprox_get_emu_id, NULL },
|
||||
{ DATA_CMD_VIKING_SET_EMU_ID, NULL, cmd_processor_viking_set_emu_id, NULL },
|
||||
{ DATA_CMD_VIKING_GET_EMU_ID, NULL, cmd_processor_viking_get_emu_id, NULL },
|
||||
{ DATA_CMD_PAC_SET_EMU_ID, NULL, cmd_processor_pac_set_emu_id, NULL },
|
||||
{ DATA_CMD_PAC_GET_EMU_ID, NULL, cmd_processor_pac_get_emu_id, NULL },
|
||||
};
|
||||
|
||||
data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
#define STATUS_LF_TAG_OK (0x40) // Some of the low -frequency cards are successful!
|
||||
#define STATUS_LF_TAG_NO_FOUND (0x41) // Can't search for valid LF tags
|
||||
#define STATUS_LF_TAG_LOGIN_REQUIRED (0x42) // Tag requires LOGIN before read
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// other status
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
#define DATA_CMD_HF14A_GET_CONFIG (2200)
|
||||
#define DATA_CMD_HF14A_SET_CONFIG (2201)
|
||||
#define DATA_CMD_HF14A_SNIFF (2020)
|
||||
|
||||
//
|
||||
// ******************************************************************
|
||||
@@ -93,6 +94,8 @@
|
||||
#define DATA_CMD_EM410X_ELECTRA_WRITE_TO_T55XX (3006)
|
||||
#define DATA_CMD_HIDPROX_SCAN (3002)
|
||||
#define DATA_CMD_HIDPROX_WRITE_TO_T55XX (3003)
|
||||
#define DATA_CMD_PAC_SCAN (3014)
|
||||
#define DATA_CMD_PAC_WRITE_TO_T55XX (3015)
|
||||
#define DATA_CMD_VIKING_SCAN (3004)
|
||||
#define DATA_CMD_VIKING_WRITE_TO_T55XX (3005)
|
||||
#define DATA_CMD_ADC_GENERIC_READ (3009)
|
||||
@@ -170,7 +173,13 @@
|
||||
#define DATA_CMD_HIDPROX_GET_EMU_ID (5003)
|
||||
#define DATA_CMD_VIKING_SET_EMU_ID (5004)
|
||||
#define DATA_CMD_VIKING_GET_EMU_ID (5005)
|
||||
#define DATA_CMD_PAC_SET_EMU_ID (5006)
|
||||
#define DATA_CMD_PAC_GET_EMU_ID (5007)
|
||||
#define DATA_CMD_IOPROX_SET_EMU_ID (5008)
|
||||
#define DATA_CMD_IOPROX_GET_EMU_ID (5009)
|
||||
|
||||
#define DATA_CMD_EM4X05_SCAN (3030)
|
||||
#define DATA_CMD_EM4X05_READSNIFF (3032)
|
||||
#define DATA_CMD_LF_SNIFF (3031)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -59,6 +59,17 @@ const uint16_t ats_fsdi_table[] = {
|
||||
static volatile bool m_is_responded = false;
|
||||
// Receiving buffer
|
||||
static uint8_t m_nfc_rx_buffer[MAX_NFC_RX_BUFFER_SIZE] = { 0x00 };
|
||||
|
||||
/* Optional sniff callback — fires for every received frame */
|
||||
static nfc_tag_14a_sniff_cb_t m_sniff_cb = NULL;
|
||||
|
||||
void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb) {
|
||||
m_sniff_cb = cb;
|
||||
}
|
||||
|
||||
void nfc_tag_14a_clear_sniff_cb(void) {
|
||||
m_sniff_cb = NULL;
|
||||
}
|
||||
static uint8_t m_nfc_tx_buffer[MAX_NFC_TX_BUFFER_SIZE] = { 0x00 };
|
||||
// The N -secondary connection needs to use SAK, when the "third 'bit' in SAK is 1 is 1, the logo UID is incomplete
|
||||
static uint8_t m_uid_incomplete_sak[] = { 0x04, 0xda, 0x17 };
|
||||
@@ -326,6 +337,11 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
|
||||
// Because of this error receiving event caused by this possible interference
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sniff hook — fire before any tag response logic */
|
||||
if (m_sniff_cb != NULL) {
|
||||
m_sniff_cb(p_data, szDataBits);
|
||||
}
|
||||
// Manually draw frame, separate data and strange school inspection
|
||||
#if !NFC_TAG_14A_RX_PARITY_AUTO_DEL_ENABLE
|
||||
if (szDataBits >= 9) {
|
||||
|
||||
@@ -82,6 +82,14 @@ typedef struct {
|
||||
|
||||
// Communication reception function that needs to be implemented
|
||||
typedef void (*nfc_tag_14a_reset_handler_t)(void);
|
||||
|
||||
/* Sniff callback — called for every received frame before the tag handler.
|
||||
* data : raw frame bytes (after parity strip)
|
||||
* szBits : number of bits received */
|
||||
typedef void (*nfc_tag_14a_sniff_cb_t)(const uint8_t *data, uint16_t szBits);
|
||||
|
||||
void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb);
|
||||
void nfc_tag_14a_clear_sniff_cb(void);
|
||||
typedef void (*nfc_tag_14a_state_handler_t)(uint8_t *data, uint16_t szBits);
|
||||
typedef nfc_tag_14a_coll_res_reference_t *(*nfc_tag_14a_coll_handler_t)(void);
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
#include "bsp_delay.h"
|
||||
#include "fds_util.h"
|
||||
#include "nrf_gpio.h"
|
||||
#include "nrf_soc.h"
|
||||
#include "nrfx_lpcomp.h"
|
||||
#include "nrfx_pwm.h"
|
||||
#include "protocols/em410x.h"
|
||||
#include "protocols/hidprox.h"
|
||||
#include "protocols/ioprox.h"
|
||||
#include "protocols/pac.h"
|
||||
#include "protocols/viking.h"
|
||||
#include "syssleep.h"
|
||||
#include "tag_emulation.h"
|
||||
@@ -136,6 +138,23 @@ static void pwm_init(void) {
|
||||
}
|
||||
|
||||
static void lf_sense_enable(void) {
|
||||
// PWM bit timing divides HFCLK by a fixed ratio. On HFINT (64 MHz RC,
|
||||
// ±1.5% at 25°C after factory trim, wider over temperature) this gives a
|
||||
// chip-to-chip spread that NRZ readers — which see cumulative error across
|
||||
// runs of same-polarity bits with no intra-run resync — reject even when
|
||||
// Manchester/FSK readers don't. Holding HFXO brings the PWM clock to
|
||||
// ±40 ppm. We can't lock to the reader's carrier (tag-mode antenna taps
|
||||
// on this board are envelope-only), so this is as good as it gets.
|
||||
//
|
||||
// Paired release in lf_sense_disable(). SD reference-counts HFXO requests,
|
||||
// so this coexists with BLE. Both functions run from thread context
|
||||
// (tag_mode_enter/tag_emulation_sense_end) where SVCs are safe.
|
||||
sd_clock_hfclk_request();
|
||||
uint32_t hfclk_running = 0;
|
||||
while (!hfclk_running) {
|
||||
sd_clock_hfclk_is_running(&hfclk_running);
|
||||
}
|
||||
|
||||
lpcomp_init();
|
||||
pwm_init(); // use precise hardware pwm to broadcast card id
|
||||
if (is_lf_field_exists()) {
|
||||
@@ -148,6 +167,7 @@ static void lf_sense_disable(void) {
|
||||
nrfx_lpcomp_uninit();
|
||||
m_pwm_seq = NULL;
|
||||
m_is_lf_emulating = false;
|
||||
sd_clock_hfclk_release();
|
||||
}
|
||||
|
||||
static enum {
|
||||
@@ -224,6 +244,15 @@ int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
|
||||
return LF_VIKING_TAG_ID_SIZE;
|
||||
}
|
||||
|
||||
if (type == TAG_TYPE_PAC && buffer->length >= LF_PAC_TAG_ID_SIZE) {
|
||||
m_tag_type = type;
|
||||
void *codec = pac.alloc();
|
||||
m_pwm_seq = pac.modulator(codec, buffer->buffer);
|
||||
pac.free(codec);
|
||||
NRF_LOG_INFO("load lf pac data finish.");
|
||||
return LF_PAC_TAG_ID_SIZE;
|
||||
}
|
||||
|
||||
NRF_LOG_ERROR("no valid data exists in buffer for tag type: %d.", type);
|
||||
return 0;
|
||||
}
|
||||
@@ -346,3 +375,13 @@ bool lf_tag_viking_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
|
||||
uint8_t tag_id[4] = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
|
||||
}
|
||||
|
||||
int lf_tag_pac_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
|
||||
return m_tag_type == TAG_TYPE_PAC ? LF_PAC_TAG_ID_SIZE : 0;
|
||||
}
|
||||
|
||||
bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
|
||||
// default id: 8 ASCII bytes
|
||||
uint8_t tag_id[8] = {'C', 'A', 'R', 'D', '0', '0', '0', '1'};
|
||||
return lf_tag_data_factory(slot, tag_type, tag_id, sizeof(tag_id));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define LF_IOPROX_TAG_ID_SIZE 16
|
||||
#define LF_HIDPROX_TAG_ID_SIZE 13
|
||||
#define LF_VIKING_TAG_ID_SIZE 4
|
||||
#define LF_PAC_TAG_ID_SIZE 8
|
||||
|
||||
void lf_tag_125khz_sense_switch(bool enable);
|
||||
int lf_tag_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
||||
@@ -21,4 +22,6 @@ int lf_tag_ioprox_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffe
|
||||
bool lf_tag_ioprox_data_factory(uint8_t slot, tag_specific_type_t tag_type);
|
||||
int lf_tag_viking_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
||||
bool lf_tag_viking_data_factory(uint8_t slot, tag_specific_type_t tag_type);
|
||||
int lf_tag_pac_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
|
||||
bool lf_tag_pac_data_factory(uint8_t slot, tag_specific_type_t tag_type);
|
||||
bool is_lf_field_exists(void);
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
#include "pac.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nordic_common.h"
|
||||
#include "nrf_pwm.h"
|
||||
#include "protocols.h"
|
||||
#include "t55xx.h"
|
||||
#include "tag_base_type.h"
|
||||
|
||||
#define NRF_LOG_MODULE_NAME pac_protocol
|
||||
#include "nrf_log.h"
|
||||
#include "nrf_log_ctrl.h"
|
||||
#include "nrf_log_default_backends.h"
|
||||
NRF_LOG_MODULE_REGISTER();
|
||||
|
||||
#define PAC_DATA_SIZE 8 // 8-byte ASCII card ID
|
||||
|
||||
// NRZ at RF/32: 32 carrier cycles per bit.
|
||||
// With SAADC sampling at 1 sample per carrier cycle, 32 samples = 1 bit.
|
||||
#define PAC_RF_PER_BIT 32
|
||||
#define PAC_HALF_BIT 16 // Half-bit for rounding interval → nbits
|
||||
#define PAC_MAX_BITS_RUN 20 // Max consecutive same-polarity bits we accept
|
||||
|
||||
// PAC frame is exactly 128 bits on T55xx (4 blocks × 32 bits):
|
||||
// 8-bit sync marker (0xFF) + 12 × 10-bit UART frames = 128 bits
|
||||
#define PAC_FRAME_BITS 128
|
||||
#define PAC_PREAMBLE_BITS 19
|
||||
|
||||
// Preamble: 1111111100100000010 (19 bits) = 0x7F902
|
||||
#define PAC_PREAMBLE 0x7F902UL
|
||||
#define PAC_PREAMBLE_INV 0x006FDUL // Bitwise inverse masked to 19 bits
|
||||
|
||||
#define PAC_UART_FRAME_BITS 10
|
||||
#define PAC_PAYLOAD_BYTES 12 // STX + '2' + '0' + 8 card ID + XOR checksum
|
||||
#define PAC_STX 0x02
|
||||
|
||||
// ADC demodulation: spike-clipping + threshold with dead zone.
|
||||
//
|
||||
// Signal has 3 amplitude zones:
|
||||
// NRZ low (~500-2000 ADC) — tag load modulation "0" state
|
||||
// NRZ high (~4000-6000) — tag load modulation "1" state
|
||||
// Spikes (~10k-14k) — LC ringing at NRZ transitions (8-20 cycles wide)
|
||||
//
|
||||
// Prescan (128 samples) finds the NRZ floor (raw_min), then spike_cap
|
||||
// = max(raw_min * SPIKE_MULT, MIN_SPIKE_CAP) clips spikes while preserving
|
||||
// the NRZ high level. MIN_SPIKE_CAP ensures spike_cap is never below the
|
||||
// NRZ high level, even when raw_min correctly captures NRZ low.
|
||||
#define PAC_PRESCAN_SAMPLES 128 // ~1ms: raw min detection
|
||||
#define PAC_WARMUP_SAMPLES 600 // ~5ms: threshold calibration on clipped signal
|
||||
#define PAC_SPIKE_MULT 3 // Clip at 3× floor
|
||||
#define PAC_MIN_SPIKE_CAP 8000 // Floor: preserves NRZ high (~5000) always
|
||||
#define PAC_THRESH_FUZZ 75 // Dead zone: 25%-75% of clipped range
|
||||
// Auto-recalibrate if no frame found within this many Phase 3 samples.
|
||||
// ~5 frame periods = 5 × 128 bits × 32 samples/bit = 20480 samples (~164ms).
|
||||
// Gives ~3 calibration attempts in a 500ms scan window.
|
||||
#define PAC_RECAL_SAMPLES 20480
|
||||
|
||||
typedef struct {
|
||||
// NRZ shift register (128 bits)
|
||||
uint64_t raw_hi; // upper 64 bits
|
||||
uint64_t raw_lo; // lower 64 bits
|
||||
bool polarity; // current NRZ level (toggled on each edge)
|
||||
uint16_t bit_count; // total bits shifted in (capped at PAC_FRAME_BITS)
|
||||
uint8_t card_id[PAC_DATA_SIZE];
|
||||
|
||||
// ADC → NRZ demodulation state (spike-clip + threshold with dead zone)
|
||||
uint32_t total_samples; // total samples processed
|
||||
int16_t raw_min; // minimum raw sample seen (for spike cap)
|
||||
int32_t spike_cap; // clip level
|
||||
int16_t clip_max; // max of clipped samples during warmup
|
||||
int16_t clip_min; // min of clipped samples during warmup
|
||||
int16_t thresh_high; // above this → bit=1
|
||||
int16_t thresh_low; // below this → bit=0, between → keep previous
|
||||
bool adc_state; // current demodulated binary level
|
||||
bool has_signal; // true after first threshold crossing
|
||||
uint32_t sample_count; // samples since last transition
|
||||
uint32_t decode_samples; // Phase 3 samples since last calibration
|
||||
} pac_codec;
|
||||
|
||||
// Shift one bit into the 128-bit register.
|
||||
static void shift_bit(pac_codec *d, bool bit) {
|
||||
d->raw_hi = (d->raw_hi << 1) | (d->raw_lo >> 63);
|
||||
d->raw_lo = (d->raw_lo << 1) | (bit ? 1 : 0);
|
||||
}
|
||||
|
||||
// Extract a single bit from the 128-bit register.
|
||||
// Position 0 = MSB of raw_hi (oldest), position 127 = LSB of raw_lo (newest).
|
||||
static bool get_bit(pac_codec *d, uint16_t pos) {
|
||||
if (pos < 64) {
|
||||
return (d->raw_hi >> (63 - pos)) & 1;
|
||||
}
|
||||
return (d->raw_lo >> (127 - pos)) & 1;
|
||||
}
|
||||
|
||||
// Decode a 10-bit UART frame at bit position 'start'.
|
||||
// Frame: start(0) + 7 data bits LSB-first + odd parity + stop(1).
|
||||
static int decode_uart_byte(pac_codec *d, uint16_t start, bool inverted) {
|
||||
#define RD(pos) (inverted ? !get_bit(d, (pos)) : get_bit(d, (pos)))
|
||||
|
||||
if (RD(start)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t byte_val = 0;
|
||||
uint8_t ones = 0;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (RD(start + 1 + i)) {
|
||||
byte_val |= (1 << i);
|
||||
ones++;
|
||||
}
|
||||
}
|
||||
|
||||
if (RD(start + 8)) {
|
||||
ones++;
|
||||
}
|
||||
if ((ones & 1) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!RD(start + 9)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef RD
|
||||
return byte_val;
|
||||
}
|
||||
|
||||
// Check if the 128-bit register contains a valid PAC frame.
|
||||
static bool try_decode_frame(pac_codec *d, bool inverted) {
|
||||
uint32_t preamble = 0;
|
||||
for (int i = 0; i < PAC_PREAMBLE_BITS; i++) {
|
||||
preamble = (preamble << 1) | (get_bit(d, i) ? 1 : 0);
|
||||
}
|
||||
|
||||
uint32_t expected = inverted ? PAC_PREAMBLE_INV : PAC_PREAMBLE;
|
||||
if (preamble != expected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t decoded[PAC_PAYLOAD_BYTES];
|
||||
for (int i = 0; i < PAC_PAYLOAD_BYTES; i++) {
|
||||
uint16_t frame_start = 8 + i * PAC_UART_FRAME_BITS;
|
||||
int val = decode_uart_byte(d, frame_start, inverted);
|
||||
if (val < 0) {
|
||||
return false;
|
||||
}
|
||||
decoded[i] = (uint8_t)val;
|
||||
}
|
||||
|
||||
if (decoded[0] != PAC_STX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t xor_check = 0;
|
||||
for (int i = 3; i < 3 + PAC_DATA_SIZE; i++) {
|
||||
xor_check ^= decoded[i];
|
||||
}
|
||||
if (xor_check != decoded[11]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(d->card_id, &decoded[3], PAC_DATA_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process a demodulated NRZ edge interval (in samples = carrier cycles).
|
||||
static bool pac_process_interval(pac_codec *d, uint32_t interval) {
|
||||
uint32_t nbits = (interval + PAC_HALF_BIT) / PAC_RF_PER_BIT;
|
||||
|
||||
if (nbits < 1 || nbits > PAC_MAX_BITS_RUN) {
|
||||
d->raw_hi = 0;
|
||||
d->raw_lo = 0;
|
||||
d->polarity = false;
|
||||
d->bit_count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < nbits; i++) {
|
||||
shift_bit(d, d->polarity);
|
||||
if (d->bit_count < PAC_FRAME_BITS) {
|
||||
d->bit_count++;
|
||||
}
|
||||
if (d->bit_count >= PAC_FRAME_BITS) {
|
||||
if (try_decode_frame(d, false) || try_decode_frame(d, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d->polarity = !d->polarity;
|
||||
return false;
|
||||
}
|
||||
|
||||
static pac_codec *pac_alloc(void) {
|
||||
pac_codec *codec = malloc(sizeof(pac_codec));
|
||||
return codec;
|
||||
}
|
||||
|
||||
static void pac_free(pac_codec *d) {
|
||||
free(d);
|
||||
}
|
||||
|
||||
static uint8_t *pac_get_data(pac_codec *d) {
|
||||
return d->card_id;
|
||||
}
|
||||
|
||||
static void pac_decoder_start(pac_codec *d, uint8_t format) {
|
||||
memset(d, 0, sizeof(pac_codec));
|
||||
d->raw_min = 32767; // INT16_MAX: first sample updates it
|
||||
d->spike_cap = 0x7FFFFFFF; // INT32_MAX: no capping until prescan completes
|
||||
d->clip_max = -32768; // INT16_MIN: first clipped sample updates it
|
||||
d->clip_min = 32767; // INT16_MAX: first clipped sample updates it
|
||||
}
|
||||
|
||||
// Feed a raw ADC sample (one per carrier cycle at 125kHz).
|
||||
// Spike-clipping + threshold with dead zone (PM3 nrzRawDemod style).
|
||||
static bool pac_decoder_feed(pac_codec *d, uint16_t raw_sample) {
|
||||
int16_t sample = (int16_t)raw_sample;
|
||||
d->total_samples++;
|
||||
|
||||
// Phase 1: Prescan — track raw minimum to find the NRZ floor.
|
||||
if (d->total_samples <= PAC_PRESCAN_SAMPLES) {
|
||||
if (sample < d->raw_min && sample > 0) {
|
||||
d->raw_min = sample;
|
||||
}
|
||||
if (d->total_samples == PAC_PRESCAN_SAMPLES) {
|
||||
d->spike_cap = (int32_t)d->raw_min * PAC_SPIKE_MULT;
|
||||
if (d->spike_cap < PAC_MIN_SPIKE_CAP) {
|
||||
d->spike_cap = PAC_MIN_SPIKE_CAP;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clip spikes: LC ringing transients are replaced with the cap level.
|
||||
if (sample > d->spike_cap) {
|
||||
sample = d->spike_cap;
|
||||
}
|
||||
|
||||
uint32_t warmup_samples = d->total_samples - PAC_PRESCAN_SAMPLES;
|
||||
|
||||
// Phase 2: Warmup — track min/max of clipped samples to find NRZ levels.
|
||||
if (warmup_samples <= PAC_WARMUP_SAMPLES) {
|
||||
if (sample > d->clip_max) d->clip_max = sample;
|
||||
if (sample < d->clip_min) d->clip_min = sample;
|
||||
if (warmup_samples == PAC_WARMUP_SAMPLES) {
|
||||
int16_t range = d->clip_max - d->clip_min;
|
||||
d->thresh_high = d->clip_min + (range * PAC_THRESH_FUZZ) / 100;
|
||||
d->thresh_low = d->clip_min + (range * (100 - PAC_THRESH_FUZZ)) / 100;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase 3: Per-sample threshold with dead zone.
|
||||
// Auto-recalibrate if no frame found after enough decode samples —
|
||||
// the one-shot calibration may have captured an unlucky NRZ segment.
|
||||
d->decode_samples++;
|
||||
if (d->decode_samples >= PAC_RECAL_SAMPLES) {
|
||||
pac_decoder_start(d, 0);
|
||||
return false;
|
||||
}
|
||||
d->sample_count++;
|
||||
|
||||
bool new_state = d->adc_state;
|
||||
if (sample >= d->thresh_high) {
|
||||
new_state = true;
|
||||
} else if (sample <= d->thresh_low) {
|
||||
new_state = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d->has_signal) {
|
||||
d->has_signal = true;
|
||||
d->adc_state = new_state;
|
||||
d->sample_count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_state == d->adc_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transition detected — process the interval
|
||||
uint32_t interval = d->sample_count;
|
||||
d->sample_count = 0;
|
||||
d->adc_state = new_state;
|
||||
|
||||
return pac_process_interval(d, interval);
|
||||
}
|
||||
|
||||
// --- Modulator (emulation) ---
|
||||
|
||||
static nrf_pwm_values_wave_form_t m_pac_pwm_seq_vals[PAC_FRAME_BITS] = {};
|
||||
|
||||
static const nrf_pwm_sequence_t m_pac_pwm_seq = {
|
||||
.values.p_wave_form = m_pac_pwm_seq_vals,
|
||||
.length = NRF_PWM_VALUES_LENGTH(m_pac_pwm_seq_vals),
|
||||
.repeats = 0,
|
||||
.end_delay = 0,
|
||||
};
|
||||
|
||||
// Build the 128-bit NRZ bitstream from 8-byte card ID.
|
||||
// Frame: 0xFF sync (8 bits) + 12 × 10-bit UART frames = 128 bits.
|
||||
// UART frame: start(0) + 7 data bits LSB-first + odd parity + stop(1).
|
||||
// Payload bytes: STX(0x02), '2', '0', card_id[0..7], XOR checksum.
|
||||
static void pac_build_bitstream(const uint8_t *card_id, uint8_t *bits_out) {
|
||||
uint8_t payload[PAC_PAYLOAD_BYTES];
|
||||
payload[0] = PAC_STX;
|
||||
payload[1] = '2';
|
||||
payload[2] = '0';
|
||||
memcpy(&payload[3], card_id, PAC_DATA_SIZE);
|
||||
|
||||
// XOR checksum over card ID bytes (indices 3..10)
|
||||
uint8_t xor_check = 0;
|
||||
for (int i = 3; i < 3 + PAC_DATA_SIZE; i++) {
|
||||
xor_check ^= payload[i];
|
||||
}
|
||||
payload[11] = xor_check;
|
||||
|
||||
int bit_pos = 0;
|
||||
|
||||
// 8-bit sync marker: 0xFF (all ones)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bits_out[bit_pos++] = 1;
|
||||
}
|
||||
|
||||
// 12 UART frames
|
||||
for (int f = 0; f < PAC_PAYLOAD_BYTES; f++) {
|
||||
uint8_t byte_val = payload[f];
|
||||
|
||||
// Start bit (0)
|
||||
bits_out[bit_pos++] = 0;
|
||||
|
||||
// 7 data bits, LSB first
|
||||
uint8_t ones = 0;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
uint8_t bit = (byte_val >> i) & 1;
|
||||
bits_out[bit_pos++] = bit;
|
||||
ones += bit;
|
||||
}
|
||||
|
||||
// Odd parity: set so total ones (data + parity) is odd
|
||||
uint8_t parity = (ones & 1) ? 0 : 1;
|
||||
bits_out[bit_pos++] = parity;
|
||||
|
||||
// Stop bit (1)
|
||||
bits_out[bit_pos++] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static const nrf_pwm_sequence_t *pac_modulator(pac_codec *d, uint8_t *buf) {
|
||||
uint8_t bits[PAC_FRAME_BITS];
|
||||
pac_build_bitstream(buf, bits);
|
||||
|
||||
// NRZ: output must be CONSTANT within each bit period (no mid-bit transition).
|
||||
// Per nRF52840 PS: compare >= counter_top → pin held HIGH; compare = 0 → pin held LOW.
|
||||
// Use compare = counter_top + 1 (not counter_top) to avoid the compare == counter_top
|
||||
// boundary where a 1-tick output glitch may occur due to simultaneous compare-match
|
||||
// and counter-wrap. Real PAC readers with hardware edge detection are sensitive to this;
|
||||
// PM3's software NRZ demod is not (it averages over the bit period).
|
||||
for (int i = 0; i < PAC_FRAME_BITS; i++) {
|
||||
m_pac_pwm_seq_vals[i].channel_0 = bits[i] ? (PAC_RF_PER_BIT + 1) : 0;
|
||||
m_pac_pwm_seq_vals[i].counter_top = PAC_RF_PER_BIT;
|
||||
}
|
||||
return &m_pac_pwm_seq;
|
||||
}
|
||||
|
||||
#define PAC_T55XX_BLOCK_COUNT 5 // 1 config + 4 data blocks
|
||||
|
||||
uint8_t pac_t55xx_writer(uint8_t *data, uint32_t *blks) {
|
||||
uint8_t bits[PAC_FRAME_BITS];
|
||||
pac_build_bitstream(data, bits);
|
||||
|
||||
blks[0] = T5577_PAC_CONFIG;
|
||||
for (int b = 0; b < 4; b++) {
|
||||
uint32_t word = 0;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
word = (word << 1) | bits[b * 32 + i];
|
||||
}
|
||||
blks[b + 1] = word;
|
||||
}
|
||||
return PAC_T55XX_BLOCK_COUNT;
|
||||
}
|
||||
|
||||
const protocol pac = {
|
||||
.tag_type = TAG_TYPE_PAC,
|
||||
.data_size = PAC_DATA_SIZE,
|
||||
.alloc = (codec_alloc)pac_alloc,
|
||||
.free = (codec_free)pac_free,
|
||||
.get_data = (codec_get_data)pac_get_data,
|
||||
.modulator = (modulator)pac_modulator,
|
||||
.decoder =
|
||||
{
|
||||
.start = (decoder_start)pac_decoder_start,
|
||||
.feed = (decoder_feed)pac_decoder_feed,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "protocols.h"
|
||||
|
||||
extern const protocol pac;
|
||||
uint8_t pac_t55xx_writer(uint8_t *data, uint32_t *blks);
|
||||
@@ -75,6 +75,12 @@ extern "C" {
|
||||
T5577_PWD | \
|
||||
(2 << T5577_MAXBLOCK_SHIFT))
|
||||
|
||||
#define T5577_PAC_CONFIG ( \
|
||||
T5577_MODULATION_DIRECT | \
|
||||
T5577_BITRATE_RF_32 | \
|
||||
T5577_PWD | \
|
||||
(4 << T5577_MAXBLOCK_SHIFT))
|
||||
|
||||
void t55xx_write_data(uint32_t passwd, uint32_t *blks, uint8_t blk_count);
|
||||
void t55xx_reset_passwd(uint32_t old_passwd, uint32_t new_passwd);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ typedef enum {
|
||||
// securakey
|
||||
// gallagher
|
||||
// PAC/Stanley
|
||||
TAG_TYPE_PAC = 150,
|
||||
// Presco
|
||||
// Visa2000
|
||||
// Viking
|
||||
@@ -107,7 +108,7 @@ typedef enum {
|
||||
}
|
||||
|
||||
#define TAG_SPECIFIC_TYPE_LF_VALUES \
|
||||
TAG_TYPE_EM410X, TAG_TYPE_EM410X_ELECTRA, TAG_TYPE_HID_PROX, TAG_TYPE_IOPROX, TAG_TYPE_VIKING
|
||||
TAG_TYPE_EM410X, TAG_TYPE_EM410X_ELECTRA, TAG_TYPE_PAC, TAG_TYPE_HID_PROX, TAG_TYPE_IOPROX, TAG_TYPE_VIKING
|
||||
|
||||
#define TAG_SPECIFIC_TYPE_HF_VALUES \
|
||||
TAG_TYPE_MIFARE_Mini, TAG_TYPE_MIFARE_1024, TAG_TYPE_MIFARE_2048, \
|
||||
|
||||
@@ -94,6 +94,7 @@ static tag_base_handler_map_t tag_base_map[] = {
|
||||
{TAG_SENSE_LF, TAG_TYPE_HID_PROX, lf_tag_data_loadcb, lf_tag_hidprox_data_savecb, lf_tag_hidprox_data_factory, &m_tag_data_lf},
|
||||
{TAG_SENSE_LF, TAG_TYPE_IOPROX, lf_tag_data_loadcb, lf_tag_ioprox_data_savecb, lf_tag_ioprox_data_factory, &m_tag_data_lf},
|
||||
{TAG_SENSE_LF, TAG_TYPE_VIKING, lf_tag_data_loadcb, lf_tag_viking_data_savecb, lf_tag_viking_data_factory, &m_tag_data_lf},
|
||||
{TAG_SENSE_LF, TAG_TYPE_PAC, lf_tag_data_loadcb, lf_tag_pac_data_savecb, lf_tag_pac_data_factory, &m_tag_data_lf},
|
||||
// MF1 tag emulation
|
||||
{TAG_SENSE_HF, TAG_TYPE_MIFARE_Mini, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf},
|
||||
{TAG_SENSE_HF, TAG_TYPE_MIFARE_1024, nfc_tag_mf1_data_loadcb, nfc_tag_mf1_data_savecb, nfc_tag_mf1_data_factory, &m_tag_data_hf},
|
||||
|
||||
@@ -80,6 +80,7 @@ void tag_emulation_init(void);
|
||||
void tag_emulation_save(void);
|
||||
|
||||
// Starting and ending of the emulation card
|
||||
void tag_emulation_load_data(void);
|
||||
void tag_emulation_sense_run(void);
|
||||
void tag_emulation_sense_end(void);
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ble_main.h"
|
||||
#include "nrfx_pwm.h"
|
||||
|
||||
/* Exposed so lf_gap.c can stop the PWM and drive LF_ANT_DRIVER directly
|
||||
* to create clean field gaps without relying on PWM pin release state. */
|
||||
extern nrfx_pwm_t m_pwm;
|
||||
|
||||
void lf_125khz_radio_init(void);
|
||||
void lf_125khz_radio_uninit(void);
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
#include "lf_em4x05_data.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app_status.h"
|
||||
#include "bsp_delay.h"
|
||||
#include "bsp_time.h"
|
||||
#include "circular_buffer.h"
|
||||
#include "lf_125khz_radio.h"
|
||||
#include "lf_gap.h"
|
||||
#include "lf_reader_data.h"
|
||||
#include "timeslot.h"
|
||||
|
||||
#include "utils/manchester.h"
|
||||
|
||||
#define NRF_LOG_MODULE_NAME lf_em4x05
|
||||
#include "nrf_log.h"
|
||||
#include "nrf_log_ctrl.h"
|
||||
#include "nrf_log_default_backends.h"
|
||||
NRF_LOG_MODULE_REGISTER();
|
||||
|
||||
#define EM4X05_CMD_BITS 9
|
||||
#define EM4X05_RESP_BITS 45
|
||||
#define EM4X05_ROWS 8
|
||||
#define EM4X05_COLS 4
|
||||
#define EM4X05_CB_SIZE 256
|
||||
|
||||
static inline uint8_t odd_parity4(uint8_t nibble) {
|
||||
nibble ^= nibble >> 2;
|
||||
nibble ^= nibble >> 1;
|
||||
return (~nibble) & 1;
|
||||
}
|
||||
|
||||
static uint8_t em4x05_cmd_parity(uint8_t opcode, uint8_t addr) {
|
||||
uint8_t o1 = (opcode >> 1) & 1;
|
||||
uint8_t o0 = (opcode) & 1;
|
||||
uint8_t a2 = (addr >> 2) & 1;
|
||||
uint8_t a1 = (addr >> 1) & 1;
|
||||
uint8_t a0 = (addr) & 1;
|
||||
uint8_t p2 = (~(o1 ^ o0 ^ a2)) & 1;
|
||||
uint8_t p1 = (~(o1 ^ a1 ^ a0)) & 1;
|
||||
uint8_t p0 = (~(o0 ^ a2 ^ a1)) & 1;
|
||||
return (p2 << 2) | (p1 << 1) | p0;
|
||||
}
|
||||
|
||||
static uint16_t em4x05_build_cmd(uint8_t opcode, uint8_t addr) {
|
||||
uint8_t parity = em4x05_cmd_parity(opcode, addr);
|
||||
return (1u << 8) | ((opcode & 0x3) << 6) | ((addr & 0x7) << 3) | (parity & 0x7);
|
||||
}
|
||||
|
||||
static bool em4x05_decode_response(const uint8_t *bits, uint32_t *data) {
|
||||
if (bits[0] != 0) {
|
||||
return false;
|
||||
}
|
||||
uint32_t result = 0;
|
||||
uint8_t col_parity[EM4X05_COLS] = {0};
|
||||
for (int row = 0; row < EM4X05_ROWS; row++) {
|
||||
int base = 1 + row * (EM4X05_COLS + 1);
|
||||
uint8_t nibble = 0;
|
||||
for (int col = 0; col < EM4X05_COLS; col++) {
|
||||
uint8_t b = bits[base + col] & 1;
|
||||
nibble = (nibble << 1) | b;
|
||||
col_parity[col] ^= b;
|
||||
}
|
||||
uint8_t rp = bits[base + EM4X05_COLS] & 1;
|
||||
if (rp != odd_parity4(nibble)) {
|
||||
NRF_LOG_DEBUG("em4x05: row %d parity fail", row);
|
||||
return false;
|
||||
}
|
||||
result = (result << EM4X05_COLS) | nibble;
|
||||
}
|
||||
int cp_base = 1 + EM4X05_ROWS * (EM4X05_COLS + 1);
|
||||
for (int col = 0; col < EM4X05_COLS; col++) {
|
||||
uint8_t received_cp = bits[cp_base + col] & 1;
|
||||
if (received_cp != ((~col_parity[col]) & 1)) {
|
||||
NRF_LOG_DEBUG("em4x05: col %d parity fail", col);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*data = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define EM4X05_T1 0x40u
|
||||
#define EM4X05_T15 0x60u
|
||||
#define EM4X05_T2 0x80u
|
||||
#define EM4X05_JIT 0x10u
|
||||
|
||||
static uint8_t em4x05_rf64_period(uint8_t interval) {
|
||||
if (interval >= (EM4X05_T1 - EM4X05_JIT) && interval <= (EM4X05_T1 + EM4X05_JIT)) return 0;
|
||||
if (interval >= (EM4X05_T15 - EM4X05_JIT) && interval <= (EM4X05_T15 + EM4X05_JIT)) return 1;
|
||||
if (interval >= (EM4X05_T2 - EM4X05_JIT) && interval <= (EM4X05_T2 + EM4X05_JIT)) return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
static circular_buffer g_cb;
|
||||
|
||||
static void em4x05_edge_cb(void) {
|
||||
uint32_t cnt = get_lf_counter_value();
|
||||
uint16_t val = (cnt > 0xff) ? 0xff : (uint16_t)(cnt & 0xff);
|
||||
cb_push_back(&g_cb, &val);
|
||||
clear_lf_counter_value();
|
||||
}
|
||||
|
||||
static uint8_t g_send_opcode;
|
||||
static uint8_t g_send_addr;
|
||||
static uint32_t g_send_password;
|
||||
static volatile bool g_timeslot_done = false;
|
||||
|
||||
/*
|
||||
* Send one EM4305 command bit.
|
||||
* Protocol: field ON for bit duration, then write gap (field OFF).
|
||||
* The write gap delay is padded to compensate for antenna ringing (~200us).
|
||||
* Field is left ON after the gap ready for the next bit or response window.
|
||||
*/
|
||||
static void send_em4305_bit(bool bit) {
|
||||
if (bit) {
|
||||
bsp_delay_us(256); /* bit 1: 32 Tc = 256us */
|
||||
} else {
|
||||
bsp_delay_us(184); /* bit 0: 23 Tc = 184us */
|
||||
}
|
||||
stop_lf_125khz_radio();
|
||||
bsp_delay_us(250); /* write gap: 128us target + ~122us ringing compensation */
|
||||
start_lf_125khz_radio();
|
||||
}
|
||||
|
||||
static void em4x05_send_timeslot_cb(void) {
|
||||
/* 1. Start gap: wake up tag */
|
||||
stop_lf_125khz_radio();
|
||||
bsp_delay_us(440); /* 55 Tc = 440us */
|
||||
|
||||
/* 2. Settle: allow tag clock recovery to lock onto carrier */
|
||||
start_lf_125khz_radio();
|
||||
bsp_delay_us(104); /* 13 carrier cycles = 104us */
|
||||
|
||||
/* 3. Send 9-bit command MSB first */
|
||||
uint16_t cmd = em4x05_build_cmd(g_send_opcode, g_send_addr);
|
||||
for (int i = 8; i >= 0; i--) {
|
||||
send_em4305_bit((cmd >> i) & 1);
|
||||
}
|
||||
|
||||
/* 4. Field stays ON (left by last start_lf in send_em4305_bit)
|
||||
* Tag will respond ~3 Tc (~24us) after the last write gap */
|
||||
g_timeslot_done = true;
|
||||
}
|
||||
|
||||
static void em4x05_build_data_word(uint32_t data, uint8_t bits[45]) {
|
||||
uint8_t col_par[4] = {0};
|
||||
int pos = 0;
|
||||
bits[pos++] = 0;
|
||||
for (int row = 0; row < 8; row++) {
|
||||
uint8_t nibble = (data >> (28 - row * 4)) & 0xF;
|
||||
uint8_t rp = 0;
|
||||
for (int col = 0; col < 4; col++) {
|
||||
uint8_t b = (nibble >> (3 - col)) & 1;
|
||||
bits[pos++] = b;
|
||||
col_par[col] ^= b;
|
||||
rp ^= b;
|
||||
}
|
||||
bits[pos++] = (~rp) & 1;
|
||||
}
|
||||
for (int col = 0; col < 4; col++) {
|
||||
bits[pos++] = (~col_par[col]) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void em4x05_login_timeslot_cb(void) {
|
||||
/* Start gap */
|
||||
stop_lf_125khz_radio();
|
||||
bsp_delay_us(440);
|
||||
start_lf_125khz_radio();
|
||||
bsp_delay_us(104);
|
||||
|
||||
/* LOGIN command: opcode=0b00 (DSBL), addr=0b000 */
|
||||
uint16_t cmd = em4x05_build_cmd(EM4X05_OPCODE_DSBL, 0);
|
||||
for (int i = 8; i >= 0; i--) {
|
||||
send_em4305_bit((cmd >> i) & 1);
|
||||
}
|
||||
|
||||
/* Send 45-bit password word using same bit encoding */
|
||||
uint8_t pwd_bits[45];
|
||||
em4x05_build_data_word(g_send_password, pwd_bits);
|
||||
for (int i = 0; i < 45; i++) {
|
||||
send_em4305_bit(pwd_bits[i]);
|
||||
}
|
||||
|
||||
g_timeslot_done = true;
|
||||
}
|
||||
|
||||
static bool em4x05_login(uint32_t password, uint32_t timeout_ms) {
|
||||
g_send_password = password;
|
||||
g_timeslot_done = false;
|
||||
|
||||
request_timeslot(15000, em4x05_login_timeslot_cb);
|
||||
|
||||
autotimer *p_wait = bsp_obtain_timer(0);
|
||||
while (!g_timeslot_done && NO_TIMEOUT_1MS(p_wait, 20)) {}
|
||||
bsp_return_timer(p_wait);
|
||||
|
||||
cb_init(&g_cb, EM4X05_CB_SIZE, sizeof(uint16_t));
|
||||
register_rio_callback(em4x05_edge_cb);
|
||||
lf_125khz_radio_gpiote_enable();
|
||||
clear_lf_counter_value();
|
||||
|
||||
bool ack = false;
|
||||
autotimer *p_at = bsp_obtain_timer(0);
|
||||
while (!ack && NO_TIMEOUT_1MS(p_at, timeout_ms)) {
|
||||
uint16_t interval = 0;
|
||||
if (!cb_pop_front(&g_cb, &interval)) {
|
||||
continue;
|
||||
}
|
||||
uint8_t period = em4x05_rf64_period((uint8_t)interval);
|
||||
if (period <= 2) {
|
||||
ack = true;
|
||||
}
|
||||
}
|
||||
bsp_return_timer(p_at);
|
||||
lf_125khz_radio_gpiote_disable();
|
||||
unregister_rio_callback();
|
||||
cb_free(&g_cb);
|
||||
return ack;
|
||||
}
|
||||
|
||||
static bool em4x05_read_block(uint8_t addr, uint32_t *data, uint32_t timeout_ms) {
|
||||
g_send_opcode = EM4X05_OPCODE_READ;
|
||||
g_send_addr = addr;
|
||||
g_timeslot_done = false;
|
||||
|
||||
/*
|
||||
* Timeslot must cover full command transmission:
|
||||
* start_gap(440) + settle(104) + 9 bits * (256+250) = 5098us
|
||||
* Use 6000us for margin.
|
||||
*/
|
||||
request_timeslot(6000, em4x05_send_timeslot_cb);
|
||||
|
||||
autotimer *p_wait = bsp_obtain_timer(0);
|
||||
while (!g_timeslot_done && NO_TIMEOUT_1MS(p_wait, 10)) {}
|
||||
bsp_return_timer(p_wait);
|
||||
|
||||
cb_init(&g_cb, EM4X05_CB_SIZE, sizeof(uint16_t));
|
||||
register_rio_callback(em4x05_edge_cb);
|
||||
lf_125khz_radio_gpiote_enable();
|
||||
clear_lf_counter_value();
|
||||
|
||||
manchester modem = {
|
||||
.sync = true,
|
||||
.rp = em4x05_rf64_period,
|
||||
};
|
||||
uint8_t resp_bits[EM4X05_RESP_BITS] = {0};
|
||||
uint8_t bit_count = 0;
|
||||
bool ok = false;
|
||||
|
||||
autotimer *p_at = bsp_obtain_timer(0);
|
||||
while (!ok && NO_TIMEOUT_1MS(p_at, timeout_ms)) {
|
||||
uint16_t interval = 0;
|
||||
if (!cb_pop_front(&g_cb, &interval)) {
|
||||
continue;
|
||||
}
|
||||
bool mbits[2] = {false, false};
|
||||
int8_t mbitlen = 0;
|
||||
manchester_feed(&modem, (uint8_t)interval, mbits, &mbitlen);
|
||||
if (mbitlen == -1) {
|
||||
manchester_reset(&modem);
|
||||
bit_count = 0;
|
||||
continue;
|
||||
}
|
||||
for (int8_t i = 0; i < mbitlen && bit_count < EM4X05_RESP_BITS; i++) {
|
||||
resp_bits[bit_count++] = mbits[i] ? 1 : 0;
|
||||
}
|
||||
if (bit_count >= EM4X05_RESP_BITS) {
|
||||
ok = em4x05_decode_response(resp_bits, data);
|
||||
if (!ok) {
|
||||
memmove(resp_bits, resp_bits + 1, EM4X05_RESP_BITS - 1);
|
||||
bit_count = EM4X05_RESP_BITS - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
bsp_return_timer(p_at);
|
||||
lf_125khz_radio_gpiote_disable();
|
||||
unregister_rio_callback();
|
||||
cb_free(&g_cb);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool em4x05_read(em4x05_data_t *out, uint32_t timeout_ms) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
uint32_t block_timeout = timeout_ms / 4;
|
||||
if (block_timeout < 100) block_timeout = 100;
|
||||
|
||||
if (!em4x05_read_block(EM4X05_BLOCK_CONFIG, &out->config, block_timeout)) {
|
||||
NRF_LOG_DEBUG("em4x05: block 0 read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out->config == 0x00000000 || out->config == 0xFFFFFFFF) {
|
||||
NRF_LOG_DEBUG("em4x05: invalid config word 0x%08X", out->config);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rl = (out->config >> 6) & 1;
|
||||
if (rl) {
|
||||
NRF_LOG_DEBUG("em4x05: RL set, attempting login pwd=%08X", out->password);
|
||||
if (!em4x05_login(out->password, block_timeout)) {
|
||||
NRF_LOG_DEBUG("em4x05: login failed");
|
||||
out->login_required = true;
|
||||
return false;
|
||||
}
|
||||
out->login_required = false;
|
||||
NRF_LOG_DEBUG("em4x05: login OK");
|
||||
}
|
||||
|
||||
uint8_t lwr = (out->config >> 16) & 0xF;
|
||||
uint8_t uid_block = (lwr >= 1 && lwr < 14) ? lwr : EM4X05_BLOCK_UID;
|
||||
|
||||
if (!em4x05_read_block(uid_block, &out->uid, block_timeout)) {
|
||||
NRF_LOG_DEBUG("em4x05: UID block %d read failed", uid_block);
|
||||
return false;
|
||||
}
|
||||
out->uid_block = uid_block;
|
||||
|
||||
uint32_t uid_lo = 0, uid_hi = 0;
|
||||
if (em4x05_read_block(EM4X69_BLOCK_UID_LO, &uid_lo, block_timeout) &&
|
||||
em4x05_read_block(EM4X69_BLOCK_UID_HI, &uid_hi, block_timeout)) {
|
||||
out->uid_hi = uid_hi;
|
||||
out->uid = uid_lo;
|
||||
out->is_em4x69 = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t scan_em4x05(em4x05_data_t *out) {
|
||||
start_lf_125khz_radio();
|
||||
bsp_delay_ms(5);
|
||||
|
||||
bool found = em4x05_read(out, 1000);
|
||||
|
||||
stop_lf_125khz_radio();
|
||||
|
||||
if (!found && out->login_required) {
|
||||
return STATUS_LF_TAG_LOGIN_REQUIRED;
|
||||
}
|
||||
return found ? STATUS_LF_TAG_OK : STATUS_LF_TAG_NO_FOUND;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Constants
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
#define EM4X05_OPCODE_READ 0x02
|
||||
#define EM4X05_OPCODE_WRITE 0x01
|
||||
#define EM4X05_OPCODE_PRCT 0x03
|
||||
#define EM4X05_OPCODE_DSBL 0x00
|
||||
|
||||
#define EM4X05_BLOCK_CONFIG 0
|
||||
#define EM4X05_BLOCK_PASSWD 1
|
||||
#define EM4X05_BLOCK_UID 15
|
||||
#define EM4X69_BLOCK_UID_LO 13
|
||||
#define EM4X69_BLOCK_UID_HI 14
|
||||
|
||||
#define EM4X05_RESPONSE_BITS 45
|
||||
#define EM4X05_RF_DIV 64
|
||||
#define EM4X05_RESPONSE_TIMEOUT_TC 300
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Data structures
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
uint32_t config; /* block 0: configuration word */
|
||||
uint32_t uid; /* UID (block determined by LWR or block 15) */
|
||||
uint32_t uid_hi; /* EM4x69 only: high word of 64-bit UID */
|
||||
bool is_em4x69; /* true if 64-bit UID was successfully read */
|
||||
uint8_t uid_block; /* block number where UID was actually read from */
|
||||
uint32_t password; /* password to use for LOGIN (default 0x00000000)*/
|
||||
bool login_required;/* true if tag has RL bit set and login failed */
|
||||
} em4x05_data_t;
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Public API
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
bool em4x05_read(em4x05_data_t *out, uint32_t timeout_ms);
|
||||
uint8_t scan_em4x05(em4x05_data_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "lf_gap.h"
|
||||
|
||||
#include "bsp_delay.h"
|
||||
#include "hw_connect.h"
|
||||
#include "lf_125khz_radio.h"
|
||||
#include "lf_reader_data.h"
|
||||
#include "nrf_gpio.h"
|
||||
|
||||
#define NRF_LOG_MODULE_NAME lf_gap
|
||||
#include "nrf_log.h"
|
||||
#include "nrf_log_ctrl.h"
|
||||
#include "nrf_log_default_backends.h"
|
||||
NRF_LOG_MODULE_REGISTER();
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Transmit side
|
||||
*
|
||||
* All functions must be called from within a timeslot callback.
|
||||
*
|
||||
* Gap generation: we cannot rely on nrfx_pwm_stop() to cut the field
|
||||
* because when the PWM stops it releases LF_ANT_DRIVER to GPIO state,
|
||||
* which may leave the antenna driver enabled. Instead we:
|
||||
* 1. Stop the PWM (releases pin to GPIO)
|
||||
* 2. Explicitly drive LF_ANT_DRIVER low (field off)
|
||||
* 3. Delay for the gap duration
|
||||
* 4. Drive LF_ANT_DRIVER high then restart PWM (field on)
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
static inline void field_off(void) {
|
||||
nrfx_pwm_stop(&m_pwm, true); /* stop PWM, releases pin */
|
||||
nrf_gpio_cfg_output(LF_ANT_DRIVER);
|
||||
nrf_gpio_pin_clear(LF_ANT_DRIVER); /* drive low = field off */
|
||||
}
|
||||
|
||||
static inline void field_on(void) {
|
||||
nrf_gpio_pin_set(LF_ANT_DRIVER); /* drive high briefly */
|
||||
start_lf_125khz_radio(); /* restart PWM on pin */
|
||||
}
|
||||
|
||||
void lf_gap_send_start(void) {
|
||||
field_off();
|
||||
bsp_delay_us(GAP_START_US);
|
||||
field_on();
|
||||
}
|
||||
|
||||
void lf_gap_send_bit(uint8_t bit) {
|
||||
if (bit & 1) {
|
||||
bsp_delay_us(GAP_BIT1_US);
|
||||
} else {
|
||||
bsp_delay_us(GAP_BIT0_US);
|
||||
}
|
||||
field_off();
|
||||
bsp_delay_us(GAP_WRITE_US);
|
||||
field_on();
|
||||
}
|
||||
|
||||
void lf_gap_send_u32(uint32_t word) {
|
||||
lf_gap_send_bits(word, 32);
|
||||
}
|
||||
|
||||
void lf_gap_send_bits(uint32_t value, uint8_t nbits) {
|
||||
for (int8_t i = (int8_t)(nbits - 1); i >= 0; i--) {
|
||||
lf_gap_send_bit((value >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool lf_gap_detect(uint32_t last_count, uint32_t *gap_tc) {
|
||||
uint32_t now = get_lf_counter_value();
|
||||
uint32_t elapsed = now - last_count;
|
||||
if (elapsed >= GAP_DETECT_TIMEOUT_TC) {
|
||||
*gap_tc = elapsed;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* LF reader-talk-first gap detection and transmission.
|
||||
*
|
||||
* Reader-talk-first (RTF) protocols like EM4x05/4x69 and EM4x50/4x70
|
||||
* communicate with the tag by briefly cutting the 125kHz carrier field.
|
||||
* A "gap" — carrier off for a calibrated number of carrier cycles — encodes
|
||||
* one bit. After the command sequence, the reader restores the field and
|
||||
* listens for the tag's Manchester- or Biphase-encoded response.
|
||||
*
|
||||
* Gap timing (EM4x05 / EM4x69, per datasheet):
|
||||
* Start gap: ~50 Tc (powers up and resets the tag)
|
||||
* Write gap: ~10 Tc (separates command bits during transmission)
|
||||
* Bit '0': ~24 Tc field on between gaps
|
||||
* Bit '1': ~56 Tc field on between gaps
|
||||
*
|
||||
* The existing T5577 writer in lf_t55xx_data.c uses the same physical
|
||||
* mechanism (stop_lf_125khz_radio / bsp_delay_us / start_lf_125khz_radio)
|
||||
* inside a timeslot callback. This module follows the same pattern.
|
||||
*
|
||||
* Gap detection on the receive side:
|
||||
* The GPIOTE edge-capture counter fires on each carrier envelope edge.
|
||||
* During a gap the carrier is absent, so no edges arrive. We detect a
|
||||
* gap by polling the counter and declaring a gap when no edge has arrived
|
||||
* within GAP_DETECT_TIMEOUT_TC carrier cycles. The gap duration is then
|
||||
* the elapsed counter value.
|
||||
*
|
||||
* Units: all timing constants are in carrier cycles (Tc = 1/125000 s = 8 µs).
|
||||
* bsp_delay_us() is used for gap transmission; the counter captures elapsed
|
||||
* carrier cycles on the receive side.
|
||||
*/
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Transmit timing constants (in microseconds = Tc × 8)
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/** Start gap: resets the tag and signals start of a command sequence. */
|
||||
#define GAP_START_TC 55 /* PM3 proven: 55*8=440us for EM4x05/4305 */
|
||||
#define GAP_START_US (GAP_START_TC * 8)
|
||||
|
||||
/** Write gap: separates command bits during transmission. */
|
||||
#define GAP_WRITE_TC 16 /* PM3 proven: 16*8=128us */
|
||||
#define GAP_WRITE_US (GAP_WRITE_TC * 8)
|
||||
|
||||
/** Field-on duration encoding bit '0' between write gaps. */
|
||||
#define GAP_BIT0_TC 23 /* PM3 proven: 23*8=184us */
|
||||
#define GAP_BIT0_US (GAP_BIT0_TC * 8)
|
||||
|
||||
/** Field-on duration encoding bit '1' between write gaps. */
|
||||
#define GAP_BIT1_TC 32 /* PM3 proven: 32*8=256us */
|
||||
#define GAP_BIT1_US (GAP_BIT1_TC * 8)
|
||||
|
||||
/**
|
||||
* Listen window after command: time the tag needs before it begins
|
||||
* transmitting its response (EM4x05 datasheet: ~3 Tc after last gap).
|
||||
* We wait a generous 50 Tc to be safe with slow tags.
|
||||
*/
|
||||
#define GAP_LISTEN_TC 50
|
||||
#define GAP_LISTEN_US (GAP_LISTEN_TC * 8)
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Receive timing constants (in carrier cycles)
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Gap detection timeout: if no edge arrives within this many carrier
|
||||
* cycles, the current interval is treated as a gap.
|
||||
* Set conservatively above the longest expected normal interval (≈ 2×RF/64
|
||||
* = 128 Tc for EM4x05 Manchester at RF/64) but below any deliberate gap.
|
||||
*/
|
||||
#define GAP_DETECT_TIMEOUT_TC 200
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* API
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
void lf_gap_send_start(void);
|
||||
void lf_gap_send_bit(uint8_t bit);
|
||||
void lf_gap_send_u32(uint32_t word);
|
||||
void lf_gap_send_bits(uint32_t value, uint8_t nbits);
|
||||
bool lf_gap_detect(uint32_t last_count, uint32_t *gap_tc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user