From cb48c928a5ed032f2849ffa8ce2de4914a714a93 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 28 Aug 2026 22:35:08 +0200 Subject: [PATCH] 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) --- CHANGELOG.md | 2 + armsrc/felica.c | 125 +++++++++++-- client/src/cmdhffelica.c | 15 +- common_arm/fpga/fpga_apis.h | 1 + fpga/Makefile | 2 +- fpga/fpga_pm3_felica.bit | Bin 42176 -> 42176 bytes fpga/hi_flite.v | 345 ++++++++++++++++++++++++++++-------- include/iso18.h | 3 + 8 files changed, 394 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 376b79059..df833ef2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/armsrc/felica.c b/armsrc/felica.c index 95bab9990..2848d33e9 100644 --- a/armsrc/felica.c +++ b/armsrc/felica.c @@ -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; + } } } } diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 7ce489725..fdcec6d7b 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -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, "", "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, "", "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) { diff --git a/common_arm/fpga/fpga_apis.h b/common_arm/fpga/fpga_apis.h index 48255bd65..d00e8e401 100644 --- a/common_arm/fpga/fpga_apis.h +++ b/common_arm/fpga/fpga_apis.h @@ -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. diff --git a/fpga/Makefile b/fpga/Makefile index 2411ab7e8..e358cea65 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -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) diff --git a/fpga/fpga_pm3_felica.bit b/fpga/fpga_pm3_felica.bit index cbc31b0e3c88e01be4377e615f0c679bf74da46c..c0c80d0acda5274c02a3c75923e757cecc8433bc 100644 GIT binary patch literal 42176 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 @Jq-LpUy8j(?*D>K2?ICOktp2^v~u)A$j)+t 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

V_Z=$qZ{#@I4x{QDbKm6< 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 TLiuz7Pw}&~&S&9UYk&FQGi*WY diff --git a/fpga/hi_flite.v b/fpga/hi_flite.v index 8346e49f2..e1a8f9ca9 100644 --- a/fpga/hi_flite.v +++ b/fpga/hi_flite.v @@ -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 diff --git a/include/iso18.h b/include/iso18.h index a38749656..1fe78de64 100644 --- a/include/iso18.h +++ b/include/iso18.h @@ -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),