hf felica: make reading actually work

FeliCa reading was broken on every card tested: 'hf felica reader' returned PM3_ETIMEOUT while the tag was answering correctly. The cause was in the FPGA demodulator, not the ARM.

fpga/hi_flite.v
---------------
Adaptive hysteresis thresholds. The envelope tracker clamped curmin to <= 70 and curmax to >= 180, so curminthres/curmaxthres were pinned near 91/160 no
matter where the signal actually sat. Measured on a RDV4 with the field on, the peak detector idles near 112 and a tag swings it by about +/-35, ie entirely
inside that window - so nothing ever crossed a threshold and every frame demodulated as a constant. The band is now derived from the tracked envelope,
3/16 of its span, floored at +/- 8 to stay clear of the 4..6 counts of carrier ripple.

Matched-filter bit detector. The slicer counted comparator trips (+1 above curmaxthres, -1 below curminthres, repeat the last crossing direction inside
the dead band), so every bit depended on where the band happened to sit. A mispositioned band railed the output to a constant and, since only the stable
branch can recompute thresholds or desync, it stayed that way for the rest of the session. It also discarded amplitude, gaining nothing from 32x
oversampling. Each half-bit is now integrated in the ADC domain and the larger half wins. Thresholds still drive bit phase and the desync, they no longer
decide bit values, so a clipped or mispositioned envelope can no longer rail the output.

Polarity lock guard. try_sync arms part way through a half-bit, so the first decision after arming is meaningless and could latch 'zero' inverted, decoding
the whole frame with the wrong polarity and losing the sync word. Skip the first two decisions; the preamble is 48 bits.

curbit re-timing. The bit decision was made in the bit-phase domain, which is aligned to the tag's edges, but sampled by the SSC in the carrier domain. The
ARM could latch a bit mid-transition at a phase that varied per frame. Both run at 64 carrier periods per bit, so re-timing curbit half an SSP bit away from the
sampling edge is a re-time, not a resample.

Envelope watchdog. FPGA registers persist across PM3 commands - only a bitstream reload clears them - so the tracker could enter a state it never left and the
first command after the client started would work while every one after it failed. Force a re-centre when the demodulator has not reached a known-good idle
for 19.3 ms, held off at the start of each frame so it cannot fire mid-reply.

state is marked (* fsm_extract = 'no' *). The project synthesises with -fsm_style bram; once XST recognised this register as a state machine it placed
the state ROM in a block RAM, and the xc2s30's six were already spoken for. MAP then failed to fit with nothing but a generic 'design is too large' error, no
BITGEN, and no new bitstream.

armsrc/felica.c
---------------
- felica_select_card() returning 4 (response too short for IDm+PMm) fell through to PM3_SUCCESS, so 'hf felica reader' reported an all-zero IDm as a good read.
- After a poll timeout the code still read FelicaFrame; with a stale POLLING_RES and len == 0, check_crc() was handed (len - 2) as a size_t, ie 65534.
- WaitForFelicaReply() could only time out from STATE_UNSYNCD/TRYING_SYNC and would spin forever if a frame never completed.
- felica_sniff() decremented and broke before LogTrace, so '-s 10' logged nine frames and '-s 0' logged none. CRC-failed noise no longer pollutes the trace.
- felica_sendraw() sent no reply at all for some flag combinations, leaving the client blocked until its own timeout.
- Polling used time slot 0 only, so several cards in the field collided forever. Retries now widen the TSN window.
- BuildFliteRdblk() warned about a bad block count and built the frame anyway.

Signal probe
------------
'hf felica raw -p' streams the per-window envelope min and max instead of demodulated bits, so reading distance and coupling can be measured rather than
guessed. This is what told 'tag out of range' apart from 'demodulator not locking', which are otherwise identical from the ARM's point of view.

Measured on a RDV4, both cards previously unreadable:
  FeliCa Standard RC-S830 (CJRC 0003): reader 4/4, info 4/4, 39 nodes discovered, dump complete in 2.0 s, 37/40 single polls.
  FeliCa Standard RC-S962 (Octopus 8008): reader 10/10, 23 nodes discovered, dump complete in 1.5 s, 40/60 single polls. This one drives the envelope onto the bottom ADC rail; the matched filter reads it anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
