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 cbc31b0e3..c0c80d0ac 100644 Binary files a/fpga/fpga_pm3_felica.bit and b/fpga/fpga_pm3_felica.bit differ 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),