diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 791082d6b..862078bf7 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -40,12 +40,204 @@ static crc_t legic_crc; static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define TAG_FRAME_WAIT 70 /* 330us from READER frame end to TAG frame start */ +#define TAG_ACK_WAIT 758 /* 3.57ms from READER frame end to TAG write ACK */ #define TAG_BIT_PERIOD 21 /* 99.1us */ #define RWD_TIME_PAUSE 4 /* 18.9us */ #define RWD_TIME_1 21 /* RWD_TIME_PAUSE 18.9us off + 80.2us on = 99.1us */ #define RWD_TIME_0 13 /* RWD_TIME_PAUSE 18.9us off + 42.4us on = 61.3us */ #define RWD_CMD_TIMEOUT 40 /* 40 * 99.1us (arbitrary value) */ +#define RWD_MIN_FRAME_LEN 6 /* Shortest frame is 6 bits */ +#define RWD_MAX_FRAME_LEN 23 /* Longest frame is 23 bits */ + +#define RWD_PULSE 1 /* Pulse is signaled with GPIO_SSC_DIN high */ +#define RWD_PAUSE 0 /* Pause is signaled with GPIO_SSC_DIN low */ + +//----------------------------------------------------------------------------- +// Demodulation +//----------------------------------------------------------------------------- + +// Returns true if a pulse/pause is received within timeout +static inline bool wait_for(bool value, const uint32_t timeout) { + while((bool)(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN) != value) { + if(GetCountSspClk() > timeout) { + return false; + } + } + return true; +} + +// Returns a demedulated bit or -1 on code violation +// +// rx_bit decodes bits using a thresholds. rx_bit has to be called by as soon as +// a frame starts (first pause is received). rx_bit checks for a pause up to +// 18.9us followed by a pulse of 80.2us or 42.4us: +// - A bit length <18.9us is a code violation +// - A bit length >80.2us is a 1 +// - A bit length <80.2us is a 0 +// - A bit length >148.6us is a code violation +static inline int8_t rx_bit() { + // backup ts for threshold calculation + uint32_t bit_start = last_frame_end; + + // wait for pause to end + if(!wait_for(RWD_PULSE, bit_start + RWD_TIME_1*3/2)) { + return -1; + } + + // wait for next pause + if(!wait_for(RWD_PAUSE, bit_start + RWD_TIME_1*3/2)) { + return -1; + } + + // update bit and frame end + last_frame_end = GetCountSspClk(); + + // check for code violation (bit to short) + if(last_frame_end - bit_start < RWD_TIME_PAUSE) { + return -1; + } + + // apply threshold (average of RWD_TIME_0 and ) + return (last_frame_end - bit_start > (RWD_TIME_0 + RWD_TIME_1) / 2); +} + +//----------------------------------------------------------------------------- +// Modulation +// +// LEGIC RF uses a very basic load modulation from card to reader: +// - Subcarrier on for a 1 +// - Subcarrier off for for a 0 +// +// The 212kHz subcarrier is generated by the FPGA as well as a mathcing ssp clk. +// Each bit is transfered in a 99.1us slot and the first timeslot starts 330us +// after the final 20us pause generated by the reader. +//----------------------------------------------------------------------------- + +// Transmits a bit +// +// Note: The Subcarrier is not disabled during bits to prevent glitches. This is +// not mandatory but results in a cleaner signal. tx_frame will disable +// the subcarrier when the frame is done. +static inline void tx_bit(bool bit) { + LED_C_ON(); + + if(bit) { + // modulate subcarrier + HIGH(GPIO_SSC_DOUT); + } else { + // do not modulate subcarrier + LOW(GPIO_SSC_DOUT); + } + + // wait for tx timeslot to end + last_frame_end += TAG_BIT_PERIOD; + while(GetCountSspClk() < last_frame_end) { }; + LED_C_OFF(); +} + +//----------------------------------------------------------------------------- +// Frame Handling +// +// The LEGIC RF protocol from reader to card does not include explicit frame +// start/stop information or length information. The tag detects end of frame +// trough an extended pulse (>99.1us) without a pause. +// In reverse direction (card to reader) the number of bites is well known +// and depends only the command received (IV, ACK, READ or WRITE). +//----------------------------------------------------------------------------- + +static void tx_frame(uint32_t frame, uint8_t len) { + // wait for next tx timeslot + last_frame_end += TAG_FRAME_WAIT; + legic_prng_forward(TAG_FRAME_WAIT/TAG_BIT_PERIOD - 1); + while(GetCountSspClk() < last_frame_end) { }; + + // transmit frame, MSB first + for(uint8_t i = 0; i < len; ++i) { + bool bit = (frame >> i) & 0x01; + tx_bit(bit ^ legic_prng_get_bit()); + legic_prng_forward(1); + }; + + // disable subcarrier + LOW(GPIO_SSC_DOUT); +} + +static void tx_ack() { + // wait for ack timeslot + last_frame_end += TAG_ACK_WAIT; + legic_prng_forward(TAG_ACK_WAIT/TAG_BIT_PERIOD - 1); + while(GetCountSspClk() < last_frame_end) { }; + + // transmit ack (ack is not encrypted) + tx_bit(true); + legic_prng_forward(1); + + // disable subcarrier + LOW(GPIO_SSC_DOUT); +} + +// Returns a demedulated frame or -1 on code violation +// +// Since TX to RX delay is arbitrary rx_frame has to: +// - detect start of frame (first pause) +// - forward prng based on ts/TAG_BIT_PERIOD +// - receive the frame +// - detect end of frame (last pause) +static int32_t rx_frame(uint8_t *len) { + int32_t frame = 0; + + // add 2 SSP clock cycles (1 for tx and 1 for rx pipeline delay) + // those will be substracted at the end of the rx phase + last_frame_end -= 2; + + // wait for first pause (start of frame) + for(uint8_t i = 0; true; ++i) { + // increment prng every TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + legic_prng_forward(1); + + // if start of frame was received exit delay loop + if(wait_for(RWD_PAUSE, last_frame_end)) { + last_frame_end = GetCountSspClk(); + break; + } + + // check for code violation + if(i > RWD_CMD_TIMEOUT) { + return -1; + } + } + + // receive frame + for(*len = 0; true; ++(*len)) { + // receive next bit + LED_B_ON(); + int8_t bit = rx_bit(); + LED_B_OFF(); + + // check for code violation and to short / long frame + if((bit < 0) && ((*len < RWD_MIN_FRAME_LEN) || (*len > RWD_MAX_FRAME_LEN))) { + return -1; + } + + // check for code violation caused by end of frame + if(bit < 0) { + break; + } + + // append bit + frame |= (bit ^ legic_prng_get_bit()) << (*len); + legic_prng_forward(1); + } + + // rx_bit sets coordination timestamp to start of pause, append pause duration + // and substract 2 SSP clock cycles (1 for rx and 1 for tx pipeline delay) to + // obtain exact end of frame. + last_frame_end += RWD_TIME_PAUSE - 2; + + return frame; +} //----------------------------------------------------------------------------- // Legic Simulator