From 1f7bce5580d627b258680cf0d461a7a0ab908b35 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Tue, 8 Feb 2022 14:47:06 +0100 Subject: [PATCH 1/8] iso15 sniffing: add support for dual subcarriers tag answers --- armsrc/Makefile | 2 +- armsrc/fpgaloader.c | 4 +- armsrc/fpgaloader.h | 10 + armsrc/iso15693.c | 380 +++++++++++++++++++++++++++++++++++-- fpga-xc2s30/Makefile | 9 +- fpga-xc2s30/fpga_hf_15.bit | Bin 0 -> 42178 bytes fpga-xc2s30/fpga_hf_15.v | 260 +++++++++++++++++++++++++ fpga-xc2s30/hi_read_fsk.v | 152 +++++++++++++++ fpga-xc2s30/xst_hf_15.scr | 1 + 9 files changed, 792 insertions(+), 26 deletions(-) create mode 100644 fpga-xc2s30/fpga_hf_15.bit create mode 100644 fpga-xc2s30/fpga_hf_15.v create mode 100644 fpga-xc2s30/hi_read_fsk.v create mode 100644 fpga-xc2s30/xst_hf_15.scr diff --git a/armsrc/Makefile b/armsrc/Makefile index 86ac7b39f..f55f9bc8e 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -103,7 +103,7 @@ endif include Standalone/Makefile.inc #the FPGA bitstream files. Note: order matters! -FPGA_BITSTREAMS = fpga_lf.bit fpga_hf.bit fpga_felica.bit +FPGA_BITSTREAMS = fpga_lf.bit fpga_hf.bit fpga_felica.bit fpga_hf_15.bit #the lz4 source files required for decompressing the fpga config at run time SRC_LZ4 = lz4.c diff --git a/armsrc/fpgaloader.c b/armsrc/fpgaloader.c index 1b6360126..ab8d57f7d 100644 --- a/armsrc/fpgaloader.c +++ b/armsrc/fpgaloader.c @@ -162,7 +162,7 @@ void FpgaSetupSsc(uint16_t fpga_mode) { // 8, 16 or 32 bits per transfer, no loopback, MSB first, 1 transfer per sync // pulse, no output sync - if ((fpga_mode & FPGA_MAJOR_MODE_MASK) == FPGA_MAJOR_MODE_HF_READER && FpgaGetCurrent() == FPGA_BITSTREAM_HF) { + if ((fpga_mode & FPGA_MAJOR_MODE_MASK) == FPGA_MAJOR_MODE_HF_READER && (FpgaGetCurrent() == FPGA_BITSTREAM_HF || FpgaGetCurrent() == FPGA_BITSTREAM_HF_15)) { AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(16) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); } else { AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(8) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); @@ -612,7 +612,7 @@ void switch_off(void) { Dbprintf("switch_off"); } FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - if (downloaded_bitstream == FPGA_BITSTREAM_HF) { + if (downloaded_bitstream == FPGA_BITSTREAM_HF || downloaded_bitstream == FPGA_BITSTREAM_HF_15) { FpgaDisableSscDma(); } set_tracing(false); diff --git a/armsrc/fpgaloader.h b/armsrc/fpgaloader.h index cff84c3e2..897031042 100644 --- a/armsrc/fpgaloader.h +++ b/armsrc/fpgaloader.h @@ -29,6 +29,7 @@ #define FPGA_BITSTREAM_LF 1 #define FPGA_BITSTREAM_HF 2 #define FPGA_BITSTREAM_HF_FELICA 3 +#define FPGA_BITSTREAM_HF_15 4 /* Communication between ARM / FPGA is done inside armsrc/fpgaloader.c (function FpgaSendCommand) @@ -77,6 +78,7 @@ thres| x x x x x x x x #define FPGA_MAJOR_MODE_HF_SNIFF (3<<6) // D #define FPGA_MAJOR_MODE_HF_ISO18092 (4<<6) // D #define FPGA_MAJOR_MODE_HF_GET_TRACE (5<<6) // D +#define FPGA_MAJOR_MODE_HF_FSK_READER (6<<6) // D // BOTH HF / LF #define FPGA_MAJOR_MODE_OFF (7<<6) // D @@ -105,6 +107,14 @@ thres| x x x x x x x x #define FPGA_HF_READER_SUBCARRIER_424_KHZ (1<<4) #define FPGA_HF_READER_SUBCARRIER_212_KHZ (2<<4) +#define FPGA_HF_FSK_READER_OUTPUT_1695_KHZ (0<<0) +#define FPGA_HF_FSK_READER_OUTPUT_848_KHZ (1<<0) +#define FPGA_HF_FSK_READER_OUTPUT_424_KHZ (2<<0) +#define FPGA_HF_FSK_READER_OUTPUT_212_KHZ (3<<0) + +#define FPGA_HF_FSK_READER_NOPOWER (0<<4) +#define FPGA_HF_FSK_READER_WITHPOWER (1<<4) + // Options for the HF simulated tag, how to modulate #define FPGA_HF_SIMULATOR_NO_MODULATION 0x0 // 0000 #define FPGA_HF_SIMULATOR_MODULATE_BPSK 0x1 // 0001 diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index c08d6130f..c41760e24 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -1204,7 +1204,7 @@ void AcquireRawAdcSamplesIso15693(void) { LEDsoff(); DbpString("Starting to acquire data..."); - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); BigBuf_free(); clear_trace(); @@ -1259,12 +1259,274 @@ void AcquireRawAdcSamplesIso15693(void) { LEDsoff(); } +//============================================================================= +// An ISO 15693 decoder for tag responses in FSK (two subcarriers) mode. +// Subcarriers frequencies are 424kHz and 484kHz (fc/32 and fc/28), +// LED handling: +// LED C -> ON once we have received the SOF and are expecting the rest. +// LED C -> OFF once we have received EOF or are unsynced +// +// Returns: true if we received a EOF +// false if we are still waiting for some more +//============================================================================= +#define DEBUG 0 +#define FREQ_IS_484(f) (f >= 26 && f <= 30) +#define FREQ_IS_424(f) (f >= 30 && f <= 34) +#define FREQ_IS_0(f) (f <= 24 || f >= 36) +#define SEOF_COUNT(c, s) ((s) ? (c >= 11 && c <= 13) : (c >= 44 && c <= 52)) +#define LOGIC_COUNT(c, s) ((s) ? (c >= 3 && c <= 6) : (c >= 13 && c <= 21)) +#define MAX_COUNT(c, s) ((s) ? (c >= 13) : (c >= 52)) +#define MIN_COUNT(c, s) ((s) ? (c <= 2) : (c <= 4)) + +typedef struct DecodeTagFSK { + enum { + STATE_FSK_BEFORE_SOF, + STATE_FSK_SOF_484, + STATE_FSK_SOF_424, + STATE_FSK_SOF_END, + STATE_FSK_RECEIVING_DATA_484, + STATE_FSK_RECEIVING_DATA_424, + STATE_FSK_EOF, + STATE_FSK_ERROR + } state; + enum { + LOGIC0_PART1, + LOGIC1_PART1, + LOGIC0_PART2, + LOGIC1_PART2, + SOF + } lastBit; + uint8_t count; + uint8_t bitCount; + uint8_t shiftReg; + uint16_t len; + uint16_t max_len; + uint8_t *output; +} DecodeTagFSK_t; + +static void DecodeTagFSKReset(DecodeTagFSK_t *DecodeTag) { + DecodeTag->state = STATE_FSK_BEFORE_SOF; + DecodeTag->bitCount = 0; + DecodeTag->len = 0; + DecodeTag->shiftReg = 0; + DbpString("FSK tag reset"); +} + +static void DecodeTagFSKInit(DecodeTagFSK_t *DecodeTag, uint8_t *data, uint16_t max_len) { + DecodeTag->output = data; + DecodeTag->max_len = max_len; + DecodeTagFSKReset(DecodeTag); +} + +// Performances of this function are crutial for stability +// as it is called in real time for every samples +static int inline __attribute__((always_inline)) Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed) +{ + switch(DecodeTag->state) { + case STATE_FSK_BEFORE_SOF: + if (FREQ_IS_484(freq)) + { // possible SOF starting + DecodeTag->state = STATE_FSK_SOF_484; + DecodeTag->lastBit = LOGIC0_PART1; + DecodeTag->count = 1; + } + break; + + case STATE_FSK_SOF_484: + //DbpString("STATE_FSK_SOF_484"); + + if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 484 + { + DecodeTag->count++; + } + else if (FREQ_IS_424(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) + { // SOF part1 continue at 424 + DecodeTag->state = STATE_FSK_SOF_424; + DecodeTag->count = 1; + } + else // SOF failed, roll back + { + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + + case STATE_FSK_SOF_424: + //DbpString("STATE_FSK_SOF_424"); + + if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 424 + DecodeTag->count++; + else if (FREQ_IS_484(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) + { // SOF part 1 finished + DecodeTag->state = STATE_FSK_SOF_END; + DecodeTag->count = 1; + } + else // SOF failed, roll back + { + if (DEBUG) + Dbprintf("SOF_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + + case STATE_FSK_SOF_END: + if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END (484) + DecodeTag->count++; + else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { // SOF END finished or SOF END 1st part finished + DecodeTag->count = 0; + if (DecodeTag->lastBit == SOF) + { // SOF finished at 424 + if (DEBUG) + DbpString("Receiving data !"); + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + LED_C_ON(); + } + DecodeTag->lastBit = SOF; + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END (424) + DecodeTag->count++; + else if (DecodeTag->lastBit == SOF && FREQ_IS_484(freq) && + LOGIC_COUNT(DecodeTag->count, recv_speed)) + { // SOF finished at 484 + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + DecodeTag->count = 1; + LED_C_ON(); + } + else // SOF failed, roll back + { + if (DEBUG) + Dbprintf("SOF_END failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + + + case STATE_FSK_RECEIVING_DATA_424: + if (DecodeTag->lastBit == LOGIC1_PART1 && + LOGIC_COUNT(DecodeTag->count, recv_speed)) + { // logic 1 finished + DecodeTag->lastBit = LOGIC1_PART2; + DecodeTag->count = 0; + + DecodeTag->shiftReg >>= 1; + DecodeTag->shiftReg |= 0x80; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 424 + DecodeTag->count++; + else if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed) && + DecodeTag->lastBit >= LOGIC0_PART2) + { // end of LOGIC0_PART1 + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + DecodeTag->lastBit = LOGIC0_PART1; + } + else if (FREQ_IS_484(freq) && MIN_COUNT(DecodeTag->count, recv_speed)) + { // it was just the end of the previous block + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + } + else if (FREQ_IS_484(freq) && DecodeTag->lastBit == LOGIC0_PART2 && + SEOF_COUNT(DecodeTag->count, recv_speed)) + { // EOF has started + if (DEBUG) + Dbprintf("RECEIVING_DATA_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_EOF; + LED_C_OFF(); + } + else // error + { + if (DEBUG) + Dbprintf("RECEIVING_DATA_424 error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + DecodeTag->state = STATE_FSK_ERROR; + LED_C_OFF(); + return true; + } + break; + + case STATE_FSK_RECEIVING_DATA_484: + if (DecodeTag->lastBit == LOGIC0_PART1 && + LOGIC_COUNT(DecodeTag->count, recv_speed)) + { // logic 0 finished + DecodeTag->lastBit = LOGIC0_PART2; + DecodeTag->count = 0; + + DecodeTag->shiftReg >>= 1; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + } + else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 + DecodeTag->count++; + else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed) && + DecodeTag->lastBit >= LOGIC0_PART2) + { // end of LOGIC1_PART1 + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + DecodeTag->lastBit = LOGIC1_PART1; + } + else if (FREQ_IS_424(freq) && MIN_COUNT(DecodeTag->count, recv_speed)) + { // it was just the end of the previous block + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + } + else // error + { + if (DEBUG) + Dbprintf("RECEIVING_DATA_484 error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + LED_C_OFF(); + DecodeTag->state = STATE_FSK_ERROR; + return true; + } + break; + + case STATE_FSK_EOF: + if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 + { + DecodeTag->count++; + if (SEOF_COUNT(DecodeTag->count, recv_speed)) + return true; // end of the transmission + } + else // error + { + if (DEBUG) + Dbprintf("EOF error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + DecodeTag->state = STATE_FSK_ERROR; + return true; + } + break; + case STATE_FSK_ERROR: + LED_C_OFF(); + return true; // error + break; + } + return false; +} void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { LEDsoff(); LED_A_ON(); - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); DbpString("Starting to sniff. Press PM3 Button to stop."); @@ -1276,6 +1538,10 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { uint8_t response[ISO15693_MAX_RESPONSE_LENGTH] = {0}; DecodeTagInit(&dtag, response, sizeof(response)); + DecodeTagFSK_t dtagfsk = {0}; + uint8_t response2[ISO15693_MAX_RESPONSE_LENGTH] = {0}; + DecodeTagFSKInit(&dtagfsk, response2, sizeof(response2)); + DecodeReader_t dreader = {0}; uint8_t cmd[ISO15693_MAX_COMMAND_LENGTH] = {0}; DecodeReaderInit(&dreader, cmd, sizeof(cmd), jam_search_len, jam_search_string); @@ -1301,6 +1567,8 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { bool tag_is_active = false; bool reader_is_active = false; bool expect_tag_answer = false; + bool expect_fsk_answer = false; + bool expect_fast_answer = false; int dma_start_time = 0; // Count of samples received so far, so that we can include timing @@ -1361,13 +1629,20 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { - 32 * 16 // time for SOF transfer - 16 * 16; // time for EOF transfer LogTrace_ISO15693(dreader.output, dreader.byteCount, (sof_time * 4), (eof_time * 4), NULL, true); + + expect_fsk_answer = dreader.output[0] & ISO15_REQ_SUBCARRIER_TWO; + expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; } // And ready to receive another command. DecodeReaderReset(&dreader); DecodeTagReset(&dtag); + DecodeTagFSKReset(&dtagfsk); reader_is_active = false; expect_tag_answer = true; - + if (expect_fsk_answer) + { + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_FSK_READER | FPGA_HF_FSK_READER_OUTPUT_212_KHZ | FPGA_HF_FSK_READER_NOPOWER); + } } else if (Handle15693SampleFromReader(sniffdata & 0x01, &dreader)) { uint32_t eof_time = dma_start_time + (samples * 16) + 16 - DELAY_READER_TO_ARM_SNIFF; // end of EOF @@ -1377,13 +1652,21 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { - 32 * 16 // time for SOF transfer - 16 * 16; // time for EOF transfer LogTrace_ISO15693(dreader.output, dreader.byteCount, (sof_time * 4), (eof_time * 4), NULL, true); + + expect_fsk_answer = dreader.output[0] & ISO15_REQ_SUBCARRIER_TWO; + expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; } // And ready to receive another command DecodeReaderReset(&dreader); DecodeTagReset(&dtag); + DecodeTagFSKReset(&dtagfsk); reader_is_active = false; expect_tag_answer = true; + if (expect_fsk_answer) + { + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_FSK_READER | FPGA_HF_FSK_READER_OUTPUT_212_KHZ | FPGA_HF_FSK_READER_NOPOWER); + } } else { reader_is_active = (dreader.state >= STATE_READER_RECEIVE_DATA_1_OUT_OF_4); } @@ -1391,25 +1674,77 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { if (reader_is_active == false && expect_tag_answer) { // no need to try decoding tag data if the reader is currently sending or no answer expected yet - if (Handle15693SamplesFromTag(sniffdata >> 2, &dtag)) { + if (!expect_fsk_answer) + { + if (Handle15693SamplesFromTag(sniffdata >> 2, &dtag)) { - uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF - if (dtag.lastBit == SOF_PART2) { - eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF + if (dtag.lastBit == SOF_PART2) { + eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + } + uint32_t sof_time = eof_time + - dtag.len * 8 * 8 * 16 // time for byte transfers + - (32 * 16) // time for SOF transfer + - (dtag.lastBit != SOF_PART2 ? (32 * 16) : 0); // time for EOF transfer + + LogTrace_ISO15693(dtag.output, dtag.len, (sof_time * 4), (eof_time * 4), NULL, false); + // And ready to receive another response. + DecodeTagReset(&dtag); + DecodeTagFSKReset(&dtagfsk); + DecodeReaderReset(&dreader); + expect_tag_answer = false; + tag_is_active = false; + } else { + tag_is_active = (dtag.state >= STATE_TAG_RECEIVING_DATA); } - uint32_t sof_time = eof_time - - dtag.len * 8 * 8 * 16 // time for byte transfers - - (32 * 16) // time for SOF transfer - - (dtag.lastBit != SOF_PART2 ? (32 * 16) : 0); // time for EOF transfer + } + else + { + if (Handle15693FSKSamplesFromTag(sniffdata >> 8, &dtagfsk, expect_fast_answer)) { + uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF + if (dtagfsk.lastBit == SOF) { + eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + } + uint32_t sof_time = eof_time + - dtagfsk.len * 8 * 8 * 16 // time for byte transfers + - (32 * 16) // time for SOF transfer + - (dtagfsk.lastBit != SOF ? (32 * 16) : 0); // time for EOF transfer - LogTrace_ISO15693(dtag.output, dtag.len, (sof_time * 4), (eof_time * 4), NULL, false); - // And ready to receive another response. - DecodeTagReset(&dtag); - DecodeReaderReset(&dreader); - expect_tag_answer = false; - tag_is_active = false; - } else { - tag_is_active = (dtag.state >= STATE_TAG_RECEIVING_DATA); + LogTrace_ISO15693(dtagfsk.output, dtagfsk.len, (sof_time * 4), (eof_time * 4), NULL, false); + // And ready to receive another response. + DecodeTagFSKReset(&dtagfsk); + DecodeTagReset(&dtag); + DecodeReaderReset(&dreader); + expect_tag_answer = false; + tag_is_active = false; + expect_fsk_answer = false; + + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE); + } + else if (Handle15693FSKSamplesFromTag(sniffdata & 0xFF, &dtagfsk, expect_fast_answer)) { + + uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF + if (dtagfsk.lastBit == SOF) { + eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + } + uint32_t sof_time = eof_time + - dtagfsk.len * 8 * 8 * 16 // time for byte transfers + - (32 * 16) // time for SOF transfer + - (dtagfsk.lastBit != SOF ? (32 * 16) : 0); // time for EOF transfer + + LogTrace_ISO15693(dtagfsk.output, dtagfsk.len, (sof_time * 4), (eof_time * 4), NULL, false); + // And ready to receive another response. + DecodeTagFSKReset(&dtagfsk); + DecodeTagReset(&dtag); + DecodeReaderReset(&dreader); + expect_tag_answer = false; + tag_is_active = false; + expect_fsk_answer = false; + + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE); + } else { + tag_is_active = (dtagfsk.state >= STATE_FSK_RECEIVING_DATA_484); + } } } @@ -1424,6 +1759,9 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { Dbprintf(" DecodeTag State........%d", dtag.state); Dbprintf(" DecodeTag byteCnt......%d", dtag.len); Dbprintf(" DecodeTag posCount.....%d", dtag.posCount); + Dbprintf(" DecodeTagFSK State........%d", dtagfsk.state); + Dbprintf(" DecodeTagFSK byteCnt......%d", dtagfsk.len); + Dbprintf(" DecodeTagFSK count.....%d", dtagfsk.count); Dbprintf(" DecodeReader State.....%d", dreader.state); Dbprintf(" DecodeReader byteCnt...%d", dreader.byteCount); Dbprintf(" DecodeReader posCount..%d", dreader.posCount); @@ -1436,7 +1774,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { void Iso15693InitReader(void) { LEDsoff(); - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); // Start from off (no field generated) FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); @@ -1679,7 +2017,7 @@ void ReaderIso15693(uint32_t parameter, iso15_card_select_t *p_card) { // When SIM: initialize the Proxmark3 as ISO15693 tag void Iso15693InitTag(void) { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); // Start from off (no field generated) FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); diff --git a/fpga-xc2s30/Makefile b/fpga-xc2s30/Makefile index 9b1f7a5ca..59eaf8b79 100644 --- a/fpga-xc2s30/Makefile +++ b/fpga-xc2s30/Makefile @@ -5,9 +5,9 @@ RMDIR = rm -rf # rmdir only if dir is empty, tolerate failure RMDIR_SOFT = -rmdir # -all: fpga_lf.bit fpga_hf.bit fpga_felica.bit +all: fpga_lf.bit fpga_hf.bit fpga_felica.bit fpga_hf_15.bit clean: - $(Q)$(RM) *.bgn *.drc *.ncd *.ngd *_par.xrpt *-placed.* *-placed_pad.* *_usage.xml xst_hf.srp xst_lf.srp xst_felica.srp + $(Q)$(RM) *.bgn *.drc *.ncd *.ngd *_par.xrpt *-placed.* *-placed_pad.* *_usage.xml xst_hf.srp xst_lf.srp xst_felica.srp xst_hf_15.srp $(Q)$(RM) *.map *.ngc *.xrpt *.pcf *.rbt *.bld *.mrp *.ngm *.unroutes *_summary.xml netlist.lst $(Q)$(RMDIR) *_auto_* xst @@ -22,6 +22,11 @@ fpga_felica.ngc: fpga_felica.v fpga.ucf xst_felica.scr util.v hi_simulate.v hi_r $(info [-] XST $@) $(Q)$(XILINX_TOOLS_PREFIX)xst -ifn xst_felica.scr +fpga_hf_15.ngc: fpga_hf_15.v fpga.ucf xst_hf.scr util.v hi_simulate.v hi_reader.v hi_iso14443a.v hi_sniffer.v hi_get_trace.v hi_read_fsk.v + $(Q)$(RM) $@ + $(info [-] XST $@) + $(Q)$(XILINX_TOOLS_PREFIX)xst -ifn xst_hf_15.scr + fpga_lf.ngc: fpga_lf.v fpga.ucf xst_lf.scr util.v clk_divider.v lo_edge_detect.v lo_read.v lo_passthru.v lp20khz_1MSa_iir_filter.v min_max_tracker.v lf_edge_detect.v $(Q)$(RM) $@ $(info [-] XST $@) diff --git a/fpga-xc2s30/fpga_hf_15.bit b/fpga-xc2s30/fpga_hf_15.bit new file mode 100644 index 0000000000000000000000000000000000000000..916d843407f2a40f6ee2bb01a94b9ce0827359a0 GIT binary patch literal 42178 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 JVp, June 2014 +// Piwi, Feb 2019 +//----------------------------------------------------------------------------- + + +// Defining commands, modes and options. This must be aligned to the definitions in fpgaloader.h +// Note: the definitions here are without shifts + +// Commands: +`define FPGA_CMD_SET_CONFREG 1 +`define FPGA_CMD_TRACE_ENABLE 2 + +// Major modes: +`define FPGA_MAJOR_MODE_HF_READER 0 +`define FPGA_MAJOR_MODE_HF_SIMULATOR 1 +`define FPGA_MAJOR_MODE_HF_ISO14443A 2 +`define FPGA_MAJOR_MODE_HF_SNIFF 3 +`define FPGA_MAJOR_MODE_HF_ISO18092 4 +`define FPGA_MAJOR_MODE_HF_GET_TRACE 5 +`define FPGA_MAJOR_MODE_HF_FSK_READER 6 +`define FPGA_MAJOR_MODE_OFF 7 + +// Options for the generic HF reader +`define FPGA_HF_READER_MODE_RECEIVE_IQ 0 +`define FPGA_HF_READER_MODE_RECEIVE_AMPLITUDE 1 +`define FPGA_HF_READER_MODE_RECEIVE_PHASE 2 +`define FPGA_HF_READER_MODE_SEND_FULL_MOD 3 +`define FPGA_HF_READER_MODE_SEND_SHALLOW_MOD 4 +`define FPGA_HF_READER_MODE_SNIFF_IQ 5 +`define FPGA_HF_READER_MODE_SNIFF_AMPLITUDE 6 +`define FPGA_HF_READER_MODE_SNIFF_PHASE 7 +`define FPGA_HF_READER_MODE_SEND_JAM 8 + +`define FPGA_HF_READER_SUBCARRIER_848_KHZ 0 +`define FPGA_HF_READER_SUBCARRIER_424_KHZ 1 +`define FPGA_HF_READER_SUBCARRIER_212_KHZ 2 + +`define FPGA_HF_FSK_READER_OUTPUT_1695_KHZ 0 +`define FPGA_HF_FSK_READER_OUTPUT_848_KHZ 1 +`define FPGA_HF_FSK_READER_OUTPUT_424_KHZ 2 +`define FPGA_HF_FSK_READER_OUTPUT_212_KHZ 3 + +`define FPGA_HF_FSK_READER_NOPOWER 0 +`define FPGA_HF_FSK_READER_WITHPOWER 1 + +// Options for the HF simulated tag, how to modulate +`define FPGA_HF_SIMULATOR_NO_MODULATION 0 +`define FPGA_HF_SIMULATOR_MODULATE_BPSK 1 +`define FPGA_HF_SIMULATOR_MODULATE_212K 2 +`define FPGA_HF_SIMULATOR_MODULATE_424K 4 +`define FPGA_HF_SIMULATOR_MODULATE_424K_8BIT 5 + +// Options for ISO14443A +`define FPGA_HF_ISO14443A_SNIFFER 0 +`define FPGA_HF_ISO14443A_TAGSIM_LISTEN 1 +`define FPGA_HF_ISO14443A_TAGSIM_MOD 2 +`define FPGA_HF_ISO14443A_READER_LISTEN 3 +`define FPGA_HF_ISO14443A_READER_MOD 4 + +//options for ISO18092 / Felica +`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 + +`include "hi_reader.v" +`include "hi_simulate.v" +//`include "hi_iso14443a.v" +`include "hi_sniffer.v" +`include "util.v" +// `include "hi_flite.v" +`include "hi_get_trace.v" +`include "hi_read_fsk.v" + +module fpga_hf_15( + input spck, output miso, input mosi, input ncs, + input pck0, input ck_1356meg, input ck_1356megb, + output pwr_lo, output pwr_hi, + output pwr_oe1, output pwr_oe2, output pwr_oe3, output pwr_oe4, + input [7:0] adc_d, output adc_clk, output adc_noe, + output ssp_frame, output ssp_din, input ssp_dout, output ssp_clk, + input cross_hi, input cross_lo, + output dbg +); + +//----------------------------------------------------------------------------- +// The SPI receiver. This sets up the configuration word, which the rest of +// the logic looks at to determine how to connect the A/D and the coil +// drivers (i.e., which section gets it). Also assign some symbolic names +// to the configuration bits, for use below. +//----------------------------------------------------------------------------- + +/* + Attempt to write up how its hooked up. + / Iceman, 2020 + + Communication between ARM / FPGA is done inside armsrc/fpgaloader.c see: function FpgaSendCommand() + Send 16 bit command / data pair to FPGA + The bit format is: C3 C2 C1 C0 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 + where + C is 4bit command + D is 12bit data + + shift_reg receive this 16bit frame + + +-----+--------- frame layout -------------------- +bit | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +-----+------------------------------------------- +cmd | x x x x +major| x x x +opt | x x x x +sub | x x +divi | x x x x x x x x +thres| x x x x x x x x +-----+------------------------------------------- +*/ + +reg [15:0] shift_reg; +reg [8:0] conf_word; +reg trace_enable; + +// We switch modes between transmitting to the 13.56 MHz tag and receiving +// from it, which means that we must make sure that we can do so without +// glitching, or else we will glitch the transmitted carrier. +always @(posedge ncs) +begin + case(shift_reg[15:12]) + `FPGA_CMD_SET_CONFREG: conf_word <= shift_reg[8:0]; + `FPGA_CMD_TRACE_ENABLE: trace_enable <= shift_reg[0]; + endcase +end + +always @(posedge spck) +begin + if(~ncs) + begin + shift_reg[15:1] <= shift_reg[14:0]; + shift_reg[0] <= mosi; + end +end + +// select module (outputs) based on major mode +wire [2:0] major_mode = conf_word[8:6]; + +// configuring the HF reader +wire [1:0] subcarrier_frequency = conf_word[5:4]; +wire [3:0] minor_mode = conf_word[3:0]; + +//----------------------------------------------------------------------------- +// And then we instantiate the modules corresponding to each of the FPGA's +// major modes, and use muxes to connect the outputs of the active mode to +// the output pins. +//----------------------------------------------------------------------------- + +// 000 - HF reader +hi_reader hr( + ck_1356megb, + hr_pwr_lo, hr_pwr_hi, hr_pwr_oe1, hr_pwr_oe2, hr_pwr_oe3, hr_pwr_oe4, + adc_d, hr_adc_clk, + hr_ssp_frame, hr_ssp_din, ssp_dout, hr_ssp_clk, + hr_dbg, + subcarrier_frequency, minor_mode +); + +// 001 - HF simulated tag +hi_simulate hs( + ck_1356meg, + hs_pwr_lo, hs_pwr_hi, hs_pwr_oe1, hs_pwr_oe2, hs_pwr_oe3, hs_pwr_oe4, + adc_d, hs_adc_clk, + hs_ssp_frame, hs_ssp_din, ssp_dout, hs_ssp_clk, + hs_dbg, + minor_mode +); + +/*// 010 - HF ISO14443-A +hi_iso14443a hisn( + ck_1356meg, + hisn_pwr_lo, hisn_pwr_hi, hisn_pwr_oe1, hisn_pwr_oe2, hisn_pwr_oe3, hisn_pwr_oe4, + adc_d, hisn_adc_clk, + hisn_ssp_frame, hisn_ssp_din, ssp_dout, hisn_ssp_clk, + hisn_dbg, + minor_mode +);*/ + +// 011 - HF sniff +hi_sniffer he( + ck_1356megb, + he_pwr_lo, he_pwr_hi, he_pwr_oe1, he_pwr_oe2, he_pwr_oe3, he_pwr_oe4, + adc_d, he_adc_clk, + he_ssp_frame, he_ssp_din, he_ssp_clk +); + +// 100 - HF ISO18092 FeliCa +/* +hi_flite hfl( + ck_1356megb, + hfl_pwr_lo, hfl_pwr_hi, hfl_pwr_oe1, hfl_pwr_oe2, hfl_pwr_oe3, hfl_pwr_oe4, + adc_d, hfl_adc_clk, + hfl_ssp_frame, hfl_ssp_din, ssp_dout, hfl_ssp_clk, + hfl_dbg, + minor_mode +); +*/ + +// 101 - HF get trace +hi_get_trace gt( + ck_1356megb, + adc_d, trace_enable, major_mode, + gt_ssp_frame, gt_ssp_din, gt_ssp_clk +); + +// 110 - HF Read FSK +hi_read_fsk hrf( + ck_1356meg, + hrf_pwr_lo, hrf_pwr_hi, hrf_pwr_oe1, hrf_pwr_oe2, hrf_pwr_oe3, hrf_pwr_oe4, + adc_d, hrf_adc_clk, + hrf_ssp_frame, hrf_ssp_din, hrf_ssp_clk, + subcarrier_frequency, minor_mode +); + +// Major modes: +// 000 -- HF reader; subcarrier frequency and modulation depth selectable +// 001 -- HF simulated tag +// 010 -- HF ISO14443-A +// 011 -- HF sniff +// 100 -- HF ISO18092 FeliCa +// 101 -- HF get trace +// 110 -- HF Read FSK +// 111 -- FPGA_MAJOR_MODE_OFF + +// 000 001 010 011 100 101 110 111 + +mux8 mux_ssp_clk (major_mode, ssp_clk, hr_ssp_clk, hs_ssp_clk, 1'b0, he_ssp_clk, hfl_ssp_clk, gt_ssp_clk, hrf_ssp_clk, 1'b0); +mux8 mux_ssp_din (major_mode, ssp_din, hr_ssp_din, hs_ssp_din, 1'b0, he_ssp_din, hfl_ssp_din, gt_ssp_din, hrf_ssp_din, 1'b0); +mux8 mux_ssp_frame (major_mode, ssp_frame, hr_ssp_frame, hs_ssp_frame, 1'b0, he_ssp_frame, hfl_ssp_frame, gt_ssp_frame, hrf_ssp_frame, 1'b0); +mux8 mux_pwr_oe1 (major_mode, pwr_oe1, hr_pwr_oe1, hs_pwr_oe1, 1'b0, he_pwr_oe1, hfl_pwr_oe1, 1'b0, hrf_pwr_oe1, 1'b0); +mux8 mux_pwr_oe2 (major_mode, pwr_oe2, hr_pwr_oe2, hs_pwr_oe2, 1'b0, he_pwr_oe2, hfl_pwr_oe2, 1'b0, hrf_pwr_oe2, 1'b0); +mux8 mux_pwr_oe3 (major_mode, pwr_oe3, hr_pwr_oe3, hs_pwr_oe3, 1'b0, he_pwr_oe3, hfl_pwr_oe3, 1'b0, hrf_pwr_oe3, 1'b0); +mux8 mux_pwr_oe4 (major_mode, pwr_oe4, hr_pwr_oe4, hs_pwr_oe4, 1'b0, he_pwr_oe4, hfl_pwr_oe4, 1'b0, hrf_pwr_oe4, 1'b0); +mux8 mux_pwr_lo (major_mode, pwr_lo, hr_pwr_lo, hs_pwr_lo, 1'b0, he_pwr_lo, hfl_pwr_lo, 1'b0, hrf_pwr_lo, 1'b0); +mux8 mux_pwr_hi (major_mode, pwr_hi, hr_pwr_hi, hs_pwr_hi, 1'b0, he_pwr_hi, hfl_pwr_hi, 1'b0, hrf_pwr_hi, 1'b0); +mux8 mux_adc_clk (major_mode, adc_clk, hr_adc_clk, hs_adc_clk, 1'b0, he_adc_clk, hfl_adc_clk, 1'b0, hrf_adc_clk, 1'b0); +mux8 mux_dbg (major_mode, dbg, hr_dbg, hs_dbg, 1'b0, he_dbg, hfl_dbg, 1'b0, 1'b0, 1'b0); + +// In all modes, let the ADC's outputs be enabled. +assign adc_noe = 1'b0; + +endmodule diff --git a/fpga-xc2s30/hi_read_fsk.v b/fpga-xc2s30/hi_read_fsk.v new file mode 100644 index 000000000..e45162111 --- /dev/null +++ b/fpga-xc2s30/hi_read_fsk.v @@ -0,0 +1,152 @@ +// lnv42, Jan 2020 +// reworked && integrated to RRG in Fev 2022 +// HF FSK reader (used for iso15 sniffing/reading) + +// output is the frequence divider from 13,56 MHz + +// (eg. for iso 15 two subcarriers mode (423,75 khz && 484,28 khz): it return 32 or 28) +// (423,75k = 13.56M / 32 and 484.28k = 13,56M / 28) + +module hi_read_fsk( + ck_1356meg, + pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4, + adc_d, adc_clk, + ssp_frame, ssp_din, ssp_clk, + subcarrier_frequency, minor_mode +); + + input ck_1356meg; + output pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4; + input [7:0] adc_d; + output adc_clk; + output ssp_frame, ssp_din, ssp_clk; + input [1:0]subcarrier_frequency; + input [3:0] minor_mode; + +assign adc_clk = ck_1356meg; // input sample frequency is 13,56 MHz + +assign power = subcarrier_frequency[0]; + +// Carrier is on if power is on, else is 0 +reg pwr_hi; +always @(ck_1356meg) +begin + if (power == `FPGA_HF_FSK_READER_WITHPOWER) + pwr_hi <= ck_1356meg; + else + pwr_hi <= 'b0; +end + +reg [7:0] adc_cnt = 8'd0; +reg [7:0] out1 = 8'd0; +reg [7:0] old = 8'd0; +reg [7:0] edge_id = 8'd0; +reg edge_started = 1'd0; +// Count clock edge between two signal edges +always @(negedge adc_clk) +begin + adc_cnt <= adc_cnt + 1'd1; + + if (& adc_d[7:5] && !(& old[7:5])) // up + begin + if (edge_started == 1'd0) // new edge starting + begin + if (edge_id <= adc_cnt) + out1 <= adc_cnt - edge_id; + else + out1 <= adc_cnt + 9'h100 - edge_id; + edge_id <= adc_cnt; + edge_started = 1'd1; + end + end + else + begin + edge_started = 1'd0; + if (edge_id <= adc_cnt) + begin + if (adc_cnt - edge_id > 8'd40) + begin + out1 <= 8'd0; + end + end + else + begin + if (adc_cnt + 9'h100 - edge_id > 8'd40) + begin + out1 <= 8'd0; + end + end + end + + old <= adc_d; +end + +// agregate out values (depending on selected output frequency) +reg [10:0] out_tmp = 11'd0; +reg [7:0] out = 8'd0; +always @(negedge adc_clk) +begin + out_tmp <= out_tmp + out1; + if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_848_KHZ && adc_cnt[0] == 1'd0) + begin // average on 2 values + out <= out_tmp[8:1]; + out_tmp <= 12'd0; + end + else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_424_KHZ && adc_cnt[1:0] == 2'd0) + begin // average on 4 values + out <= out_tmp[9:2]; + out_tmp <= 12'd0; + end + else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_212_KHZ && adc_cnt[2:0] == 3'd0) + begin // average on 8 values + out <= out_tmp[10:3]; + out_tmp <= 12'd0; + end + else // 1695_KHZ + out <= out1; +end + +// Set output (ssp) clock +(* clock_signal = "yes" *) reg ssp_clk; +always @(ck_1356meg) +begin + if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_1695_KHZ) + ssp_clk <= ~ck_1356meg; + else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_848_KHZ) + ssp_clk <= ~adc_cnt[0]; + else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_424_KHZ) + ssp_clk <= ~adc_cnt[1]; + else // 212 KHz + ssp_clk <= ~adc_cnt[2]; +end + +// Transmit output +reg ssp_frame; +reg [7:0] ssp_out = 8'd0; +reg [2:0] ssp_cnt = 4'd0; +always @(posedge ssp_clk) +begin + ssp_cnt <= ssp_cnt + 1'd1; + if(ssp_cnt == 3'd15) + begin + ssp_out <= out; + ssp_frame <= 1'b1; + end + else + begin + ssp_out <= {ssp_out[6:0], 1'b0}; + ssp_frame <= 1'b0; + end +end + +assign ssp_din = ssp_out[7]; + +// Unused. +assign pwr_oe4 = 1'b0; +assign pwr_oe1 = 1'b0; +assign pwr_oe3 = 1'b0; +assign pwr_lo = 1'b0; +assign pwr_oe2 = 1'b0; + +endmodule + diff --git a/fpga-xc2s30/xst_hf_15.scr b/fpga-xc2s30/xst_hf_15.scr new file mode 100644 index 000000000..d8bb5ae7c --- /dev/null +++ b/fpga-xc2s30/xst_hf_15.scr @@ -0,0 +1 @@ +run -ifn fpga_hf_15.v -ifmt Verilog -ofn fpga_hf_15.ngc -ofmt NGC -p xc2s30-5-vq100 -top fpga_hf_15 -opt_mode area -opt_level 2 -resource_sharing yes -fsm_style bram -fsm_encoding compact From 38d49097f981240a0664319a1ff9a9c104bca86b Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 4 Mar 2022 09:45:12 +0100 Subject: [PATCH 2/8] iso15 sniffing: intragrate 2SC sniffing in same FPGA mode switching Fpgamode while sniffing with FpgaWriteConfWord() was sometimes too long so the tag answer start was lost. Now, (only with FPGA_BITSTREAM_HF_15) with "FPGA_HF_READER_MODE_SNIFF_AMPLITUDE | FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ": the amplitude is shrank from its 2 LSB bits and those 2 bits are now used to return the current frequency. From my tests, this 2 bits reduction does not affect quality of 1SC sniffing, but it may have slightly reduced the receiving range. FPGA FSK decoding code is also improved. --- armsrc/fpgaloader.c | 4 +- armsrc/fpgaloader.h | 9 +- armsrc/iso15693.c | 27 +-- fpga-xc2s30/Makefile | 2 +- fpga-xc2s30/fpga_hf_15.bit | Bin 42178 -> 42178 bytes fpga-xc2s30/fpga_hf_15.v | 36 ++- fpga-xc2s30/hi_reader_15.v | 443 +++++++++++++++++++++++++++++++++++++ 7 files changed, 469 insertions(+), 52 deletions(-) create mode 100644 fpga-xc2s30/hi_reader_15.v diff --git a/armsrc/fpgaloader.c b/armsrc/fpgaloader.c index ab8d57f7d..80d793b82 100644 --- a/armsrc/fpgaloader.c +++ b/armsrc/fpgaloader.c @@ -162,7 +162,9 @@ void FpgaSetupSsc(uint16_t fpga_mode) { // 8, 16 or 32 bits per transfer, no loopback, MSB first, 1 transfer per sync // pulse, no output sync - if ((fpga_mode & FPGA_MAJOR_MODE_MASK) == FPGA_MAJOR_MODE_HF_READER && (FpgaGetCurrent() == FPGA_BITSTREAM_HF || FpgaGetCurrent() == FPGA_BITSTREAM_HF_15)) { + if (((fpga_mode & FPGA_MAJOR_MODE_MASK) == FPGA_MAJOR_MODE_HF_READER || + (fpga_mode & FPGA_MAJOR_MODE_MASK) == FPGA_MAJOR_MODE_HF_FSK_READER) && + (FpgaGetCurrent() == FPGA_BITSTREAM_HF || FpgaGetCurrent() == FPGA_BITSTREAM_HF_15)) { AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(16) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); } else { AT91C_BASE_SSC->SSC_RFMR = SSC_FRAME_MODE_BITS_IN_WORD(8) | AT91C_SSC_MSBF | SSC_FRAME_MODE_WORDS_PER_TRANSFER(0); diff --git a/armsrc/fpgaloader.h b/armsrc/fpgaloader.h index 897031042..6069d3257 100644 --- a/armsrc/fpgaloader.h +++ b/armsrc/fpgaloader.h @@ -106,14 +106,7 @@ thres| x x x x x x x x #define FPGA_HF_READER_SUBCARRIER_848_KHZ (0<<4) #define FPGA_HF_READER_SUBCARRIER_424_KHZ (1<<4) #define FPGA_HF_READER_SUBCARRIER_212_KHZ (2<<4) - -#define FPGA_HF_FSK_READER_OUTPUT_1695_KHZ (0<<0) -#define FPGA_HF_FSK_READER_OUTPUT_848_KHZ (1<<0) -#define FPGA_HF_FSK_READER_OUTPUT_424_KHZ (2<<0) -#define FPGA_HF_FSK_READER_OUTPUT_212_KHZ (3<<0) - -#define FPGA_HF_FSK_READER_NOPOWER (0<<4) -#define FPGA_HF_FSK_READER_WITHPOWER (1<<4) +#define FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ (3<<4) // Options for the HF simulated tag, how to modulate #define FPGA_HF_SIMULATOR_NO_MODULATION 0x0 // 0000 diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index c41760e24..919040d0e 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -1269,10 +1269,10 @@ void AcquireRawAdcSamplesIso15693(void) { // Returns: true if we received a EOF // false if we are still waiting for some more //============================================================================= -#define DEBUG 0 -#define FREQ_IS_484(f) (f >= 26 && f <= 30) -#define FREQ_IS_424(f) (f >= 30 && f <= 34) -#define FREQ_IS_0(f) (f <= 24 || f >= 36) +//#define DEBUG 1 +#define FREQ_IS_484(f) ((f & 1) == 1) //(f >= 26 && f <= 30) +#define FREQ_IS_424(f) ((f & 2) == 2) //(f >= 30 && f <= 34) +#define FREQ_IS_0(f) ((f & 3) == 0) // (f <= 24 || f >= 36) #define SEOF_COUNT(c, s) ((s) ? (c >= 11 && c <= 13) : (c >= 44 && c <= 52)) #define LOGIC_COUNT(c, s) ((s) ? (c >= 3 && c <= 6) : (c >= 13 && c <= 21)) #define MAX_COUNT(c, s) ((s) ? (c >= 13) : (c >= 52)) @@ -1320,7 +1320,7 @@ static void DecodeTagFSKInit(DecodeTagFSK_t *DecodeTag, uint8_t *data, uint16_t // Performances of this function are crutial for stability // as it is called in real time for every samples -static int inline __attribute__((always_inline)) Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed) +static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed, int samples) { switch(DecodeTag->state) { case STATE_FSK_BEFORE_SOF: @@ -1546,7 +1546,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { uint8_t cmd[ISO15693_MAX_COMMAND_LENGTH] = {0}; DecodeReaderInit(&dreader, cmd, sizeof(cmd), jam_search_len, jam_search_string); - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE | FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ); LED_D_OFF(); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); @@ -1639,10 +1639,6 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { DecodeTagFSKReset(&dtagfsk); reader_is_active = false; expect_tag_answer = true; - if (expect_fsk_answer) - { - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_FSK_READER | FPGA_HF_FSK_READER_OUTPUT_212_KHZ | FPGA_HF_FSK_READER_NOPOWER); - } } else if (Handle15693SampleFromReader(sniffdata & 0x01, &dreader)) { uint32_t eof_time = dma_start_time + (samples * 16) + 16 - DELAY_READER_TO_ARM_SNIFF; // end of EOF @@ -1662,11 +1658,6 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { DecodeTagFSKReset(&dtagfsk); reader_is_active = false; expect_tag_answer = true; - if (expect_fsk_answer) - { - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_FSK_READER | FPGA_HF_FSK_READER_OUTPUT_212_KHZ | FPGA_HF_FSK_READER_NOPOWER); - - } } else { reader_is_active = (dreader.state >= STATE_READER_RECEIVE_DATA_1_OUT_OF_4); } @@ -1676,7 +1667,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { if (!expect_fsk_answer) { - if (Handle15693SamplesFromTag(sniffdata >> 2, &dtag)) { + if (Handle15693SamplesFromTag((sniffdata >> 4) << 2, &dtag)) { uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF if (dtag.lastBit == SOF_PART2) { @@ -1700,7 +1691,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { } else { - if (Handle15693FSKSamplesFromTag(sniffdata >> 8, &dtagfsk, expect_fast_answer)) { + if (Handle15693FSKSamplesFromTag((sniffdata >> 2) & 0x3, &dtagfsk, expect_fast_answer, samples-fsksamples)) { uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF if (dtagfsk.lastBit == SOF) { eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) @@ -1719,7 +1710,6 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { tag_is_active = false; expect_fsk_answer = false; - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE); } else if (Handle15693FSKSamplesFromTag(sniffdata & 0xFF, &dtagfsk, expect_fast_answer)) { @@ -1741,7 +1731,6 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { tag_is_active = false; expect_fsk_answer = false; - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE); } else { tag_is_active = (dtagfsk.state >= STATE_FSK_RECEIVING_DATA_484); } diff --git a/fpga-xc2s30/Makefile b/fpga-xc2s30/Makefile index 59eaf8b79..d6f05c430 100644 --- a/fpga-xc2s30/Makefile +++ b/fpga-xc2s30/Makefile @@ -22,7 +22,7 @@ fpga_felica.ngc: fpga_felica.v fpga.ucf xst_felica.scr util.v hi_simulate.v hi_r $(info [-] XST $@) $(Q)$(XILINX_TOOLS_PREFIX)xst -ifn xst_felica.scr -fpga_hf_15.ngc: fpga_hf_15.v fpga.ucf xst_hf.scr util.v hi_simulate.v hi_reader.v hi_iso14443a.v hi_sniffer.v hi_get_trace.v hi_read_fsk.v +fpga_hf_15.ngc: fpga_hf_15.v fpga.ucf xst_hf.scr util.v hi_simulate.v hi_reader_15.v hi_iso14443a.v hi_sniffer.v hi_get_trace.v $(Q)$(RM) $@ $(info [-] XST $@) $(Q)$(XILINX_TOOLS_PREFIX)xst -ifn xst_hf_15.scr diff --git a/fpga-xc2s30/fpga_hf_15.bit b/fpga-xc2s30/fpga_hf_15.bit index 916d843407f2a40f6ee2bb01a94b9ce0827359a0..678e746c48393129982605bf251edeb50880c306 100644 GIT binary patch literal 42178 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 JVp avg128[63:56]) + diff16 = avg - avg128[63:56]; + else + diff16 = avg128[63:56] - avg; + + if (avg > avg128[111:104]) + diff28 = avg - avg128[111:104]; + else + diff28 = avg128[111:104] - avg; + + if (avg > avg128[127:120]) + diff32 = avg - avg128[127:120]; + else + diff32 = avg128[127:120] - avg; + + avg128[127:8] = avg128[119:0]; + avg128[7:0] = avg; + + + if (corr_i_cnt[4:1] == 4'b0000) // every 32 clock (8*4) + begin + match16 = diff16; + match28 = diff28; + match32 = diff32; + end + else + begin + match16 = match16 + diff16; + match28 = match28 + diff28; + match32 = match32 + diff32; + + if (corr_i_cnt[4:1] == 4'b1111) // every 32 clock (8*4) + begin + last0 = (fskout == 2'b0); + if (match16 < 12'd64 && last0) + fskout = 2'b00; // not yet started + else if ((match16 | match28 | match32) == 12'b0) + fskout = 2'b00; // signal likely ended + else if (((match16 <= match28 + 12'd16) && (match16 <= match32+ 12'd16)) || + (match28 <= 12'd16 && match32 <= 12'd16)) + begin + if (!last0) + fskout = 2'b11; // 16 match better than 28 or 32 but already started + end + else + begin + if (match28 < match32) + begin + diff28 = match32 - match28; + diff16 = match16 - match28; + if (diff28*2 > diff16) + fskout = 2'b01; + else if (!last0) + begin + fskout = 2'b01; + end + end + else //if (match32 <= match28) + begin + diff32 = match28 - match32; + diff16 = match16 - match32; + if (diff32*2 > diff16) + fskout = 2'b10; + else if (!last0) + begin + fskout = 2'b10; + end + end + end + end + end + end + end +end + + +// A couple of registers in which to accumulate the correlations. From the 64 samples +// we would add at most 32 times the difference between unmodulated and modulated signal. It should +// be safe to assume that a tag will not be able to modulate the carrier signal by more than 25%. +// 32 * 255 * 0,25 = 2040, which can be held in 11 bits. Add 1 bit for sign. +// Temporary we might need more bits. For the 212kHz subcarrier we could possible add 32 times the +// maximum signal value before a first subtraction would occur. 32 * 255 = 8160 can be held in 13 bits. +// Add one bit for sign -> need 14 bit registers but final result will fit into 12 bits. +reg signed [13:0] corr_i_accum; +reg signed [13:0] corr_q_accum; +// we will report maximum 8 significant bits +reg signed [7:0] corr_i_out; +reg signed [7:0] corr_q_out; + + +// the amplitude of the subcarrier is sqrt(ci^2 + cq^2). +// approximate by amplitude = max(|ci|,|cq|) + 1/2*min(|ci|,|cq|) +reg [13:0] corr_amplitude, abs_ci, abs_cq, max_ci_cq; +reg [12:0] min_ci_cq_2; // min_ci_cq / 2 + +always @(*) +begin + if (corr_i_accum[13] == 1'b0) + abs_ci <= corr_i_accum; + else + abs_ci <= -corr_i_accum; + + if (corr_q_accum[13] == 1'b0) + abs_cq <= corr_q_accum; + else + abs_cq <= -corr_q_accum; + + if (abs_ci > abs_cq) + begin + max_ci_cq <= abs_ci; + min_ci_cq_2 <= abs_cq / 2; + end + else + begin + max_ci_cq <= abs_cq; + min_ci_cq_2 <= abs_ci / 2; + end + + corr_amplitude <= max_ci_cq + min_ci_cq_2; + +end + + +// The subcarrier reference signals +reg subcarrier_I; +reg subcarrier_Q; + +always @(*) +begin + if (subcarrier_frequency == `FPGA_HF_READER_SUBCARRIER_848_KHZ) + begin + subcarrier_I = ~corr_i_cnt[3]; + subcarrier_Q = ~(corr_i_cnt[3] ^ corr_i_cnt[2]); + end + else if (subcarrier_frequency == `FPGA_HF_READER_SUBCARRIER_212_KHZ) + begin + subcarrier_I = ~corr_i_cnt[5]; + subcarrier_Q = ~(corr_i_cnt[5] ^ corr_i_cnt[4]); + end + else + begin // 424 kHz + subcarrier_I = ~corr_i_cnt[4]; + subcarrier_Q = ~(corr_i_cnt[4] ^ corr_i_cnt[3]); + end +end + + +// ADC data appears on the rising edge, so sample it on the falling edge +always @(negedge adc_clk) +begin + // These are the correlators: we correlate against in-phase and quadrature + // versions of our reference signal, and keep the (signed) results or the + // resulting amplitude to send out later over the SSP. + if (corr_i_cnt == 6'd0) + begin + if (minor_mode == `FPGA_HF_READER_MODE_SNIFF_AMPLITUDE) + begin + if (subcarrier_frequency == `FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ) + begin + // send amplitude + 2 bits fsk (2sc) signal + 2 bits reader signal + corr_i_out <= corr_amplitude[13:6]; + corr_q_out <= {corr_amplitude[5:2], fskout, after_hysteresis_prev_prev, after_hysteresis_prev}; + end + else + begin + // send amplitude plus 2 bits reader signal + corr_i_out <= corr_amplitude[13:6]; + corr_q_out <= {corr_amplitude[5:0], after_hysteresis_prev_prev, after_hysteresis_prev}; + end + end + else if (minor_mode == `FPGA_HF_READER_MODE_SNIFF_IQ) + begin + // Send 7 most significant bits of in phase tag signal (signed), plus 1 bit reader signal + if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) + corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev}; + else // truncate to maximum value + if (corr_i_accum[13] == 1'b0) + corr_i_out <= {7'b0111111, after_hysteresis_prev_prev}; + else + corr_i_out <= {7'b1000000, after_hysteresis_prev_prev}; + + // Send 7 most significant bits of quadrature phase tag signal (signed), plus 1 bit reader signal + if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) + corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev}; + else // truncate to maximum value + if (corr_q_accum[13] == 1'b0) + corr_q_out <= {7'b0111111, after_hysteresis_prev}; + else + corr_q_out <= {7'b1000000, after_hysteresis_prev}; + end + else if (minor_mode == `FPGA_HF_READER_MODE_RECEIVE_AMPLITUDE) + begin + // send amplitude + corr_i_out <= {2'b00, corr_amplitude[13:8]}; + corr_q_out <= corr_amplitude[7:0]; + end + else if (minor_mode == `FPGA_HF_READER_MODE_RECEIVE_IQ) + begin + // Send 8 bits of in phase tag signal + if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) + corr_i_out <= corr_i_accum[11:4]; + else // truncate to maximum value + if (corr_i_accum[13] == 1'b0) + corr_i_out <= 8'b01111111; + else + corr_i_out <= 8'b10000000; + + // Send 8 bits of quadrature phase tag signal + if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) + corr_q_out <= corr_q_accum[11:4]; + else // truncate to maximum value + if (corr_q_accum[13] == 1'b0) + corr_q_out <= 8'b01111111; + else + corr_q_out <= 8'b10000000; + end + + // for each Q/I pair report two reader signal samples when sniffing. Store the 1st. + after_hysteresis_prev_prev <= after_hysteresis; + + // Initialize next correlation. + // Both I and Q reference signals are high when corr_i_nct == 0. Therefore need to accumulate. + corr_i_accum <= $signed({1'b0, adc_d}); + corr_q_accum <= $signed({1'b0, adc_d}); + end + else + begin + if (subcarrier_I) + corr_i_accum <= corr_i_accum + $signed({1'b0, adc_d}); + else + corr_i_accum <= corr_i_accum - $signed({1'b0, adc_d}); + + if (subcarrier_Q) + corr_q_accum <= corr_q_accum + $signed({1'b0, adc_d}); + else + corr_q_accum <= corr_q_accum - $signed({1'b0, adc_d}); + end + + // for each Q/I pair report two reader signal samples when sniffing. Store the 2nd. + if (corr_i_cnt == 6'd32) + after_hysteresis_prev <= after_hysteresis; + + // Then the result from last time is serialized and send out to the ARM. + // We get one report each cycle, and each report is 16 bits, so the + // ssp_clk should be the adc_clk divided by 64/16 = 4. + // ssp_clk frequency = 13,56MHz / 4 = 3.39MHz + + if (corr_i_cnt[1:0] == 2'b00) + begin + // Don't shift if we just loaded new data, obviously. + if (corr_i_cnt != 6'd0) + begin + corr_i_out[7:0] <= {corr_i_out[6:0], corr_q_out[7]}; + corr_q_out[7:1] <= corr_q_out[6:0]; + end + end + +end + + +// ssp clock and frame signal for communication to and from ARM +// _____ _____ _____ _ +// ssp_clk | |_____| |_____| |_____| +// _____ +// ssp_frame ___| |____________________________ +// ___________ ___________ ___________ _ +// ssp_d_in X___________X___________X___________X_ +// +// corr_i_cnt 0 1 2 3 4 5 6 7 8 9 10 11 12 ... +// + +reg ssp_clk; +reg ssp_frame; + +always @(negedge adc_clk) +begin + if (corr_i_cnt[1:0] == 2'b00) + ssp_clk <= 1'b1; + + if (corr_i_cnt[1:0] == 2'b10) + ssp_clk <= 1'b0; + + // set ssp_frame signal for corr_i_cnt = 1..3 + // (send one frame with 16 Bits) + if (corr_i_cnt == 6'd1) + ssp_frame <= 1'b1; + + if (corr_i_cnt == 6'd3) + ssp_frame <= 1'b0; +end + + +assign ssp_din = corr_i_out[7]; + + +// a jamming signal +reg jam_signal; +reg [3:0] jam_counter; + +always @(negedge adc_clk) +begin + if (corr_i_cnt == 6'd0) + begin + jam_counter <= jam_counter + 1; + jam_signal <= jam_counter[1] ^ jam_counter[3]; + end +end + +// Antenna drivers +reg pwr_hi, pwr_oe4; + +always @(*) +begin + if (minor_mode == `FPGA_HF_READER_MODE_SEND_SHALLOW_MOD) + begin + pwr_hi = ck_1356meg; + pwr_oe4 = ssp_dout; + end + else if (minor_mode == `FPGA_HF_READER_MODE_SEND_FULL_MOD) + begin + pwr_hi = ck_1356meg & ~ssp_dout; + pwr_oe4 = 1'b0; + end + else if (minor_mode == `FPGA_HF_READER_MODE_SEND_JAM) + begin + pwr_hi = ck_1356meg & jam_signal; + pwr_oe4 = 1'b0; + end + else if (minor_mode == `FPGA_HF_READER_MODE_SNIFF_IQ + || minor_mode == `FPGA_HF_READER_MODE_SNIFF_AMPLITUDE + || minor_mode == `FPGA_HF_READER_MODE_SNIFF_PHASE) + begin // all off + pwr_hi = 1'b0; + pwr_oe4 = 1'b0; + end + else // receiving from tag + begin + pwr_hi = ck_1356meg; + pwr_oe4 = 1'b0; + end +end + +// always on +assign pwr_oe1 = 1'b0; +assign pwr_oe3 = 1'b0; + +// Unused. +assign pwr_lo = 1'b0; +assign pwr_oe2 = 1'b0; + +// Debug Output +assign dbg = corr_i_cnt[3]; + +endmodule From 07013a5aafebc778ba2ce6fd2e6398e94ea1b8c4 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 4 Mar 2022 12:06:41 +0100 Subject: [PATCH 3/8] iso15 sniff: commit some unused FPGA FSK code for history --- fpga-xc2s30/hi_read_fsk.v | 246 +++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 6 deletions(-) diff --git a/fpga-xc2s30/hi_read_fsk.v b/fpga-xc2s30/hi_read_fsk.v index e45162111..a8c2003ff 100644 --- a/fpga-xc2s30/hi_read_fsk.v +++ b/fpga-xc2s30/hi_read_fsk.v @@ -37,6 +37,130 @@ begin pwr_hi <= 'b0; end +reg [4:0] adc_cnt = 5'd0; +always @(negedge adc_clk) +begin + adc_cnt <= adc_cnt + 1'd1; +end + +reg [7:0] out1 = 8'd0; +reg [7:0] out2 = 8'd0; + +reg [7:0] avg = 8'd0; +reg [7:0] avg1 = 8'd0; +reg [7:0] avg2 = 8'd0; +reg [7:0] avg3 = 8'd0; +reg [7:0] avg4 = 8'd0; +reg [7:0] avg5 = 8'd0; +reg [7:0] avg6 = 8'd0; +reg [7:0] avg7 = 8'd0; +reg [7:0] avg8 = 8'd0; +reg [7:0] avg9 = 8'd0; +reg [7:0] avg10 = 8'd0; +reg [7:0] avg11 = 8'd0; +reg [7:0] avg12 = 8'd0; +reg [7:0] avg13 = 8'd0; +reg [7:0] avg14 = 8'd0; +reg [7:0] avg15 = 8'd0; +reg [7:0] avg16 = 8'd0; + +reg [7:0] diff28 = 8'd0; +reg [7:0] diff32 = 8'd0; + +reg [11:0] match32 = 12'd0; +reg [11:0] match28 = 12'd0; + +always @(negedge adc_clk) +begin + if (adc_cnt[0] == 1'b0) // every 4 clock + begin + avg = adc_d[7:1]; + end + else + begin + avg = avg + adc_d[7:1]; + if (adc_cnt[0] == 1'b1) // every 4 clock + begin + if (avg > avg14) + diff28 = avg - avg14; + else + diff28 = avg14 - avg; + + if (avg > avg16) + diff32 = avg - avg16; + else + diff32 = avg16 - avg; + + avg16 = avg15; + avg15 = avg14; + avg14 = avg13; + avg13 = avg12; + avg12 = avg11; + avg11 = avg10; + avg10 = avg9; + avg9 = avg8; + avg8 = avg7; + avg7 = avg6; + avg6 = avg5; + avg5 = avg4; + avg4 = avg3; + avg3 = avg2; + avg2 = avg1; + avg1 = avg; + + if (adc_cnt[4:1] == 4'b0000) // every 32 clock (8*4) + begin + match28 = diff28; + match32 = diff32; + end + else + begin + match28 = match28 + diff28; + match32 = match32 + diff32; + + if (adc_cnt[4:1] == 4'b1111) // every 32 clock (8*4) + begin + if (match28[11:3] > 10'b0 || match32[11:3] > 10'b0) // if not only noise + begin + if (match28 < match32) + begin + //if (match32 - match28 < 12'd24) + //out1 = out1; // out1 stay at is old value + //else + if (match32 - match28 > 12'd32) + out1 = 8'd28; + else if (match32 - match28 < 12'd16) + out1 = 8'd0; + end + else //if (match32 <= match28) + begin + //if (match28 - match32 < 12'd24) + //out1 = 8'd30; // out1 stay at is old value + //else + if (match28 - match32 > 12'd32) + out1 = 8'd32; + else if (match28 - match32 < 12'd16) + out1 = 8'd0; + end + end + else + begin + out1 = 8'd0; + //out2 = match32[8:1]; + end + //out1 = match28[7:0]; + //out2 = match32[7:0]; + //out2 = 8'hFF; + + //out2 = out1; + + end + end + end + end +end + +/* reg [7:0] adc_cnt = 8'd0; reg [7:0] out1 = 8'd0; reg [7:0] old = 8'd0; @@ -80,9 +204,107 @@ begin old <= adc_d; end +*/ +// Other version of FSK reader, probably better but not working yet... +/*reg [7:0] out1 = 8'd0; +//reg [7:0] old = 8'd0; +reg [5:0] old1 = 4'd0; +reg [5:0] old2 = 4'd0; +//reg [7:0] edge_id = 8'd0; +//reg edge_started = 1'd0; +reg [5:0] edge_cnt = 6'd0; +reg [3:0] last_values = 4'd0; + +// Count clock edge between two signal edges +always @(negedge adc_clk) +begin + edge_cnt <= edge_cnt + 1'd1; + + last_values[3:1] <= last_values[2:0]; +// last_values[0] <= (& adc_d[7:5]); // adc_d >= 192 + + last_values[0] <= (adc_d[7:2] > old1 && old1 > old2); + + + //out1[7:4] <= out1[3:0]; + //out1[3:0] <= last_values; + //out1 <= 8'd28; + //out1 <= out1+1; + + if (edge_cnt > 6'd22 || out1 == 8'd0) + begin + if ((last_values[3:2] == 2'b0) && (last_values[1:0] == 2'b11)) // edge start detected + begin // 2 not high (low or mid) values followed by 2 high values + out1 <= edge_cnt; + edge_cnt <= 6'd0; + end + else if (edge_cnt > 6'd44) // average(32, 2*28) == 44 : ideal value for iso15 FSK + begin // /!\ MIN FREQ SUPPORTED = 13MHz/44 ~= 308kHz /!\ + out1 <= 8'd0; + edge_cnt <= 6'd0; + end + end + + old2 <= old1; + old1 <= adc_d[7:2]; + + /*if (last_values[0]) + out1 <= 8'h7F; + else if (last_values[1]) + out1 <= 8'hFF; + else + out1 <= 8'h0; + + /* + + if (& adc_d[7:5] && !(& old[7:5])) // up + begin + if (edge_started == 1'd0) // new edge starting + begin + //if (edge_id <= edge_cnt) + // out1 <= edge_cnt - edge_id; + //else + // out1 <= edge_cnt + (9'h100 - edge_id); + out1 <= edge_cnt; + //edge_id <= edge_cnt; + edge_started = 1'd1; + edge_cnt <= 8'd0; + end + end + else + begin + edge_started = 1'd0; + if (edge_cnt > 8'd80) + begin + out1 <= 8'd0; + //edge_id <= 8'd0; + edge_cnt <= 8'd0; + end +*//* if (edge_id <= edge_cnt) // NO EDGE + begin + if (edge_cnt - edge_id > 8'd40) + begin + out1 <= 8'd0; + edge_id <= 8'd0; + edge_cnt <= 8'd0; + end + end + else + begin + if (edge_cnt + (9'h100 - edge_id) > 8'd40) // NO EDGE + begin + out1 <= 8'd0; + edge_id <= 8'd0; + edge_cnt <= 8'd0; + end + end*//* + //end + + //old <= adc_d; +end*/ // agregate out values (depending on selected output frequency) -reg [10:0] out_tmp = 11'd0; +/*reg [10:0] out_tmp = 11'd0; reg [7:0] out = 8'd0; always @(negedge adc_clk) begin @@ -104,8 +326,14 @@ begin end else // 1695_KHZ out <= out1; -end +// if (adc_cnt > 8'd192 && edge_id < 8'd64) // WUT ? +// begin +// out <= 8'd0; +// out_tmp <= 11'd0; +// end +end +*/ // Set output (ssp) clock (* clock_signal = "yes" *) reg ssp_clk; always @(ck_1356meg) @@ -121,20 +349,26 @@ begin end // Transmit output +reg [379:0] megatmpout = 380'd0; + reg ssp_frame; reg [7:0] ssp_out = 8'd0; -reg [2:0] ssp_cnt = 4'd0; +reg [3:0] ssp_cnt = 4'd0; always @(posedge ssp_clk) begin ssp_cnt <= ssp_cnt + 1'd1; - if(ssp_cnt == 3'd15) + if (ssp_cnt[2:0] == 3'd7) begin - ssp_out <= out; + ssp_out = {2'b0, megatmpout[379:378], megatmpout[378], megatmpout[378], 2'b0}; + megatmpout[379:2] = megatmpout[377:0]; + megatmpout[1:0] = out2[5:4]; + out2 = out1; + ssp_frame <= 1'b1; end else begin - ssp_out <= {ssp_out[6:0], 1'b0}; + ssp_out = {ssp_out[6:0], 1'b0}; ssp_frame <= 1'b0; end end From b2c5a52fd799b03e320c22be5980eefdd7d9ac6e Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 4 Mar 2022 12:09:01 +0100 Subject: [PATCH 4/8] iso15 sniff: remove unused FPGA hi_read_fsk.v --- fpga-xc2s30/hi_read_fsk.v | 386 -------------------------------------- 1 file changed, 386 deletions(-) delete mode 100644 fpga-xc2s30/hi_read_fsk.v diff --git a/fpga-xc2s30/hi_read_fsk.v b/fpga-xc2s30/hi_read_fsk.v deleted file mode 100644 index a8c2003ff..000000000 --- a/fpga-xc2s30/hi_read_fsk.v +++ /dev/null @@ -1,386 +0,0 @@ -// lnv42, Jan 2020 -// reworked && integrated to RRG in Fev 2022 -// HF FSK reader (used for iso15 sniffing/reading) - -// output is the frequence divider from 13,56 MHz - -// (eg. for iso 15 two subcarriers mode (423,75 khz && 484,28 khz): it return 32 or 28) -// (423,75k = 13.56M / 32 and 484.28k = 13,56M / 28) - -module hi_read_fsk( - ck_1356meg, - pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4, - adc_d, adc_clk, - ssp_frame, ssp_din, ssp_clk, - subcarrier_frequency, minor_mode -); - - input ck_1356meg; - output pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4; - input [7:0] adc_d; - output adc_clk; - output ssp_frame, ssp_din, ssp_clk; - input [1:0]subcarrier_frequency; - input [3:0] minor_mode; - -assign adc_clk = ck_1356meg; // input sample frequency is 13,56 MHz - -assign power = subcarrier_frequency[0]; - -// Carrier is on if power is on, else is 0 -reg pwr_hi; -always @(ck_1356meg) -begin - if (power == `FPGA_HF_FSK_READER_WITHPOWER) - pwr_hi <= ck_1356meg; - else - pwr_hi <= 'b0; -end - -reg [4:0] adc_cnt = 5'd0; -always @(negedge adc_clk) -begin - adc_cnt <= adc_cnt + 1'd1; -end - -reg [7:0] out1 = 8'd0; -reg [7:0] out2 = 8'd0; - -reg [7:0] avg = 8'd0; -reg [7:0] avg1 = 8'd0; -reg [7:0] avg2 = 8'd0; -reg [7:0] avg3 = 8'd0; -reg [7:0] avg4 = 8'd0; -reg [7:0] avg5 = 8'd0; -reg [7:0] avg6 = 8'd0; -reg [7:0] avg7 = 8'd0; -reg [7:0] avg8 = 8'd0; -reg [7:0] avg9 = 8'd0; -reg [7:0] avg10 = 8'd0; -reg [7:0] avg11 = 8'd0; -reg [7:0] avg12 = 8'd0; -reg [7:0] avg13 = 8'd0; -reg [7:0] avg14 = 8'd0; -reg [7:0] avg15 = 8'd0; -reg [7:0] avg16 = 8'd0; - -reg [7:0] diff28 = 8'd0; -reg [7:0] diff32 = 8'd0; - -reg [11:0] match32 = 12'd0; -reg [11:0] match28 = 12'd0; - -always @(negedge adc_clk) -begin - if (adc_cnt[0] == 1'b0) // every 4 clock - begin - avg = adc_d[7:1]; - end - else - begin - avg = avg + adc_d[7:1]; - if (adc_cnt[0] == 1'b1) // every 4 clock - begin - if (avg > avg14) - diff28 = avg - avg14; - else - diff28 = avg14 - avg; - - if (avg > avg16) - diff32 = avg - avg16; - else - diff32 = avg16 - avg; - - avg16 = avg15; - avg15 = avg14; - avg14 = avg13; - avg13 = avg12; - avg12 = avg11; - avg11 = avg10; - avg10 = avg9; - avg9 = avg8; - avg8 = avg7; - avg7 = avg6; - avg6 = avg5; - avg5 = avg4; - avg4 = avg3; - avg3 = avg2; - avg2 = avg1; - avg1 = avg; - - if (adc_cnt[4:1] == 4'b0000) // every 32 clock (8*4) - begin - match28 = diff28; - match32 = diff32; - end - else - begin - match28 = match28 + diff28; - match32 = match32 + diff32; - - if (adc_cnt[4:1] == 4'b1111) // every 32 clock (8*4) - begin - if (match28[11:3] > 10'b0 || match32[11:3] > 10'b0) // if not only noise - begin - if (match28 < match32) - begin - //if (match32 - match28 < 12'd24) - //out1 = out1; // out1 stay at is old value - //else - if (match32 - match28 > 12'd32) - out1 = 8'd28; - else if (match32 - match28 < 12'd16) - out1 = 8'd0; - end - else //if (match32 <= match28) - begin - //if (match28 - match32 < 12'd24) - //out1 = 8'd30; // out1 stay at is old value - //else - if (match28 - match32 > 12'd32) - out1 = 8'd32; - else if (match28 - match32 < 12'd16) - out1 = 8'd0; - end - end - else - begin - out1 = 8'd0; - //out2 = match32[8:1]; - end - //out1 = match28[7:0]; - //out2 = match32[7:0]; - //out2 = 8'hFF; - - //out2 = out1; - - end - end - end - end -end - -/* -reg [7:0] adc_cnt = 8'd0; -reg [7:0] out1 = 8'd0; -reg [7:0] old = 8'd0; -reg [7:0] edge_id = 8'd0; -reg edge_started = 1'd0; -// Count clock edge between two signal edges -always @(negedge adc_clk) -begin - adc_cnt <= adc_cnt + 1'd1; - - if (& adc_d[7:5] && !(& old[7:5])) // up - begin - if (edge_started == 1'd0) // new edge starting - begin - if (edge_id <= adc_cnt) - out1 <= adc_cnt - edge_id; - else - out1 <= adc_cnt + 9'h100 - edge_id; - edge_id <= adc_cnt; - edge_started = 1'd1; - end - end - else - begin - edge_started = 1'd0; - if (edge_id <= adc_cnt) - begin - if (adc_cnt - edge_id > 8'd40) - begin - out1 <= 8'd0; - end - end - else - begin - if (adc_cnt + 9'h100 - edge_id > 8'd40) - begin - out1 <= 8'd0; - end - end - end - - old <= adc_d; -end -*/ -// Other version of FSK reader, probably better but not working yet... -/*reg [7:0] out1 = 8'd0; -//reg [7:0] old = 8'd0; -reg [5:0] old1 = 4'd0; -reg [5:0] old2 = 4'd0; -//reg [7:0] edge_id = 8'd0; -//reg edge_started = 1'd0; -reg [5:0] edge_cnt = 6'd0; -reg [3:0] last_values = 4'd0; - -// Count clock edge between two signal edges -always @(negedge adc_clk) -begin - edge_cnt <= edge_cnt + 1'd1; - - last_values[3:1] <= last_values[2:0]; -// last_values[0] <= (& adc_d[7:5]); // adc_d >= 192 - - last_values[0] <= (adc_d[7:2] > old1 && old1 > old2); - - - //out1[7:4] <= out1[3:0]; - //out1[3:0] <= last_values; - //out1 <= 8'd28; - //out1 <= out1+1; - - if (edge_cnt > 6'd22 || out1 == 8'd0) - begin - if ((last_values[3:2] == 2'b0) && (last_values[1:0] == 2'b11)) // edge start detected - begin // 2 not high (low or mid) values followed by 2 high values - out1 <= edge_cnt; - edge_cnt <= 6'd0; - end - else if (edge_cnt > 6'd44) // average(32, 2*28) == 44 : ideal value for iso15 FSK - begin // /!\ MIN FREQ SUPPORTED = 13MHz/44 ~= 308kHz /!\ - out1 <= 8'd0; - edge_cnt <= 6'd0; - end - end - - old2 <= old1; - old1 <= adc_d[7:2]; - - /*if (last_values[0]) - out1 <= 8'h7F; - else if (last_values[1]) - out1 <= 8'hFF; - else - out1 <= 8'h0; - - /* - - if (& adc_d[7:5] && !(& old[7:5])) // up - begin - if (edge_started == 1'd0) // new edge starting - begin - //if (edge_id <= edge_cnt) - // out1 <= edge_cnt - edge_id; - //else - // out1 <= edge_cnt + (9'h100 - edge_id); - out1 <= edge_cnt; - //edge_id <= edge_cnt; - edge_started = 1'd1; - edge_cnt <= 8'd0; - end - end - else - begin - edge_started = 1'd0; - if (edge_cnt > 8'd80) - begin - out1 <= 8'd0; - //edge_id <= 8'd0; - edge_cnt <= 8'd0; - end -*//* if (edge_id <= edge_cnt) // NO EDGE - begin - if (edge_cnt - edge_id > 8'd40) - begin - out1 <= 8'd0; - edge_id <= 8'd0; - edge_cnt <= 8'd0; - end - end - else - begin - if (edge_cnt + (9'h100 - edge_id) > 8'd40) // NO EDGE - begin - out1 <= 8'd0; - edge_id <= 8'd0; - edge_cnt <= 8'd0; - end - end*//* - //end - - //old <= adc_d; -end*/ - -// agregate out values (depending on selected output frequency) -/*reg [10:0] out_tmp = 11'd0; -reg [7:0] out = 8'd0; -always @(negedge adc_clk) -begin - out_tmp <= out_tmp + out1; - if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_848_KHZ && adc_cnt[0] == 1'd0) - begin // average on 2 values - out <= out_tmp[8:1]; - out_tmp <= 12'd0; - end - else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_424_KHZ && adc_cnt[1:0] == 2'd0) - begin // average on 4 values - out <= out_tmp[9:2]; - out_tmp <= 12'd0; - end - else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_212_KHZ && adc_cnt[2:0] == 3'd0) - begin // average on 8 values - out <= out_tmp[10:3]; - out_tmp <= 12'd0; - end - else // 1695_KHZ - out <= out1; - -// if (adc_cnt > 8'd192 && edge_id < 8'd64) // WUT ? -// begin -// out <= 8'd0; -// out_tmp <= 11'd0; -// end -end -*/ -// Set output (ssp) clock -(* clock_signal = "yes" *) reg ssp_clk; -always @(ck_1356meg) -begin - if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_1695_KHZ) - ssp_clk <= ~ck_1356meg; - else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_848_KHZ) - ssp_clk <= ~adc_cnt[0]; - else if (minor_mode == `FPGA_HF_FSK_READER_OUTPUT_424_KHZ) - ssp_clk <= ~adc_cnt[1]; - else // 212 KHz - ssp_clk <= ~adc_cnt[2]; -end - -// Transmit output -reg [379:0] megatmpout = 380'd0; - -reg ssp_frame; -reg [7:0] ssp_out = 8'd0; -reg [3:0] ssp_cnt = 4'd0; -always @(posedge ssp_clk) -begin - ssp_cnt <= ssp_cnt + 1'd1; - if (ssp_cnt[2:0] == 3'd7) - begin - ssp_out = {2'b0, megatmpout[379:378], megatmpout[378], megatmpout[378], 2'b0}; - megatmpout[379:2] = megatmpout[377:0]; - megatmpout[1:0] = out2[5:4]; - out2 = out1; - - ssp_frame <= 1'b1; - end - else - begin - ssp_out = {ssp_out[6:0], 1'b0}; - ssp_frame <= 1'b0; - end -end - -assign ssp_din = ssp_out[7]; - -// Unused. -assign pwr_oe4 = 1'b0; -assign pwr_oe1 = 1'b0; -assign pwr_oe3 = 1'b0; -assign pwr_lo = 1'b0; -assign pwr_oe2 = 1'b0; - -endmodule - From 3e784557fc4457ee6b47d3ba264f9f67787d802d Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Fri, 4 Mar 2022 12:32:08 +0100 Subject: [PATCH 5/8] iso15 sniff: improve iso15 FSK decoding (ARM part) --- armsrc/iso15693.c | 331 ++++++++++++++++++++++++++-------------------- 1 file changed, 187 insertions(+), 144 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 919040d0e..eb0df9ba0 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -1280,21 +1280,22 @@ void AcquireRawAdcSamplesIso15693(void) { typedef struct DecodeTagFSK { enum { + STATE_FSK_ERROR, STATE_FSK_BEFORE_SOF, STATE_FSK_SOF_484, STATE_FSK_SOF_424, - STATE_FSK_SOF_END, + STATE_FSK_SOF_END_484, + STATE_FSK_SOF_END_424, STATE_FSK_RECEIVING_DATA_484, STATE_FSK_RECEIVING_DATA_424, - STATE_FSK_EOF, - STATE_FSK_ERROR + STATE_FSK_EOF } state; enum { LOGIC0_PART1, LOGIC1_PART1, LOGIC0_PART2, LOGIC1_PART2, - SOF + SOF } lastBit; uint8_t count; uint8_t bitCount; @@ -1309,7 +1310,6 @@ static void DecodeTagFSKReset(DecodeTagFSK_t *DecodeTag) { DecodeTag->bitCount = 0; DecodeTag->len = 0; DecodeTag->shiftReg = 0; - DbpString("FSK tag reset"); } static void DecodeTagFSKInit(DecodeTagFSK_t *DecodeTag, uint8_t *data, uint16_t max_len) { @@ -1320,7 +1320,7 @@ static void DecodeTagFSKInit(DecodeTagFSK_t *DecodeTag, uint8_t *data, uint16_t // Performances of this function are crutial for stability // as it is called in real time for every samples -static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed, int samples) +static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed) { switch(DecodeTag->state) { case STATE_FSK_BEFORE_SOF: @@ -1334,16 +1334,15 @@ static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *De case STATE_FSK_SOF_484: //DbpString("STATE_FSK_SOF_484"); - - if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 484 - { - DecodeTag->count++; - } - else if (FREQ_IS_424(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) + if (FREQ_IS_424(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) { // SOF part1 continue at 424 DecodeTag->state = STATE_FSK_SOF_424; DecodeTag->count = 1; } + else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 484 + { + DecodeTag->count++; + } else // SOF failed, roll back { DecodeTag->state = STATE_FSK_BEFORE_SOF; @@ -1352,103 +1351,136 @@ static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *De case STATE_FSK_SOF_424: //DbpString("STATE_FSK_SOF_424"); - - if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 424 - DecodeTag->count++; - else if (FREQ_IS_484(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) + if (FREQ_IS_484(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) { // SOF part 1 finished - DecodeTag->state = STATE_FSK_SOF_END; + DecodeTag->state = STATE_FSK_SOF_END_484; DecodeTag->count = 1; } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 424 + DecodeTag->count++; else // SOF failed, roll back { +#ifdef DEBUG if (DEBUG) Dbprintf("SOF_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif DecodeTag->state = STATE_FSK_BEFORE_SOF; } break; - case STATE_FSK_SOF_END: - if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END (484) - DecodeTag->count++; - else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) - { // SOF END finished or SOF END 1st part finished - DecodeTag->count = 0; - if (DecodeTag->lastBit == SOF) - { // SOF finished at 424 - if (DEBUG) - DbpString("Receiving data !"); - DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; - LED_C_ON(); - } - DecodeTag->lastBit = SOF; - } - else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END (424) - DecodeTag->count++; - else if (DecodeTag->lastBit == SOF && FREQ_IS_484(freq) && - LOGIC_COUNT(DecodeTag->count, recv_speed)) - { // SOF finished at 484 - DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + case STATE_FSK_SOF_END_484: + if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { + DecodeTag->state = STATE_FSK_SOF_END_424; DecodeTag->count = 1; - LED_C_ON(); } - else // SOF failed, roll back + else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END_484 + DecodeTag->count++; + else // SOF failed, roll back { +#ifdef DEBUG if (DEBUG) - Dbprintf("SOF_END failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + Dbprintf("SOF_END_484 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif DecodeTag->state = STATE_FSK_BEFORE_SOF; } break; + case STATE_FSK_SOF_END_424: + if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { // SOF finished at 484 + DecodeTag->count = 1; + DecodeTag->lastBit = SOF; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + LED_C_ON(); + } + else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed)) + { // SOF finished at 424 (wait count+2 to be sure that next freq is 424) + DecodeTag->count = 2; + DecodeTag->lastBit = SOF; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + LED_C_ON(); + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END_424 + DecodeTag->count++; + else // SOF failed, roll back + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("SOF_END_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; case STATE_FSK_RECEIVING_DATA_424: - if (DecodeTag->lastBit == LOGIC1_PART1 && - LOGIC_COUNT(DecodeTag->count, recv_speed)) - { // logic 1 finished - DecodeTag->lastBit = LOGIC1_PART2; - DecodeTag->count = 0; + if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { + if (DecodeTag->lastBit == LOGIC1_PART1) + { // logic 1 finished, goto 484 + DecodeTag->lastBit = LOGIC1_PART2; - DecodeTag->shiftReg >>= 1; - DecodeTag->shiftReg |= 0x80; - DecodeTag->bitCount++; - if (DecodeTag->bitCount == 8) { - DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; - if (DecodeTag->len > DecodeTag->max_len) { - // buffer overflow, give up - LED_C_OFF(); - return true; - } - DecodeTag->bitCount = 0; - DecodeTag->shiftReg = 0; - } - } - else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 424 + DecodeTag->shiftReg >>= 1; + DecodeTag->shiftReg |= 0x80; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + } + else + { // end of LOGIC0_PART1 + DecodeTag->lastBit = LOGIC0_PART1; + } + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + } + else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed) && + DecodeTag->lastBit == LOGIC1_PART1) + { // logic 1 finished, stay in 484 + DecodeTag->lastBit = LOGIC1_PART2; + + DecodeTag->shiftReg >>= 1; + DecodeTag->shiftReg |= 0x80; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + DecodeTag->count = 2; + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 424 DecodeTag->count++; - else if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed) && - DecodeTag->lastBit >= LOGIC0_PART2) - { // end of LOGIC0_PART1 - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; - DecodeTag->lastBit = LOGIC0_PART1; - } - else if (FREQ_IS_484(freq) && MIN_COUNT(DecodeTag->count, recv_speed)) - { // it was just the end of the previous block - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; - } + else if (FREQ_IS_484(freq) && DecodeTag->lastBit == LOGIC0_PART2 && SEOF_COUNT(DecodeTag->count, recv_speed)) { // EOF has started +#ifdef DEBUG if (DEBUG) - Dbprintf("RECEIVING_DATA_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + Dbprintf("RECEIVING_DATA_424->EOF: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); +#endif DecodeTag->count = 1; DecodeTag->state = STATE_FSK_EOF; LED_C_OFF(); } else // error { +#ifdef DEBUG if (DEBUG) - Dbprintf("RECEIVING_DATA_424 error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + Dbprintf("RECEIVING_DATA_424 error: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); +#endif DecodeTag->state = STATE_FSK_ERROR; LED_C_OFF(); return true; @@ -1456,43 +1488,59 @@ static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *De break; case STATE_FSK_RECEIVING_DATA_484: - if (DecodeTag->lastBit == LOGIC0_PART1 && - LOGIC_COUNT(DecodeTag->count, recv_speed)) - { // logic 0 finished - DecodeTag->lastBit = LOGIC0_PART2; - DecodeTag->count = 0; + if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { + if (DecodeTag->lastBit == LOGIC0_PART1) + { // logic 0 finished, goto 424 + DecodeTag->lastBit = LOGIC0_PART2; - DecodeTag->shiftReg >>= 1; - DecodeTag->bitCount++; - if (DecodeTag->bitCount == 8) { - DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; - if (DecodeTag->len > DecodeTag->max_len) { - // buffer overflow, give up - LED_C_OFF(); - return true; - } - DecodeTag->bitCount = 0; - DecodeTag->shiftReg = 0; - } - } + DecodeTag->shiftReg >>= 1; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + } + else + { // end of LOGIC1_PART1 + DecodeTag->lastBit = LOGIC1_PART1; + } + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + } + else if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed) && + DecodeTag->lastBit == LOGIC0_PART1) + { // logic 0 finished, stay in 424 + DecodeTag->lastBit = LOGIC0_PART2; + + DecodeTag->shiftReg >>= 1; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + DecodeTag->count = 2; + } else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 DecodeTag->count++; - else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed) && - DecodeTag->lastBit >= LOGIC0_PART2) - { // end of LOGIC1_PART1 - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; - DecodeTag->lastBit = LOGIC1_PART1; - } - else if (FREQ_IS_424(freq) && MIN_COUNT(DecodeTag->count, recv_speed)) - { // it was just the end of the previous block - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; - } - else // error + else // error { +#ifdef DEBUG if (DEBUG) - Dbprintf("RECEIVING_DATA_484 error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); + Dbprintf("RECEIVING_DATA_484 error: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); +#endif LED_C_OFF(); DecodeTag->state = STATE_FSK_ERROR; return true; @@ -1508,14 +1556,20 @@ static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *De } else // error { +#ifdef DEBUG if (DEBUG) Dbprintf("EOF error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif DecodeTag->state = STATE_FSK_ERROR; return true; } break; case STATE_FSK_ERROR: LED_C_OFF(); +#ifdef DEBUG + if (DEBUG) + Dbprintf("FSK error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif return true; // error break; } @@ -1587,7 +1641,9 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { dma_start_time = GetCountSspClk() & 0xfffffff0; } - volatile uint16_t sniffdata = *upTo++; + volatile uint16_t sniffdata; + volatile uint16_t sniffdata_prev = sniffdata; + sniffdata = *upTo++; // we have read all of the DMA buffer content if (upTo >= dma->buf + DMA_BUFFER_SIZE) { @@ -1618,7 +1674,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { } // no need to try decoding reader data if the tag is sending - if (tag_is_active == false) { + if (!tag_is_active) { if (Handle15693SampleFromReader((sniffdata & 0x02) >> 1, &dreader)) { @@ -1634,7 +1690,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; } // And ready to receive another command. - DecodeReaderReset(&dreader); + //DecodeReaderReset(&dreader); // already reseted DecodeTagReset(&dtag); DecodeTagFSKReset(&dtagfsk); reader_is_active = false; @@ -1653,7 +1709,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; } // And ready to receive another command - DecodeReaderReset(&dreader); + //DecodeReaderReset(&dreader); // already reseted DecodeTagReset(&dtag); DecodeTagFSKReset(&dtagfsk); reader_is_active = false; @@ -1663,7 +1719,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { } } - if (reader_is_active == false && expect_tag_answer) { // no need to try decoding tag data if the reader is currently sending or no answer expected yet + if (!reader_is_active && expect_tag_answer) { // no need to try decoding tag data if the reader is currently sending or no answer expected yet if (!expect_fsk_answer) { @@ -1691,48 +1747,35 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { } else { - if (Handle15693FSKSamplesFromTag((sniffdata >> 2) & 0x3, &dtagfsk, expect_fast_answer, samples-fsksamples)) { - uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF - if (dtagfsk.lastBit == SOF) { - eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) - } - uint32_t sof_time = eof_time - - dtagfsk.len * 8 * 8 * 16 // time for byte transfers - - (32 * 16) // time for SOF transfer - - (dtagfsk.lastBit != SOF ? (32 * 16) : 0); // time for EOF transfer + if (FREQ_IS_0((sniffdata >> 2) & 0x3)) // tolerate 1 00 + sniffdata = sniffdata_prev; - LogTrace_ISO15693(dtagfsk.output, dtagfsk.len, (sof_time * 4), (eof_time * 4), NULL, false); - // And ready to receive another response. - DecodeTagFSKReset(&dtagfsk); - DecodeTagReset(&dtag); - DecodeReaderReset(&dreader); - expect_tag_answer = false; - tag_is_active = false; + if (Handle15693FSKSamplesFromTag((sniffdata >> 2) & 0x3, &dtagfsk, expect_fast_answer)) { expect_fsk_answer = false; - } - else if (Handle15693FSKSamplesFromTag(sniffdata & 0xFF, &dtagfsk, expect_fast_answer)) { + else { + tag_is_active = (dtagfsk.state >= STATE_FSK_RECEIVING_DATA_484); + } + if (!expect_fsk_answer) + { // FSK answer no more expected: switch back to ASK + if (dtagfsk.len > 0) + { + uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF + if (dtagfsk.lastBit == SOF) { + eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + } + uint32_t sof_time = eof_time + - dtagfsk.len * 8 * 8 * 16 // time for byte transfers + - (32 * 16) // time for SOF transfer + - (dtagfsk.lastBit != SOF ? (32 * 16) : 0); // time for EOF transfer - uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF - if (dtagfsk.lastBit == SOF) { - eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + LogTrace_ISO15693(dtagfsk.output, dtagfsk.len, (sof_time * 4), (eof_time * 4), NULL, false); } - uint32_t sof_time = eof_time - - dtagfsk.len * 8 * 8 * 16 // time for byte transfers - - (32 * 16) // time for SOF transfer - - (dtagfsk.lastBit != SOF ? (32 * 16) : 0); // time for EOF transfer - LogTrace_ISO15693(dtagfsk.output, dtagfsk.len, (sof_time * 4), (eof_time * 4), NULL, false); - // And ready to receive another response. DecodeTagFSKReset(&dtagfsk); - DecodeTagReset(&dtag); DecodeReaderReset(&dreader); expect_tag_answer = false; tag_is_active = false; - expect_fsk_answer = false; - - } else { - tag_is_active = (dtagfsk.state >= STATE_FSK_RECEIVING_DATA_484); } } } From 6d37410d337bcd5e248e3d7ca86881194abee4fa Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Tue, 8 Mar 2022 17:29:34 +0100 Subject: [PATCH 6/8] iso15 sniff: fix alignment --- armsrc/iso15693.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index eb0df9ba0..c864d6368 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -1791,8 +1791,8 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { Dbprintf(" DecodeTag State........%d", dtag.state); Dbprintf(" DecodeTag byteCnt......%d", dtag.len); Dbprintf(" DecodeTag posCount.....%d", dtag.posCount); - Dbprintf(" DecodeTagFSK State........%d", dtagfsk.state); - Dbprintf(" DecodeTagFSK byteCnt......%d", dtagfsk.len); + Dbprintf(" DecodeTagFSK State.....%d", dtagfsk.state); + Dbprintf(" DecodeTagFSK byteCnt...%d", dtagfsk.len); Dbprintf(" DecodeTagFSK count.....%d", dtagfsk.count); Dbprintf(" DecodeReader State.....%d", dreader.state); Dbprintf(" DecodeReader byteCnt...%d", dreader.byteCount); From c5f216558c5ec60618124e261bfd3ba3f3cc991d Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 16 Mar 2022 11:26:07 +0100 Subject: [PATCH 7/8] iso15sniff: add an "iclass" bool parameter to SniffIso15693() This is required to disable ISO15 flags parsing when sniffing iClass because those flags don't exist in iClass coms (iClass iso15 based communications are always fast and using only one subcarrier). --- armsrc/Standalone/hf_15sniff.c | 2 +- armsrc/appmain.c | 2 +- armsrc/iclass.c | 2 +- armsrc/iso15693.c | 21 ++++++++++++++------- armsrc/iso15693.h | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/armsrc/Standalone/hf_15sniff.c b/armsrc/Standalone/hf_15sniff.c index 45d837859..069266580 100644 --- a/armsrc/Standalone/hf_15sniff.c +++ b/armsrc/Standalone/hf_15sniff.c @@ -100,7 +100,7 @@ void RunMod(void) { Dbprintf(_YELLOW_("HF 15693 SNIFF started")); rdv40_spiffs_lazy_mount(); - SniffIso15693(0, NULL); + SniffIso15693(0, NULL, false); Dbprintf("Stopped sniffing"); SpinDelay(200); diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 07b44d8a4..01ba06a60 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1232,7 +1232,7 @@ static void PacketReceived(PacketCommandNG *packet) { break; } case CMD_HF_ISO15693_SNIFF: { - SniffIso15693(0, NULL); + SniffIso15693(0, NULL, false); reply_ng(CMD_HF_ISO15693_SNIFF, PM3_SUCCESS, NULL, 0); break; } diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 134b633a8..e261efc59 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -91,7 +91,7 @@ static uint8_t get_pagemap(const picopass_hdr_t *hdr) { // Both sides of communication! //============================================================================= void SniffIClass(uint8_t jam_search_len, uint8_t *jam_search_string) { - SniffIso15693(jam_search_len, jam_search_string); + SniffIso15693(jam_search_len, jam_search_string, true); } static void rotateCSN(const uint8_t *original_csn, uint8_t *rotated_csn) { diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index c864d6368..c064d7733 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -1575,7 +1575,8 @@ static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *De } return false; } -void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { + +void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string, bool iclass) { LEDsoff(); LED_A_ON(); @@ -1601,6 +1602,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { DecodeReaderInit(&dreader, cmd, sizeof(cmd), jam_search_len, jam_search_string); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SNIFF_AMPLITUDE | FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ); + LED_D_OFF(); SetAdcMuxFor(GPIO_MUXSEL_HIPKD); @@ -1622,7 +1624,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { bool reader_is_active = false; bool expect_tag_answer = false; bool expect_fsk_answer = false; - bool expect_fast_answer = false; + bool expect_fast_answer = true; // default to true is required for iClass int dma_start_time = 0; // Count of samples received so far, so that we can include timing @@ -1686,8 +1688,11 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { - 16 * 16; // time for EOF transfer LogTrace_ISO15693(dreader.output, dreader.byteCount, (sof_time * 4), (eof_time * 4), NULL, true); - expect_fsk_answer = dreader.output[0] & ISO15_REQ_SUBCARRIER_TWO; - expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; + if (!iclass) // Those flags don't exist in iClass + { + expect_fsk_answer = dreader.output[0] & ISO15_REQ_SUBCARRIER_TWO; + expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; + } } // And ready to receive another command. //DecodeReaderReset(&dreader); // already reseted @@ -1704,9 +1709,11 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string) { - 32 * 16 // time for SOF transfer - 16 * 16; // time for EOF transfer LogTrace_ISO15693(dreader.output, dreader.byteCount, (sof_time * 4), (eof_time * 4), NULL, true); - - expect_fsk_answer = dreader.output[0] & ISO15_REQ_SUBCARRIER_TWO; - expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; + if (!iclass) // Those flags don't exist in iClass + { + expect_fsk_answer = dreader.output[0] & ISO15_REQ_SUBCARRIER_TWO; + expect_fast_answer = dreader.output[0] & ISO15_REQ_DATARATE_HIGH; + } } // And ready to receive another command //DecodeReaderReset(&dreader); // already reseted diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 29d266709..31ca6e6c1 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -50,7 +50,7 @@ void SimTagIso15693(uint8_t *uid); // simulate an ISO15693 tag - greg void BruteforceIso15693Afi(uint32_t speed); // find an AFI of a tag - atrox void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint8_t *data); // send arbitrary commands from CLI - atrox -void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string); +void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string, bool iclass); int SendDataTag(uint8_t *send, int sendlen, bool init, bool speed_fast, uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint16_t timeout, uint32_t *eof_time); From 352850d158dd799d039b95b1368ce39822e7610d Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 16 Mar 2022 15:33:44 +0100 Subject: [PATCH 8/8] iso15: update TODO / BUGS / ISSUES with support of iso15 2SC sniff --- armsrc/iso15693.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index c064d7733..ac5c0fdaa 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -54,7 +54,7 @@ // *) signal decoding is unable to detect collisions. // *) add anti-collision support for inventory-commands // *) read security status of a block -// *) sniffing and simulation do not support two subcarrier modes. +// *) simulation do not support two subcarrier modes. // *) remove or refactor code under "deprecated" // *) document all the functions