mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
fix: make each PR self-contained with all required source files
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user