iceman1001
2026-08-28 22:35:08 +02:00
co-authored by Claude Opus 5
parent ff6954f8a6
commit cb48c928a5
8 changed files with 394 additions and 99 deletions
+2
View File
@@ -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]
- Changed `hf felica raw` - now can stream per-window envelope for better measurments (@iceman1001)
- Updated `fpga_pm3_felica.bit` - FeliCa signal handling got refactored. (@iceman1001)
- Changed client tab completion - vocabulary is now built at runtime from the live command tree and uses the same availability rules as `help`, removing the generated `pm3line_vocabulary.h` and `pm3_help2list.py` (@Msprg)
- Fixed standalone modes - the one second button hold trigger was a no-op on all non-PM5 platforms, leaving `hw standalone` over USB as the only entry point (@ShawInnes)
- Added `sim022.bin` - enabled burst mode transfers, which allows us to do TA1=96 in speeds (@iceman1001)
+107 -18
View File
@@ -25,6 +25,7 @@
#include "dbprint.h"
#include "ticks_apis.h"
#include "fpga_apis.h"
#include "rssi_apis.h"
#include "iso18.h"
#define AddCrc(data, len) compute_crc(CRC_FELICA, (data), (len), (data)+(len)+1, (data)+(len))
@@ -35,6 +36,9 @@ static uint8_t frameSpace[FELICA_MAX_RF_FRAME_SIZE];
#define FELICA_PREAMBLE_BYTES 6U
// how much raw demodulator output to keep for `hw dbg -4` diagnostics
#define FELICA_RAW_CAPTURE_BYTES 128U
// Keep a conservative reader-to-reader guard of one FeliCa polling slot-0 wait
// (512 bit periods). The spec minimum of 6800 carrier periods is shorter.
#ifndef FELICA_REQUEST_GUARD_TIME
@@ -55,6 +59,8 @@ static uint32_t felica_timeout;
uint32_t felica_nexttransfertime;
static uint32_t felica_lasttime_prox2air_start;
static bool felica_field_active;
// when set, the FPGA streams envelope peak-to-peak instead of demodulated bits
static bool felica_probe_mode = false;
felica_frame_t FelicaFrame;
@@ -253,28 +259,42 @@ static uint8_t felica_select_card(felica_card_select_t *card) {
// b0 = fc/64 (212kbps)
// 0x00 = timeslot
// 0x09 0x21 = crc
static uint8_t poll[10] = {0xb2, 0x4d, 0x06, FELICA_POLLING_REQ, 0xFF, 0xFF, 0x00, 0x00, 0x09, 0x21};
uint8_t poll[10] = {0xb2, 0x4d, 0x06, FELICA_POLLING_REQ, 0xFF, 0xFF, 0x00, 0x00, 0x09, 0x21};
// Number of time slots offered to the card(s), the FeliCa polling TSN field.
// Slot 0 only is what a single card wants, but when several cards share the
// field they all answer in slot 0 and keep colliding. Widen the window on
// some attempts so a colliding card gets a slot of its own.
static const uint8_t timeslots[] = {0x00, 0x00, 0x00, 0x01, 0x03, 0x07};
// We try 10 times, or if answer was received.
int len = 25;
bool got_reply = false;
// We try 24 times, or stop early once an answer was received.
int len = 24;
do {
poll[7] = timeslots[(unsigned int)len % ARRAYLEN(timeslots)];
AddCrc(poll + 2, 6);
// end-of-reception response packet data, wait approx. 501µs
// end-of-transmission command packet data, wait approx. 197µs
// polling card
TransmitFor18092_AsReader(poll, sizeof(poll), NULL, 1, 0);
// polling card, break if success
if (WaitForFelicaReply(1024) && FelicaFrame.framebytes[3] == FELICA_POLLING_RES) {
break;
if (WaitForFelicaReply(1024)) {
if (FelicaFrame.framebytes[3] == FELICA_POLLING_RES) {
got_reply = true;
break;
}
}
WDT_HIT();
} while (--len);
// 1. timed-out
if (len == 0) {
// 1. timed-out. Never inspect FelicaFrame past this point without a reply,
// it still holds whatever a previous exchange left in it.
if (got_reply == false) {
return 1;
}
@@ -284,7 +304,10 @@ static uint8_t felica_select_card(felica_card_select_t *card) {
}
// 3. wrong crc. residue is 0, hence if crc is a value it failed.
if (check_crc(CRC_FELICA, FelicaFrame.framebytes + 2, FelicaFrame.len - 2) == false) {
// len covers sync(2) + payload + crc(2); anything shorter would underflow
// the size_t length handed to check_crc().
if (FelicaFrame.len < 5 ||
check_crc(CRC_FELICA, FelicaFrame.framebytes + 2, FelicaFrame.len - 2) == false) {
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("Error: CRC check failed!");
@@ -335,7 +358,10 @@ static uint8_t felica_select_card(felica_card_select_t *card) {
static void BuildFliteRdblk(const uint8_t *idm, uint8_t blocknum, const uint16_t *blocks) {
if (blocknum > 4 || blocknum == 0) {
Dbprintf("Invalid number of blocks, %d != 4", blocknum);
Dbprintf("Invalid number of blocks, %d, expected 1..4", blocknum);
// make sure a caller that ignores this cannot transmit a stale frame
frameSpace[2] = 0;
return;
}
uint8_t c = 0, i = 0;
@@ -468,11 +494,17 @@ bool WaitForFelicaReply(uint16_t maxbytes) {
// if (g_dbglevel >= DBG_DEBUG) { Dbprintf("WaitForFelicaReply Start"); }
uint32_t c = 0;
uint32_t rx_bytes = 0;
uint16_t crc_fail_normal = 0;
uint16_t crc_fail_inverted = 0;
// raw demodulator output, only kept when the user asks for extended debug.
uint8_t rawcap[FELICA_RAW_CAPTURE_BYTES];
uint16_t rawcnt = 0;
// power, no modulation
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO18092 | FPGA_HF_ISO18092_FLAG_READER | FPGA_HF_ISO18092_FLAG_NOMOD);
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO18092 | FPGA_HF_ISO18092_FLAG_READER | FPGA_HF_ISO18092_FLAG_NOMOD
| (felica_probe_mode ? FPGA_HF_ISO18092_FLAG_PROBE : 0));
FelicaFrameReset(&FelicaFrame);
// clear RXRDY:
@@ -488,6 +520,11 @@ bool WaitForFelicaReply(uint16_t maxbytes) {
if (FPGA_SSC_RX_Ready()) {
b = (uint8_t)(FPGA_SSC_RX_Value());
rx_bytes++;
if (g_dbglevel >= DBG_EXTENDED && rawcnt < sizeof(rawcap)) {
rawcap[rawcnt++] = b;
}
Process18092Byte(&FelicaFrame, b, felica_get_rx_byte_start_time());
felica_frame_t *received = NULL;
@@ -533,8 +570,12 @@ bool WaitForFelicaReply(uint16_t maxbytes) {
return true;
} else if (
c++ > timeout
&& (FelicaFrame.state == STATE_UNSYNCD || FelicaFrame.state == STATE_TRYING_SYNC)
(c++ > timeout
&& (FelicaFrame.state == STATE_UNSYNCD || FelicaFrame.state == STATE_TRYING_SYNC))
// A frame that never completes (bogus length byte, demodulator stuck
// mid-frame) leaves the states above, so it would never hit the check
// above. Bound the total wait as well.
|| (rx_bytes > timeout + FELICA_MAX_RF_FRAME_SIZE)
) {
// if (g_dbglevel >= DBG_DEBUG) Dbprintf("Error: Timeout! STATE_UNSYNCD");
@@ -542,6 +583,14 @@ bool WaitForFelicaReply(uint16_t maxbytes) {
Dbprintf("FeliCa RX timeout, CRC fails normal=%u inverted=%u", crc_fail_normal, crc_fail_inverted);
}
if (g_dbglevel >= DBG_EXTENDED) {
Dbprintf("FeliCa RX timeout, state %u, %u bytes from the front end. Below is %s:",
FelicaFrame.state, rx_bytes,
felica_probe_mode ? "envelope peak-to-peak per 8 bit periods"
: "raw demodulator output");
Dbhexdump(rawcnt, rawcap, 0);
}
return false;
}
}
@@ -591,6 +640,10 @@ bool iso18092_setup_ex(uint8_t fpga_minor_mode, uint32_t preserve_low_bytes) {
//20.4 ms generate field, start sending polling command afterwars.
SpinDelay(100);
if (g_dbglevel >= DBG_EXTENDED) {
Dbprintf("FeliCa field: HF antenna %u mV", AdcRssiAvgToMilliVolt(ADC_RSSI_CH_HF));
}
// Start the timer
StartCountSspClk();
@@ -639,6 +692,13 @@ void felica_sendraw(const PacketCommandNG *c) {
bool do_connect = ((param & FELICA_CONNECT) == FELICA_CONNECT);
bool no_disconnect = ((param & FELICA_NO_DISCONNECT) == FELICA_NO_DISCONNECT);
bool replied = false;
// Signal probe. The FPGA hands us envelope peak-to-peak per 8 bit periods
// instead of demodulated bits, so nothing will decode while this is on.
// It is how you tell "tag out of range" apart from "demodulator not
// locking", which are otherwise identical from up here.
felica_probe_mode = ((param & FELICA_PROBE) == FELICA_PROBE);
if ((param & FELICA_CLEARTRACE) == FELICA_CLEARTRACE) {
clear_trace();
@@ -661,6 +721,8 @@ void felica_sendraw(const PacketCommandNG *c) {
int select_status = PM3_SUCCESS;
switch (select_result) {
case 0:
break;
case 1:
select_status = PM3_ETIMEOUT;
break;
@@ -670,11 +732,17 @@ void felica_sendraw(const PacketCommandNG *c) {
case 3:
select_status = PM3_ECRC;
break;
case 4:
// polling response too short to hold IDm + PMm, card is all zeroes
select_status = PM3_ELENGTH;
break;
default:
select_status = PM3_ESOFT;
break;
}
reply_ng(CMD_HF_FELICA_COMMAND, select_status, (uint8_t *)&card, sizeof(felica_card_select_t));
replied = true;
if (select_status != PM3_SUCCESS) {
felica_reset_frame_mode();
return;
@@ -685,6 +753,7 @@ void felica_sendraw(const PacketCommandNG *c) {
if (len > FELICA_MAX_DATA_SIZE) {
Dbprintf("FeliCa raw payload too long: %u (max %u)", len, FELICA_MAX_DATA_SIZE);
reply_ng(CMD_HF_FELICA_COMMAND, PM3_ELENGTH, NULL, 0);
replied = true;
if (!no_disconnect) {
felica_reset_frame_mode();
}
@@ -731,6 +800,13 @@ void felica_sendraw(const PacketCommandNG *c) {
int status = got_frame ? PM3_SUCCESS : PM3_ERFTRANS;
uint16_t frame_len = got_frame ? FelicaFrame.len : 0;
reply_ng(CMD_HF_FELICA_COMMAND, status, got_frame ? FelicaFrame.framebytes : NULL, frame_len);
replied = true;
}
if (replied == false) {
// Nothing above answered the client. Say so instead of letting it block
// until its own timeout expires.
reply_ng(CMD_HF_FELICA_COMMAND, PM3_EINVARG, NULL, 0);
}
if (no_disconnect) {
@@ -738,6 +814,7 @@ void felica_sendraw(const PacketCommandNG *c) {
}
felica_reset_frame_mode();
felica_probe_mode = false;
return;
}
@@ -750,7 +827,8 @@ void felica_sniff(uint32_t samplesToSkip, uint32_t triggersToSkip) {
LED_D_ON();
int retval = PM3_SUCCESS;
int remFrames = (samplesToSkip) ? samplesToSkip : 0;
// 0 == no limit, keep sniffing until the user aborts
int remFrames = (samplesToSkip) ? (int)samplesToSkip : -1;
int trigger_cnt = 0;
bool isReaderFrame;
@@ -791,16 +869,22 @@ void felica_sniff(uint32_t samplesToSkip, uint32_t triggersToSkip) {
break;
}
if (FelicaFrame.state == STATE_FULL) {
// A frame that failed CRC is demodulator noise, not traffic.
// Logging it only pollutes the trace and eats the frame budget.
if (FelicaFrame.crc_ok == false) {
FelicaFrameReset(&FelicaFrame);
continue;
}
if ((FelicaFrame.framebytes[3] % 2) == 0) {
isReaderFrame = true; // All Reader Frames are even and all Tag frames are odd
} else {
isReaderFrame = false;
}
remFrames--;
if (remFrames <= 0) {
Dbprintf("Stop Sniffing - samples To skip reached!");
break;
}
// log first, then decide whether we are done. The old order threw
// away the very frame that reached the limit.
LogTrace(FelicaFrame.framebytes,
FelicaFrame.len,
felica_timer_to_carrier_periods(FelicaFrame.startTime, false) - DELAY_AIR2ARM_AS_READER,
@@ -809,6 +893,11 @@ void felica_sniff(uint32_t samplesToSkip, uint32_t triggersToSkip) {
isReaderFrame
);
FelicaFrameReset(&FelicaFrame);
if (remFrames > 0 && --remFrames == 0) {
Dbprintf("Stop Sniffing - samples To skip reached!");
break;
}
}
}
}
+11 -4
View File
@@ -9372,7 +9372,8 @@ static int CmdHFFelicaCmdRaw(const char *Cmd) {
CLIParserInit(&ctx, "hf felica raw ",
"Send raw hex data to tag",
"hf felica raw -cs 20\n"
"hf felica raw -cs 2008"
"hf felica raw -cs 2008\n"
"hw dbg -4; hf felica raw -acp 00ffff0000 -> measure antenna envelope, for reading distance"
);
void *argtable[] = {
@@ -9381,6 +9382,7 @@ static int CmdHFFelicaCmdRaw(const char *Cmd) {
arg_lit0("c", NULL, "calculate and append CRC"),
arg_lit0("k", NULL, "keep signal field ON after receive"),
arg_u64_0("n", NULL, "<dec>", "number of bits"),
arg_lit0("p", "probe", "report antenna envelope instead of decoding (needs `hw dbg -4`)"),
arg_lit0("r", NULL, "do not read response"),
arg_lit0("s", NULL, "active signal field ON with select"),
arg_str1(NULL, NULL, "<hex>", "raw bytes to send"),
@@ -9392,14 +9394,15 @@ static int CmdHFFelicaCmdRaw(const char *Cmd) {
bool crc = arg_get_lit(ctx, 2);
bool keep_field_on = arg_get_lit(ctx, 3);
uint16_t numbits = arg_get_u32_def(ctx, 4, 0) & 0xFFFF;
bool reply = (arg_get_lit(ctx, 5) == false);
bool active_select = arg_get_lit(ctx, 6);
bool probe = arg_get_lit(ctx, 5);
bool reply = (arg_get_lit(ctx, 6) == false);
bool active_select = arg_get_lit(ctx, 7);
int datalen = 0;
uint8_t data[PM3_CMD_DATA_SIZE];
memset(data, 0, sizeof(data));
CLIGetHexWithReturn(ctx, 7, data, &datalen);
CLIGetHexWithReturn(ctx, 8, data, &datalen);
CLIParserFree(ctx);
uint8_t flags = 0;
@@ -9408,6 +9411,10 @@ static int CmdHFFelicaCmdRaw(const char *Cmd) {
flags |= FELICA_APPEND_CRC;
}
if (probe) {
flags |= FELICA_PROBE;
}
if (active || active_select) {
flags |= FELICA_CONNECT | FELICA_CLEARTRACE;
if (active) {
+1
View File
@@ -142,6 +142,7 @@
#define FPGA_HF_ISO18092_FLAG_NOMOD ( 1 ) // 0001 disable modulation module
#define FPGA_HF_ISO18092_FLAG_424K ( 2 ) // 0010 should enable 414k mode (untested). No autodetect
#define FPGA_HF_ISO18092_FLAG_READER ( 4 ) // 0100 enables antenna power, to act as a reader instead of tag
#define FPGA_HF_ISO18092_FLAG_PROBE ( 8 ) // 1000 signal probe: stream envelope peak-to-peak instead of demodulated bits
// Options for adc mux.
// The mux is no longer set directly through the GPIO PIN to solve the problem of high coupling with the platform.
+1 -1
View File
@@ -72,7 +72,7 @@ TARGET2_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF2 WITH_HF3 WITH_HF5\}
# RDV40/Generic - Enable all HF modules except Felica and ISO14443, select HF_15 instead of HF
TARGET3_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF5 WITH_HF_15 WITH_HF_15_LOWSIGNAL\}
# RDV40/Generic - Enable all HF modules except ISO14443
TARGET4_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF4 WITH_HF5\}
TARGET4_OPTIONS = -define \{WITH_HF0 WITH_HF1 WITH_HF3 WITH_HF4 WITH_HF5 WITH_FELICA_PROBE\}
# ICOPYX
TARGET5_OPTIONS = $(TARGET1_OPTIONS)
TARGET6_OPTIONS = $(TARGET1_OPTIONS)
Binary file not shown.
+269 -76
View File
@@ -53,17 +53,63 @@ assign debug = 0;
wire power = mod_type[2];
wire speed = mod_type[1];
wire disabl = mod_type[0];
// Signal probe: stream how much the envelope is actually moving instead of
// demodulated bits, so reading distance can be measured rather than guessed.
wire probe = mod_type[3];
// 512x64/fc -wait before ts0, 32768 ticks
// tslot: 256*64/fc
assign adc_clk = ck_1356meg;
///heuristic values for initial thresholds. seem to work OK
// Initial envelope guess, only used until the first edges are seen.
`define imin 70 // (13'd256)
`define imax 180 // (-13'd256)
`define ithrmin 91 // -13'd8
`define ithrmax 160 // 13'd8
// Narrowest hysteresis band the threshold generator will produce. An
// unmodulated carrier collapses curmin onto curmax, and without a floor the
// band would collapse with it, so every ADC noise count would look like an
// edge. This floor is also the sensitivity limit: a tag whose modulation is
// smaller than the band can never arm the edge detector, so it sets the
// reading distance.
//
// Measured on a RDV4 with the reader field on: the raw carrier ripples 4..6
// counts peak to peak, about +/- 1 after the lowpass above. +/- 4 keeps a 4x
// noise margin while halving the smallest tag modulation we can still see
// compared to sizing the floor against the unfiltered ripple.
`define minhalfband 8
// Recovery watchdog, counting samples since the demodulator was last in a
// known-good idle state (the desync in the stable branch below).
//
// Nothing else in this module can put the hysteresis band back onto the signal
// once it has drifted off it, and there are two ways to get there. The band can
// end up somewhere the signal never visits, which makes the stable branch -
// the only place thresholds are recomputed and the only place a desync can
// happen - permanently unreachable. Or try_sync can latch on with the band
// mis-positioned, where edges of one polarity keep clearing tsinceedge so the
// desync never fires either. Both freeze the slicer, and both survive the
// field being switched off between commands, because these are FPGA registers
// and only a bitstream reload clears them. The symptom is that the first
// command after the client starts works and everything after it fails on a
// signal that is plainly strong enough.
//
// 2^18 samples is 19.3 ms. The longest legal FeliCa frame, 255 bytes plus
// preamble and sync, is 9.9 ms, so this cannot fire part way through a reply.
// During quiet it fires harmlessly and keeps the band centred on the carrier.
`define stalebit 18
// Bit decisions to ignore right after the edge detector arms. try_sync is
// armed part way through a bit, so its first accumulation covers only part of
// a half-bit and the decision that follows is meaningless. Acting on it can
// latch `zero` inverted, which decodes the entire frame with the wrong
// polarity and loses the sync word. The preamble is 48 bits, so skipping the
// first couple of decisions costs nothing and lets the real preamble-to-sync
// transition be the one that locks polarity.
`define syncguard 2
`define min_bitdelay_212 8
//minimum values and corresponding thresholds
reg [8:0] curmin=`imin;
@@ -71,10 +117,45 @@ reg [8:0] curminthres=`ithrmin;
reg [8:0] curmaxthres=`ithrmax;
reg [8:0] curmax=`imax;
// Hysteresis band, derived from the tracked envelope rather than from fixed
// levels. The old code blended curmin/curmax with fixed weights, but clamped
// curmin to <= `imin and curmax to >= `imax, so the thresholds could never
// leave ~91/160 no matter where the signal actually sat. A tag whose envelope
// lives inside that window - which is the normal case for a reader field, the
// peak detector idles near 112 and a tag swings it by about +/-20 - never
// crossed a threshold at all and demodulated as a constant.
//
// The band is now centred on the tracked envelope and set to 3/16 of its span.
// The old 0.8125/0.1875 blend worked out to 5/16, which on a real card leaves
// only about 1.6x margin between the threshold and the modulation peaks and
// bit-slips often; 3/16 roughly doubles that margin.
wire [8:0] span = (curmax > curmin) ? (curmax - curmin) : 9'd0;
wire [9:0] envsum = {1'b0, curmax} + {1'b0, curmin};
wire [8:0] centre = envsum[9:1];
// 3/16 of the tracked span, floored. Keep the intermediate wide: span reaches
// 255 and 3 * 255 needs 10 bits, a 9 bit intermediate silently wraps and hands
// back a far too narrow band exactly when the envelope is widest.
wire [11:0] scaledspan = ({3'd0, span} << 1) + {3'd0, span};
wire [8:0] rawhalf = scaledspan[11:4];
wire [8:0] halfband = (rawhalf < `minhalfband) ? `minhalfband : rawhalf;
wire [8:0] lothres = (centre > halfband) ? (centre - halfband) : 9'd0;
// where to put the band when re-centring on the level actually present
wire [9:0] rc_hisum = {2'd0, adc_d} + `minhalfband;
wire [8:0] rc_hi = (rc_hisum > 10'd255) ? 9'd255 : rc_hisum[8:0];
wire [8:0] rc_lo = (adc_d > `minhalfband) ? ({1'b0, adc_d} - `minhalfband) : 9'd0;
wire [9:0] hisum = {1'b0, centre} + {1'b0, halfband};
wire [8:0] hithres = (hisum > 10'd255) ? 9'd255 : hisum[8:0];
//signal state, 1-not modulated, 0 -modulated
reg after_hysteresis = 1'b1;
//state machine for envelope tracking
// Keep this out of block RAM. The project synthesises with -fsm_style bram, and
// once this got large enough for XST to recognise it as a state machine it put
// the state ROM in a block RAM - the xc2s30 has six and the design already uses
// all of them, so the build failed to fit with nothing but a MAP error to say
// so. It is two bits; LUTs are the right home for it.
(* fsm_extract = "no" *)
reg [1:0] state = 1'd0;
//lower edge detected, trying to detect first bit of SYNC (b24d, 1011001001001101)
@@ -83,6 +164,12 @@ reg try_sync = 1'b0;
//detected first sync bit, phase frozen
reg did_sync=0;
//samples since the last known-good idle, see `stalebit
reg [`stalebit:0] stale = 0;
//decisions still to skip before did_sync may latch, see `syncguard
reg [1:0] guard = 2'd0;
`define bithalf_212 32 // half-bit length for 212 kbit
`define bitmlen_212 63 // bit transition edge
@@ -92,6 +179,14 @@ reg did_sync=0;
wire [7:0] bithalf = speed ? `bithalf_424 : `bithalf_212;
wire [7:0] bitmlen = speed ? `bitmlen_424 : `bitmlen_212;
// curbit_raw is decided in the bit-phase domain, which is aligned to the tag's
// edges by try_sync and so drifts against ssp_cnt. curbit is that decision
// re-timed into the ssp domain: the SSC latches ssp_din when ssp_clk rises at
// ssp_cnt[5:0] == 0, so updating half an ssp bit away from that keeps the ARM
// from ever sampling a bit while it is changing. Both run at 64 carrier
// periods per bit, so this is a re-time and not a resample - no bit is
// duplicated or dropped.
reg curbit_raw = 1'b0;
reg curbit = 1'b0;
reg [7:0] fccount = 8'd0; // in-bit tick counter. Counts carrier cycles from the first lower edge detected, reset on every manchester bit detected
@@ -107,6 +202,43 @@ reg [8:0] ssp_cnt = 9'd0;
always @(posedge adc_clk)
ssp_cnt <= (ssp_cnt + 1);
always @(negedge adc_clk)
if (ssp_cnt[5:0] == 6'd32)
curbit <= curbit_raw;
`ifdef WITH_FELICA_PROBE
// Signal probe: min and max of the envelope over each ssp byte window (512
// carrier periods, 8 bit periods), reported in alternating bytes - min, max,
// min, max. That gives both the carrier level and the tag modulation depth,
// which are different questions: the level says whether the front end is
// running out of ADC range, the depth says whether the tag is in range at all.
// Measured on a RDV4: idle ripples 4..6 counts, an ordinary card on the
// antenna swings about 70, a strongly coupled one over 200.
reg [7:0] pmin = 8'hff;
reg [7:0] pmax = 8'd0;
reg [7:0] pout = 8'd0;
reg ptog = 1'b0;
reg [7:0] probe_sr = 8'd0;
always @(negedge adc_clk)
begin
if (ssp_cnt[8:0] == 9'd0)
begin
pout <= ptog ? pmax : pmin;
ptog <= ~ptog;
pmin <= adc_d;
pmax <= adc_d;
end
else
begin
if (adc_d < pmin) pmin <= adc_d;
if (adc_d > pmax) pmax <= adc_d;
end
end
`endif
//maybe change it so that ARM sends preamble as well.
//then: ready bits sent to ARM, 8 bits sent from ARM (all ones), then preamble (all zeros, presumably) - which starts modulation
@@ -117,7 +249,26 @@ begin
begin
ssp_clk <= 1'b1;
//send current bit (detected in SNIFF mode or the one being modulated in MOD mode, 0 otherwise)
ssp_din <= curbit;
`ifdef WITH_FELICA_PROBE
if (probe)
begin
// one 8 bit reading per ssp byte, LSB first so the ARM reads it verbatim
if (ssp_cnt[8:6] == 3'd0)
begin
ssp_din <= pout[0];
probe_sr <= {1'b0, pout[7:1]};
end
else
begin
ssp_din <= probe_sr[0];
probe_sr <= {1'b0, probe_sr[7:1]};
end
end
else
`endif
begin
ssp_din <= curbit;
end
end
if( ( (~speed) && (ssp_cnt[5:0] == 6'b100000)) ||(speed && ssp_cnt[4:0] == 5'b10000))
ssp_clk <= 1'b0;
@@ -133,11 +284,24 @@ begin
end
end
//previous signal value, mostly to detect SYNC
reg prv = 1'b1;
// Matched-filter accumulators. acc integrates the envelope over the half-bit
// in progress, h1 holds the completed first half.
//
// The top 6 ADC bits are enough and the xc2s30 has no room for more: 32 samples
// of 6 bits needs 11 bits of accumulator against 13 for the full 8. Resolution
// is not the limit here - a 70 count tag swing is 17 counts at 6 bits, times 32
// samples is a difference of ~560, against a noise floor of about 6 after the
// same averaging. Two extra bits would buy nothing and cost slices the device
// does not have.
wire [5:0] samp = adc_d[7:2];
reg [11:0] acc = 12'd0;
reg [11:0] h1 = 12'd0;
wire [11:0] h2 = acc + {6'd0, samp};
wire firsthalfhigh = (h1 > h2);
// for simple error correction in mod/demod detection, use maximum of modded/demodded in given interval. Maybe 1 bit is extra? but better safe than sorry.
reg[7:0] mid = 8'd128;
// which half was the larger on the previous bit, ie the previous bit value
// before polarity is known. A change in it is a Manchester bit transition.
reg prv_s = 1'b0;
// set TAGSIM__MODULATE on ARM if we want to write... (frame would get lost if done mid-frame...)
// start sending over 1s on ssp->arm when we start sending preamble
@@ -148,6 +312,11 @@ reg [11:0] bit_counts = 12'd0; // for timeslots. only support ts=0 for now, at 2
reg dlay;
always @(negedge adc_clk) // every data ping?
begin
// Watchdog clock, see `stalebit. It runs first so that the three places
// that clear it below - a clean desync, a successful Manchester lock, and
// the watchdog firing itself - all override it.
stale <= stale + 1;
//envelope follow code...
////////////
if (fccount == bitmlen)
@@ -188,13 +357,13 @@ begin
begin
case (state)
0: begin
curmax <= adc_d > `imax? adc_d : `imax;
curmax <= adc_d;
state <= 2;
end
1: begin
curminthres <= ((curmin >> 1) + (curmin >> 2) + (curmin >> 4) + (curmax >> 3) + (curmax >> 4)); //threshold: 0.1875 max + 0.8125 min
curmaxthres <= ((curmax >> 1) + (curmax >> 2) + (curmax >> 4) + (curmin >> 3) + (curmin >> 4));
curmax <= adc_d > 155 ? adc_d : 155; // to hopefully prevent overflow from spikes going up to 255
curminthres <= lothres;
curmaxthres <= hithres;
curmax <= adc_d;
state <= 2;
end
2: begin
@@ -213,7 +382,7 @@ begin
begin
case (state)
0: begin
curmin <= adc_d<`imin? adc_d :`imin;
curmin <= adc_d;
state <= 1;
end
1: begin
@@ -221,9 +390,9 @@ begin
curmin <= adc_d;
end
2: begin
curminthres <= ( (curmin >> 1) + (curmin >> 2) + (curmin >> 4) + (curmax >> 3) + (curmax >> 4));
curmaxthres <= ( (curmax >> 1) + (curmax >> 2) + (curmax >> 4) + (curmin >> 3) + (curmin >> 4));
curmin <= adc_d < `imin ? adc_d : `imin;
curminthres <= lothres;
curmaxthres <= hithres;
curmin <= adc_d;
state <= 1;
end
default:
@@ -236,10 +405,18 @@ begin
try_sync <= 1;
fccount <= 1;
did_sync <= 0;
curbit <= 0;
mid <= 8'd127;
guard <= `syncguard;
// A frame is starting: give it the full watchdog interval. Doing
// this only on did_sync below is not enough, because did_sync stays
// latched between frames unless a desync clears it, so during a
// long exchange - `hf felica dump` walking a card's nodes - the
// watchdog clock keeps running across frames and eventually fires
// in the middle of one. try_sync arming is the one event that
// happens at the start of every frame.
stale <= 0;
curbit_raw <= 0;
acc <= 0;
tsinceedge <= 0;
prv <= 1;
end
else
begin
@@ -248,8 +425,8 @@ begin
end
else //stable state, low or high
begin
curminthres <= ( (curmin >> 1) + (curmin >> 2) + (curmin >> 4) + (curmax >> 3) + (curmax >> 4));
curmaxthres <= ( (curmax >> 1) + (curmax >> 2) + (curmax >> 4) + (curmin >> 3) + (curmin >> 4));
curminthres <= lothres;
curmaxthres <= hithres;
state <= 0;
if (try_sync )
@@ -260,15 +437,20 @@ begin
bit_counts <= 1;// i think? 128 is about 2 bits passed... but 1 also works
try_sync <= 0;
did_sync <= 0;//desync
curmin <= `imin; //reset envelope
curmax <= `imax;
curminthres <= `ithrmin;
curmaxthres <= `ithrmax;
prv <= 1;
stale <= 0; //this is the known-good state the watchdog looks for
// Re-centre the envelope on the carrier level that is actually
// there. Resetting to the compile-time constants instead threw
// away the only measurement we had and, when the real level sat
// inside `ithrmin..`ithrmax, guaranteed the next frame could not
// produce a single edge.
curmin <= adc_d;
curmax <= adc_d;
curminthres <= lothres;
curmaxthres <= hithres;
tsinceedge <= 0;
after_hysteresis <= 1'b1;
curbit <= 0;
mid <= 8'd128;
curbit_raw <= 0;
acc <= 0;
end
else
tsinceedge <= (tsinceedge + 1);
@@ -277,65 +459,76 @@ begin
if (try_sync && tsinceedge < 128)
begin
//detect bits in their middle ssp sampling is in sync, so it would sample all bits in order
// Matched-filter bit detector. Each Manchester bit is two half-bits of
// opposite level, so integrating the raw ADC over each half and taking
// the larger recovers the bit without ever consulting a threshold.
//
// The old detector counted comparator trips instead: +1 per sample
// above curmaxthres, -1 below curminthres, and inside the dead band it
// just repeated the previous crossing direction. That made every bit
// depend on where the hysteresis band happened to sit, which is what
// made a mispositioned band rail the output to a constant, a clipped
// envelope mis-slice, and a weak tag undetectable. It also threw away
// amplitude, so it gained nothing from the 32x oversampling.
//
// Integrating 32 samples per half instead averages the noise down by
// sqrt(32) and cancels any offset common to both halves, which is what
// slew and clipping asymmetry look like. Thresholds still drive the bit
// phase and the desync below, they just no longer decide bit values.
if (fccount == bithalf)
begin
if ((~did_sync) && ((prv == 1 && (mid > 128))||(prv == 0 && (mid <= 128))))
h1 <= acc; // first half: samples 0..31
acc <= {6'd0, samp}; // second half starts here, sample 32
end
else if (fccount == bitmlen)
begin
// h2 is acc plus this sample, so both halves are 32 samples and
// neither is biased by an extra sample of carrier.
if (guard != 2'd0)
guard <= guard - 2'd1;
// A bit value change flips which half is the larger. The preamble
// is 48 identical bits, so the first flip is the preamble meeting
// the sync word, whose first bit is a 1. Lock polarity there.
if ((~did_sync) && (guard == 2'd0) && (firsthalfhigh != prv_s))
begin
//sync the Zero, and set curbit roperly
did_sync <= 1'b1;
zero <= ~prv;// 1-prv
curbit <= 1;
zero <= ~firsthalfhigh;
curbit_raw <= 1;
// A Manchester lock means the demodulator is working, so hold
// the watchdog off for the frame that is now starting. Without
// this it eventually fires part way through a reply and clears
// try_sync and did_sync mid-frame, slipping every bit after
// that point. It is deterministic rather than rare: the delay
// from field-on to reply barely varies, so the watchdog phase
// lines up with the reply on attempt after attempt.
stale <= 0;
end
else
curbit <= (mid > 128) ? (~zero) : zero;
curbit_raw <= firsthalfhigh ? (~zero) : zero;
prv <= (mid > 128) ? 1 : 0;
if (adc_d > curmaxthres)
mid <= 8'd129;
else if (adc_d < curminthres)
mid <= 8'd127;
else
begin
if (after_hysteresis)
begin
mid <= 8'd129;
end
else
begin
mid <= 8'd127;
end
end
prv_s <= firsthalfhigh;
acc <= 0;
end
else
begin
if (fccount==bitmlen)
begin
// fccount <= 0;
prv <= (mid > 128) ? 1 : 0;
mid <= 128;
end
else
begin
// minimum-maximum calc
if(adc_d > curmaxthres)
mid <= mid + 1;
else if (adc_d < curminthres)
mid <= mid - 1;
else
begin
if (after_hysteresis)
begin
mid <= mid + 1;
end
else
begin
mid <= mid - 1;
end
end
end
end
acc <= acc + {6'd0, samp};
end
// Watchdog, see `stalebit. Put the band back on the signal directly, since
// nothing above is able to any more.
if (stale[`stalebit])
begin
curmin <= adc_d;
curmax <= adc_d;
curminthres <= rc_lo;
curmaxthres <= rc_hi;
try_sync <= 1'b0;
did_sync <= 1'b0;
curbit_raw <= 1'b0;
after_hysteresis <= 1'b1;
tsinceedge <= 0;
state <= 0;
stale <= 0;
end
// sending <= 0;
end
+3
View File
@@ -86,6 +86,9 @@
typedef enum FELICA_COMMAND {
FELICA_CONNECT = (1 << 0),
FELICA_NO_DISCONNECT = (1 << 1),
// stream the antenna envelope peak-to-peak instead of demodulated bits,
// so reading distance and coupling can be measured rather than guessed
FELICA_PROBE = (1 << 2),
FELICA_RAW = (1 << 3),
FELICA_APPEND_CRC = (1 << 5),
FELICA_NO_SELECT = (1 << 6),